Context
#14031 added the config-independent fail-closed test-write guard to the MC ChromaManager resolver (resolveChromaClientConfig), keyed off the production-namespace constant (CHROMA_PRODUCTION_DATABASE). It closes the demonstrated #14010 bleed (test-memory/test-session = MC collections).
The Problem
The KB ChromaManager (ai/services/knowledge-base/ChromaManager.mjs) reaches Chroma through a structurally different seam: it reads host/port from the root AiConfig and constructs new ChromaClient({host, port}) with no per-call database param. Its test-vs-prod isolation is daemon/port-keyed (test daemon 18180 via #14022), not database-keyed — so the #14031 database-constant guard does not cover it. A config-resolution bypass (stale overlay / bare npx playwright) that resolves the prod port (8000) would let KB test writes target the live daemon — the same bleed class, on the KB path.
The Fix
Add the symmetric config-independent guard to the KB ChromaManager (construct/connect): when isTestRunnerContext() is true (TEST_WORKER_INDEX/UNIT_TEST_MODE) AND the resolved port is the production port, throw before connecting. Mirrors #14031 / #13639 / #13683.
Acceptance Criteria
Notes / Risk
The port reference is a config-leaf (engines.chroma.portProd), not a stable module constant like CHROMA_PRODUCTION_DATABASE. On a stale/fresh local clone the generated config.mjs overlay may not have the #14022 port formulas materialized (the same staleness that fails the dataDir===dataDirTest assertion locally), so a guard that reads the leaf is not reliably unit-testable via the overlay — the test must pass explicit coords. This structural difference is exactly why it was split from #14031 (whose guard keys off a stable constant and is cleanly testable).
Out of Scope
- The #14022 physical daemon/port isolation (this is its config-independent KB backstop, not a replacement).
- The "tests must always use config.template" issue (separately ticketed per operator).
Related
- #14031 — the MC guard this mirrors (config-independent, database-keyed).
- #14022 — physical daemon/port isolation (this backstops its KB fallback case).
- #14010 — the test-bleed regression.
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)
Context
#14031 added the config-independent fail-closed test-write guard to the MC ChromaManager resolver (
resolveChromaClientConfig), keyed off the production-namespace constant (CHROMA_PRODUCTION_DATABASE). It closes the demonstrated #14010 bleed (test-memory/test-session = MC collections).The Problem
The KB ChromaManager (
ai/services/knowledge-base/ChromaManager.mjs) reaches Chroma through a structurally different seam: it readshost/portfrom the rootAiConfigand constructsnew ChromaClient({host, port})with no per-calldatabaseparam. Its test-vs-prod isolation is daemon/port-keyed (test daemon 18180 via #14022), not database-keyed — so the #14031 database-constant guard does not cover it. A config-resolution bypass (stale overlay / barenpx playwright) that resolves the prod port (8000) would let KB test writes target the live daemon — the same bleed class, on the KB path.The Fix
Add the symmetric config-independent guard to the KB ChromaManager (
construct/connect): whenisTestRunnerContext()is true (TEST_WORKER_INDEX/UNIT_TEST_MODE) AND the resolved port is the production port, throw before connecting. Mirrors #14031 / #13639 / #13683.Acceptance Criteria
TEST_WORKER_INDEX/UNIT_TEST_MODE) resolves the production port, regardless of config resolution.config.mjsoverlay (pass explicit coords) — see Risk.Notes / Risk
The port reference is a config-leaf (
engines.chroma.portProd), not a stable module constant likeCHROMA_PRODUCTION_DATABASE. On a stale/fresh local clone the generatedconfig.mjsoverlay may not have the #14022 port formulas materialized (the same staleness that fails thedataDir===dataDirTestassertion locally), so a guard that reads the leaf is not reliably unit-testable via the overlay — the test must pass explicit coords. This structural difference is exactly why it was split from #14031 (whose guard keys off a stable constant and is cleanly testable).Out of Scope
Related
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)