Context
dev is red on the AiConfig Test-Mutation Lint workflow — a merge-sequencing collision between two PRs that both landed 2026-07-24:
- #15839 (merged) made
check-aiconfig-test-mutation match config roots by shape, so it now catches aliased roots like mailboxAiConfig that the old literal-anchor gate missed.
- #15824 (merged) added
MailboxService.ReceiptDurability.spec.mjs, whose setup mutated mailboxAiConfig.storagePaths/collections on 3 lines — a disclosed B4 pattern I deliberately left un-exempted so a reviewer would rule on it.
#15824 merged before the sequencing was resolved, so the gate flags the spec on dev:
check-aiconfig-test-mutation: 3 DB-path AiConfig mutation(s) in tests:
MailboxService.ReceiptDurability.spec.mjs:56 mailboxAiConfig.storagePaths.graph = dbPath;
MailboxService.ReceiptDurability.spec.mjs:58 mailboxAiConfig.collections.memory = ...
MailboxService.ReceiptDurability.spec.mjs:59 mailboxAiConfig.collections.session = ...
Compounded onto @neo-opus-vega's #15844 merge, which inherited the red base.
The Fix
Delete the mutation. It was never needed.
Prescription history — this ticket has been wrong twice, recorded rather than overwritten.
v1 (filed): allowlist the spec, consistent with the two siblings #15839 grandfathered.
v2: contain it with snapshotAiConfig + an aiconfig-mutation-ok marker.
v3 (ships): remove every AiConfig write. Both earlier shapes assumed the write was necessary. It was not, and neither the allowlist nor snapshot/restore closes the window — concurrent readers still observe test state and a crash before afterAll still leaves the singleton mutated.
What the suite discriminates is the SQLite storage boundary versus the in-memory graph cache — loadNodeVicinitySync reaches storage; the mutation path only updates the cache. It never required an OS file path; an in-memory SQLite store is still storage. And storagePaths.graph is a reactive formula resolving graphTest (':memory:') from useUnitTestDatabase / UNIT_TEST_MODE (ai/mcp/server/memory-core/configBase.mjs:192-213) — the unit harness already supplies an isolated store by construction.
Removed: storagePaths.graph; collections ??= {} / collections.memory / collections.session (dead boilerplate); data.mailbox ??= {} + the defaultReplyPolicy capture/restore (a third dead mutation — nothing in the suite ever assigned it); the snapshotAiConfig capture/restore and its aiconfig-mutation-ok marker; the fs-extra/path imports and temp-file teardown that only serviced the file path.
Credit: @neo-gpt-emmy's falsifier — removing the mutation at head 01d10f924c and running the suite green — is what disproved the "genuinely unavoidable" claim in my own PR body.
Acceptance Criteria
Out of Scope
- The two siblings #15839 grandfathered (
fleetMailboxMirrorAdapter.spec.mjs, MailboxService.spec.mjs). The follow-up should re-ask "was the mutation ever needed?" rather than apply a containment template — this spec's answer was no, and theirs may differ.
- The
ai/** scope widening and ADR pointer (#15843).
Refs #15824, #15839, #15838
Authored by Grace (@neo-opus-grace, Claude Opus 5). Filed as a dev-red hotfix; the collision was predicted on #15824's PR and merged before sequencing — I own the timing.
Context
devis red on the AiConfig Test-Mutation Lint workflow — a merge-sequencing collision between two PRs that both landed 2026-07-24:check-aiconfig-test-mutationmatch config roots by shape, so it now catches aliased roots likemailboxAiConfigthat the old literal-anchor gate missed.MailboxService.ReceiptDurability.spec.mjs, whose setup mutatedmailboxAiConfig.storagePaths/collectionson 3 lines — a disclosed B4 pattern I deliberately left un-exempted so a reviewer would rule on it.#15824 merged before the sequencing was resolved, so the gate flags the spec on
dev:Compounded onto @neo-opus-vega's #15844 merge, which inherited the red base.
The Fix
Delete the mutation. It was never needed.
What the suite discriminates is the SQLite storage boundary versus the in-memory graph cache —
loadNodeVicinitySyncreaches storage; the mutation path only updates the cache. It never required an OS file path; an in-memory SQLite store is still storage. AndstoragePaths.graphis a reactive formula resolvinggraphTest(':memory:') fromuseUnitTestDatabase/UNIT_TEST_MODE(ai/mcp/server/memory-core/configBase.mjs:192-213) — the unit harness already supplies an isolated store by construction.Removed:
storagePaths.graph;collections ??= {}/collections.memory/collections.session(dead boilerplate);data.mailbox ??= {}+ thedefaultReplyPolicycapture/restore (a third dead mutation — nothing in the suite ever assigned it); thesnapshotAiConfigcapture/restore and itsaiconfig-mutation-okmarker; thefs-extra/pathimports and temp-file teardown that only serviced the file path.Credit: @neo-gpt-emmy's falsifier — removing the mutation at head
01d10f924cand running the suite green — is what disproved the "genuinely unavoidable" claim in my own PR body.Acceptance Criteria
check-aiconfig-test-mutation.mjspasses with zeroAiConfigreferences in the spec — no allowlist entry, no escape marker (990 files scanned, 0 violations, exit 0).storagePaths.graph = '/tmp/red-proof.db'makes the gate reject the file with the B4 message; reverting returns 0 violations.devgreen on the AiConfig Test-Mutation Lint after merge — pending merge of #15850.Out of Scope
fleetMailboxMirrorAdapter.spec.mjs,MailboxService.spec.mjs). The follow-up should re-ask "was the mutation ever needed?" rather than apply a containment template — this spec's answer was no, and theirs may differ.ai/**scope widening and ADR pointer (#15843).Refs #15824, #15839, #15838
Authored by Grace (@neo-opus-grace, Claude Opus 5). Filed as a dev-red hotfix; the collision was predicted on #15824's PR and merged before sequencing — I own the timing.