<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-046 — the adoption-facts registry (`vibefacts/`)</title>
  <status stage="spec" state="work" comment="owner design ruling 2026-08-22 (chat, the vibefacts dialogue); five refinements accepted the same sitting; build order: this wave first, then the STATIC materialisation-format wave, then the cache-stable-prefix wave"/>
  <section id="mandate" title="1. Mandate — whose status is it">
    <p p="1"><fact id="REGISTRY-MANDATE" status="impl/done">**The owner's design (2026-08-22, chat, near-verbatim):** «сделать в корне отдельную директорию vibefacts в структурном и grep-совместимом формате (toml), в которой собираем данные о том, как реализован или не реализован факт. Внутри реестра — признак: факт из пакета или из spec; каждый факт хранится вместе с адресом своего пакета-источника. Утилита `vibe facts` даёт CRUD, search, переводы статусов. При полной ре-материализации статусы мерджатся в vibedeps в момент ре-материализации. Для спецификаций внутри spec статус всегда синхронизирован. Когда статус пакетного факта меняется в ходе кодирования — точечно меняем его в vibedeps, а STATIC не трогаем: новое значение накатывается динамическим лоадером. Статус опционален: нет статуса — неопределённое состояние, типовое для внешних пакетов. При импорте пакета авторские статусы игнорируются. При удалении пакета статусы не исчезают; `vibe uninstall` спрашивает, чистить или хранить; `vibe facts clean` удаляет ничейные факты исчезнувших пакетов.» Landed across W1–W3 (commits 700a91f1, 7548144f + the W3 landing): the registry, the full `vibe facts` surface, derivation merge with point re-derivation, and the lifecycle all live; the cache-stable-STATIC strong form remains the follow-up wave.</fact></p>
    <p p="2"><fact id="WHY-THE-AXIS-IS-MISSING" status="spec/done">**The problem the registry closes.** A status marker in a package SOURCE is the author's claim about the author's own scope. For packages that ship their implementation (engines travelling inside `core-ai-native`) the claim stays true for every consumer. For practice packages (`wal`, `git-practices`, redbook adoption facts) «impl/done» means «implemented at the author's reference project» — it says NOTHING about the importing project, and the markup has no per-consumer axis (stage/state, `action`, `audience` all describe the claim, not the adopter). The genre boundary runs per FACT, not per package — one package holds both kinds. The registry is the consumer-owned overlay that carries the missing axis without ever rewriting the package source.</fact></p>
  </section>
  <section id="model" title="2. The model">
    <p p="3"><fact id="REGISTRY-HOME" status="impl/done" action="continue" actionstage="doc" audience="user">**Home and format.** `vibefacts/` at the project root, tracked in git (it is project state a teammate must see), one TOML file per source: `vibefacts/spec.toml` for the host's own `spec/` tree, `vibefacts/&lt;group&gt;.&lt;name&gt;.toml` per installed package (the vibedeps slot-naming convention). Grep-friendly, small diffs, per-package lifecycle: removing a package's overlay is removing one file. Landed in W1.</fact></p>
    <p p="4"><fact id="REGISTRY-KEY" status="impl/done">**The key is the fully-qualified anchor.** Every entry is keyed by the fact's full `spec://` address (PROP-029 grammar); beside it the entry records the origin kind (`package` | `spec`) and the source package address — the owner's «каждый факт хранится вместе с адресом своего пакета-источника». Landed in W1; addresses are minted by the router's forward law (`canonical_doc_path`).</fact></p>
    <p p="5"><fact id="TRI-STATE" status="impl/done">**Status is optional; absence is the indeterminate state.** The dialect already models status as optional on a fact — no grammar change. What changes is the reading law, per corpus: in the HOST's judged corpus «every fact carries a marker» stays binding (the court's unmarked-fact debt is unchanged); in the registry's view of PACKAGE facts, absence of an adoption status is the NORMAL initial state — «типовое состояние для спецификаций во внешних пакетах». Landed in W1.</fact></p>
  </section>
  <section id="laws" title="3. The five laws">
    <p p="6"><fact id="LAW-SOVEREIGNTY" status="impl/done" action="continue" actionstage="doc" audience="user">**L1 — consumer sovereignty: imported statuses are ignored.** On package import the authored statuses in the package source are NOT copied into the registry — the package may use them for its own internal purposes, and the consumer's adoption state starts indeterminate. Acceptance of authored statuses is a deliberate act, never a default: `vibe facts adopt --package &lt;X&gt; [--from-source] [filter]` bulk-copies the author's statuses into the overlay in one auditable gesture (the escape hatch for implementation-shipping packages whose facts are done-by-construction). Landed: import never touches the registry by construction (W1); `adopt` fills absent entries only and reports added/kept (W2).</fact></p>
    <p p="7"><fact id="LAW-SYNC" status="impl/done">**L2 — the two-home sync law, with an asymmetric tie-break.** For HOST facts (origin `spec`) the spec file is authoritative and the registry mirrors it; for PACKAGE facts the registry is authoritative and the materialised copies mirror it. The sync is a MACHINE gate, not an instruction: a `facts_sync` check (panel + `vibe check`) fails when `vibefacts/spec.toml` and the `spec/` markers disagree; `vibe facts sync` reconciles mechanically in the direction the tie-break dictates. The boot lane carries the convention line (change one home → sync the other), but the gate is what makes it true. Landed in W1: the facts_sync cell sits in the roster with its red/green proven live; the sync reads only the documents the registry cites, so an unrelated spec file's defect cannot fail the gate.</fact></p>
    <p p="8"><fact id="LAW-DERIVATION" status="impl/done">**L3 — the registry is a declared derivation input; point edits are point re-derivations.** Statuses merge into materialised vibedeps AT derivation time: the derived output becomes a pure function of (package source, converter recipe, that package's registry file), and the per-package registry file's hash joins the derived manifest's inputs — so a registry change makes exactly the affected slot stale and «точечная ре-материализация» is driven by ordinary slot-staleness. Editing slot bytes in place is NOT a path: it would break the hash law (PROP-045 ##HASH-LAW) and slot verification; `vibe facts set …` writes the registry and re-derives the slot. Landed in W2: overlay_hash in the derived manifest (old wire reads), replace-if-present with authored extras preserved, targeted same-format files enter the pivot, freshness and the slot verifier both compare the live registry file, set/rm point-re-derive the one slot — proven live on the wal slot both ways.</fact></p>
    <p p="9"><fact id="LAW-STATIC-INVARIANT" status="spec/work">**L4 — status mutation never touches STATIC.** The owner's ruling (2026-08-22): STATIC is a globally static prefix, loaded once per agent/subagent and cache-stable in the LLM-prompt-cache sense; it is invalidated only by large structural causes (the dependency list changed), never by status flips. The strong form adopted: **statuses are not compiled into STATIC at all** — the static lane is a pure function of structure (dependency list, versions, snippet structure) and NOT a function of the mutable (registry, statuses, dates). A fact's current status is a point query against the registry / the dynamic lane, not prefix knowledge. This law is the bridge to the follow-up cache-stable-prefix wave and is testable as byte-identity of STATIC across rebuilds with unchanged structural inputs. The prefix economy's canon is PROP-048 §3 (##STATIC-ROLE and its neighbours); this fact is that canon's registry-side half.</fact></p>
    <p p="10"><fact id="LAW-LIFECYCLE" status="impl/done" action="continue" actionstage="doc" audience="user">**L5 — lifecycle: removal keeps, cleaning reports.** Removing a package does not silently erase its overlay; `vibe uninstall` (the CLI verb; built out if found unimplemented) asks whether to clean or keep the package's facts file. `vibe facts clean` is the revision pass that removes orphaned overlays of vanished packages; on dependency UPGRADE, `vibe facts sync` reports anchors that disappeared or moved (orphaned entries with candidates) rather than dropping them — the tombstone discipline, applied to overlays. Landed in W3: lockfile-driven `clean` with dry-run and named removals, the attended-only uninstall dialog (automation flags never imply consent to delete adoption data), spec.toml never an orphan.</fact></p>
  </section>
  <section id="cli" title="4. The CLI surface">
    <p p="11"><fact id="CLI-FACTS" status="impl/done" action="continue" actionstage="doc" audience="user">**`vibe facts` — the explicit lever.** CRUD over the registry, search by attributes (package, status, stage, indeterminate-only), status transitions (`vibe facts set &lt;address&gt; &lt;status&gt;`), `adopt` (L1), `sync` (L2), `clean` (L5), and the adoption report (`vibe facts report [--package X]` — «12/40 adopted»). An agent flips a fact through the tool — an auditable command — never by editing derived files. Landed across W1–W3: list/get/set/rm/sync (W1), adopt with point re-derivation (W2), clean and the per-package report with `?` for unavailable denominators (W3).</fact></p>
  </section>
  <section id="build-order" title="5. Build order">
    <p p="12"><fact id="WAVES" status="spec/work">**Waves, owner-ordered (2026-08-22).** W1: registry format + `vibe facts` core (CRUD/search/set) + the `facts_sync` gate. W2: L3 — registry joins derivation inputs, point re-derivation, `adopt`. W3: L5 lifecycle (`clean`, the uninstall dialog) + the adoption report. Then, as the owner's next act after this PROP's waves: the **STATIC materialisation-format wave** (STATIC follows the project's materialisation format — `STATIC.xml` under `spec_format="xml"`; the extension-stable-`STATIC.md` residue law of PROP-045's parity landing is owner-revised); after it, the **cache-stable-prefix wave** (L4 hardened into measured byte-stability and prefix-sharing across agents).</fact></p>
  </section>
  <section id="open" title="6. Open questions">
    <p p="13"><fact id="OPEN-MIXED-STATIC" status="spec/work">**The mixed-format STATIC extension.** Under `mixed` materialisation the slots are copy-through per file, but STATIC is a generated compilate with no source format of its own — the STATIC-format wave decides its extension law (likely the markdown default, as the single canonical compilate).</fact></p>
    <p p="14"><fact id="OPEN-JUDGED-CORPUS" status="spec/work">**Does `vibefacts/` join the judged corpus?** Adoption claims are claims — they may deserve verdicts and evidence like any fact. Deferred until W1 lands and the first real overlay exists.</fact></p>
  </section>
</spec>
