Context
Found 2026-06-26 while reviewing PR #14117 (the data-integrity sweep's audited collection set). The destructive-operation guard that refuses to drop canonical Chroma collections protects a phantom collection name and leaves the live Memory Core graph collection unprotected.
The Bug (verified)
ai/mcp/server/shared/services/DestructiveOperationGuard.mjs defines:
export const GUARDED_CANONICAL_COLLECTION_NAMES = Object.freeze(new Set([
'neo-agent-memory', 'neo-agent-sessions', 'neo-agent-graph', 'neo-knowledge-base'
]));used at :48 (isCanonical = GUARDED_CANONICAL_COLLECTION_NAMES.has(name)) to throw CanonicalCollectionGuardError (:117) and refuse a deleteCollection({name}). This is the collection-name-layer guard that exists (per its own JSDoc :88-94) to backstop cases where the path-target guard is bypassed.
But the live MC graph collection is neo-native-graph, NOT neo-agent-graph. Verified against the SSOT:
ai/mcp/server/memory-core/config.mjs:267 → graph: leaf('neo-native-graph', 'NEO_GRAPH_COLLECTION_NAME', 'string')
ai/scripts/maintenance/knowledgeBaseArtifact.mjs:49 → MEMORY_CORE_COLLECTION_PREFIXES = ['neo-agent-memory','neo-agent-sessions','neo-native-graph']
ai/scripts/maintenance/purgeTestCollections.mjs:54 PROTECTED set uses neo-native-graph; #14084's live recovery repaired 941/942 neo-native-graph rows
grep -rln 'neo-agent-graph' ai/ src/ → only DestructiveOperationGuard.mjs + ChromaManager.mjs (both JSDoc/lists). So neo-agent-graph is purely stale.
Impact: GUARDED_CANONICAL_COLLECTION_NAMES.has('neo-native-graph') is false → a destructive drop of the real ~900-row graph collection is not blocked at this layer (fail-open).
The Fix
DestructiveOperationGuard.mjs: neo-agent-graph → neo-native-graph in GUARDED_CANONICAL_COLLECTION_NAMES + the JSDoc at :19.
ai/services/memory-core/managers/ChromaManager.mjs:331: same stale name in JSDoc → correct it.
- Drift-catch test (the durable defense): assert the guarded set's MC entries match the live config collection names (memory/session/graph), so a future rename can't silently re-open the gap. The guard's JSDoc deliberately hardcodes (not config-derived) so a caller without the isolation layer still gets the canonical name blocked — so the test asserts hardcoded-set ⊇ config-names (parity), rather than making the guard config-derived.
Acceptance Criteria
Avoided Traps
- Do NOT make the guard config-derived (it deliberately hardcodes for the no-isolation-layer case); add the parity TEST instead.
- Confirm
neo-knowledge-base + neo-agent-memory + neo-agent-sessions are still the live names before touching them (only the graph name drifted).
Related
Refs #14117 (where it surfaced), #14084 (the recovery work that touches neo-native-graph). Lesson: derive identity sets from config, never a hand-maintained literal — [[verify-identifiers-against-runtime-ssot]].
Authored by Grace (Claude Opus 4.8, Claude Code).
Context
Found 2026-06-26 while reviewing PR #14117 (the data-integrity sweep's audited collection set). The destructive-operation guard that refuses to drop canonical Chroma collections protects a phantom collection name and leaves the live Memory Core graph collection unprotected.
The Bug (verified)
ai/mcp/server/shared/services/DestructiveOperationGuard.mjsdefines:export const GUARDED_CANONICAL_COLLECTION_NAMES = Object.freeze(new Set([ 'neo-agent-memory', 'neo-agent-sessions', 'neo-agent-graph', 'neo-knowledge-base' ]));used at
:48(isCanonical = GUARDED_CANONICAL_COLLECTION_NAMES.has(name)) to throwCanonicalCollectionGuardError(:117) and refuse adeleteCollection({name}). This is the collection-name-layer guard that exists (per its own JSDoc:88-94) to backstop cases where the path-target guard is bypassed.But the live MC graph collection is
neo-native-graph, NOTneo-agent-graph. Verified against the SSOT:ai/mcp/server/memory-core/config.mjs:267→graph: leaf('neo-native-graph', 'NEO_GRAPH_COLLECTION_NAME', 'string')ai/scripts/maintenance/knowledgeBaseArtifact.mjs:49→MEMORY_CORE_COLLECTION_PREFIXES = ['neo-agent-memory','neo-agent-sessions','neo-native-graph']ai/scripts/maintenance/purgeTestCollections.mjs:54PROTECTED set usesneo-native-graph; #14084's live recovery repaired941/942 neo-native-graph rowsgrep -rln 'neo-agent-graph' ai/ src/→ onlyDestructiveOperationGuard.mjs+ChromaManager.mjs(both JSDoc/lists). Soneo-agent-graphis purely stale.Impact:
GUARDED_CANONICAL_COLLECTION_NAMES.has('neo-native-graph')isfalse→ a destructive drop of the real ~900-row graph collection is not blocked at this layer (fail-open).The Fix
DestructiveOperationGuard.mjs:neo-agent-graph→neo-native-graphinGUARDED_CANONICAL_COLLECTION_NAMES+ the JSDoc at:19.ai/services/memory-core/managers/ChromaManager.mjs:331: same stale name in JSDoc → correct it.Acceptance Criteria
GUARDED_CANONICAL_COLLECTION_NAMEScontainsneo-native-graphand not the staleneo-agent-graph.ChromaManagerJSDoc names the liveneo-native-graph.neo-agent-graphcollection exists (verified — only the two guard files).Avoided Traps
neo-knowledge-base+neo-agent-memory+neo-agent-sessionsare still the live names before touching them (only the graph name drifted).Related
Refs #14117 (where it surfaced), #14084 (the recovery work that touches
neo-native-graph). Lesson: derive identity sets from config, never a hand-maintained literal — [[verify-identifiers-against-runtime-ssot]].Authored by Grace (Claude Opus 4.8, Claude Code).