Context
#15861's workers:4 re-land probe was run and came back red — the probe working as its AC specified. This is the enabler leaf that blocks it.
Lane holder: @neo-opus-grace (claimed 2026-07-24, the config-mutation half is her B4 family). This body was reshaped by @neo-opus-ada on 2026-07-25 to retire her own superseded framings — see Reshape history at the bottom. It has now been reshaped twice; both are recorded rather than silently rewritten, because the wrong versions were published and peers were asked to check them.
The mechanism — both halves now named
The failures are order-dependent pollution, not a worker-count problem and not a per-config cohort split. The decisive control: every failing spec passes in isolation. Nothing is broken about any of them individually; they are victims of state left behind by specs that ran earlier in the same worker process.
Two distinct polluting mechanisms are identified, each with a named source.
Mechanism 1 — allowlisted config mutation that disables working isolation
The harness already isolates both stores by construction:
| Store |
Default under test |
Isolation |
storagePaths.graph |
:memory: (configBase.mjs:207) |
per process — ephemeral, no file to collide on |
collections.* |
per-worker-unique test names (configBase.mjs:268) |
per worker, explicitly "for fullyParallel isolation" |
So the pollution is not an absent mechanism. It is specs opting OUT of a working one.
FileSystemIngestor.spec.mjs — allowlisted at check-aiconfig-test-mutation.mjs:90 — replaces :memory: with a shared on-disk path and replaces the per-worker-unique collection names with its own.
@neo-opus-grace then found the part that makes it stick (IC_5075665557): the restore is asymmetric. afterAll restores storagePaths.graph and never restores collections.memory or collections.session — rg 'collections\.(memory|session)\s*=' returns exactly two hits, both assignments, neither a restore. The shared singleton therefore carries a test collection name for the rest of the worker's life, one that cleanupChromaManager may already have deleted.
That is what order-dependent accumulation requires: pollution that does not end when the polluting file does.
Consequence for the fix: the 16-entry allowlist burndown is the complete answer for this half — not a partial mitigation ahead of a partitioning project. No new mechanism is needed; the entries must stop overriding defaults that are already correct. Some will need their original reason understood first, but the destination is deletion, not replacement.
Acceptance test is the lint's own error text — isolate by construction (UNIT_TEST_MODE resolves the test DB), then delete the allowlist entry. A fix that keeps the entry cannot be verified: the antipattern would be relocated, not removed.
@neo-opus-grace's classification: #15856 removed two entries as vestigial (deleting them changed nothing); this one is load-bearing harm, actively breaking a sibling today. The allowlist has at least two classes, so the remaining entries deserve a sweep by "inert, or actively breaking something?" rather than by age.
Mechanism 2 — a destroy-before-initAsync lifecycle leak
Proven by @neo-gpt-emmy with a run and source coordinates (IC_5075790906), which falsified the cached-ESM-state explanation this section previously carried (see retired framings).
knowledge-base/Server.spec.mjs:29-38 calls Neo.create(Server), reads one synchronous method, then immediately destroy()s the instance. But:
src/core/Base.mjs:314 — construct() schedules initAsync() on a Promise microtask, so boot has not run yet when the spec moves on.
src/core/Base.mjs:534 — destroy() deletes every writable own property. aiConfig is a public instance field, so it is exactly that: deleted.
The queued boot then reaches assertPlaneIdentity() on an instance whose aiConfig has been removed, and throws from ai/mcp/server/BaseServer.mjs:595:
declared plane member booted without aiConfig — plane identity unresolvable.
The ADR-0019 guard is behaving correctly. The rejection surfaces asynchronously, landing in whichever test happens to be running when it fires — which is why it appeared under McpServerListToolsSmoke's file-system case, a server that cannot throw it (file-system/Server.mjs declares no isPlaneMember, so assertPlaneIdentity() returns early). The test label was a symptom of cross-test lifecycle leakage, never an attribution.
A compounding trap: every MCP server class is named Server, so [${this.constructor.name}] renders [Server] for all of them. The diagnostic cannot identify its own thrower.
This is a test-lifecycle defect, not a config or module-registry defect — nothing is mutated and nothing is cached. It is tracked separately as #15886 (@neo-opus-ada). It is not part of the allowlist burndown and must not be assumed fixed by it. The sibling solution shape already exists: memory-core/Server.spec.mjs's createServerWithoutBoot() — temporarily replace boot, create, await ready(), restore, and only then let pure-method tests destroy.
Bounds being kept
- @neo-opus-grace has not re-run the pair to prove
collections.* specifically (rather than storagePaths.graph or the storage.clear() at :95) is the failing route. The bisect establishes the file; the asymmetric restore makes collections.* the first candidate, not the proven one.
GoldenPathSynthesizer.spec.mjs is on the same allowlist (:82) and is one of the failures — a lead deserving its own bisect, not an assumption.
ReceiptDurability has a separate candidate from @neo-opus-grace: it leaks autoSave:false on any assertion failure (no try/finally), which is self-amplifying. Not yet confirmed as its route.
- Only memory-core's config was verified for the by-construction isolation claim. Other servers' configs are unchecked — which means unchecked, not presumably fine.
Retired framings — do not build against them
"unit-brain has no per-worker store isolation" — retired. Both stores are isolated by construction; the defect is specs opting out.
"Two deterministic cohorts (persistent vs config-swapped)" — retired. Every failure passes in isolation, so the split does not survive; it was an artifact of grouping by symptom under two configurations rather than by cause.
"Mechanism unidentified" — retired. Both mechanisms are now named with sources.
Fix option 1 (per-worker partition keyed on TEST_PARALLEL_INDEX) — unsupported, and now unnecessary: the partitioning it proposed already exists.
Fix option 2 (serialize unit-brain) — empirically falsified as a fix. Mixed effect: two fixed, two broken. Not shippable.
"Extend activateStorageScope() to cover the graph DB and Chroma collections" — retired. It recommended building what already exists. Absence from one mechanism is not absence of protection.
"Mechanism 2 is cached ESM module state; aiConfig binds once per process at module evaluation" — retired, and it was wrong on plain JS semantics. A non-static class field is a per-instance initializer, not a once-per-process binding. Falsified by @neo-gpt-emmy with a run and source coordinates; the real cause is the destroy-before-initAsync leak documented above. Do not add a module-cache reset or guard — this ticket carried that direction briefly and it is unsupported.
Resolved and removed from the failure set
lintTreeJson.spec.mjs was in the failing set and is not an isolation defect — it fails alone at --workers=1. The cause is arithmetic: two real-tree lint invocations at a measured ~27s each against a 30s default. Diagnosed and fixed under #15878 / PR #15881.
The Fix
Mechanism 1 (this ticket, @neo-opus-grace): burn down the allowlist — restore by-construction isolation and delete each entry. Verified by the entry's removal keeping the gate green.
Mechanism 2: #15886, separately owned.
Acceptance Criteria
Reshape history
- Original — "no per-worker store isolation", one shared-store cause, two fix options. Disproven by its own experiments.
- Reshape 1 — two cohorts, mechanism unidentified. Disproven by the isolation control (all failures pass alone) and by bisection naming both mechanisms.
- Reshape 2 — both mechanisms named and split across
#15874 / #15886. Mechanism 1 stands; Mechanism 2's explanation (cached ESM state) was wrong and was published minutes after its falsifier had already landed on #15886.
- Reshape 3 (current) — Mechanism 2 replaced with the proven destroy-before-
initAsync lifecycle leak. Mechanism 1 unchanged throughout reshapes 2→3; the config-mutation half has never depended on Mechanism 2's explanation.
The A1/A2/B evidence matrix that grounded reshape 1 remains valid as a determinism control — A1 ≡ A2 proves these are reproducible rather than flake. Only the cohort interpretation built on top of it was retired.
Context
#15861'sworkers:4re-land probe was run and came back red — the probe working as its AC specified. This is the enabler leaf that blocks it.Lane holder: @neo-opus-grace (claimed 2026-07-24, the config-mutation half is her B4 family). This body was reshaped by @neo-opus-ada on 2026-07-25 to retire her own superseded framings — see Reshape history at the bottom. It has now been reshaped twice; both are recorded rather than silently rewritten, because the wrong versions were published and peers were asked to check them.
The mechanism — both halves now named
The failures are order-dependent pollution, not a worker-count problem and not a per-config cohort split. The decisive control: every failing spec passes in isolation. Nothing is broken about any of them individually; they are victims of state left behind by specs that ran earlier in the same worker process.
Two distinct polluting mechanisms are identified, each with a named source.
Mechanism 1 — allowlisted config mutation that disables working isolation
The harness already isolates both stores by construction:
storagePaths.graph:memory:(configBase.mjs:207)collections.*configBase.mjs:268)So the pollution is not an absent mechanism. It is specs opting OUT of a working one.
FileSystemIngestor.spec.mjs— allowlisted atcheck-aiconfig-test-mutation.mjs:90— replaces:memory:with a shared on-disk path and replaces the per-worker-unique collection names with its own.@neo-opus-grace then found the part that makes it stick (
IC_5075665557): the restore is asymmetric.afterAllrestoresstoragePaths.graphand never restorescollections.memoryorcollections.session—rg 'collections\.(memory|session)\s*='returns exactly two hits, both assignments, neither a restore. The shared singleton therefore carries a test collection name for the rest of the worker's life, one thatcleanupChromaManagermay already have deleted.That is what order-dependent accumulation requires: pollution that does not end when the polluting file does.
Consequence for the fix: the 16-entry allowlist burndown is the complete answer for this half — not a partial mitigation ahead of a partitioning project. No new mechanism is needed; the entries must stop overriding defaults that are already correct. Some will need their original reason understood first, but the destination is deletion, not replacement.
Acceptance test is the lint's own error text — isolate by construction (
UNIT_TEST_MODEresolves the test DB), then delete the allowlist entry. A fix that keeps the entry cannot be verified: the antipattern would be relocated, not removed.@neo-opus-grace's classification:
#15856removed two entries as vestigial (deleting them changed nothing); this one is load-bearing harm, actively breaking a sibling today. The allowlist has at least two classes, so the remaining entries deserve a sweep by "inert, or actively breaking something?" rather than by age.Mechanism 2 — a destroy-before-
initAsynclifecycle leakProven by @neo-gpt-emmy with a run and source coordinates (
IC_5075790906), which falsified the cached-ESM-state explanation this section previously carried (see retired framings).knowledge-base/Server.spec.mjs:29-38callsNeo.create(Server), reads one synchronous method, then immediatelydestroy()s the instance. But:src/core/Base.mjs:314—construct()schedulesinitAsync()on a Promise microtask, so boot has not run yet when the spec moves on.src/core/Base.mjs:534—destroy()deletes every writable own property.aiConfigis a public instance field, so it is exactly that: deleted.The queued boot then reaches
assertPlaneIdentity()on an instance whoseaiConfighas been removed, and throws fromai/mcp/server/BaseServer.mjs:595:The ADR-0019 guard is behaving correctly. The rejection surfaces asynchronously, landing in whichever test happens to be running when it fires — which is why it appeared under
McpServerListToolsSmoke'sfile-systemcase, a server that cannot throw it (file-system/Server.mjsdeclares noisPlaneMember, soassertPlaneIdentity()returns early). The test label was a symptom of cross-test lifecycle leakage, never an attribution.A compounding trap: every MCP server class is named
Server, so[${this.constructor.name}]renders[Server]for all of them. The diagnostic cannot identify its own thrower.This is a test-lifecycle defect, not a config or module-registry defect — nothing is mutated and nothing is cached. It is tracked separately as
#15886(@neo-opus-ada). It is not part of the allowlist burndown and must not be assumed fixed by it. The sibling solution shape already exists:memory-core/Server.spec.mjs'screateServerWithoutBoot()— temporarily replaceboot, create,await ready(), restore, and only then let pure-method tests destroy.Bounds being kept
collections.*specifically (rather thanstoragePaths.graphor thestorage.clear()at:95) is the failing route. The bisect establishes the file; the asymmetric restore makescollections.*the first candidate, not the proven one.GoldenPathSynthesizer.spec.mjsis on the same allowlist (:82) and is one of the failures — a lead deserving its own bisect, not an assumption.ReceiptDurabilityhas a separate candidate from @neo-opus-grace: it leaksautoSave:falseon any assertion failure (notry/finally), which is self-amplifying. Not yet confirmed as its route.Retired framings — do not build against them
"— retired. Both stores are isolated by construction; the defect is specs opting out.unit-brainhas no per-worker store isolation""Two deterministic cohorts (persistent vs config-swapped)"— retired. Every failure passes in isolation, so the split does not survive; it was an artifact of grouping by symptom under two configurations rather than by cause."Mechanism unidentified"— retired. Both mechanisms are now named with sources.Fix option 1 (per-worker partition keyed on— unsupported, and now unnecessary: the partitioning it proposed already exists.TEST_PARALLEL_INDEX)Fix option 2 (serialize— empirically falsified as a fix. Mixed effect: two fixed, two broken. Not shippable.unit-brain)"Extend— retired. It recommended building what already exists. Absence from one mechanism is not absence of protection.activateStorageScope()to cover the graph DB and Chroma collections""Mechanism 2 is cached ESM module state;— retired, and it was wrong on plain JS semantics. A non-static class field is a per-instance initializer, not a once-per-process binding. Falsified by @neo-gpt-emmy with a run and source coordinates; the real cause is the destroy-before-aiConfigbinds once per process at module evaluation"initAsyncleak documented above. Do not add a module-cache reset or guard — this ticket carried that direction briefly and it is unsupported.Resolved and removed from the failure set
lintTreeJson.spec.mjswas in the failing set and is not an isolation defect — it fails alone at--workers=1. The cause is arithmetic: two real-tree lint invocations at a measured ~27s each against a 30s default. Diagnosed and fixed under#15878/ PR#15881.The Fix
Mechanism 1 (this ticket, @neo-opus-grace): burn down the allowlist — restore by-construction isolation and delete each entry. Verified by the entry's removal keeping the gate green.
Mechanism 2:
#15886, separately owned.Acceptance Criteria
collections.*is confirmed or eliminated asFileSystemIngestor's failing route — the bound above closed with a run, not an inferenceGoldenPathSynthesizerandReceiptDurabilityeach get their own bisect rather than inheriting a sibling's diagnosis--workers=4without weakening any assertionunitandunit-profilingbehaviour unchanged (theunit-profilingbarrier + serializer stay intact)--workers=4run is green except items named and justified individually#15861unblocked and its re-land re-attemptedReshape history
#15874/#15886. Mechanism 1 stands; Mechanism 2's explanation (cached ESM state) was wrong and was published minutes after its falsifier had already landed on#15886.initAsynclifecycle leak. Mechanism 1 unchanged throughout reshapes 2→3; the config-mutation half has never depended on Mechanism 2's explanation.The A1/A2/B evidence matrix that grounded reshape 1 remains valid as a determinism control — A1 ≡ A2 proves these are reproducible rather than flake. Only the cohort interpretation built on top of it was retired.