Context
Sub-leaf of #13056 (Extended-NL coordination) — the contract-bearing umbrella for the single blocker holding the enforcement epic. The in-heap enforcement primitives are merged on dev — LockRegistry (#13125), WriteGuard (#13134), deriveSubtreePath (#13138) — but unconsumed: nothing wires them into the write path, because the in-app Client has no requesting-agent identity. WriteGuard keys held locks on (agentId, sessionId); the Bridge knows the sender but forwards only the bare JSON-RPC message (ai/mcp/server/neural-link/Bridge.mjs:245), dropping the agentId.
Scope correction (@neo-opus-grace's + @neo-gpt's V-B-A — load-bearing). The original premise "no new auth — just forward what the Bridge already holds" was wrong. Bridge.handleConnection reads the agentId from the ?id= query param with only a non-empty check (:131) — a raw, unverified client CLAIM, not riding the #13065 bridge-token. Keying WriteGuard on it as-is would be a real spoofing path (any agent sets ?id=<other> to misattribute / bypass enforcement). So authentication is a prerequisite, not skipped.
The Problem
The enforcement needs the authenticated sender agentId to reach the write services so they can call WriteGuard.requestWrite(...) and deny a conflicting cross-agent write. The identity exists at the Bridge but is (a) unverified and (b) dropped at the forward boundary. This ticket is the contract of record; implementation is the three leaves below — it is deliberately not one PR (that would bind enforcement to a spoofable id and force too many contract decisions into a single review).
Contract Ledger
(Authored via @neo-gpt's ticket-intake — issuecomment-4700342636.)
| Surface |
Current |
Required |
Consumers / Compatibility |
Test proof |
| Bridge agent handshake |
?id= query claim accepted as identity |
authenticated Bridge-stamped agentId before any enforcement use |
agent connections; bridge-token / credential-class surface (#13065) |
spoofed id rejected |
| Bridge → Client frame |
bare JSON-RPC payload.message |
{type:'agent_message', agentId, message} sidecar, agentId Bridge-stamped |
src/ai/Client.mjs; legacy raw JSON-RPC path |
sidecar forwarded; raw read path unchanged |
| Client request dispatch |
handleRequest(method, params) |
handleRequest(method, params, context) with {agentId} when present |
all service dispatchers |
reads unaffected; writes receive context |
| Write-service enforcement |
direct mutation |
WriteGuard.requestWrite({agentId, sessionId, subtreePath}) before mutation |
InstanceService.setInstanceProperties / callMethod |
denied conflict does not mutate |
| Subtree path source |
none in write path |
deriveSubtreePath(targetId, parentOf) from the live parent chain |
topological-lock semantics |
missing path fails closed |
| No-agent write context |
implicit allow |
deny write-class calls (fail-closed), or explicitly justify an alternative |
legacy / non-harness callers |
fail-closed proof |
The Split (implementation leaves)
- Leaf A — Bridge auth + sidecar (@neo-opus-grace; security-load-bearing). Authenticate Bridge agent connections (bridge-token at
handleConnection, riding #13065) + emit the Bridge-stamped {type:'agent_message', agentId, message} sidecar — mirroring the existing handleAppMessage → broadcastToAgents({type:'app_message', message}) envelope (Bridge.mjs:281). Covers ledger rows 1–2.
- Leaf B/C — Body-side context-threading + enforcement (@neo-opus-ada; depends on A). The Client unwraps the sidecar + threads
{agentId} as a request context (handleRequest(method, params, {agentId})), preserving the raw-JSON-RPC read path; InstanceService / callMethod call WriteGuard.requestWrite({agentId, sessionId, subtreePath: deriveSubtreePath(...)}) before mutating, deny-no-mutate on conflict, fail-closed when no authenticated agentId. Covers ledger rows 3–6. (B = threading, C = enforcement; both Body-side + tightly coupled, so filed as one reviewable Body-side leaf unless review prefers them split.)
Per-leaf ACs live in Leaf A / Leaf B-C. Umbrella closure = both leaves merged + a live denied-cross-agent-write integration proof.
Out of Scope
- Lock lease / TTL (a later #13056 leaf).
- The Bridge-advisory surface (agents querying current locks).
- Session-id canonicalization (#12984 — the
sessionId half).
Decision Record impact
aligned-with — wires the merged enforcement primitives into the live write path; no ADR conflict.
Related
- Parent #13056. Consumes #13125 (LockRegistry), #13134 (WriteGuard), #13138 (deriveSubtreePath) — all merged. Leaf A coordinates with @neo-opus-grace (identity / bridge-token surface, #13065 / #13106). Contract Ledger authored via @neo-gpt's ticket-intake.
Live latest-open sweep: the #13056 sub-issues + an identity-transport search at 2026-06-14T01:04Z; no equivalent open ticket.
Release classification: post-release (Agent Harness product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e
Context
Sub-leaf of #13056 (Extended-NL coordination) — the contract-bearing umbrella for the single blocker holding the enforcement epic. The in-heap enforcement primitives are merged on dev — LockRegistry (#13125), WriteGuard (#13134), deriveSubtreePath (#13138) — but unconsumed: nothing wires them into the write path, because the in-app Client has no requesting-agent identity. WriteGuard keys held locks on
(agentId, sessionId); the Bridge knows the sender but forwards only the bare JSON-RPC message (ai/mcp/server/neural-link/Bridge.mjs:245), dropping the agentId.Scope correction (@neo-opus-grace's + @neo-gpt's V-B-A — load-bearing). The original premise "no new auth — just forward what the Bridge already holds" was wrong.
Bridge.handleConnectionreads the agentId from the?id=query param with only a non-empty check (:131) — a raw, unverified client CLAIM, not riding the #13065 bridge-token. Keying WriteGuard on it as-is would be a real spoofing path (any agent sets?id=<other>to misattribute / bypass enforcement). So authentication is a prerequisite, not skipped.The Problem
The enforcement needs the authenticated sender agentId to reach the write services so they can call
WriteGuard.requestWrite(...)and deny a conflicting cross-agent write. The identity exists at the Bridge but is (a) unverified and (b) dropped at the forward boundary. This ticket is the contract of record; implementation is the three leaves below — it is deliberately not one PR (that would bind enforcement to a spoofable id and force too many contract decisions into a single review).Contract Ledger
(Authored via @neo-gpt's ticket-intake —
issuecomment-4700342636.)?id=query claim accepted as identityagentIdbefore any enforcement usepayload.message{type:'agent_message', agentId, message}sidecar,agentIdBridge-stampedsrc/ai/Client.mjs; legacy raw JSON-RPC pathhandleRequest(method, params)handleRequest(method, params, context)with{agentId}when presentWriteGuard.requestWrite({agentId, sessionId, subtreePath})before mutationInstanceService.setInstanceProperties/callMethodderiveSubtreePath(targetId, parentOf)from the live parent chainThe Split (implementation leaves)
handleConnection, riding #13065) + emit the Bridge-stamped{type:'agent_message', agentId, message}sidecar — mirroring the existinghandleAppMessage→broadcastToAgents({type:'app_message', message})envelope (Bridge.mjs:281). Covers ledger rows 1–2.{agentId}as a request context (handleRequest(method, params, {agentId})), preserving the raw-JSON-RPC read path;InstanceService/callMethodcallWriteGuard.requestWrite({agentId, sessionId, subtreePath: deriveSubtreePath(...)})before mutating, deny-no-mutate on conflict, fail-closed when no authenticated agentId. Covers ledger rows 3–6. (B = threading, C = enforcement; both Body-side + tightly coupled, so filed as one reviewable Body-side leaf unless review prefers them split.)Per-leaf ACs live in Leaf A / Leaf B-C. Umbrella closure = both leaves merged + a live denied-cross-agent-write integration proof.
Out of Scope
sessionIdhalf).Decision Record impact
aligned-with— wires the merged enforcement primitives into the live write path; no ADR conflict.Related
Live latest-open sweep: the #13056 sub-issues + an identity-transport search at 2026-06-14T01:04Z; no equivalent open ticket.
Release classification: post-release (Agent Harness product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e