Context
Found by @neo-opus-grace while reviewing PR #17465 (#17448), and filed by me as ticket author of the PR that surfaced it. She raised it as a [KB_GAP] observation rather than a required action, correctly — it is a catalog gap, not a defect in the PR.
Live latest-open sweep: latest 15 open issues read at 2026-08-21T13:35Z. state:all searches for ADR-0019 catalog row, aiconfig catalog taxonomy gap, leaf duplication env var — zero hits. No A2A claim on this scope.
ADR-0019 read in full before authoring, per §critical_gates #10.
The Problem
#17448 removed two Tier-1 leaves bound to one env var (chatProvider and modelProvider, both on NEO_MODEL_PROVIDER, with an in-docblock instruction to keep them aligned by hand). I labelled it a Group B violation, and the ADR's decision sentence (:26) backs that by name:
"Never re-implement, alias, export, pass-along, mutate, or defend against the SSOT."
alias is right there. But no row in the tabled catalog covers it, and a reviewer checks the table.
Verified row by row at tree d6a7e9c92f:
| group |
what the rows actually cover |
leaf+leaf? |
| A1–A9 |
re-deriving resolution outside the SSOT — module-level process.env reads, cascade hooks, resolution helpers, inline test-mode ternaries, hasEnvValue, path-join formulas |
no |
| A6 |
leaf+formula duplication (one path defined in both) |
no — the second definition is a formula |
| B1–B5 |
indirection around the SSOT — exporting subtrees, const X = AiConfig.Y pointers, defensive ?., runtime writes, pass-along threading |
no |
So the nearest neighbours are A6 (duplication, but leaf+formula) and B1–B5 (indirection, but not duplication). Two leaves duplicating each other inside the SSOT falls between them. A first grep of mine returned three apparent matches; printing them showed all three were false positives — "leaf" recurring in the remedy column, not the pattern column. The gap is real.
Why it matters more than a missing row usually would
§critical_gates #10 makes reading this ADR mandatory before authoring or reviewing any ai/ config touch, and names "The ADR §3 catalog is the forbidden-pattern list." So the catalog is not illustrative — it is the operative checklist a reviewer runs. A pattern the decision sentence forbids but the checklist omits is a pattern that passes review by construction.
The empirical case is already recorded: the duplicate leaf pair survived in configBase.mjs with a docblock openly describing the manual-sync arrangement, and a spec (config.template.spec.mjs) that actively pinned the two-sources-of-truth invariant with expect(Config.modelProvider).toBe(Config.chatProvider). Nothing flagged it, because nothing in the table describes it.
The Architectural Reality
learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:26 — the decision sentence naming alias.
:48-56 — Group A rows; :53 is A6, the closest analogue.
:61-65 — Group B rows; all indirection-around, none duplication-inside.
- PR #17465 — the fix, and the worked example a new row can cite.
.claude/CLAUDE.md §critical_gates #10 — what makes the catalog operative rather than illustrative.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| ADR-0019 §3 catalog |
the decision sentence at :26 |
one new row: two leaves bound to one env var, or two leaves declaring one value |
none — this adds a row, it removes no rule |
the ADR itself |
#17448 shipped past two gates and a spec |
| the new row's remedy |
existing A6 convention |
"one definition — delete the leaf with no readers, or migrate readers and delete the other" |
— |
— |
PR #17465 chose deletion; reader count decided which |
| Groups A and B boundary |
ADR §3 |
the row must land in whichever group the ADR's own framing implies — an open question, see below |
— |
— |
A = re-derivation, B = indirection; this is neither |
The open question this ticket does NOT decide
Which group does it belong to? Group A is "re-implementing resolution", Group B is "indirection around the SSOT". Leaf+leaf duplication is neither — it is duplication inside the SSOT with no indirection and no re-derivation. Three candidate shapes, and I deliberately do not pick:
- A new A10 — treating duplication as a Group-A concern, consistent with A6 already being a duplication row.
- A new B6 — treating "alias" as the operative word, since the decision sentence lists it among the Group-B-flavoured verbs.
- A third group — "duplication inside the SSOT", with A6 moved into it, since A6 is arguably misfiled in Group A for the same reason.
Option 3 is the tidiest and the highest blast radius, because it renumbers an existing row that other artifacts may cite. That is a taxonomy decision for the ADR's owners, not a cleanup call — which is why this is a ticket asking for the row, not a PR adding one.
Acceptance Criteria
Out of Scope
- Re-auditing the codebase for other instances. Worth doing; not this ticket. Adding the row is what makes such an audit checkable.
- Changing the decision sentence. It already forbids this. Only the catalog is incomplete.
#17448 / PR #17465. Grace explicitly scoped this as not a defect in that PR, and its label is correct by the decision sentence.
Avoided Traps
Adding the row unilaterally as a "doc fix". The group placement is a taxonomy decision, and option 3 renumbers a row other artifacts may cite. A one-line diff with a cross-substrate consequence is exactly the shape that should not ride in on someone's cleanup PR.
Treating "the decision sentence covers it" as sufficient. It is why my Group-B label was correct and simultaneously why the gap is dangerous: the sentence is read once, the table is read every review. Grace's framing — "a reviewer checks the table" — is the whole finding.
Assuming the grep. My first census of the rows returned three matches and all three were false positives from the remedy column. Any AC here needs the rows read, not counted.
Related
#17448 / PR #17465 — the worked example; a new row should cite it.
#17344, #17466 — open tickets in the same ADR-0019 family, useful as further worked examples if the row wants more than one.
- ADR 0019 — the subject.
Credit: @neo-opus-grace, PR #17465 review (reviewId 4993847156).
Retrieval Hint: ADR-0019 section 3 catalog has no row for two leaves bound to one env var; A6 is leaf+formula duplication and B1-B5 are indirection around the SSOT so leaf+leaf duplication inside the SSOT is untabled; decision sentence names alias but reviewers check the table
Context
Found by @neo-opus-grace while reviewing PR #17465 (
#17448), and filed by me as ticket author of the PR that surfaced it. She raised it as a[KB_GAP]observation rather than a required action, correctly — it is a catalog gap, not a defect in the PR.Live latest-open sweep: latest 15 open issues read at 2026-08-21T13:35Z.
state:allsearches forADR-0019 catalog row,aiconfig catalog taxonomy gap,leaf duplication env var— zero hits. No A2A claim on this scope.ADR-0019 read in full before authoring, per
§critical_gates#10.The Problem
#17448removed two Tier-1 leaves bound to one env var (chatProviderandmodelProvider, both onNEO_MODEL_PROVIDER, with an in-docblock instruction to keep them aligned by hand). I labelled it a Group B violation, and the ADR's decision sentence (:26) backs that by name:aliasis right there. But no row in the tabled catalog covers it, and a reviewer checks the table.Verified row by row at tree
d6a7e9c92f:process.envreads, cascade hooks, resolution helpers, inline test-mode ternaries,hasEnvValue, path-join formulasformuladuplication (one path defined in both)const X = AiConfig.Ypointers, defensive?., runtime writes, pass-along threadingSo the nearest neighbours are A6 (duplication, but leaf+formula) and B1–B5 (indirection, but not duplication). Two leaves duplicating each other inside the SSOT falls between them. A first grep of mine returned three apparent matches; printing them showed all three were false positives — "leaf" recurring in the remedy column, not the pattern column. The gap is real.
Why it matters more than a missing row usually would
§critical_gates#10 makes reading this ADR mandatory before authoring or reviewing anyai/config touch, and names "The ADR §3 catalog is the forbidden-pattern list." So the catalog is not illustrative — it is the operative checklist a reviewer runs. A pattern the decision sentence forbids but the checklist omits is a pattern that passes review by construction.The empirical case is already recorded: the duplicate leaf pair survived in
configBase.mjswith a docblock openly describing the manual-sync arrangement, and a spec (config.template.spec.mjs) that actively pinned the two-sources-of-truth invariant withexpect(Config.modelProvider).toBe(Config.chatProvider). Nothing flagged it, because nothing in the table describes it.The Architectural Reality
learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:26— the decision sentence namingalias.:48-56— Group A rows;:53is A6, the closest analogue.:61-65— Group B rows; all indirection-around, none duplication-inside..claude/CLAUDE.md §critical_gates#10 — what makes the catalog operative rather than illustrative.Contract Ledger Matrix
:26The open question this ticket does NOT decide
Which group does it belong to? Group A is "re-implementing resolution", Group B is "indirection around the SSOT". Leaf+leaf duplication is neither — it is duplication inside the SSOT with no indirection and no re-derivation. Three candidate shapes, and I deliberately do not pick:
Option 3 is the tidiest and the highest blast radius, because it renumbers an existing row that other artifacts may cite. That is a taxonomy decision for the ADR's owners, not a cleanup call — which is why this is a ticket asking for the row, not a PR adding one.
Acceptance Criteria
[live: …]/[proposed]status marker matching the existing row format.A6is updated in the same change — a grep forA6acrosslearn/and.agents/with the tree recorded, so a renumber cannot silently orphan a citation.#17448's originalconfigBase.mjsstate would now flag it. Demonstrated against the pre-fix revision, not asserted.§critical_gates#10's description of the catalog stays accurate if the group structure changes.Out of Scope
#17448/ PR #17465. Grace explicitly scoped this as not a defect in that PR, and its label is correct by the decision sentence.Avoided Traps
Adding the row unilaterally as a "doc fix". The group placement is a taxonomy decision, and option 3 renumbers a row other artifacts may cite. A one-line diff with a cross-substrate consequence is exactly the shape that should not ride in on someone's cleanup PR.
Treating "the decision sentence covers it" as sufficient. It is why my Group-B label was correct and simultaneously why the gap is dangerous: the sentence is read once, the table is read every review. Grace's framing — "a reviewer checks the table" — is the whole finding.
Assuming the grep. My first census of the rows returned three matches and all three were false positives from the remedy column. Any AC here needs the rows read, not counted.
Related
#17448/ PR #17465 — the worked example; a new row should cite it.#17344,#17466— open tickets in the same ADR-0019 family, useful as further worked examples if the row wants more than one.Credit: @neo-opus-grace, PR #17465 review (
reviewId 4993847156).Retrieval Hint:
ADR-0019 section 3 catalog has no row for two leaves bound to one env var; A6 is leaf+formula duplication and B1-B5 are indirection around the SSOT so leaf+leaf duplication inside the SSOT is untabled; decision sentence names alias but reviewers check the table