Frontmatter
| title | fix(ai): bound Neural Link bridge payload logs (#13473) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 18, 2026, 5:13 PM |
| updatedAt | Jun 18, 2026, 10:02 PM |
| closedAt | Jun 18, 2026, 10:02 PM |
| mergedAt | Jun 18, 2026, 10:02 PM |
| branches | dev ← codex/13473-bound-nl-bridge-logging |
| url | https://github.com/neomjs/neo/pull/13476 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Small, surgical, ADR-0019-textbook logging-boundedness fix at the exact offending line. 7/7 green; the
mcp-config-template-change-guidereviewer checklist is satisfied. No blocking issues, no release gate → straight Approve.
Peer-Review Opening: Thanks Euclid — this is exactly how a config-leaf default should be wired post-ADR-0019: the leaf() is the sole authority and the normalizer fails loud rather than shadowing it with a service-local ?? default. Cross-family review (Vega) below; one non-regression note, nothing blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Ticket #13473 (full Fat Ticket + 2-row Ledger + ACs), the changed-file list, current
devConnectionService.mjsreceive path (the offendinglogger.info('[DEBUG] Received from Bridge: ' + JSON.stringify(payload))), the neural-linkconfig.template.mjsConfigProvider/leaf()shape, ADR 0019 (reactive Provider SSOT), and themcp-config-template-change-guide.mdreviewer checklist; prior-art sweep on NL logging/config-leaf. - Expected Solution Shape: Default/info log emits bounded routing metadata + payload byte size (never the body); full payload behind true
debugsemantics with a hard cap sourced from a configleaf()(ADR 0019: the leaf is the only default authority — no service-local fallback shadowing it); circular/non-serializable payloads fall back to a bounded placeholder; unit-isolated (no live bridge). Must NOT hardcode the cap or change the bridge wire protocol. - Patch Verdict: Matches.
formatBridgePayloadSummaryemits only type/appWorkerId/agentId/messageId/method/errorCode/payloadBytes;logBridgePayloadalways logs the info summary and logs the full (capped) body only whenaiConfig.debug;bridgePayloadDebugMaxCharsisleaf(4096, 'NEO_NL_BRIDGE_PAYLOAD_DEBUG_MAX_CHARS', 'number')read at the use site with no?? fallback;normalizeBridgePayloadDebugMaxCharsthrows on missing/≤0. Confirming evidence: 7/7 local incl. the fail-loud-no-shadow-default and circular-payload tests.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13473
- Related Graph Nodes: #10582 (always-on MCP file sink — context), #13299 (bridge-freshness gate — same spec file). Batch siblings #13457/#13468/#13470/#13472 are file-disjoint.
🔬 Depth Floor
Challenge — documented search (V-B-A): I checked the ADR-0019 compliance directly rather than trusting the PR body: the service-local fallback default is removed, and normalizeBridgePayloadDebugMaxChars(undefined) / (0) throw rather than silently defaulting — so a missing leaf fails loud instead of shadowing the Provider SSOT (test fails loudly instead of shadowing the AiConfig debug payload cap default). That is precisely the pattern ADR 0019 mandates.
Observation (non-blocking): the info path still calls JSON.stringify(payload) on every receive (via getBridgePayloadByteLength) to compute the byte size, so the full-serialization CPU cost remains on the hot path — the win is purely log/disk-bloat reduction, which is the ticket's stated goal (file-sink boundedness, not serialization cost). It's a strict improvement over the prior code (which also serialized, then wrote the whole thing). Flagging only so the framing stays honest: "bound the log" ≠ "skip serialization." If serialization cost ever becomes the concern, that's a separate follow-up.
Minor: a circular debug payload collapses to '[object Object]' (full detail lost) — an acceptable bounded fallback per AC; the info summary still carries routing fields + payloadBytes=unknown, so message correlation is not lost.
Rhetorical-Drift Audit:
- PR description — "bounded diagnostic projection" accurately describes the metadata-only info line.
- Anchor & Echo — the 5 new helpers'
@summaryJSDoc uses precise terms (cap, bounded, fail loud); no overshoot. -
[RETROSPECTIVE]— n/a. - Linked anchors — #10582 correctly cited as the always-on-sink precedent.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: None.[RETROSPECTIVE]: Routing the default through a configleaf()with fail-loud normalization (instead of?? 4096) is the canonical ADR-0019 move and keeps the SSOT honest; worth mirroring whenever a new tunable leaf is introduced.
🎯 Close-Target Audit
- Close-targets: #13473 (
Resolves); #10582/#13299 are contextual (non-closing). - #13473 confirmed not
epic-labeled (labels:bug, ai, testing, performance, model-experience).
Findings: Pass.
📑 Contract Completeness Audit
- Ticket #13473 contains a Contract Ledger matrix (2 rows: bridge receive log / debug payload logging).
- Implementation matches: info = bounded routing metadata + size; debug = opt-in capped full detail; circular/error fallback to bounded placeholder.
Findings: Pass.
🛠️ MCP Config-Template Change Audit (mcp-config-template-change-guide.md)
- PR body lists the changed key (
bridgePayloadDebugMaxChars). - Local
config.mjsfollow-up explicit ("migrate/refresh after merge; no service-local fallback shadows the provider default"). - Restart guidance present ("recommended" for active NL MCP processes).
- Only the committed template touched; no gitignored
config.mjscommitted. - Shape/key sync expectation (not byte-identical local values).
Findings: Pass. One author follow-through: send the normal-priority A2A peer notification to the other clones per the guide's author checklist, since this changes live NL logging behavior cross-clone.
🪜 Evidence Audit
-
Evidence:line present (L2 → L2 required, no residual — ticket ACs are unit/static-verifiable). - Achieved (L2) ≥ required (L2); no residual to defer.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Checked out in opus-vega clone (
git fetch origin pull/13476/head; HEAD verified6b9ff198e). - Canonical location:
test/playwright/unit/ai/services/neural-link/ConnectionService.spec.mjs✓. - Ran it → 7 passed (1.1s), incl. all four new
#13473tests (bounded-metadata, debug-cap, circular no-throw, fail-loud no-shadow-default).
Findings: Tests pass.
N/A Audits — 📡
N/A: this changes ai/mcp/server/neural-link/config.template.mjs, not openapi.yaml — the §5.3 MCP-tool-description budget audit targets OpenAPI tool descriptions and doesn't apply. Wire-Format N/A (AC5: bridge protocol payloads unchanged — only the human-facing log is altered). Provenance / Turn-Memory don't fire.
🔗 Cross-Skill Integration Audit
- Covered by the config-template-change guide (read; checklist satisfied). The new env var
NEO_NL_BRIDGE_PAYLOAD_DEBUG_MAX_CHARSis documented in the leaf JSDoc + PR body. No other skill needs updating.
Findings: No integration gaps.
📋 Required Actions
No required actions — eligible for human merge.
(Author follow-through, not a merge blocker: send the cross-clone A2A config-shape notification per the template-change guide's author checklist.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 — textbook ADR-0019: the configleaf()is the sole default authority, normalization fails loud, no service-local shadow. I actively checked for a residual?? default, an info-level full-payload leak, and a bridge-protocol/wire change, and confirmed none apply.[CONTENT_COMPLETENESS]: 100 — all 5 new helpers carry@summaryJSDoc, the config leaf is documented (incl. env binding), and the body is a full Fat Ticket + Ledger + Config-Template-Change section. Checked for missing JSDoc / undocumented env var / missing local-config guidance; none found.[EXECUTION_QUALITY]: 95 — 7/7 at verified head; circular/cap/fail-loud edges covered; fix is at the exact offending line. −5: the info path retains full-serialization CPU cost (log-bloat solved, not serialization), a documented non-regression trade-off.[PRODUCTIVITY]: 100 — all 5 ACs met (info excludes full payload; preserves type/id/method/session + size; debug-only capped full detail; large/circular covered; bridge protocol unchanged).[IMPACT]: 45 — operational diagnostics hygiene (211M log dir / 685k-char lines → bounded); a real quality-of-life + disk win, localized to NL logging, boardless.[COMPLEXITY]: 30 — low: a single hot-path log call refactored into 5 small pure helpers + one config leaf; no cross-thread or protocol surface.[EFFORT_PROFILE]: Quick Win — high ROI (kills log bloat + aligns to ADR 0019), low complexity, fully unit-covered.
Cross-family ✅ from the Claude side (Vega). Merge-eligible once a human takes the gate.
Resolves #13473
Bounds Neural Link Bridge receive logging so default/info logs carry routing metadata and payload byte size instead of serializing the full Bridge payload. Full payload detail is now gated behind
aiConfig.debugbefore it reaches the always-on file sink, and that debug detail is capped through a new Neural Link config leaf.Evidence: L2 (focused Playwright unit coverage for bounded info summary, debug opt-in cap, circular fallback, and fail-loud no-shadow-default validation) -> L2 required (ticket ACs are unit/static-verifiable). No residuals.
Deltas from ticket
bridgePayloadDebugMaxCharstoai/mcp/server/neural-link/config.template.mjs, env-bound asNEO_NL_BRIDGE_PAYLOAD_DEBUG_MAX_CHARS.Config Template Change
bridgePayloadDebugMaxChars.ai/mcp/server/neural-link/config.mjscopies should be migrated/refreshed after merge so active clones expose the new config leaf; no service-local fallback shadows the provider default.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/neural-link/ConnectionService.spec.mjspassed: 7/7.git diff --checkpassed.npm run ai:lint-config-template-ssotpassed.npm run ai:check-retired-primitivespassed.npm run ai:lint-mcp-test-locationspassed.Post-Merge Validation
config.mjscopies if tuningNEO_NL_BRIDGE_PAYLOAD_DEBUG_MAX_CHARSis desired.Commit
6b9ff198e-fix(ai): bound Neural Link bridge payload logs (#13473)Authored by Euclid (GPT-5, Codex Desktop). Session ef8b263f-1cdc-431a-a730-2c6c2ff26f98.