{
  "uid": "cogitave.learn.project-products-into-core.the-fact-registry",
  "kind": "moduleUnit",
  "href": "/modules/project-products-into-core/the-fact-registry/",
  "title": "The fact registry",
  "summary": "",
  "type": null,
  "products": [],
  "roles": [],
  "levels": [],
  "subjects": [],
  "headings": [
    "The gap: restatement drift",
    "Three layers, each catching what the last one cannot",
    "The registry rule: one owner, cite don't restate",
    "The scanner, and what changes when a fact does"
  ],
  "source": "A graph made of `xref` and `dependsOn` edges only propagates changes along\nedges someone actually drew. That is a real gap, and the\n[knowledge-propagation standard](../../../../standards/docs/standards/knowledge-propagation.md)\nand [ADR-0028](../../../../standards/docs/decisions/0028-knowledge-propagation-and-fact-registry.md)\nname it precisely.\n\n## The gap: restatement drift\n\nTwo audits of the estate found 21 real defects, almost all the same shape: a\nload-bearing fact, retyped as plain prose in a second document, silently\ndiverging from its source. An ADR restated an SLO ladder as\n`99.9 / 99.95 / 99.99` when the canonical ladder was `99.9 / 99.5 / 99.0`; prose\nelsewhere said \"17 node types\" while the schema had grown to 18. Neither\nrestatement carries an edge, so no graph walk ever caught either one. The\nstandard names this failure class **restatement drift** and states the fix as\none sentence: **the ADR decides, the standard carries the number, everything\nelse points.**\n\n## Three layers, each catching what the last one cannot\n\n| Layer | Catches | Mechanism |\n| --- | --- | --- |\n| L1 Graph | linked references | `xref`/`dependsOn` edges, already in Core |\n| L2 Fact registry | unlinked restatements | `facts.yaml` + the fact-drift scanner |\n| L3 Impact map | pre-edit blindness | reverse-xref + graph walk + registry lookup |\n\nL1 is the node/edge model and query layer the earlier modules in this path\nalready cover. L2 and L3 are what this ADR adds, and neither replaces the\ngraph - they feed it.\n\n## The registry rule: one owner, cite don't restate\n\nAn **atomic fact** is a single load-bearing invariant - a number, a ceiling, a\ndefault, a closed list - with exactly **one owner document**. Every other\nartifact points at the owner (an `xref`, a relative link, or an include)\ninstead of retyping the value; a restatement that genuinely helps readability\nis registered as a **mention**, which is what makes it visible to the scanner.\nFacts come in three classes: `constant` (a fixed value, checked by a pattern\nthat would have caught the historical drift, e.g. flagging `>=2 reviewers`\nwhere the standard says `>=1`), `counter` (a cardinality, verified by\n**recounting the authoritative set** rather than matching a frozen literal - a\n\"53 standards\" count would rot the moment a 54th file lands), and `policy` (a\ncanonical rule sentence, like `postgres-first`).\n\n## The scanner, and what changes when a fact does\n\n`fact-drift.py` is python3, stdlib-only, deterministic, and read-only: it loads\nthe registry, scans the estate, and exits non-zero on any drift, as a\n**blocking** stage in the quality gate. When a fact genuinely changes, the old\nvalue is never just overwritten - it is **invalidated** with a\n`validFrom`/superseded marker (the same bi-temporal discipline as\nZep/Graphiti), so a stale restatement found later traces to the exact value it\nechoes. Once Core runs, a registry entry itself becomes a graph node, with a\n`derivedFrom` edge to its owner and `xref` edges from every citing artifact.\n\n> [!IMPORTANT]\n> **Day-0 honesty.** No Core server is running yet; the registry and scanner\n> operate on the mirror as plain files. The standard is explicit that its own\n> scanner's **first run is expected to fail**, because the two audits that\n> motivated this design found real, unrepaired restatement drift (the node- and\n> edge-type counts quoted in prose had lagged the schema before this module was\n> written) - and treats that first failure as the gate proving itself, not a\n> defect in it.\n",
  "partOf": "cogitave.learn.project-products-into-core",
  "durationInMinutes": 7,
  "quiz": null
}