LearnNewsExamplesServices
Frontmatter
id15838
titlecheck-aiconfig-test-mutation keys on a literal identifier — the approved #13532 rename would silently make the B4 safety gate inert
stateClosed
labels
bugaiarchitecture
assigneesneo-opus-grace
createdAtJul 24, 2026, 8:32 PM
updatedAtJul 24, 2026, 9:48 PM
githubUrlhttps://github.com/neomjs/neo/issues/15838
authorneo-opus-grace
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 13532 Sweep the AiConfig SSOT singleton binding to consistent PascalCase (aiConfig → AiConfig)
closedAtJul 24, 2026, 9:48 PM

check-aiconfig-test-mutation keys on a literal identifier — the approved #13532 rename would silently make the B4 safety gate inert

Closed Backlog/active-chunk-9 bugaiarchitecture
neo-opus-grace
neo-opus-grace commented on Jul 24, 2026, 8:32 PM

Context

check-aiconfig-test-mutation is the fail-build guard for ADR-0019 §3 B4"SAFETY-CRITICAL — runtime writes to AiConfig" — the mechanism ADR-0019 §4 identifies as how test data bleeds into live DBs (the #12335 orphan incident, ~1,281 orphans reclaimed by purgeTestCollections). ADR-0019 §3 Group D/E is explicit that this lint, not reviewer diligence, is the structural answer: "reviewer diligence (empirically insufficient)."

The lint anchors on literal identifier names:

`\\b(?:aiConfig|Memory_Config)\\b[\\w.$[\\]'"\`-]*` + `(?:\\.${DB_PATH_LEAVES}\\b|…)` + `…\\s*=(?![=>])`

Anything binding the same singleton under a different name is invisible to it.

The Problem — two measured holes, one root cause

Probed directly against the exported DB_PATH_MUTATION:

source verdict
aiConfig.storagePaths.graph = dbPath FLAGGED
aiConfig['storagePaths'].graph = dbPath FLAGGED ✅ (documented bracket case holds)
Memory_Config.collections.memory = x FLAGGED
AiConfig.storagePaths.graph = dbPath NOT flagged
AiConfig['storagePaths'].graph = dbPath NOT flagged
mailboxAiConfig.storagePaths.graph = dbPath NOT flagged

1. The approved PascalCase sweep makes the gate inert. #13532 (open, @neo-opus-vega) normalizes 1,526 occurrences across 198 files from aiConfigAiConfig, on @tobiu's confirmed direction. The lint is case-sensitive, so after that sweep its pattern matches nothing in the repo.

The trap is #13532's own AC3: "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." That AC would be satisfied trivially — green because the lint has become inert, not because the mutations are gone. A safety gate would be retired by a refactor whose acceptance criteria certify the retirement as success. Neither ticket is wrong on its own; the interaction is invisible from inside either.

2. Aliased roots already evade it today. Measured across test/playwright/unit with the lint's own regex versus an alias-tolerant one: 18 spec files assign a Class-A DB-path leaf on a config-shaped root, 15 caught, 3 missedfleetMailboxMirrorAdapter.spec.mjs (4 occurrences, mirrorAiConfig), MailboxService.spec.mjs (7, mailboxAiConfig), MailboxService.ReceiptDurability.spec.mjs (3, mine — disclosed on PR #15824). 14 live B4 mutations invisible to a fail-build gate.

Note #12435 — the tracked B4 cleanup ADR-0019 §4 cites — is CLOSED. So the ADR's own footnote points at a finished ticket while the class is still reachable by rename.

The Architectural Reality

  • buildScripts/util/check-aiconfig-test-mutation.mjsDB_PATH_MUTATION, identifier-anchored; DB_PATH_LEAVES = (?:storagePaths|database|collections|logPath); ESCAPE_MARKER = 'aiconfig-mutation-ok'; plus a grandfathered file list.
  • The lint already does the hard part well: it tokenizes with acorn to avoid matching inside strings/comments/regex, and it deliberately excludes ==/=>. The tokenizer is not the weakness — the identifier anchor is.
  • #13532 is the forcing function: it is approved, assigned, and large.

The Fix (direction)

Match the structure, not the name. Options, cheapest first:

  1. Config-shaped root — accept any identifier matching /\\b[A-Za-z_$][\\w$]*(?:[Aa]i)?Config\\b/ (or an explicit alias list) before a Class-A leaf. Cheap, closes both holes, risks false positives on unrelated *Config objects — which the existing ESCAPE_MARKER already handles for genuinely-unavoidable cases.
  2. Resolve the binding via the AST — flag assignments whose object traces to a **/config.mjs default import. Precise, more work, and the acorn parse is already there.
  3. Both: (1) as the immediate guard, (2) as the durable one.

Sequencing is the point: this must land before #13532's sweep, or the sweep's green CI will certify a dead gate. Filed as a blocker on that ticket rather than a parallel improvement.

Acceptance Criteria

  • AiConfig.storagePaths.graph = x and AiConfig['storagePaths'].graph = x are FLAGGED — spec-pinned against the exported regex/checker, not asserted in prose.
  • An aliased root (mailboxAiConfig, mirrorAiConfig) is FLAGGED.
  • The three currently-invisible spec files are dispositioned: migrated to by-construction isolation, or grandfathered explicitly with the escape marker and a reason — never left silently passing.
  • Red-proof: reverting the matcher turns the new PascalCase and alias specs RED. A guard shipped without its own falsifier is the defect this ticket is about.
  • #13532's AC3 is amended to require the lint fire on a seeded PascalCase mutation rather than merely "stay green" — a green inert lint must not satisfy it.
  • ADR-0019 §4's footnote pointing at the closed #12435 is corrected to point at the live disposition.

Out of Scope

  • The PascalCase rename itself (#13532, @neo-opus-vega).
  • Widening beyond Class-A DB-path leaves; config-varying leaves stay out of scope as the lint already documents.
  • The .DS_Store / blocklist class (#15831) — unrelated substrate-classification gap.

Refs #13532, #15824, #12435

Authored by Grace (@neo-opus-grace, Claude Opus 4.8). Found while reading ADR-0019 for #15800's config-adjacent read gate — the gate caught a B4 mutation in my own just-pushed spec, and asking why the pre-commit check passed surfaced the identifier anchor.

tobiu closed this issue on Jul 24, 2026, 9:48 PM