LearnNewsExamplesServices
Frontmatter
id13324
titleLive e2e proof: a disconnected writer''s WriteGuard lock is released
stateClosed
labels
enhancementaitesting
assigneesneo-opus-ada
createdAtJun 15, 2026, 11:41 AM
updatedAtJun 15, 2026, 12:22 PM
githubUrlhttps://github.com/neomjs/neo/issues/13324
authorneo-opus-ada
commentsCount0
parentIssue13056
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 15, 2026, 12:22 PM

Live e2e proof: a disconnected writer's WriteGuard lock is released

Closed v13.1.0/archive-v13-1-0-chunk-3 enhancementaitesting
neo-opus-ada
neo-opus-ada commented on Jun 15, 2026, 11:41 AM

Context

#13294 (Resolves #13167, merged) proved the WriteGuard deny-while-held path live: two distinct raw role=agent writers, writer-1 holds a subtree lock, writer-2's overlapping write is DENIED. #13229 (closed) implemented the disconnect-release: a disconnected writer's held locks are swept. Vega's #13294 review follow-up-1 flagged that the release → re-admit half of the lock lifecycle is unit-covered (#13226 / #13284) but not e2e-proven live. This ticket adds that live proof — the symmetric counterpart to #13294.

The Problem

The lock lifecycle has two halves. The deny-while-held half is live-proven (#13294). The release-on-disconnect → re-admit half is implemented (#13229) and unit-covered, but no live e2e exercises the full transport→sweep→re-admit path. Without it, a regression in the Bridge's app-side agent_disconnected emission or the client sweep would pass CI (which doesn't run e2e) and unit tests, yet break real two-agent lock handoff.

The Architectural Reality

The live path: Bridge agent ws closebroadcastToApps({type:'agent_disconnected', agentId, sessionId}) (ai/mcp/server/neural-link/Bridge.mjs:232, the app-side broadcast includes sessionId — the agent-side broadcast at :226 omits it) → Client.onSocketMessage (src/ai/Client.mjs:313) → handleAgentDisconnectedwriteGuard.releaseAgent({agentId, sessionId}) (src/ai/Client.mjs:301) + transactionService.sweep. The e2e is the live-app counterpart to the unit coverage, the same shape as #13294.

The Fix

A sibling spec test/playwright/e2e/WriteGuardDisconnectReleaseNL.spec.mjs: writer-1 (raw ws) acquires + holds a button's subtree; a distinct writer-2 (raw ws) overlapping write → DENIED; writer-1 disconnects → its lock is swept; writer-2 → ADMITTED.

Critical harness ordering (proven empirically): writer-1 must be fully established and holding the lock BEFORE writer-2 opens. Opening both sockets back-to-back races the lock acquisition and yields non-deterministic deny/admit — establishing the holder first is rock-solid (proven deterministic 2/2 in a throwaway: deny-while-held, then admit-after-disconnect).

Acceptance Criteria

  • WriteGuardDisconnectReleaseNL.spec.mjs: established writer-1 holds A; writer-2 overlapping → DENIED (conflict naming writer-1's agentId/sessionId); writer-1 disconnect → writer-2 retry → ADMITTED.
  • Deterministic via the establish-holder-before-opening-writer-2 ordering.
  • (manual L3, like #13294 — neo CI does not run the e2e suite) green against a fresh Neural Link bridge.

Out of Scope

  • The deny-while-held proof — #13294 / #13167 (merged).
  • The NL undo/redo e2e proofs — #13286 / #13306 (Vega's lane; undo ≠ lock-release).
  • The disconnect-release implementation — #13229 (closed).

Decision Record impact

none — e2e verification of an existing, shipped behavior; aligned-with the #13056 topological-locking model.

Related

  • #13056 (epic parent — Extended-NL coordination: locking) / #13229 (the disconnect-release impl this proves)
  • #13294 / #13167 (the deny-while-held e2e sibling) / #13226 / #13284 (unit coverage)

Release classification: post-release (verification proof of shipped behavior — non-blocking).

Origin Session ID: 2d993feb-ea2f-4468-8fbd-c53e62365f4d

Retrieval Hint: "WriteGuard disconnect release e2e agent_disconnected sweep re-admit lock lifecycle"

Authored by Claude Opus 4.8 (1M context), @neo-opus-ada (Ada).