Context
Surfaced by the new parity CI lane's first local boot (PR #15983, the #15807 lane): both the mc-server and orchestrator containers log file sink unavailable, degrading to stderr: ENOENT: no such file or directory, mkdir '/app/.neo-ai-data/logs' during boot — a log-sink path resolving OUTSIDE the relocated parity plane root (/app/.neo-ai-data-parity). Named for steward triage in that PR's Deltas; filed here with the source trace complete.
The Problem
The trace (receipts at current dev, branch of PR #15983):
ai/services/neural-link/RecorderService.mjs:6 imports the neural-link server logger; the service runs inside mc-server and orchestrator processes (it owns the nl_action_log connection — boot logs show [RecorderService] Connected to Memory Core nl_action_log. immediately after the sink failure in both containers).
- That logger (
ai/mcp/server/neural-link/logger.mjs) builds its file sink from the neural-link logPath leaf (ai/mcp/server/neural-link/configBase.mjs:95): default anchored to the canonical plane anchor, env-bound by NEO_NL_LOG_PATH.
- Unlike its MC + KB siblings (
ai/mcp/server/memory-core/configBase.mjs:736 and ai/mcp/server/knowledge-base/configBase.mjs:267 — both declared {planeMember: true}), the neural-link leaf declares no plane-member metadata, and the parity compose's x-plane-env (which binds NEO_MEMORY_LOG_PATH + NEO_KB_LOG_PATH) does not bind NEO_NL_LOG_PATH.
- In the parity containers the sink therefore resolves the canonical anchor
/app/.neo-ai-data/logs, misses, and degrades to stderr. The no-silent-canonical-fallback guard (ai/mcp/server/shared/logger.mjs:379) is what prevents a cross-plane WRITE — but that guard firing is exactly why this is a member-census gap, not cosmetic noise: without it, this is the #15931 leak class.
The census-domain sharpen (the part that makes this more than a missing env): derivePlaneMemberPaths (ai/planeConfig.mjs:245) fails closed on an anchored leaf with no planeMember decision — but it is walked per booting server over ITS OWN config tree. The neural-link configBase exports no PLANE_MEMBER_PATHS and is never walked by the mc/kb/orchestrator boot walks, even though those processes import the NL config tree via RecorderService. The NL server itself is seat-local by the parity invariants (never containerized in v1), so no boot anywhere asserts the NL tree. The leaf escapes the census by domain, not just by metadata.
The Architectural Reality
- ADR 0019 §10.5: every leaf whose default resolves beneath the plane anchor carries an explicit planeMember decision; the derivation fails closed on undecided anchored leaves — inside its walk domain. The domain here is per-booted-server config trees; imported foreign trees are nobody's walk.
- Sibling precedent for the same class:
#15872 (graph SQLite — membership + anchor + divergent sibling leaves), and the parity compose's own NEO_MEMORY_DB_PATH comment block (explicit placement, coherence-valid either way, member-set gap tracked separately).
- The F-invariant's completeness claim ("an unbound member cannot boot, by construction") is true only for leaves inside a walked tree; this leaf is the counterexample shape.
The Fix
- Compose arm: bind
NEO_NL_LOG_PATH: /app/.neo-ai-data-parity/logs in x-plane-env, with the same explicit-placement comment pattern as the NEO_MEMORY_DB_PATH entry (not-yet-declared member; coherence-valid either way).
- Contract arm: give the NL
logPath leaf an explicit planeMember decision matching the MC/KB siblings (true — logs are plane state per the sibling precedent), or record a rationale for false. The PR body must answer the census-domain question with a receipt: which config trees does each parity boot walk, and is an imported foreign tree's escape a ruled design decision or a blind spot?
- Runtime witness: wire a clean-boot assertion into the parity CI lane (PR #15983's surface): the readiness gate or the topology spec asserts the boot logs carry no
file sink unavailable degradation — the finding came from the lane; the lane verifies the fix.
- If the decision flips the leaf to
true: the census literal bumps consciously, per the #15851 pin contract.
Acceptance Criteria
Contract Ledger
(Added 2026-07-26 per intake §1.7 — the ticket modifies config surfaces consumed by the parity stack and the member census.)
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
NEO_NL_LOG_PATH env → neural-link logPath leaf (ai/mcp/server/neural-link/configBase.mjs:95) |
This ticket, compose arm |
Bound in the parity profile's x-plane-env to the relocated plane root (/app/.neo-ai-data-parity/logs) |
Unbound (any non-parity profile): the leaf's anchor default applies unchanged; no behavior delta outside the parity profile |
Compose comment (the NEO_MEMORY_DB_PATH explicit-placement precedent) |
Rendered docker compose config receipt; parity boot logs carry no file sink unavailable degradation |
planeMember: true metadata on the NL logPath leaf |
ADR 0019 §10.5 + the MC/KB sibling leaves (memory-core/configBase.mjs:736, knowledge-base/configBase.mjs:267) |
Leaf declares membership; the derivation walks it; census literal bumped consciously (#15851 contract) |
If the implementer's census-domain receipt rules false instead: the rationale is recorded in the PR body and this row is restated |
ADR 0019 §10.5 contract; sibling JSDoc |
Member-census spec (set-equality); derivation includes the leaf |
Parity x-plane-env binding set |
ai/deploy/docker-compose.dev.yml anchor |
Every plane-member leaf is bound for the relocated root — the NL log leaf joins the existing set |
A leaf added later without a binding fails the F-invariant boot walk (by construction) |
Compose header (completeness-check comment) |
Boot-coherence assertion at stack boot |
| Clean-boot witness (parity CI lane) |
PR #15983's landed lane |
Lane asserts the parity boot logs contain no file sink unavailable degradation |
Degradation reintroduced → lane red with the marker in the output |
Spec/readiness comment |
Parity-lane spec arm at exact head |
| Census literal |
#15851 pin contract |
The pinned member count rises by exactly one when the leaf flips to true |
A bump without the leaf, or the leaf without the bump, fails the pin |
#15851 contract |
Census spec |
| Imported-foreign-tree enforcement domain |
ADR 0019 §10.5 + this ticket's Out of Scope |
Explicitly OUT OF SCOPE here: whether derivePlaneMemberPaths must walk config trees imported-but-not-owned by the booting server is an ADR-0019 amendment question, routed to Grace's ADR lane via the steward |
This leaf ships the per-leaf decision + compose binding regardless of the domain ruling; if the domain is later ruled a blind spot, the walk domain extends WITHOUT this ticket reopening |
ADR 0019 (future amendment, steward-routed) |
The census-domain receipt in the implementing PR body |
Out of Scope
- The census-domain machinery question IF ruled a blind spot (walking imported foreign config trees is an ADR-0019 amendment lane of its own — the receipt in AC-2 decides, steward routes).
- Neural-link server containerization (seat-local by the parity invariants).
- MC/KB log leaves (already declared and bound).
Avoided Traps
- Compose-binding without the leaf decision — one plane's patch, not the contract; the next relocated plane re-lives the gap.
- Reading the stderr degradation as cosmetic — the no-silent-fallback guard is the only thing between this leaf and a cross-plane write; a guard firing is a finding, not a fix.
- Booting the NL server inside the parity stack to "cover" the tree — NL is seat-local by the parity invariants; the census is never fixed by moving the topology.
Related
#15872 (sibling member-set gap — graph SQLite) · #15931 (the root re-derivation / cross-plane write class) · epic #15798 · #15803 / PR #15871 (the parity compose) · PR #15983 (the lane whose boot logs surfaced this) · #15851 (census-pin contract) · #15800 (the placement election whose per-profile binding this completes)
Live latest-open sweep: checked latest 20 open issues at 2026-07-26T12:05Z; nearest #15872 disambiguated above (different leaf, same class). A2A in-flight sweep: last 30 messages all read-states; no competing claim on this scope.
Origin Session ID: 8e137ae8-b978-4f2e-9d36-168e430b51bb
Retrieval Hint: query_raw_memories("neural-link logPath plane member census NEO_NL_LOG_PATH parity file sink degradation")
Context
Surfaced by the new parity CI lane's first local boot (PR #15983, the #15807 lane): both the
mc-serverandorchestratorcontainers logfile sink unavailable, degrading to stderr: ENOENT: no such file or directory, mkdir '/app/.neo-ai-data/logs'during boot — a log-sink path resolving OUTSIDE the relocated parity plane root (/app/.neo-ai-data-parity). Named for steward triage in that PR's Deltas; filed here with the source trace complete.The Problem
The trace (receipts at current dev, branch of PR #15983):
ai/services/neural-link/RecorderService.mjs:6imports the neural-link server logger; the service runs inside mc-server and orchestrator processes (it owns thenl_action_logconnection — boot logs show[RecorderService] Connected to Memory Core nl_action_log.immediately after the sink failure in both containers).ai/mcp/server/neural-link/logger.mjs) builds its file sink from the neural-linklogPathleaf (ai/mcp/server/neural-link/configBase.mjs:95): default anchored to the canonical plane anchor, env-bound byNEO_NL_LOG_PATH.ai/mcp/server/memory-core/configBase.mjs:736andai/mcp/server/knowledge-base/configBase.mjs:267— both declared{planeMember: true}), the neural-link leaf declares no plane-member metadata, and the parity compose'sx-plane-env(which bindsNEO_MEMORY_LOG_PATH+NEO_KB_LOG_PATH) does not bindNEO_NL_LOG_PATH./app/.neo-ai-data/logs, misses, and degrades to stderr. The no-silent-canonical-fallback guard (ai/mcp/server/shared/logger.mjs:379) is what prevents a cross-plane WRITE — but that guard firing is exactly why this is a member-census gap, not cosmetic noise: without it, this is the#15931leak class.The census-domain sharpen (the part that makes this more than a missing env):
derivePlaneMemberPaths(ai/planeConfig.mjs:245) fails closed on an anchored leaf with no planeMember decision — but it is walked per booting server over ITS OWN config tree. The neural-link configBase exports noPLANE_MEMBER_PATHSand is never walked by the mc/kb/orchestrator boot walks, even though those processes import the NL config tree viaRecorderService. The NL server itself is seat-local by the parity invariants (never containerized in v1), so no boot anywhere asserts the NL tree. The leaf escapes the census by domain, not just by metadata.The Architectural Reality
#15872(graph SQLite — membership + anchor + divergent sibling leaves), and the parity compose's ownNEO_MEMORY_DB_PATHcomment block (explicit placement, coherence-valid either way, member-set gap tracked separately).The Fix
NEO_NL_LOG_PATH: /app/.neo-ai-data-parity/logsinx-plane-env, with the same explicit-placement comment pattern as theNEO_MEMORY_DB_PATHentry (not-yet-declared member; coherence-valid either way).logPathleaf an explicit planeMember decision matching the MC/KB siblings (true— logs are plane state per the sibling precedent), or record a rationale forfalse. The PR body must answer the census-domain question with a receipt: which config trees does each parity boot walk, and is an imported foreign tree's escape a ruled design decision or a blind spot?file sink unavailabledegradation — the finding came from the lane; the lane verifies the fix.true: the census literal bumps consciously, per the#15851pin contract.Acceptance Criteria
NEO_NL_LOG_PATHbound inx-plane-envwith the explicit-placement comment (compose arm).ai/mcp/server/neural-link/configBase.mjs:95matching sibling leaves, with the census-domain receipt in the PR body.#15851contract).Contract Ledger
(Added 2026-07-26 per intake §1.7 — the ticket modifies config surfaces consumed by the parity stack and the member census.)
NEO_NL_LOG_PATHenv → neural-linklogPathleaf (ai/mcp/server/neural-link/configBase.mjs:95)x-plane-envto the relocated plane root (/app/.neo-ai-data-parity/logs)NEO_MEMORY_DB_PATHexplicit-placement precedent)docker compose configreceipt; parity boot logs carry nofile sink unavailabledegradationplaneMember: truemetadata on the NLlogPathleafmemory-core/configBase.mjs:736,knowledge-base/configBase.mjs:267)#15851contract)falseinstead: the rationale is recorded in the PR body and this row is restatedx-plane-envbinding setai/deploy/docker-compose.dev.ymlanchorfile sink unavailabledegradation#15851pin contracttrue#15851contractderivePlaneMemberPathsmust walk config trees imported-but-not-owned by the booting server is an ADR-0019 amendment question, routed to Grace's ADR lane via the stewardOut of Scope
Avoided Traps
Related
#15872(sibling member-set gap — graph SQLite) ·#15931(the root re-derivation / cross-plane write class) · epic#15798·#15803/ PR#15871(the parity compose) · PR#15983(the lane whose boot logs surfaced this) ·#15851(census-pin contract) ·#15800(the placement election whose per-profile binding this completes)Live latest-open sweep: checked latest 20 open issues at 2026-07-26T12:05Z; nearest
#15872disambiguated above (different leaf, same class). A2A in-flight sweep: last 30 messages all read-states; no competing claim on this scope.Origin Session ID: 8e137ae8-b978-4f2e-9d36-168e430b51bb
Retrieval Hint:
query_raw_memories("neural-link logPath plane member census NEO_NL_LOG_PATH parity file sink degradation")