Context
Two merged PRs changed how ai/ config declarations are written, and neither swept the documentation that describes them. Both gaps are mine: PR #15896 deleted the plane twin, and I authored the ADR §10.1 rewrite that retired its sanction; PR #15914 established a new declaration form, and I claimed the corresponding ADR clause in my review of it rather than requiring the author to route my documentation debt through their branch.
This is one failure class, not two tickets: the declaration-form documentation lags the declaration form. A future author reading either surface is told to write a shape the substrate no longer accepts.
The Problem
Surface 1 — ai/configBase.mjs describes a deleted architecture. Five JSDoc lines still narrate the twin module that #15896 removed:
13: // const (env-free twin resolution — the leaf machinery owns all env binding), so no member
57: * twin `ai/planeConfig.mjs` (ticket-ref-ok: ADR 0019 §5.5 names that module shape):
58: * the leaf imports the twin's frozen literals + env names, so leaf↔twin drift is
71: * The env layer routes through the twin's `parsePlaneIdEnv`, so a path-shaped
93: * 10s budget, so no twin indirection is warranted.
Line 57 is the worst of them, and its citation is worse than a dead link. §5.5 (ADR §5, list item 5) still resolves — it names the module-scope anchor as the one mechanical reason a literal may live outside the leaf. But its own parenthetical now reads "retires ... the pure-defaults-twin shape it sanctioned". So the comment cites, as its authority, the exact passage that abolished it.
Line 71 compounds it from a second direction: PR #15914 converted that declaration from a raw descriptor object to leaf(CANONICAL_PLANE_ID, 'NEO_PLANE_ID', 'string', {parse: parsePlaneIdEnv}). The sentence is now wrong about the twin and about the shape, from two PRs neither of which touched the sentence.
Surface 2 — ADR §5 item 2 documents a signature that can no longer express a sanctioned case. It reads:
- Leaves are declarative:
leaf(default, env, type). No inline env-ternaries (A4), no hasEnvValue (A5).
PR #15914 added a fourth parameter and made metadata.parse the override for the type-derived parser — and that was not cosmetic. It was the enabler for the parity-collector fix: before it, a leaf needing a custom env parser had to be written as a raw descriptor object literal, which the config-path collector classifies as a namespace rather than a leaf. Four live declarations were in that state and were converted in that PR (plane.id, two logLevels, memory.sharing.defaultPolicy).
Nothing now records that the raw-descriptor form is non-canonical. The ADR is the SSOT for declaration form; a future author reading §5 item 2 sees a 3-arg signature, needs a custom parser, and reaches for the descriptor object — reintroducing the exact shape #15914 removed, in a form the parity collector silently misreads.
The Architectural Reality
ai/configBase.mjs:13,57,58,71,93 — Tier-1 config, sibling to the five per-server configBase.mjs files (structure-map confirms the placement).
learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:99 — §5 item 2, the sanctioned-declaration line.
learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:107 — §5 item 5's parenthetical, which retires the twin shape line 57 cites as authority.
ai/ConfigProvider.mjs:67 — parse: env ? (metadata?.parse ?? typeParsers[resolvedType] ?? Env.parseString) : null, the resolution order §5 item 2 must now describe.
ai/scripts/lint/lint-config-template-ssot.mjs — the collector whose isDescriptor test ('default' in v && 'env' in v && 'type' in v) is why a raw descriptor missing type classifies as a namespace. Relevant beyond parity: the module-scope capture rule allows namespace-proxy captures and forbids primitive-leaf captures, so the misclassification can let a real leaf capture pass.
The Fix
- Rewrite the five
ai/configBase.mjs JSDoc lines to describe the shipped shape — one shared constant module, leaves declaring from it, the env layer bound by leaf's own parse hook. Drop the §5.5 citation entirely rather than repointing it: the durable-comment rule (check-ticket-archaeology) wants behavior, and the behavior is statable without any ADR reference.
- Amend ADR §5 item 2 to state the 4-arg form, name
metadata.parse as the sanctioned way to declare a custom env parser, and record that a raw descriptor object literal is non-canonical — with the collector consequence, so the reason travels with the rule.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
leaf(default, env, type, metadata) — documented signature |
ADR-0019 §5 item 2 |
Records 4-arg form; metadata.parse overrides the type-derived parser |
n/a — documentation of shipped behavior, no code change |
ADR §5 item 2 |
ai/ConfigProvider.mjs:67; test/playwright/unit/ai/ConfigProvider.spec.mjs pins the override + the env-free-null case |
| Raw descriptor object literal as a declaration form |
ADR-0019 §5 item 2 (new clause) |
Declared non-canonical; leaf() is the form |
n/a |
ADR §5 item 2 |
PR #15914 converted all four live instances; collector isDescriptor requires type |
ai/configBase.mjs plane JSDoc |
the shipped module + #15896 |
Describes one shared constant module, no twin |
n/a |
in-file JSDoc |
ai/planeConfig.mjs exports CANONICAL_PLANE_ID + resolvePlaneDataRoot only |
Decision Record impact
amends ADR 0019 — additive to §5 item 2 (records a shipped signature change and names a non-canonical form). It does not challenge or supersede: §5 item 5's retirement of the twin already landed, and this only stops the retired shape being reachable through a stale sentence.
Acceptance Criteria
Out of Scope
- Any mechanical guard against raw descriptor objects. This ticket documents; it does not enforce. A lint rule rejecting descriptor-shaped literals in config trees is a separate, larger change and would need its own red proof. Named here so the absence is a recorded decision rather than an oversight.
- Changing
leaf() or the collector. Both shipped in #15914 and are correct; this only records them.
- The remaining
isDescriptor sensitivity (a raw descriptor missing type classifying as a namespace, which can let a module-scope leaf capture pass). Pre-existing, no live instances after #15914, and it belongs with the guard above.
Avoided Traps
- Repointing the §5.5 citation instead of removing it. The tempting minimal edit is to update the section reference. It would re-create the same rot on the next ADR reshape, and the durable-comment rule exists precisely because tracking refs decay. State the behavior; drop the ref.
- Filing this as two tickets. The JSDoc and the ADR clause are one failure class in one neighbourhood, and both are one PR's worth of work. Splitting would be the over-fragmentation anti-pattern.
- Treating the ADR gap as §3 (antipattern catalog). My own first framing, and wrong: §3 catalogues what not to do, but the gap is that the sanctioned-patterns list documents a signature that can no longer express a sanctioned case. §5 item 2 is the home.
Related
#15896 (twin removal — the JSDoc residue is its unswept half), #15914 / #15908 (the collector fix that established metadata.parse), ADR-0019 §5, §10.1.
Live latest-open sweep: checked latest 20 open issues at 2026-07-25T18:00Z; no equivalent found. A2A in-flight sweep: 30 most recent messages scanned; active claims are parity-chain (#15803), consumer census (#15879), wake-side (#15919), and film lanes — none overlap config-declaration documentation.
Origin Session ID: 26e73986-66fa-4d28-9b02-6053541a5671
Retrieval Hint: "configBase twin JSDoc residue ADR-0019 leaf metadata.parse declaration form"
Context
Two merged PRs changed how
ai/config declarations are written, and neither swept the documentation that describes them. Both gaps are mine: PR#15896deleted the plane twin, and I authored the ADR §10.1 rewrite that retired its sanction; PR#15914established a new declaration form, and I claimed the corresponding ADR clause in my review of it rather than requiring the author to route my documentation debt through their branch.This is one failure class, not two tickets: the declaration-form documentation lags the declaration form. A future author reading either surface is told to write a shape the substrate no longer accepts.
The Problem
Surface 1 —
ai/configBase.mjsdescribes a deleted architecture. Five JSDoc lines still narrate the twin module that#15896removed:Line 57 is the worst of them, and its citation is worse than a dead link. §5.5 (ADR §5, list item 5) still resolves — it names the module-scope anchor as the one mechanical reason a literal may live outside the leaf. But its own parenthetical now reads "retires ... the pure-defaults-twin shape it sanctioned". So the comment cites, as its authority, the exact passage that abolished it.
Line 71 compounds it from a second direction: PR
#15914converted that declaration from a raw descriptor object toleaf(CANONICAL_PLANE_ID, 'NEO_PLANE_ID', 'string', {parse: parsePlaneIdEnv}). The sentence is now wrong about the twin and about the shape, from two PRs neither of which touched the sentence.Surface 2 — ADR §5 item 2 documents a signature that can no longer express a sanctioned case. It reads:
PR
#15914added a fourth parameter and mademetadata.parsethe override for the type-derived parser — and that was not cosmetic. It was the enabler for the parity-collector fix: before it, a leaf needing a custom env parser had to be written as a raw descriptor object literal, which the config-path collector classifies as a namespace rather than a leaf. Four live declarations were in that state and were converted in that PR (plane.id, twologLevels,memory.sharing.defaultPolicy).Nothing now records that the raw-descriptor form is non-canonical. The ADR is the SSOT for declaration form; a future author reading §5 item 2 sees a 3-arg signature, needs a custom parser, and reaches for the descriptor object — reintroducing the exact shape
#15914removed, in a form the parity collector silently misreads.The Architectural Reality
ai/configBase.mjs:13,57,58,71,93— Tier-1 config, sibling to the five per-serverconfigBase.mjsfiles (structure-map confirms the placement).learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:99— §5 item 2, the sanctioned-declaration line.learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:107— §5 item 5's parenthetical, which retires the twin shape line 57 cites as authority.ai/ConfigProvider.mjs:67—parse: env ? (metadata?.parse ?? typeParsers[resolvedType] ?? Env.parseString) : null, the resolution order §5 item 2 must now describe.ai/scripts/lint/lint-config-template-ssot.mjs— the collector whoseisDescriptortest ('default' in v && 'env' in v && 'type' in v) is why a raw descriptor missingtypeclassifies as a namespace. Relevant beyond parity: the module-scope capture rule allows namespace-proxy captures and forbids primitive-leaf captures, so the misclassification can let a real leaf capture pass.The Fix
ai/configBase.mjsJSDoc lines to describe the shipped shape — one shared constant module, leaves declaring from it, the env layer bound byleaf's ownparsehook. Drop the §5.5 citation entirely rather than repointing it: the durable-comment rule (check-ticket-archaeology) wants behavior, and the behavior is statable without any ADR reference.metadata.parseas the sanctioned way to declare a custom env parser, and record that a raw descriptor object literal is non-canonical — with the collector consequence, so the reason travels with the rule.Contract Ledger Matrix
leaf(default, env, type, metadata)— documented signaturemetadata.parseoverrides the type-derived parserai/ConfigProvider.mjs:67;test/playwright/unit/ai/ConfigProvider.spec.mjspins the override + the env-free-null caseleaf()is the form#15914converted all four live instances; collectorisDescriptorrequirestypeai/configBase.mjsplane JSDoc#15896ai/planeConfig.mjsexportsCANONICAL_PLANE_ID+resolvePlaneDataRootonlyDecision Record impact
amends ADR 0019— additive to §5 item 2 (records a shipped signature change and names a non-canonical form). It does not challenge or supersede: §5 item 5's retirement of the twin already landed, and this only stops the retired shape being reachable through a stale sentence.Acceptance Criteria
twinremains inai/configBase.mjs; the replacement prose names the shipped shape (shared constant module + leaf-bound env) rather than deleting the explanation.ai/configBase.mjscarries no ADR section citation for the plane declaration; theticket-ref-okmarker on line 57 is removed with it.leaf(default, env, type, metadata)and namesmetadata.parseas the sanctioned custom-parser declaration.npm run --silent ai:lint-config-template-ssot(or the lint entry) stays green — documentation-only change must not move the parity surface.twinhits inai/configBase.mjs, and the ADR line quoted before/after.Out of Scope
leaf()or the collector. Both shipped in#15914and are correct; this only records them.isDescriptorsensitivity (a raw descriptor missingtypeclassifying as a namespace, which can let a module-scope leaf capture pass). Pre-existing, no live instances after#15914, and it belongs with the guard above.Avoided Traps
Related
#15896(twin removal — the JSDoc residue is its unswept half),#15914/#15908(the collector fix that establishedmetadata.parse), ADR-0019 §5, §10.1.Live latest-open sweep: checked latest 20 open issues at 2026-07-25T18:00Z; no equivalent found. A2A in-flight sweep: 30 most recent messages scanned; active claims are parity-chain (
#15803), consumer census (#15879), wake-side (#15919), and film lanes — none overlap config-declaration documentation.Origin Session ID: 26e73986-66fa-4d28-9b02-6053541a5671
Retrieval Hint: "configBase twin JSDoc residue ADR-0019 leaf metadata.parse declaration form"