LearnNewsExamplesServices
Frontmatter
id15325
titleNo mechanical leaf-parity guard: a config leaf can vanish in a refactor and every gate stays green
stateOpen
labels
enhancementaitesting
assigneesneo-opus-ada
createdAt1:35 AM
updatedAt5:31 AM
githubUrlhttps://github.com/neomjs/neo/issues/15325
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

No mechanical leaf-parity guard: a config leaf can vanish in a refactor and every gate stays green

Open Backlog/active-chunk-6 enhancementaitesting
neo-opus-ada
neo-opus-ada commented on 1:35 AM

Context

Found while running a reviewer falsifier on PR #15314 (server config overlays inherit, #15294). Not that PR's defect — it neither introduced this hole nor claimed to close it, and its own split is provably clean. But the falsifier only exists because I happened to run it, and that is the problem.

The Problem

Nothing mechanically proves a config leaf survives a refactor. PR #15314 moved ~2,000 lines out of five config.template.mjs files into sibling configBase.mjs files. A leaf dropped in that move reads undefined at runtime, and none of the three guards would have noticed:

  1. ai/scripts/lint/lint-config-template-ssot.mjs classifies template↔proxy paths. Grepped it: no parity check of any kind. It cannot see a leaf that vanished.
  2. test/playwright/unit/ai/config.template.spec.mjs asserts ledger placement — that config ledgers live inside config classes. PR #15314's diff simply redirects that assertion from the templates to the new base files. It never counts leaves.
  3. CI green says nothing about a leaf whose only consumer has no test.

I verified PR #15314's split by hand — dev template leaf-count vs PR (base + thin template), per server:

server dev PR (base+tpl) delta
github-workflow 41 41 0
gitlab-workflow 7 7 0
knowledge-base 56 56 0
memory-core 106 106 0
neural-link 17 17 0

227 leaves, zero lost. That census is the evidence the PR deserved — and it exists only because a reviewer thought to run it at 23:40. The next base/template edit has no such guard.

Why this class is worth mechanizing rather than remembering: a dropped leaf is silent at every gate and loud only at runtime, in a peer's process, as undefined. That is the same shape as the drift class PR #15314 just eliminated — a config truth that no gate could see. ADR-0019 §D/E is explicit that the structural answer is the lint, not reviewer diligence, which is "empirically insufficient" (4/4 missed on #12420). A hand-run census is reviewer diligence wearing a table.

The Architectural Reality

  • ai/mcp/server/*/configBase.mjs — post-#15314, defaults and leaves live here.
  • ai/mcp/server/*/config.template.mjs — thin subclass claiming the runtime namespace.
  • ai/scripts/lint/lint-config-template-ssot.mjs — already parses both files and computes the union of declared paths for classification (its own JSDoc says so). It already has the data. It just never asserts anything about the set surviving.
  • ADR-0019 §7.2 — the lint is the sanctioned home for the flaggable subset of §3.

The lint already reads what a parity check needs. This is closer to an assertion than a new capability.

The Fix

Give the leaf set a guard that outlives the reviewer who noticed:

  1. Add a parity assertion to the SSOT lint family: the declared leaf-path set of a configBase.mjs + its thin config.template.mjs must be checkable against a committed expectation, so a leaf cannot silently leave the union.
  2. Fail loud with the missing path(s) — not a count. "106 → 105" tells nobody which leaf died.
  3. A deliberate removal updates the expectation in the same commit, which is the point: leaf removal becomes a reviewable act rather than an invisible one.

Explicitly rejected: a count-only check. A count is satisfied by deleting one leaf and adding another, which is exactly the refactor that would hide the bug.

Open question worth deciding rather than guessing: whether the expectation is a committed snapshot of declared paths, or a base↔template invariant checked structurally. The former catches removals across time; the latter catches only same-commit split errors. The removal-across-time case is the one that bit us, so the snapshot shape is likelier correct — but that is a design call for whoever takes this.

Acceptance Criteria

  • Deleting any single leaf from a configBase.mjs fails the lint, and the failure names the exact missing path — verified by deleting one and observing the message.
  • A deliberate leaf removal passes once the expectation is updated in the same commit.
  • A leaf renamed (removed + added) fails rather than netting to zero — the count-only trap.
  • The check covers all five servers plus the Tier-1 root.
  • No false positive on PR #15314's shipped state (227 leaves across five servers is the known-good baseline).

Out of Scope

  • PR #15314 itself — approved, merge-eligible, split provably lossless.
  • The overlay-drift class — #15294 removed it by construction (inheritance), which is the correct fix and not to be re-litigated with a detector.
  • Broader ADR-0019 catalog lint coverage (#12456 sub #2).

Avoided Traps

  • Blaming PR #15314. It didn't dig this hole; its own census is clean.
  • Proposing a drift detector for the drift class. That class is already dead by construction. This ticket is about leaf loss, a different failure.
  • Trusting a hand-run census. ADR-0019 §D/E settles that reviewer diligence is not the structural answer. If this stays a thing reviewers remember to do, it will be missed.

Related

  • PR #15314 / #15294 — where this surfaced; its 227-leaf census is the baseline.
  • ADR-0019 §3, §7.2, §D/E — the lint is the sanctioned home; diligence is empirically insufficient.
  • #12456 / #12457 — the ADR + lint stack.

Live latest-open sweep: open issues checked 2026-07-16 ~23:42Z; searched config leaf parity lint — no equivalent found.

Retrieval Hint: config leaf parity lint configBase template split silent leaf loss 227 leaves

Origin Session ID: ad475320-6bdc-4555-ba3f-b78d51de0b17