LearnNewsExamplesServices
Frontmatter
id16008
titleNeural Link Bridge stdio must fail without the injected logPath
stateClosed
labels
bugai
assigneesneo-gpt-emmy
createdAtJul 26, 2026, 6:52 PM
updatedAtJul 26, 2026, 7:48 PM
githubUrlhttps://github.com/neomjs/neo/issues/16008
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 26, 2026, 7:48 PM

Neural Link Bridge stdio must fail without the injected logPath

neo-gpt-emmy
neo-gpt-emmy commented on Jul 26, 2026, 6:52 PM

Context

Narrow delivery leaf under #15931, created after its required full-path census showed that the umbrella covers more than the Neural Link consumer named in its original evidence. This ticket gives the already-bounded ConnectionService correction an honest close target while #15931 stays open for the remaining runtime-writer census.

Prior art is #13899, which first routed detached Bridge stdout/stderr into .neo-ai-data/logs. The missing boundary is what happens when the injected Neural Link logPath is absent.

The Problem

At current dev, ai/services/neural-link/ConnectionService.mjs resolves Bridge stdio with:

logPath || path.resolve(neoRootDir || cwd, '.neo-ai-data/logs')

That second branch independently reconstructs the canonical data root. On a relocated overlay plane with a real-directory seat, a missing logPath can therefore create and write <canonical>/.neo-ai-data/logs/neural-link-bridge-stdio.log instead of failing. A symlinked seat can mask the defect because both paths may resolve to the same inode.

Patch-Blind Premise Check

Question: Can the current consumer derive a writable canonical path without the injected config leaf?

Result: Yes. Passing logPath: '' plus a canonical-looking neoRootDir returns a canonical .neo-ai-data/logs path; spawnBridge() then opens it before spawning the child.

The Architectural Reality

ADR-0019 §3 A1 forbids a consumer from re-deriving an AiConfig-owned path: require the injected value and fail loud. PR #15876 established that rule at the shared logger boundary; this leaf applies the same rule to the remaining Neural Link child-process consumer.

This is distinct from #15984. That ticket decides and binds the Neural Link logPath leaf as a parity plane member. This ticket ensures that, regardless of profile, ConnectionService cannot silently invent a replacement when the leaf is absent.

The Fix

  • Make getBridgeStdioLogPath() accept only logPath (defaulting to aiConfig.logPath).
  • Throw for missing, empty, or whitespace-only values before any directory open or child spawn.
  • Remove neoRootDir from the spawnBridge() log-path contract.
  • Preserve the injected overlay path exactly and keep the child working-directory behavior unchanged.

Acceptance Criteria

  • Missing/blank logPath fails before openBridgeLogFile() or spawnBridgeProcess().
  • A real-directory test seat writes the stdio file only beneath an injected overlay logPath; the canonical .neo-ai-data/logs directory remains absent.
  • The configured-path and spawn-wiring regressions remain green.
  • Focused Neural Link unit coverage and the AiConfig template-SSOT lint pass.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
getBridgeStdioLogPath({logPath}) ADR-0019 §3 A1; #15931 Resolve only <logPath>/neural-link-bridge-stdio.log Missing/blank path throws; no root derivation Function JSDoc Direct helper negative + configured-path unit
ConnectionService.spawnBridge({logPath}) This ticket Pass the injected directory into the helper before opening or spawning Helper rejection prevents both side effects Method JSDoc Side-effect spies remain false on rejection
Real-directory overlay write boundary #15931 falsifier File and parent directory exist only under overlay root Canonical root remains absent Unit-spec summary Filesystem assertion under a disposable real directory

Out of Scope

  • The broader #15931 census and any Fleet/concept-store dispositions it yields.
  • #15984's planeMember decision, compose binding, census literal, and parity boot witness.
  • TenantRepoSyncService placement; #15800's live record owns that cloud-profile decision.
  • Changing the Bridge child working directory or startup-delay behavior.

Avoided Traps

  • Closing #15931 with a partial enumeration. This leaf closes only the consumer it fully proves.
  • Treating #15984 as the same defect. A correctly bound leaf and a fail-loud consumer are complementary boundaries.
  • Testing only a symlinked seat. The filesystem witness uses a real disposable directory and independently checks canonical absence.

Evidence Plan

npx playwright test test/playwright/unit/ai/services/neural-link/ConnectionService.spec.mjs \
  -c test/playwright/playwright.config.unit.mjs --project=unit-brain --workers=1
npm run ai:lint-config-template-ssot

Related

Parent #15931 · sibling #15984 · #15875 / PR #15876 · prior #13899 · ADR-0019 §3

Origin Session ID: 019f9e1e-2ef1-72c3-a04d-6bc67a531a8b