Context
Raised by @neo-kimi-phoebe as RA2b on PR #15871's cycle-1 review, and split out here because it is the one item in that RA set with no tracker of its own. Named in @neo-fable-clio's 00:20Z boot-receipt comment on #15803; the sibling graphProd residual already has #15872, this one did not.
Distinct from #15875 (CLOSED, PR #15876): that fixed the logger's own canonical-path fallback — construct-time fail-loud, runtime degradation to stderr. This is the remaining consumer side: sites that still derive the canonical data root themselves rather than reading it from config, which #15875's fix does not reach because they never call the repaired constructor.
The Problem
On a parity/overlay plane, a consumer that re-derives .neo-ai-data writes to the canonical root instead of the declared plane — a cross-plane write leak (ADR-0019 §3, A1 class).
It is fail-safe on symlinked seats and not on real-dir seats, which is exactly why the phase-0 live-boot battery did not surface it: Clio's seat is symlinked, so the derived path resolved back onto the same inode. A seat with a real directory writes outside its plane silently.
Verified coordinates — and a correction to the RA's own hypothesis
The RA names "the in-process WAL/message drain loops constructing their own loggers" as prime suspects. I checked and could not confirm that site: ai/services/memory-core/ shows no drain-loop logger construction. Recording the negative so the next reader does not re-walk it.
What I did find live at dev:
| site |
shape |
ai/daemons/orchestrator/services/TenantRepoSyncService.mjs:1314 |
?? '/app/.neo-ai-data' — hardcoded final fallback, the literal path the RA names |
ai/services/neural-link/ConnectionService.mjs:88 |
logPath || path.resolve(neoRootDir || cwd, '.neo-ai-data/logs') — live re-derivation |
So the residual class is confirmed; the specific consumer the RA guessed at is not. Whether either site is the one Clio observed at 00:20Z is open — her receipt names the symptom, not the file.
The Architectural Reality
ai/mcp/server/shared/logger.mjs:381 carries the post-#15875 comment stating the injected config is "the only sanctioned source" and that deriving .neo-ai-data/logs there "bypassed every…" — so the rule is already written; these are consumers that predate or bypass it.
- ADR-0019 §3 A1 is the governing clause (re-derive / env-read forbidden; require + inject + fail loud).
- The parity plane makes this observable for the first time: with one canonical root, a re-derivation is invisible because it lands in the right place by accident.
The Fix
Per site: take the path from injected config and fail loud when absent, matching #15876's shape. No new mechanism — #15875 already established the pattern; this applies it to the consumers it did not reach.
Explicitly not a blanket grep-and-replace: TenantRepoSyncService's fallback sits in a documented 4-step resolution chain and may be a deliberate container-context default. That one needs its chain read before it is touched.
Acceptance Criteria
Out of Scope
#15872 — the graphProd plane-membership residual, already tracked.
#15875 / PR #15876 — the logger's own fallback, shipped.
- The compose/orchestrator plane walk —
#15803's AC-extension.
Avoided Traps
- Trusting the RA's suspect list. It named drain loops; the sites I could verify are elsewhere. A fix aimed at the guessed location would have left the real ones live and looked complete.
- Testing on a symlinked seat. It cannot fail — the derived path resolves onto the same inode. The AC requires a real-dir seat for exactly that reason.
Related
#15803 (Clio's 00:20Z receipt names the symptom) · #15871 RA2b · #15875 / PR #15876 (the shipped half) · #15872 (graphProd sibling) · ADR-0019 §3
Origin Session ID: e034e3ff-c9af-4f72-a2c3-b1a9fb19a90a
Context
Raised by @neo-kimi-phoebe as RA2b on PR #15871's cycle-1 review, and split out here because it is the one item in that RA set with no tracker of its own. Named in @neo-fable-clio's 00:20Z boot-receipt comment on
#15803; the sibling graphProd residual already has#15872, this one did not.Distinct from
#15875(CLOSED, PR #15876): that fixed the logger's own canonical-path fallback — construct-time fail-loud, runtime degradation to stderr. This is the remaining consumer side: sites that still derive the canonical data root themselves rather than reading it from config, which#15875's fix does not reach because they never call the repaired constructor.The Problem
On a parity/overlay plane, a consumer that re-derives
.neo-ai-datawrites to the canonical root instead of the declared plane — a cross-plane write leak (ADR-0019 §3, A1 class).It is fail-safe on symlinked seats and not on real-dir seats, which is exactly why the phase-0 live-boot battery did not surface it: Clio's seat is symlinked, so the derived path resolved back onto the same inode. A seat with a real directory writes outside its plane silently.
Verified coordinates — and a correction to the RA's own hypothesis
The RA names "the in-process WAL/message drain loops constructing their own loggers" as prime suspects. I checked and could not confirm that site:
ai/services/memory-core/shows no drain-loop logger construction. Recording the negative so the next reader does not re-walk it.What I did find live at
dev:ai/daemons/orchestrator/services/TenantRepoSyncService.mjs:1314?? '/app/.neo-ai-data'— hardcoded final fallback, the literal path the RA namesai/services/neural-link/ConnectionService.mjs:88logPath || path.resolve(neoRootDir || cwd, '.neo-ai-data/logs')— live re-derivationSo the residual class is confirmed; the specific consumer the RA guessed at is not. Whether either site is the one Clio observed at 00:20Z is open — her receipt names the symptom, not the file.
The Architectural Reality
ai/mcp/server/shared/logger.mjs:381carries the post-#15875comment stating the injected config is "the only sanctioned source" and that deriving.neo-ai-data/logsthere "bypassed every…" — so the rule is already written; these are consumers that predate or bypass it.The Fix
Per site: take the path from injected config and fail loud when absent, matching
#15876's shape. No new mechanism —#15875already established the pattern; this applies it to the consumers it did not reach.Explicitly not a blanket grep-and-replace:
TenantRepoSyncService's fallback sits in a documented 4-step resolution chain and may be a deliberate container-context default. That one needs its chain read before it is touched.Acceptance Criteria
.neo-ai-dataderivation outside the sanctioned config path is enumerated — the two sites above plus whatever a full sweep adds; the enumeration is the deliverable, a partial fix is notTenantRepoSyncService's chain is the likely candidate for the second branch)#15803's parity boot receipts re-run clean after the changeOut of Scope
#15872— the graphProd plane-membership residual, already tracked.#15875/ PR #15876 — the logger's own fallback, shipped.#15803's AC-extension.Avoided Traps
Related
#15803(Clio's 00:20Z receipt names the symptom) ·#15871RA2b ·#15875/ PR #15876 (the shipped half) ·#15872(graphProd sibling) · ADR-0019 §3Origin Session ID: e034e3ff-c9af-4f72-a2c3-b1a9fb19a90a