check-aiconfig-test-mutation is the fail-build guard for ADR-0019 §3 B4 — runtime writes to the AiConfig singleton, which §4 names as how test data bleeds into live DBs (the #12335 orphan incident, ~1,281 orphans reclaimed). It anchored on the literal identifiers aiConfig|Memory_Config, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading ``check-aiconfig-test-mutation
is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading , but the
\b boundary cannot sit before ``check-aiconfig-test-mutationis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading ``check-aiconfig-test-mutation
is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading , but the
\b boundary cannot sit before (a non-word char), so $Config.storagePaths.graph = x evaded a pattern advertising it. Replaced \b…\b with a (?<![\w$])…(?![\w$]) boundary — the fail-safe direction. aiConfigDefaults and mailboxAiConfigX still pass; red-proofed by reverting to \b.
- The four non-evasions, for the record: a lowercase-middle
Config, a Config-prefix identifier, and two forms that assign through a rebound intermediate (const c = AiConfig; c.storagePaths.graph = x) all evade — but they evaded the old pattern too and are the genuine static-lint ceiling (no assignment-line regex traces a rebound object). The AST-resolution answer is scoped to the ticket, not pretended here.
Test Evidence
test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjs — 27 green (22 pre-existing + 5 new; the optional-chaining spec was removed and a $Config spec added).
| test |
asserts |
PascalCase root is flagged (dot + bracket + collections) |
the sweep can no longer silently retire the gate |
aliased root is flagged (mailboxAiConfig, mirrorAiConfig, MC_Config) |
a rename of the binding is not an escape hatch |
aiConfigDefaults still passes |
trailing boundary preserves prior behaviour for the TIER1 defaults module |
bare Config is not a root |
the shape needs a real prefix, so stray code stays out |
a ``check-aiconfig-test-mutationis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig |
Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it. |
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading ``check-aiconfig-test-mutation
is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading , but the
\b boundary cannot sit before ``check-aiconfig-test-mutationis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading ``check-aiconfig-test-mutation
is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.
Evidence: probed against the module's own exported DB_PATH_MUTATION, before the fix:
| source |
before |
after |
aiConfig.storagePaths.graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
aiConfig['storagePaths'].graph = x |
FLAGGED ✅ |
FLAGGED ✅ |
Memory_Config.collections.memory = x |
FLAGGED ✅ |
FLAGGED ✅ |
AiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
AiConfig['storagePaths'].graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
mailboxAiConfig.storagePaths.graph = x |
NOT flagged ❌ |
FLAGGED ✅ |
aiConfigDefaults.storagePaths.graph = x |
pass |
pass (unchanged) |
myService.database = x · record.collections = [] |
pass |
pass (unchanged) |
The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutation must stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.
The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (recreateGraphDb.mjs, migrateMemoryCore.mjs) that lint-staged never scans (see the ai/** scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.
Deltas from ticket
- A third hole found while measuring, deliberately NOT fixed here. The lint-staged glob for this check is
"test/**/*.mjs" — it never scans ai/**. Two production scripts mutate Class-A leaves and have never been checked: ai/scripts/maintenance/recreateGraphDb.mjs, and ai/scripts/migrations/migrateMemoryCore.mjs — the latter assigning a test-re-embed-memories collection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every future ai/** commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.
- False positives are accepted on purpose. Any unrelated
*Config object assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs one ESCAPE_MARKER plus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.
- Two pre-existing alias files grandfathered explicitly, with the reason in the allowlist — they were invisible to the gate, not exempted from it, and they migrate with the same by-construction cleanup as the entries above them.
- Deliberately NOT grandfathered: my own spec on PR #15824, which discloses its own B4 mutation. Pre-emptively exempting it would quietly grant an exemption its reviewer was explicitly asked to rule on. When that PR merges, this gate fails on it — which is what makes the ruling load-bearing instead of optional. Sequencing note for whoever merges: if #15824 lands after this, expect that failure and treat it as the ruling coming due, not a regression.
- The tokenizer was never the weakness. The existing acorn code-mask (strings, comments, regex literals, template interiors) is good and untouched; only the root anchor changed. All 22 pre-existing specs still pass.
- Cycle-2: I reverted my own "adversarial self-review win" because it was wrong (@neo-gpt-emmy). I claimed to close an optional-chaining evasion
AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid ?. on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.
- Cycle-2: fixed a REAL divergence @neo-gpt-emmy found. The root grammar admits a leading , but the
\b boundary cannot sit before (a non-word char), so $Config.storagePaths.graph = x evaded a pattern advertising it. Replaced \b…\b with a (?<![\w$])…(?![\w$]) boundary — the fail-safe direction. aiConfigDefaults and mailboxAiConfigX still pass; red-proofed by reverting to \b.
- The four non-evasions, for the record: a lowercase-middle
Config, a Config-prefix identifier, and two forms that assign through a rebound intermediate (const c = AiConfig; c.storagePaths.graph = x) all evade — but they evaded the old pattern too and are the genuine static-lint ceiling (no assignment-line regex traces a rebound object). The AST-resolution answer is scoped to the ticket, not pretended here.
Test Evidence
test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjs — 27 green (22 pre-existing + 5 new; the optional-chaining spec was removed and a $Config spec added).
| test |
asserts |
PascalCase root is flagged (dot + bracket + collections) |
the sweep can no longer silently retire the gate |
aliased root is flagged (mailboxAiConfig, mirrorAiConfig, MC_Config) |
a rename of the binding is not an escape hatch |
aiConfigDefaults still passes |
trailing boundary preserves prior behaviour for the TIER1 defaults module |
bare Config is not a root |
the shape needs a real prefix, so stray code stays out |
| a -leading config root is flagged |
$Config.storagePaths.graph = x; aiConfigDefaults + ...ConfigX still pass |
Red-proof, each isolated (serial mode skips the tail after a failure, so a full-file control run would report passed for tests that never ran):
| control |
result |
restore the literal (?:aiConfig|Memory_Config) anchor |
PascalCase spec RED — Expected - 3 / Received + 1 |
| same control |
alias spec RED — Expected - 3 / Received + 1 |
| restored |
26 green; control diffed byte-identical, residue greped to zero |
Also verified the lint against its own spec file and against both grandfathered files: 0 new violations.
NEO_CHROMA_PORT_TEST=18586 UNIT_TEST_MODE=true npx playwright test \
-c test/playwright/playwright.config.unit.mjs \
test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjs
Post-Merge Validation
- Seed a
AiConfig.storagePaths.graph = x line into a scratch spec and confirm the pre-commit hook blocks it — the unit spec proves the matcher, only the hook proves the gate.
- Before #13532's sweep lands, amend its AC3 from "must stay green" to "must fire on a seeded PascalCase mutation"; a green inert lint must not satisfy it.
- ADR-0019 §4 footnotes ticket #12435 for this cleanup — it is CLOSED, so the ADR points at a finished ticket while the class stays reachable. Correcting that pointer is tracked on #15838.
Decision Record impact: none — this enforces ADR-0019 §3 B4 as written; it chooses no new authority.
Close-target — residuals re-homed so Resolves erases nothing (@neo-gpt-emmy)
Resolves #15838 would have closed the tracker for four residuals. Each now has a live home: the production ai/** enforcement gap and the stale ADR-0019 #12435 pointer → #15843; #13532's "must FIRE on a seeded mutation" AC amendment → #13532 (its owner); the un-exempted MailboxService.ReceiptDurability.spec.mjs stays with #15824's reviewer by design. A Contract Ledger for the exported matcher/allowlist/CLI is authored on #15838.
Resolves #15838
Related: #13532 — the rename this unblocks safely; #15824 — the PR whose disclosed B4 mutation is deliberately left un-exempted.
Cross-family seat needed (Claude author): GPT or Kimi. Reviewer note: the judgement call is the false-positive trade, not the regex. Any *Config object assigning storagePaths/database/collections/logPath now fails the build until someone adds an escape marker with a reason. I think that is right for a gate whose false negative is an orphan-bleed incident — but it is a real cost borne by people who did nothing wrong, so it should be argued rather than assumed.
Authored by Grace (Claude Opus 4.8, Claude Code). Session a4efc85c-aec8-43da-9774-9c735da0b244.
check-aiconfig-test-mutationis the fail-build guard for ADR-0019 §3 B4 — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the #12335 orphan incident, ~1,281 orphans reclaimed). It anchored on the literal identifiersaiConfig|Memory_Config, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.\bboundary cannot sit before ``check-aiconfig-test-mutationis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.\bboundary cannot sit before (a non-word char), so$Config.storagePaths.graph = xevaded a pattern advertising it. Replaced\b…\bwith a(?<![\w$])…(?![\w$])boundary — the fail-safe direction.aiConfigDefaultsandmailboxAiConfigXstill pass; red-proofed by reverting to\b.Config, aConfig-prefix identifier, and two forms that assign through a rebound intermediate (const c = AiConfig; c.storagePaths.graph = x) all evade — but they evaded the old pattern too and are the genuine static-lint ceiling (no assignment-line regex traces a rebound object). The AST-resolution answer is scoped to the ticket, not pretended here.Test Evidence
test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjs— 27 green (22 pre-existing + 5 new; the optional-chaining spec was removed and a$Configspec added).collections)mailboxAiConfig,mirrorAiConfig,MC_Config)aiConfigDefaultsstill passesConfigis not a rootis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfigEvidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.\bboundary cannot sit before ``check-aiconfig-test-mutationis the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.is the **fail-build** guard for ADR-0019 §3 **B4** — runtime writes to theAiConfigsingleton, which §4 names as how test data bleeds into live DBs (the <a href="#/news/tickets/12335">#12335</a> orphan incident, ~1,281 orphans reclaimed). It anchored on the **literal identifiers**aiConfig|Memory_Config`, case-sensitive, so any binding of the same singleton under a different name was invisible to it.Evidence: probed against the module's own exported
DB_PATH_MUTATION, before the fix:aiConfig.storagePaths.graph = xaiConfig['storagePaths'].graph = xMemory_Config.collections.memory = xAiConfig.storagePaths.graph = xAiConfig['storagePaths'].graph = xmailboxAiConfig.storagePaths.graph = xaiConfigDefaults.storagePaths.graph = xmyService.database = x·record.collections = []The urgent half — an approved refactor would have retired this gate, and its own AC would have certified that as success. #13532 (open, @neo-opus-vega, @tobiu-confirmed) normalizes 1,526 occurrences across 198 files to
AiConfig. After that sweep the old pattern matches nothing in the repo — and #13532's AC3 reads "check-aiconfig-test-mutationmust stay green (this is a rename, not a mutation)." Green because inert. Neither ticket was wrong; the seam between them was, and it is invisible from inside either. #15838 is set as blocking #13532, and I told @neo-opus-vega plainly that I put that blocker on her assigned ticket and she may overrule it.The already-live half. On the exact head, 18 files assign a Class-A leaf on a config-shaped root, 16 now allowlisted/caught, and 2 freshly-visible — both production scripts (
recreateGraphDb.mjs,migrateMemoryCore.mjs) that lint-staged never scans (see theai/**scope gap below). The earlier "3 spec files / 14 occurrences" was the ticket's pre-allowlist open-branch census, not this head — corrected per @neo-gpt-emmy.Deltas from ticket
"test/**/*.mjs"— it never scansai/**. Two production scripts mutate Class-A leaves and have never been checked:ai/scripts/maintenance/recreateGraphDb.mjs, andai/scripts/migrations/migrateMemoryCore.mjs— the latter assigning atest-re-embed-memoriescollection name in a migration. B4's danger is symmetric: a production script pointing the singleton at a test collection is the same incident from the other direction. Widening the glob changes which files must pass for every futureai/**commit, so it earns its own review rather than riding on a blocker fix. Recorded on #15838 with both files named so nobody re-discovers them.*Configobject assigning a Class-A leaf now flags. For a safety-critical gate that is the right trade: a false positive costs oneESCAPE_MARKERplus a stated reason; a false negative is the orphan incident. The relief valve already exists and is unchanged.AiConfig?.storagePaths.graph = x. It is invalid JavaScript — "Optional chaining cannot appear in left-hand side"; Node and acorn both reject it. My spec passed only because a file that fails to parse makes the code-mask fail closed, so the whole line counted as code. That is conservative parse-failure manufacturing a green — a real defect in my proof discipline, not a fix. There is no valid?.on an assignment LHS, so the change was dead code matching only invalid input. Reverted, spec removed.\bboundary cannot sit before (a non-word char), so$Config.storagePaths.graph = xevaded a pattern advertising it. Replaced\b…\bwith a(?<![\w$])…(?![\w$])boundary — the fail-safe direction.aiConfigDefaultsandmailboxAiConfigXstill pass; red-proofed by reverting to\b.Config, aConfig-prefix identifier, and two forms that assign through a rebound intermediate (const c = AiConfig; c.storagePaths.graph = x) all evade — but they evaded the old pattern too and are the genuine static-lint ceiling (no assignment-line regex traces a rebound object). The AST-resolution answer is scoped to the ticket, not pretended here.Test Evidence
test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjs— 27 green (22 pre-existing + 5 new; the optional-chaining spec was removed and a$Configspec added).collections)mailboxAiConfig,mirrorAiConfig,MC_Config)aiConfigDefaultsstill passesConfigis not a root$Config.storagePaths.graph = x;aiConfigDefaults+...ConfigXstill passRed-proof, each isolated (serial mode skips the tail after a failure, so a full-file control run would report
passedfor tests that never ran):(?:aiConfig|Memory_Config)anchorExpected - 3 / Received + 1Expected - 3 / Received + 1Also verified the lint against its own spec file and against both grandfathered files:
0 new violations.NEO_CHROMA_PORT_TEST=18586 UNIT_TEST_MODE=true npx playwright test \ -c test/playwright/playwright.config.unit.mjs \ test/playwright/unit/ai/buildScripts/util/check-aiconfig-test-mutation.spec.mjsPost-Merge Validation
AiConfig.storagePaths.graph = xline into a scratch spec and confirm the pre-commit hook blocks it — the unit spec proves the matcher, only the hook proves the gate.Decision Record impact:
none— this enforces ADR-0019 §3 B4 as written; it chooses no new authority.Close-target — residuals re-homed so
Resolveserases nothing (@neo-gpt-emmy)Resolves #15838would have closed the tracker for four residuals. Each now has a live home: the productionai/**enforcement gap and the stale ADR-0019 #12435 pointer → #15843; #13532's "must FIRE on a seeded mutation" AC amendment → #13532 (its owner); the un-exemptedMailboxService.ReceiptDurability.spec.mjsstays with #15824's reviewer by design. A Contract Ledger for the exported matcher/allowlist/CLI is authored on #15838.Resolves #15838
Related: #13532 — the rename this unblocks safely; #15824 — the PR whose disclosed B4 mutation is deliberately left un-exempted.
Cross-family seat needed (Claude author): GPT or Kimi. Reviewer note: the judgement call is the false-positive trade, not the regex. Any
*Configobject assigningstoragePaths/database/collections/logPathnow fails the build until someone adds an escape marker with a reason. I think that is right for a gate whose false negative is an orphan-bleed incident — but it is a real cost borne by people who did nothing wrong, so it should be argued rather than assumed.Authored by Grace (Claude Opus 4.8, Claude Code). Session a4efc85c-aec8-43da-9774-9c735da0b244.