Context
Leaf of #13056 (Extended-NL coordination), closing the Isolation / conflict-semantics surface of its Criteria Mapping. Epic #13012 guardrail #1 binds: Topological Locking before any multi-writer slice — or the slice explicitly stays single-writer. The curated-tool-surface sibling already landed (#13064/#13082/#13084/#13106 closed); this is the locking surface's pure conflict-detection core — the deterministic logic an in-heap authority will enforce.
Release classification: boardless — harness coordination line (board 13), post-v13, not release-gating.
The Problem
The Neural Link is single-agent-trusted-localhost by construction. ConnectionService.call() auto-targets the most recent session when none is passed (ai/services/neural-link/ConnectionService.mjs:103-107) — correct for one writer, a cross-writer footgun for two. The co-habitation premise (ADR 0020 §1) makes two-writers-one-app (Scenario-C) load-bearing, and guardrail #1 sequences locking ahead of it. Before any in-heap enforcement can exist, the conflict semantics themselves need a deterministic, testable definition.
Scope (this leaf — pure logic only)
A standalone LockRegistry (placement via structural-pre-flight; pure data-structure logic, not a transport-side authority — the epic explicitly rejects Bridge-authoritative locking; the heap is the truth and this module is the logic an in-heap authority consumes):
- Lock key
(agentId, sessionId, subtreePath) per the epic's converged shape; sessionId consumed as an opaque string (the #12984 canonical-id mechanics are not re-implemented here).
acquire(lock) → {granted} or {conflict, heldBy}; release(lock); releaseAll({agentId|sessionId}) (disconnect-sweep entry point).
- Subtree-overlap conflict semantics: two locks conflict iff their component-subtree paths overlap (equal, or one is an ancestor/descendant of the other) and they are held by different
(agentId, sessionId) writers. Same-writer re-acquisition is re-entrant (granted), never self-conflict.
- Explicit-target enforcement: a pure predicate that, given the live-session count, requires an explicit target when more than one session is live (the auto-targeting-deprecation rule).
- Fail-closed: a malformed key, missing path, or ambiguous request denies rather than guesses.
Out of Scope (named follow-up consumer slices)
- In-heap (App-Worker) enforcement wiring — the authoritative layer; this leaf delivers the logic it will call, not the interception.
- Bridge-side advisory integration; the NL-mutation→
MESSAGE observability glue (rides the coordination slice per the epic).
#12984 canonical-session-id mechanics (consumed as an opaque key here; a blocked_by edge attaches at the enforcement slice, not this logic core).
- Lock expiry / lease semantics on worker restart (the registry exposes
releaseAll as the sweep hook; the policy that calls it is the enforcement slice).
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Evidence |
LockRegistry.acquire/release/releaseAll |
this leaf (new) |
deterministic grant/conflict over (agentId, sessionId, subtreePath); re-entrant same-writer |
fail-closed deny on malformed key |
unit: conflict matrix + re-entrancy + release |
| Subtree-overlap conflict predicate |
this leaf (new) |
equal/ancestor/descendant overlap across different writers = conflict |
sibling (non-overlapping) paths never conflict |
unit: overlap matrix incl. root, depth, siblings |
| Explicit-target predicate |
epic §Intended solution (auto-targeting deprecation) |
>1 live session ⇒ explicit target required |
single session ⇒ implicit allowed (back-comat) |
unit: 0/1/N session gating |
Decision Record impact
aligned-with ADR 0020 (guardrail #1 — locking precedes multi-writer); consumes (does not own) #12984. No ADR amendment — the locking leaf's placement + granularity finalize in this Ledger per the epic.
Acceptance Criteria
Related
Parent: #13056. Refs #13012 (guardrail #1), #12984 (opaque id consumer), ai/services/neural-link/ConnectionService.mjs:103-107. Design-of-record: #13056 body (converged, plan-of-record rev 11+).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e
Retrieval Hint: "topological lock registry subtree conflict detection multi-writer neural link agentId sessionId explicit target fail-closed"
Context
Leaf of #13056 (Extended-NL coordination), closing the Isolation / conflict-semantics surface of its Criteria Mapping. Epic #13012 guardrail #1 binds: Topological Locking before any multi-writer slice — or the slice explicitly stays single-writer. The curated-tool-surface sibling already landed (#13064/#13082/#13084/#13106 closed); this is the locking surface's pure conflict-detection core — the deterministic logic an in-heap authority will enforce.
Release classification:boardless — harness coordination line (board 13), post-v13, not release-gating.The Problem
The Neural Link is single-agent-trusted-localhost by construction.
ConnectionService.call()auto-targets the most recent session when none is passed (ai/services/neural-link/ConnectionService.mjs:103-107) — correct for one writer, a cross-writer footgun for two. The co-habitation premise (ADR 0020 §1) makes two-writers-one-app (Scenario-C) load-bearing, and guardrail #1 sequences locking ahead of it. Before any in-heap enforcement can exist, the conflict semantics themselves need a deterministic, testable definition.Scope (this leaf — pure logic only)
A standalone
LockRegistry(placement viastructural-pre-flight; pure data-structure logic, not a transport-side authority — the epic explicitly rejects Bridge-authoritative locking; the heap is the truth and this module is the logic an in-heap authority consumes):(agentId, sessionId, subtreePath)per the epic's converged shape;sessionIdconsumed as an opaque string (the #12984 canonical-id mechanics are not re-implemented here).acquire(lock)→{granted}or{conflict, heldBy};release(lock);releaseAll({agentId|sessionId})(disconnect-sweep entry point).(agentId, sessionId)writers. Same-writer re-acquisition is re-entrant (granted), never self-conflict.Out of Scope (named follow-up consumer slices)
MESSAGEobservability glue (rides the coordination slice per the epic).#12984canonical-session-id mechanics (consumed as an opaque key here; ablocked_byedge attaches at the enforcement slice, not this logic core).releaseAllas the sweep hook; the policy that calls it is the enforcement slice).Contract Ledger
LockRegistry.acquire/release/releaseAll(agentId, sessionId, subtreePath); re-entrant same-writerDecision Record impact
aligned-with ADR 0020 (guardrail #1 — locking precedes multi-writer); consumes (does not own) #12984. No ADR amendment — the locking leaf's placement + granularity finalize in this Ledger per the epic.
Acceptance Criteria
LockRegistrypure module exists at thestructural-pre-flight-chosen location; JSDoc states it is conflict logic for an in-heap authority, not a transport-side lock owner.Related
Parent: #13056. Refs #13012 (guardrail #1), #12984 (opaque id consumer),
ai/services/neural-link/ConnectionService.mjs:103-107. Design-of-record: #13056 body (converged, plan-of-record rev 11+).Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e Retrieval Hint: "topological lock registry subtree conflict detection multi-writer neural link agentId sessionId explicit target fail-closed"