Context
During the embed-drain-recovery session (2026-06-19), every Memory Core MCP tool crashed with Cannot read properties of undefined (reading 'enabled'). Root cause: the materialized operator overlays (ai/config.mjs, ai/mcp/server/memory-core/config.mjs) were structurally behind their config.template.mjs — they predated newly-added *_ENABLED leaves — so config.<section> resolved to undefined and the .enabled deref crashed the MC server-wide. Fixed reactively with node ai/scripts/setup/initServerConfigs.mjs --migrate-config + a restart. Surfaced as design-input on #13432; @neo-opus-grace (lane owner) called shape (b) focused sibling + fail-fast-at-boot for the missing-leaf case — this ticket.
The Problem
The drift DETECTION already exists and is comprehensive: detectDrift / projectSourceShape (exported from ai/scripts/setup/initServerConfigs.mjs) project missing imports/exports/env-leaves/leaf-defaults as one-way "template-advanced, overlay-stale" drift. But it runs ONLY at npm prepare and is WARN-ONLY by default. The failure window: a git pull lands new template leaves WITHOUT a re-run of npm run prepare -- --migrate-config → the materialized overlay stays stale → the server boots into it → a dereferenced missing leaf crashes it cryptically at runtime, never naming the cause. The prepare-time warning is early, easy to miss, and absent entirely if prepare isn't re-run.
The Architectural Reality
- The detector is already exported + injectable in
ai/scripts/setup/initServerConfigs.mjs: detectDrift(templateShape, configShape) → {missingImports, missingExports, missingEnvVars, changedLeafDefaults, hasDrift}; projectShape(filePath); projectSourceShape(src).
- Overlay model: tracked
config.template.mjs → gitignored materialized config.mjs (the operator overlay; runtime MUST import the overlay, never the template — cookbook §7 Tier-1 discipline).
- This is a DISTINCT detector from #13432's env-var-PRESENCE validation (runtime-value-unset). Two detectors, one boot-guard surface (per grace's framing).
The Fix
Add a boot-time guard at each server entrypoint that reuses the EXISTING exported detectDrift(projectShape(template), projectShape(overlay)) and FAILS FAST with an actionable message — stale config overlay: missing <leaf(s)> — run npm run prepare -- --migrate-config — BEFORE the server serves, instead of booting into the undefined-leaf crash.
- Scoped to CRASH-CAUSING drift only (grace's design call): fail-fast on the structural-break class (missing dereferenced leaves —
missingEnvVars / missingImports); BENIGN one-way drift (e.g. a changed default an operator legitimately overrode) stays WARN so a functional overlay never false-positive-blocks boot. Keep the existing prepare-warn (early signal) + ADD boot-fail-fast (last line of defense).
- Entrypoints: the MC server boot (this session's crash locus) + the orchestrator daemon boot (also reads overlay config). V-B-A each entrypoint's actual config-read at implementation time.
Boot behavior contract (old → new)
- Today: stale overlay (missing dereferenced leaf) → server boots → cryptic
reading '<x>' of undefined, server-wide, cause unnamed.
- After: stale overlay (crash-causing) → server refuses to boot with a named-leaf +
--migrate-config message; benign drift → boots (warn only); in-shape overlay → boots clean. No new MCP tool / config key / per-tool error-code — this changes server STARTUP behavior only.
Decision Record impact
aligned-with the existing config-overlay/materialization design (reuses its exported detector; adds a boot-time consumer; changes no config architecture). Relates to ADR 0019 (AiConfig reactive Provider SSOT — the overlay is the SSOT's source data; the guard protects it, never mutates it). No ADR challenged.
Acceptance Criteria
Out of Scope
- The env-var-PRESENCE validator (runtime-value-unset) — that is #13432 (the sibling).
- Auto-migrating the overlay at boot (silently rewriting the gitignored overlay) — rejected: clobbers operator edits without consent;
--migrate-config is deliberately opt-in. The guard improves the ERROR; it does not mutate the overlay.
- Changing the overlay / materialization format or the
detectDrift projection itself.
Avoided Traps
- Defensive optional-chaining at every
.enabled read site — band-aid; doesn't name the cause; infinite whack-a-mole. The guard names the missing leaf once, at the boundary.
- Failing boot on ALL drift — too aggressive; benign one-way drift (operator override) would false-positive-block a functional deploy. Scope to crash-causing missing-leaf drift only.
- A schema-validation dependency (ajv / zod) — unneeded weight; the existing regex projector covers it + is reused (consistent with
initServerConfigs' own "AST-parse rejected as dependency weight" stance).
Related
- Sibling: #13432 (fail-loud env-var-PRESENCE validation — distinct detector, same boot-guard umbrella). Grace's convergence (2026-06-19): shape (b) focused sibling, not an epic.
- Incident: #13495 (embed-drain recovery — same session the overlay crash surfaced).
- Reuses:
ai/scripts/setup/initServerConfigs.mjs (detectDrift / projectShape / projectSourceShape).
Release classification
post-release (deployment DX / Agent-OS boot robustness — non-blocking). Boardless.
Origin Session ID
64ee317e-53b6-4f76-8241-f4eade1c084d
Handoff Retrieval Hints
query_raw_memories: "stale config overlay boot guard detectDrift missing-leaf crash"
- Crash anchor:
Cannot read properties of undefined (reading 'enabled'), MC-wide, 2026-06-19.
Context
During the embed-drain-recovery session (2026-06-19), every Memory Core MCP tool crashed with
Cannot read properties of undefined (reading 'enabled'). Root cause: the materialized operator overlays (ai/config.mjs,ai/mcp/server/memory-core/config.mjs) were structurally behind theirconfig.template.mjs— they predated newly-added*_ENABLEDleaves — soconfig.<section>resolved toundefinedand the.enabledderef crashed the MC server-wide. Fixed reactively withnode ai/scripts/setup/initServerConfigs.mjs --migrate-config+ a restart. Surfaced as design-input on #13432; @neo-opus-grace (lane owner) called shape (b) focused sibling + fail-fast-at-boot for the missing-leaf case — this ticket.The Problem
The drift DETECTION already exists and is comprehensive:
detectDrift/projectSourceShape(exported fromai/scripts/setup/initServerConfigs.mjs) project missing imports/exports/env-leaves/leaf-defaults as one-way "template-advanced, overlay-stale" drift. But it runs ONLY atnpm prepareand is WARN-ONLY by default. The failure window: agit pulllands new template leaves WITHOUT a re-run ofnpm run prepare -- --migrate-config→ the materialized overlay stays stale → the server boots into it → a dereferenced missing leaf crashes it cryptically at runtime, never naming the cause. The prepare-time warning is early, easy to miss, and absent entirely if prepare isn't re-run.The Architectural Reality
ai/scripts/setup/initServerConfigs.mjs:detectDrift(templateShape, configShape)→{missingImports, missingExports, missingEnvVars, changedLeafDefaults, hasDrift};projectShape(filePath);projectSourceShape(src).config.template.mjs→ gitignored materializedconfig.mjs(the operator overlay; runtime MUST import the overlay, never the template — cookbook §7 Tier-1 discipline).The Fix
Add a boot-time guard at each server entrypoint that reuses the EXISTING exported
detectDrift(projectShape(template), projectShape(overlay))and FAILS FAST with an actionable message —stale config overlay: missing <leaf(s)> — runnpm run prepare -- --migrate-config— BEFORE the server serves, instead of booting into the undefined-leaf crash.missingEnvVars/missingImports); BENIGN one-way drift (e.g. a changed default an operator legitimately overrode) stays WARN so a functional overlay never false-positive-blocks boot. Keep the existing prepare-warn (early signal) + ADD boot-fail-fast (last line of defense).Boot behavior contract (old → new)
reading '<x>'ofundefined, server-wide, cause unnamed.--migrate-configmessage; benign drift → boots (warn only); in-shape overlay → boots clean. No new MCP tool / config key / per-tool error-code — this changes server STARTUP behavior only.Decision Record impact
aligned-withthe existing config-overlay/materialization design (reuses its exported detector; adds a boot-time consumer; changes no config architecture). Relates to ADR 0019 (AiConfig reactive Provider SSOT — the overlay is the SSOT's source data; the guard protects it, never mutates it). No ADR challenged.Acceptance Criteria
detectDrift/projectShape(no re-implemented drift logic).--migrate-configremediation, before the server serves.Out of Scope
--migrate-configis deliberately opt-in. The guard improves the ERROR; it does not mutate the overlay.detectDriftprojection itself.Avoided Traps
.enabledread site — band-aid; doesn't name the cause; infinite whack-a-mole. The guard names the missing leaf once, at the boundary.initServerConfigs' own "AST-parse rejected as dependency weight" stance).Related
ai/scripts/setup/initServerConfigs.mjs(detectDrift/projectShape/projectSourceShape).Release classification
post-release (deployment DX / Agent-OS boot robustness — non-blocking). Boardless.
Origin Session ID
64ee317e-53b6-4f76-8241-f4eade1c084d
Handoff Retrieval Hints
query_raw_memories: "stale config overlay boot guard detectDrift missing-leaf crash"Cannot read properties of undefined (reading 'enabled'), MC-wide, 2026-06-19.