A class of governed reference is declared, not coded — and a reference becomes historic by recording it, not by being erased

Accepted

ontoref
ONTOREF WAS BUILT AROUND ADRs, and everything that arrived afterwards arrived without a role.

Context

ONTOREF WAS BUILT AROUND ADRs, and everything that arrived afterwards arrived without a role.

The ADR was the foundational reference: declaration, validator, jurisprudence. Backlog items, Q&A entries, bonds, terms, instruments, biops — each was added when it was needed, and none of them was ever placed. Nothing declared how they intervene, what they contribute to a session, a work order, a verification or an ontological operation, or how a task should notice that one of them bears on it. In practice they became prose registries whose use requires an explicit human mention, and whose currency nobody maintains because nothing can express staleness.

BIOPS OPENED THE DOOR AND MADE THE GAP LEGIBLE. A cell carries `routes_from`/`routes_to` over a frozen vocabulary — `adr-066`, `bl-036`, `qa:<slug>`, `biop:<slug>` — and a `lineage` in which a descendant answers a mutated parent with 'Rederive, 'Hold or 'Retire, with the hold obliged by contract to state its return. Cross-reference and becoming, both real, both reaching exactly one kind.

MEASURED 2026-08-27, and the uniformity is the finding.

ADDRESSING — not one governed reference was addressable by its own id:

ontoref bkl 9 · bkl 009 · bkl bl-009 unknown subcommand ontoref adr 78 · adr adr-078 unknown subcommand ontoref adr show 78 ADR '78' not found ontoref qa 9 · biop <slug> unknown subcommand

Nothing had ever declared what shape a kind's id has, so normalisation could not be DERIVED for any kind, and a per-kind reimplementation was never worth writing five times, so it was written zero. `adr show`'s own docstring promised `adr-001-slug` and had never accepted it: the glob demanded a trailing `-<something>`, so a complete stem matched nothing and the ADR reported as missing — since the function was written.

ROUTING — `schemas/biop.ncl` froze the route vocabulary and stated, correctly, that it checks «the SHAPE of a route, never that it resolves», because resolving inside a data contract turns it into a resolver. Nobody built the resolver. 35 declared routes, and nothing anywhere could say whether any of them addressed something that exists.

BECOMING — of five kinds, two could express it (`adr`, `biop`), one could close an occupant with no way back (`backlog`; bl-089 is the item that says so), and two could express nothing at all (`qa`, `mode`): an entry that stopped being true either stayed served as if current, or was deleted leaving no tombstone. And the one kind with both exits had fired only one: the ADR schema's own comment records that «77 of 77 ADRs were edited after their first commit, 'Superseded has never been used once, and superseded_by has never been set» — so every horizontal move took the undeclared exit, git, outside the substrate the decision governs.

THE CASE THAT NAMES ITSELF. The session that produced this ADR was routed by the orientation hook to `qa:ontoref-three-layer-model`, whose closing line points at `bl-009` as «the open codification question». bl-009 has been InProgress since 2026-06-02, asking for the very framework this session was being asked to design, and nothing had surfaced it in eleven weeks. The Q&A entry could point at the backlog item; the backlog item could point at nothing; and no session could be told that either bore on what it was doing.

Decision

TWO CONTRACTS, ONE FRAME, and every part of both has a consumer in the change that introduces it.

PART 1 — `reflection/schemas/reference-kind.ncl` + `reflection/kinds.ncl` (migration 0080).

A class of governed reference DECLARES itself: `role` (one line, for a reader, never a filter axis), `addressing` (`id_prefix`, `id_pad`, `route_prefix` — the id pattern is DERIVED from them so a declared pattern cannot disagree with them), `store`, `show` (optional), `lifecycle`, and `routable`. Both records are CLOSED, including the nested one: a field the contract does not name is a nickel export error, not a convention someone remembers.

From that one declaration are derived, once and for all kinds: id normalisation (`9`, `009`, `bl-9`, `bl-009` are one address), id dispatch, and route resolution. Dispatch is wired at the SINGLE point 43 command groups funnel through — `missing-target` — and the explicit form (`bkl show 9`) normalises through the same registry, because a rule applied on one path and not the other is two behaviours wearing one name.

`show` is OPTIONAL and the absence is load-bearing. `biop` has load / validate / run / resolve / consumers and no way to render one cell. Nothing had noticed until a registry asked all five kinds the same question. `kinds gaps` counts the absence; the dispatcher does not invent a verb the group does not have.

PART 2 — `reflection/schemas/becoming.ncl` (migration 0081).

The three responses and the rule that a hold states its return are LIFTED out of `cell.ncl`, and `cell.ncl` imports them rather than keeping a copy. `parent_ref` and `diff` are NOT lifted: they are what a CELL's becoming is — differentiation from a parent — and mean nothing on a Q&A entry. `qa` and `backlog` adopt `becoming | Log | default = []`.

It is an APPEND-ONLY LOG, never a status field. Validity is the LAST entry's response, or 'Active on an empty log — which is every occupant that already existed, which is why this landed on 54 Q&A entries and 94 backlog items without editing one of them. On the backlog `status` and `becoming` are two axes and must not be collapsed: `status` is the work state, `becoming` is validity, and a Done item whose fix was reverted is both Done and not current.

