The operations catalog was never one thing — it splits into four populations, two retire now, two stay
Accepted
Context
adr-093 froze `catalog/` as a destination and bound its retirement to "a Rust reconciliation that has not happened", naming a minimum scope and explicitly declining to decide retire-vs- absorb-vs-keep. Its own amendments already corrected two premises measured wrong on the day they were written (`--invoke-op` was reachable the whole time; the coupling bullet overstated `criteria_validators.rs` by citing a test as a runtime reader). This ADR is the reconciliation adr-093 bound its exit to, and it corrects a THIRD premise: the freeze reasoned about `catalog/` as if it held two things — a dead operations mirror and a load-bearing validators directory. It held four, and the fourth was never named.
MEASURED 2026-09-01/02, walking every reader in the workspace rather than trusting the earlier census (which grepped `crates/` alone and missed the Nushell reflection tree entirely — the same scope-of-measurement error this project has now paid for twice, adr-093's own amendments being the first):
POPULATION A — eleven mutating-operation declarations (`close_dev_session`, `evaluate_ondaod`, `manage_backlog_item`, `move_fsm_state`, `propose_adr`, `start_dev_session`, `sync_apply`, `transition_adr`, `transition_tier`, `update_ontology_edge`, `update_ontology_node`). Confirmed dead a second time, more thoroughly: `dispatch_op` iterates `inventory::iter::<OperationEntry>()` with no file IO; `verify_catalog_coherence` (the function whose own comment calls the catalog "authoritative") iterates the SAME inventory and never opens a file; the only Rust reader is a test with ten hardcoded ids; every remaining Rust mention is a doc comment. Zero readers, anywhere, in any language.
POPULATION B — FOUR read-query declarations (`chronicle_query`, `criteria_query`, `panels_query`, `roadmap_query`) that adr-093's own amendment misread as evidence `criteria_query` "is not registered" — i.e. as MORE of population A's defect. It is not. `test_memory_j.nu` freezes their existence, parseability, `id` field and `effects = []` as a CONTRACT — because they are queries, not mutating operations, and were never meant to carry a `#[onto_operation]` counterpart. `presentation.nu`'s `declared-operations` reads them to validate panel `data_source.op` references, and does so correctly: `panel-chronicle.ncl`, `panel-roadmap.ncl` and `panel-dissemination-roadmap.ncl` all resolve or, in the one case that does not (`dissemination_query`, planned and never authored because adr-093 froze exactly the destination it would have been written to), CONFESS what they are pending on rather than silently failing — `presentation validate --check data-source-is-declared-operation` PASSES today, verified live, citing that confession by name.
POPULATION C — `catalog/validators/*.ncl` (2 files). Was load-bearing via `daemon/memory.rs:212`'s `criteria_list`. Also, MEASURED THIS SESSION and missed by the original census: two Nushell TIER-0 fallbacks depend on the files directly — `chronicle.nu :: criteria-fallback` (the no-daemon path behind `criteria list`) and `coder.nu :: coder prompt-data`'s validator-id listing. Repointing only the Rust/daemon path, as an earlier draft of this reconciliation did, would have retired the directory out from under two tier-0 consumers ADR-029's `no-forced-tier-migration` exists to protect.
POPULATION D — `catalog/schema.ncl`. Types A, B and C. Stays as long as any of them do; nothing in this ADR touches it.
WHAT WAS BUILT TO CLOSE THE GAP POPULATION C EXPOSED, verified live, not merely written:
- `ontoref_ops::registry::dump_catalog_ncl` / `--dump-op-catalog` — projects `OperationEntry` as NCL, derived, never hand-mirrored. Already existed when this ADR's reconciliation began; used here to confirm population A's eleven ids have no Rust counterpart and population B's four never had one by design. - `#[onto_validator]`'s optional `criterion_ref` envelope and `CriterionEntry` — `ValidatorEntry` extended for nine op-validators that have no criterion would have shipped dead fields to nine of eleven entries (adr-030's `'NclTransform`/`'Wasm` mistake, repeated). A second, conditionally-emitted inventory type avoids it, joined by `predicate_ref`, never merged into one struct. - `ontoref_ops::validation::dump_criteria_ncl` / `--dump-criteria-catalog` — the same projection for population C. `description` was added to `CriterionEntry` after the first version shipped with it blank: the daemon path joins `ValidatorEntry` at read time, but the ONE consumer this flag exists for — a no-daemon Nushell caller — has nothing else to join against. One macro-time source (`#[onto_validator(description = "...")]`), written into both entries. - `store.nu :: daemon-dump-ncl` — one helper for "mktemp under `.ontoref/`, run a `--dump-*` one-shot, `nickel export`, clean up", shared by `validate.nu`'s mutation-sovereignty check and `chronicle.nu`'s criteria fallback, rather than a third hand-copy — the exact registry-shadowed-by-a-local-copy shape this project has paid for repeatedly. `daemon-bin` moved here from `sync.nu`, which had it for one caller's convenience, to where a daemon-client primitive belongs. - `chronicle.nu :: criteria-fallback` and `coder.nu`'s validator-id listing — repointed. `id`/`validator_ids` are now `predicate_ref` (snake_case) rather than the old kebab-case NCL `id`, matching the switch `memory.rs :: criteria_list` already made; nothing downstream reads the old spelling (`validator_ids` is display-only, joined into a prompt-pack line).
TWO BUGS THE BUILDING CAUGHT, neither hypothetical: `do {} \| complete` does not work on an internal Nushell function call (only external commands) — the first `criteria-fallback` draft used it, and the mistake was caught by running it, not by review. And `chronicle.nu`'s own `ontoref-root` (returns `.ontoref`) is not `shared.nu`'s `project-root` (returns the constellation root) — passing the wrong one doubled `daemon-dump-ncl`'s scratch path and the resulting error was silently absorbed by `criteria-fallback`'s own `try`/`catch`, printing `[]` with no visible fault. Live output, not exit codes, is what caught it.
WHAT THIS ADR DOES NOT REOPEN. The `biop` reconciliation adr-093's second amendment already closed — a biop PRODUCES an operation, it does not replace the catalog's mechanism, and the generative step (`evaluate_biop`, a real `#[onto_operation]` witnessing a nucleus evaluation) is built and tested as of the session immediately preceding this one. That work is independent of which of the four catalog populations retire.
Decision
POPULATION A RETIRES NOW. The eleven mutating-operation declarations under `.ontoref/catalog/operations/` are deleted by migration 0093. No consumer is affected: of the fourteen registered projects, only this one carries content, and `dispatch_op` never read the directory to begin with — deleting it changes nothing any running system observes.
POPULATION C RETIRES NOW, CONDITIONALLY ON WHAT THIS ADR ALSO REQUIRES: that both tier-0 Nushell fallbacks (`chronicle.nu`, `coder.nu`) and the daemon path (`memory.rs`) already read from `--dump-criteria-catalog` / `dump_criteria_ncl`'s inventory instead of the files. That condition is MET as of this ADR's acceptance — verified live, not assumed — so `.ontoref/catalog/validators/*.ncl` (2 files) is deleted by the same migration.
POPULATION B STAYS, RENAMED IN UNDERSTANDING BUT NOT IN LOCATION. `chronicle_query`, `criteria_query`, `panels_query`, `roadmap_query` remain exactly where they are, under `.ontoref/catalog/operations/`. They are not operations that failed to get a Rust body; they are a DIFFERENT KIND — declarative read-query metadata, contract-tested by `test_memory_j.nu`, read by `presentation.nu` for panel validation. `qa:ontoref-catalog-extension-howto` is corrected to say so explicitly, because the previous text's silence on this distinction is what let this ADR's own predecessor misread `criteria_query`'s absence from `dispatch_op`'s inventory as more evidence of the same defect population A actually has.
POPULATION D STAYS UNCHANGED. `catalog/schema.ncl` continues to type population B (and would type a future population A occupant, or C occupant, should either ever be re-populated — nothing in this ADR forbids authoring there again; `no-new-catalog-operation` is not re-imposed, because the mirror it protected is gone and nothing is left to protect from growth).
`layout.rs`'s `catalog_dir` FIELD DOES NOT CHANGE. The premise that motivated splitting it — operations dead, validators load-bearing, so separate their layout sections before deciding either's fate — dissolved once population C's readers were migrated rather than the section split: both populations B and D still live under one `catalog_dir`, nothing in Rust runtime reads either subdirectory, and a directory-level split protects nothing that still needs protecting. Splitting a layout section to prepare for a retirement that then makes the split unnecessary would have been exactly the premature-formalization this project's own ondaod baseline forbids.
THE HOWTO IS CORRECTED, NOT RE-FROZEN. `qa:ontoref-catalog-extension-howto` drops the adr-093 freeze language (Population A's destination no longer exists to redirect away from) and states plainly: population B is a declarative-query kind with no Rust counterpart by design; authoring a NEW mutating operation goes through `#[onto_operation]` directly (population A's own destination, which was never the problem — the problem was a hand-written NCL mirror nothing read); authoring a new criterion validator goes through `#[onto_validator]`'s `criterion_ref` envelope.
adr-093 is marked `'Superseded`. Its operative content is not wrong — the freeze was the correct call on the evidence it had, twice amended to the evidence it was missing — but its own text names the reconciliation this ADR performs as the condition for its own retirement, and that condition is now met.
Constraints
- Hard None of the eleven retired operation ids or the two retired validator files exist under .ontoref/catalog/ after migration 0093 runs.
- Hard All four query declarations (chronicle_query, criteria_query, panels_query, roadmap_query) still exist under .ontoref/catalog/operations/ and still typecheck against OperationDecl.
- Hard qa:ontoref-catalog-extension-howto no longer instructs a reader that the catalog is frozen, and states that the four query declarations are a distinct kind with no #[onto_operation] counterpart by design.
Alternatives considered
- Retire population B too, replacing the four query declarations with something derived from memory.rs directly. — rejected: Population B is not dead — it is read correctly by presentation.nu and frozen as a contract by test_memory_j.nu. Retiring a working, tested surface to make the catalog directory conceptually cleaner would be exactly the derived-criterion trap this project's ondaod baseline names: 'the catalog should be simpler' is not the declared goal, and no consumer is asking for it.
- Rename catalog/operations/ to separate population B (queries) from where population A used to live. — rejected: A rename touches presentation.nu's declared-operations, test_memory_j.nu's fixture paths, and three panel files' data_source.op resolution, all of which work correctly today — for a naming clarity gain with no functional motivation. Filed as residue below rather than done reflexively alongside an unrelated retirement.
- Keep population C (validators) in place alongside its now-derived replacement, rather than deleting the files. — rejected: A file and its derived projection coexisting is exactly the hand-synchronised-mirror shape this whole reconciliation exists to close — the two would read identically today and silently diverge the first time either side changes without the other. Deleting the files is what makes the projection the only source rather than a second one that happens to agree for now.
- Wait for a consumer project to actually need catalog/ retired before doing anything. — rejected: adr-093 already tried the 'wait for a real need' posture on the ACT half of the reconciliation and adr-105's differential-test discipline argues against waiting for demand where the fix is this cheap and this verified. Ten consumer projects carry an empty skeleton of exactly what retires here; none of them is worse off, and the protocol's own declarative surface stops lying about what it contains.
Anti-patterns
- Declaring a surface fully retired on evidence gathered from one language's call sites — A reader retirement is measured by grepping the crates most familiar to the question — Rust, in a protocol whose reflection layer is Nushell — and the retirement proceeds on that partial count. A real consumer in the language not searched breaks silently, often absorbed by that consumer's own error handling into something that reads as a graceful no-op rather than a fault.
Related ADRs
ADR-024 · ADR-026 · ADR-029 · ADR-030 · ADR-032 · ADR-033 · ADR-034 · ADR-050 · ADR-089 · ADR-092 · ADR-093