Surfaced by @tobiu (2026-06-03) while challenging the #12420 resolveAiDataRoot over-engineering — the same root antipattern recurs across the config templates, pre-dating #12420.
The antipattern
config.template.mjs is the declarative config SSOT — each value is leaf(default, envVarName, type), where the env override is a string-literal arg the config system resolves. But several leaves embed imperative process.env.UNIT_TEST_MODE === 'true' ? test : prod branching in the default expression — env-resolution logic that belongs at the env/test layer, not baked into the canonical config. Same root as resolveAiDataRoot (#12420): imperative env-resolution leaking into the declarative SSOT.
Instances (V-B-A grep)
ai/config.template.mjs:313 — engines.chroma.database (the lone inline process.env read in that file) — from #12335/#12397 (eccfe225e).
ai/mcp/server/memory-core/config.template.mjs:133 — graph (:memory: vs sqlite).
ai/mcp/server/memory-core/config.template.mjs:148,149 — memory/session collection names.
ai/mcp/server/memory-core/config.mjs:122,137,138 — the gitignored overlay copies.
Reshape
- Config leaves go declarative: e.g.
database: leaf(CHROMA_PRODUCTION_DATABASE, 'NEO_CHROMA_DATABASE', 'string').
- The single test chokepoint
test/playwright/playwright.config.unit.mjs:8 (already sets UNIT_TEST_MODE=true) sets the test overrides beside it (NEO_CHROMA_DATABASE=neo-unit-test, NEO_MEMORY_DB_PATH=:memory:, …).
- The shared SSOT constants (
chromaTestIsolation.mjs) stay — they're legit (consumed by the purge script, ChromaManager, specs). The import is not the problem; the inline branch is.
Safety is preserved (evidence it's removable)
The "by construction" test-isolation does not depend on the config branch — it's independently enforced at runtime:
ChromaManager.mjs:114 — refuses CHROMA_PRODUCTION_DATABASE under UNIT_TEST_MODE (fails loud, never pollutes prod).
dropChromaTestDatabase — refuses to drop prod.
So the inline config branch is largely redundant with the runtime guard; removing it does not regress the orphan-prevention motivation (the 1,281-orphan incident behind #12335).
Wrinkle (not a one-liner)
The test-*-${Date.now()}-${Math.random()} collection-name leaves need per-run/per-worker uniqueness → the dynamic generation must move to the test bootstrap (computed per worker), not a static env value. The chroma DATABASE (static neo-unit-test) is the clean case; the dynamic names are the harder sub-case.
The mechanical guard (the real friction→gold)
A lint banning inline process.env. reads inside config.template.mjs / config.mjs leaf defaults (env access only via the leaf's env-var-name arg) would have caught both this AND resolveAiDataRoot (#12420) at PR time — un-mergeable, not "review harder." Validate false-positive surface during implementation; this is the same class of fix as the #12442 premise gate but mechanically enforceable.
Related
- Sibling instance: #12420 (
resolveAiDataRoot) — REQUEST_CHANGES posted.
- Recurring "wrong-shape merges" theme: #12442 (pr-review premise gate) → subs #12447–#12449.
Origin: @tobiu-surfaced, session e886ae3e-13c0-4a94-9713-f8316e2342d0.
Surfaced by @tobiu (2026-06-03) while challenging the #12420
resolveAiDataRootover-engineering — the same root antipattern recurs across the config templates, pre-dating #12420.The antipattern
config.template.mjsis the declarative config SSOT — each value isleaf(default, envVarName, type), where the env override is a string-literal arg the config system resolves. But several leaves embed imperativeprocess.env.UNIT_TEST_MODE === 'true' ? test : prodbranching in the default expression — env-resolution logic that belongs at the env/test layer, not baked into the canonical config. Same root asresolveAiDataRoot(#12420): imperative env-resolution leaking into the declarative SSOT.Instances (V-B-A grep)
ai/config.template.mjs:313—engines.chroma.database(the lone inlineprocess.envread in that file) — from #12335/#12397 (eccfe225e).ai/mcp/server/memory-core/config.template.mjs:133—graph(:memory:vs sqlite).ai/mcp/server/memory-core/config.template.mjs:148,149—memory/sessioncollection names.ai/mcp/server/memory-core/config.mjs:122,137,138— the gitignored overlay copies.Reshape
database: leaf(CHROMA_PRODUCTION_DATABASE, 'NEO_CHROMA_DATABASE', 'string').test/playwright/playwright.config.unit.mjs:8(already setsUNIT_TEST_MODE=true) sets the test overrides beside it (NEO_CHROMA_DATABASE=neo-unit-test,NEO_MEMORY_DB_PATH=:memory:, …).chromaTestIsolation.mjs) stay — they're legit (consumed by the purge script,ChromaManager, specs). The import is not the problem; the inline branch is.Safety is preserved (evidence it's removable)
The "by construction" test-isolation does not depend on the config branch — it's independently enforced at runtime:
ChromaManager.mjs:114— refusesCHROMA_PRODUCTION_DATABASEunderUNIT_TEST_MODE(fails loud, never pollutes prod).dropChromaTestDatabase— refuses to drop prod.So the inline config branch is largely redundant with the runtime guard; removing it does not regress the orphan-prevention motivation (the 1,281-orphan incident behind #12335).
Wrinkle (not a one-liner)
The
test-*-${Date.now()}-${Math.random()}collection-name leaves need per-run/per-worker uniqueness → the dynamic generation must move to the test bootstrap (computed per worker), not a static env value. The chroma DATABASE (staticneo-unit-test) is the clean case; the dynamic names are the harder sub-case.The mechanical guard (the real friction→gold)
A lint banning inline
process.env.reads insideconfig.template.mjs/config.mjsleaf defaults (env access only via the leaf's env-var-name arg) would have caught both this ANDresolveAiDataRoot(#12420) at PR time — un-mergeable, not "review harder." Validate false-positive surface during implementation; this is the same class of fix as the #12442 premise gate but mechanically enforceable.Related
resolveAiDataRoot) — REQUEST_CHANGES posted.Origin: @tobiu-surfaced, session
e886ae3e-13c0-4a94-9713-f8316e2342d0.