THE CONSUMER THAT MAKES PART 2 REAL. `qa search` — and therefore `ontoref q` and the orientation hook that calls it — NO LONGER OFFERS a retired entry, while `qa show <id>` still serves it under a banner carrying reason, successor and date. Offering and reading are opposite answers about the same entry, and that difference IS the distance between a historic reference and a deleted one. A 'Hold entry keeps being offered, carrying its reactivation criterion: a held reference that stops surfacing has been retired without saying so.

Constraints

  • Hard The reference-kind registry exports, and every kind that declares a `show` verb declares one the dispatcher actually has — a registry naming a verb that does not exist would send id dispatch to a subcommand that cannot answer.
  • Hard A bare number, a zero-padded number and a prefixed id are ONE address for a numeric kind, on the short path and the explicit path alike, and the padding comes from the registry rather than from a rule written again per kind.
  • Hard The three responses are declared in `schemas/becoming.ncl` and nowhere else. `cell.ncl` imports them; it does not keep a copy.
  • Hard A 'Hold entry without a reactivation criterion is refused by the contract, including one whose criterion is only whitespace — a hold with no way back is a retirement that will not admit it.
  • Hard A retired reference is dropped from what `qa search` OFFERS and remains served by `qa show <id>` under its validity banner; a held one is NOT dropped.
  • Hard `becoming` is an append-only array on every adopting kind, and current validity is derived from its last entry. A kind must not collapse it into a scalar status.
  • Hard A context query reaches every kind that declares `search_fields`, excludes retired references, ranks a head match above a body match, and NAMES the kinds it could not search.
  • Hard When the resolver bounds its shortlist, it reports how many candidates it did NOT load. A cap that says nothing reads as «nothing else matched», which is a different claim from «I did not look».
  • Hard Which governed references a session engaged is a typed array on the record, never recovered by matching text. The pointer event stays — it carries the act — but the filter axis is the field.
  • Hard A dependency between governed references is a typed edge with a required `why`, addressed in the frozen route vocabulary. An item cannot block itself, including behind a valid edge, and the inverse relation is derived rather than declared.
  • Hard A blocker whose kind declares no satisfaction condition is reported as UNDETERMINED — counted, listed, and neither treated as blocking nor as cleared.
  • Hard No validator may decide that a reference stopped being true by reading its prose. Staleness is asserted by a human or an agent in a `becoming` entry; the mechanism records the judgement and never makes it.

Alternatives considered

  • Add `depends_on`/`blocked_by` and a closure predicate to the backlog schema onlyrejected: The original request, and it was the wrong altitude — it would have made the newest complaint go away while leaving qa, terms, bonds and modes exactly as they were. It is also the fourth instance of the pattern this ADR exists to end: extend the kind in front of you rather than declare what every kind was re-deriving.
  • A mandatory closure witness on every backlog item and a mandatory kind on every Q&A entryrejected: A whole-corpus classification to satisfy a rule about a subset — 94 items and 54 entries to be triaged before anything works. `schemas/qa.ncl` had already refused exactly this shape in writing for its own `kind` field. Absence is reported instead, which is the confession idiom `governs confessions` already runs over ungateable constraints.
  • Give every kind a `status` enum with its own retired valuerejected: A status holds one word and forgets how it got there. A held reference that is later retired has two facts, and a reader deciding whether to trust it needs the second without losing the first. It would also have produced a fifth private vocabulary, which is the disease rather than the cure.
  • Copy `cell.ncl`'s `lineage` wholesale to every kindrejected: `parent_ref` is a content digest of a parent nucleus and `diff` is differentiation from it. Neither means anything on a Q&A entry, which has no parent. Copying them would push one tissue's history into every kind — precisely the move `cell.ncl` refused when it pushed routes out of the nucleus, for the reason it stated: a nucleus carrying its own history cannot be exported.
  • Delete stale references instead of retiring themrejected: It is what the absence of the field already forced, and its cost is measurable: no tombstone, so the next session re-derives the answer from nothing and cannot tell a question that was answered and retired from one that was never asked.
  • Auto-detect staleness and retire automaticallyrejected: No schema can read an answer and decide it stopped being true. A validator gating on that is a Hard biconditional on prose — the pattern adr-037 forbids by name for the interaction trace, and it applies here unchanged. The judgement stays human; only its RECORD is mechanised.

Anti-patterns

  • Extending the kind in front of you instead of declaring what every kind re-derives — A capability is missing on one kind, so it is added to that kind's schema. The next kind that needs it adds its own slightly different version. Four such copies existed before this ADR: amendment on ADR, lineage on cell, routes on biop, and a WarrantRef shape adr-schema.ncl documents itself as a deliberate duplicate.
  • Collapsing validity into a scalar status — Validity is stored as one word, so a reference that was held and then retired reports only the retirement, and a reader cannot see that a return condition was once declared. The backlog's one-way `status` is the instance that cost this project bl-089.
  • Deleting a stale reference rather than recording its death — A Q&A entry or item that stopped being true is removed. Nothing records that the question was ever asked or why the answer stopped holding, so the next session re-derives it from nothing and cannot distinguish an answered-and-retired question from one never asked.
  • Parking a reference without declaring what would revive it — A reference is marked held, deferred or dormant with no condition for its return. The word promises a comeback the record cannot deliver, and the reference is retired in everything but name.
  • Deciding a reference is stale by reading it — A validator reads an answer, a rationale or a detail and returns a verdict about whether it is still current. This is a Hard biconditional on a Spiral question and produces confident wrong retirements, which are worse than none.

Related ADRs

ADR-037 · ADR-069 · ADR-070 · ADR-072 · ADR-074 · ADR-078 · ADR-089 · ADR-096

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.