LearnNewsExamplesServices
Frontmatter
id13207
titleExtended-NL Leaf C (part 2a): resolveWriteLock — the pure write-enforcement decision core
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtJun 14, 2026, 11:26 AM
updatedAtJun 14, 2026, 2:01 PM
githubUrlhttps://github.com/neomjs/neo/issues/13207
authorneo-opus-ada
commentsCount0
parentIssue13167
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 14, 2026, 2:01 PM

Extended-NL Leaf C (part 2a): resolveWriteLock — the pure write-enforcement decision core

Closed v13.1.0/archive-v13-1-0-chunk-2 enhancementaiarchitecture
neo-opus-ada
neo-opus-ada commented on Jun 14, 2026, 11:26 AM

Context

Sub-leaf of #13167 (the Extended-NL enforcement contract-of-record), advancing its Contract Ledger rows 4–6 (write-service enforcement / subtree-path source / no-agent fail-closed). The in-heap primitives are all merged on dev: LockRegistry (#13125), WriteGuard (#13134), deriveSubtreePath (#13138). Part 1 (#13204 / PR #13205) threads the Bridge (agentId, sessionId) writer pair into the request context.

What is still missing is the decision that sits between a parsed write request and WriteGuard.requestWrite: given the request context + the target component id, should this write be enforced, and if so, with what lock — or denied fail-closed? Today that branching lives nowhere; the write services (InstanceService) mutate unconditionally.

The Problem

The enforce/skip/deny decision is security-critical and has four distinct outcomes that are easy to get subtly wrong:

  • a legacy / non-agent request (context absent) must still write — backward compatibility, or all non-harness writes break;
  • an agent request with an incomplete (agentId, sessionId) pair must deny (fail-closed) — a malformed identity must never acquire a lock;
  • an agent request whose target id is unresolvable (malformed / cyclic → deriveSubtreePath returns null) must deny (fail-closed) — never lock on a corrupt path;
  • a valid agent request must produce the exact {agentId, sessionId, subtreePath} lock WriteGuard.requestWrite expects.

Threading this through InstanceService first would make those four outcomes only testable via a live heap + WriteGuard. The subsystem's established shape is the opposite: pure decision cores (deriveSubtreePath, parseAgentEnvelope) shipped + exhaustively unit-tested in isolation, then wired. (On #13205, @neo-gpt explicitly endorsed shipping the pure parser ahead of the wiring.)

The Fix (contract)

A pure src/ai/resolveWriteLock.mjsresolveWriteLock(context, componentId, parentOf) — that composes deriveSubtreePath with the identity/fail-closed branching and returns a decision, not a side effect:

Input Decision
context absent (null/undefined) {enforced: false} — legacy / non-agent, caller writes unguarded
context object, agentId or sessionId missing/non-string/empty {enforced: true, lock: null, reason: 'incomplete-identity'} — caller denies, no mutate
valid identity, deriveSubtreePathnull (malformed/cyclic target) {enforced: true, lock: null, reason: 'unresolvable-target'} — caller denies, no mutate
valid identity + path {enforced: true, lock: {agentId, sessionId, subtreePath}} — caller passes lock to WriteGuard.requestWrite

Pure (no live heap, no WriteGuard, no socket): parentOf is injected exactly as deriveSubtreePath already takes it, so every outcome is unit-testable in isolation against a mock tree.

Acceptance Criteria

  • resolveWriteLock(context, componentId, parentOf) returns the four decision shapes above; absent context → enforced:false; any incomplete pair or unresolvable target under a present context → enforced:true, lock:null (fail-closed).
  • Pure — no import of WriteGuard / Client / a socket; parentOf injected; composes deriveSubtreePath.
  • Exhaustive unit spec (test/playwright/unit/ai/resolveWriteLock.spec.mjs) covering each row incl. the malformed-identity matrix (undefined/''/null/non-string for each of agentId/sessionId) and the cyclic/malformed target id.
  • Anchor & Echo JSDoc.

Out of Scope

  • The InstanceService.setInstanceProperties / callMethod wiring that consumes this decision + calls WriteGuard.requestWrite (the next slice — it reads context.sessionId at runtime, so it is gated on #13205 merging; landing it before would deny every agent write on current dev).
  • The agent_disconnectedWriteGuard.releaseAgent sweep (needs a Bridge disconnect frame not yet on dev).
  • #12984 session-id canonicalization (not on the critical path).

Related

  • Parent #13167 (Leaf B/C contract-of-record); sibling #13204 (part 1, parse). Consumes #13138 (deriveSubtreePath); produces the lock shape #13134 (WriteGuard) consumes. Epic #13056#13012.

Live latest-open sweep: grep -rl resolveWriteLock src/ ai/ test/ → none; no equivalent open ticket under #13167 / #13056.

Release classification: post-release (Agent Harness product line; nothing v13.x-blocking).

Authored by @neo-opus-ada (Ada, Claude Opus 4.8 [1M context]) — origin session 4c598c8f-d8a7-4288-9420-e825a45d310e.