Problem
ConceptDiscoveryService.appendCandidates (ai/services/ingestion/ConceptDiscoveryService.mjs:458) writes concept candidates to conceptsDir/nodes.jsonl, where conceptsDir = ConceptService.defaultConceptsDir || .neo-ai-data/concepts — the production, non-disposable concepts dir. A test that exercises appendCandidates without overriding conceptsDir appends to the live concept ontology → silent pollution (the orphan-bleed / backlog-corruption class, #12335 / #13624).
The shared store-write guard (ai/services/shared/storeWriteGuard.mjs, from #13665/#13671) and the graph write-guard (SQLite.mjs, #13639/#13658) already defend the graph store. The file-store concept funnel is still unguarded — the remaining defense-in-depth slice.
Fix
ConceptDiscoveryService imports + calls assertTestWriteIsolated({storePath: conceptsDir, subsystem: 'concept-ontology'}) at the top of appendCandidates (after conceptsDir resolves, before the read/append). It throws on test-runner context × production-like conceptsDir (TEST_WORKER_INDEX/UNIT_TEST_MODE set, path not :memory:/tmp/test) and is a no-op in production runtime (no test signal) and for disposable dirs. The file-store parallel to the merged graph write-guard.
Acceptance Criteria
Context
- Adopts #13665 / #13671 (the shared
assertTestWriteIsolated util) — the file-store parallel to #13639 / #13658 (the graph write-guard).
- Was deferred behind #13670 (the block-alignment
--fix corrupted this file's prompt-template JSON on commit); unblocked now that #13676 (the template-literal mask) merged.
- Defends against the #13624 backlog-corruption / #12335 orphan-bleed class.
Problem
ConceptDiscoveryService.appendCandidates(ai/services/ingestion/ConceptDiscoveryService.mjs:458) writes concept candidates toconceptsDir/nodes.jsonl, whereconceptsDir = ConceptService.defaultConceptsDir || .neo-ai-data/concepts— the production, non-disposable concepts dir. A test that exercisesappendCandidateswithout overridingconceptsDirappends to the live concept ontology → silent pollution (the orphan-bleed / backlog-corruption class, #12335 / #13624).The shared store-write guard (
ai/services/shared/storeWriteGuard.mjs, from #13665/#13671) and the graph write-guard (SQLite.mjs, #13639/#13658) already defend the graph store. The file-store concept funnel is still unguarded — the remaining defense-in-depth slice.Fix
ConceptDiscoveryServiceimports + callsassertTestWriteIsolated({storePath: conceptsDir, subsystem: 'concept-ontology'})at the top ofappendCandidates(afterconceptsDirresolves, before the read/append). It throws on test-runner context × production-likeconceptsDir(TEST_WORKER_INDEX/UNIT_TEST_MODEset, path not:memory:/tmp/test) and is a no-op in production runtime (no test signal) and for disposable dirs. The file-store parallel to the merged graph write-guard.Acceptance Criteria
appendCandidatesthrowsSTORE_WRITE_GUARDin a test context writing to a production-likeconceptsDir.conceptsDir.Context
assertTestWriteIsolatedutil) — the file-store parallel to #13639 / #13658 (the graph write-guard).--fixcorrupted this file's prompt-template JSON on commit); unblocked now that #13676 (the template-literal mask) merged.