LearnNewsExamplesServices
Frontmatter
id13224
titleExtended-NL Leaf C (part 2b): InstanceService write-path WriteGuard enforcement
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtJun 14, 2026, 2:18 PM
updatedAtJun 14, 2026, 3:00 PM
githubUrlhttps://github.com/neomjs/neo/issues/13224
authorneo-opus-ada
commentsCount0
parentIssue13167
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 14, 2026, 3:00 PM

Extended-NL Leaf C (part 2b): InstanceService write-path WriteGuard enforcement

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

Context

Sub-leaf of #13167 (Leaf B/C), the write-path half. The decision core resolveWriteLock (#13207) + the parse transport parseAgentEnvelope+sessionId (#13204/#13205) are now merged on dev, and Client.handleRequest already threads the request context as the 2nd service arg (#13174). The primitives exist but nothing consults them on the mutation pathInstanceService.setInstanceProperties / callMethod mutate unconditionally, ignoring context. This is the slice that makes the enforcement actually enforce (#13167 Contract Ledger rows 4–6).

The Problem

Two agents on one App-Worker heap can issue overlapping write-class NL ops against the same subtree. The merged WriteGuard / LockRegistry / resolveWriteLock are unconsumed: InstanceService calls instance.set(...) / scope[method](...) with no enforcement. Also unresolved: WriteGuard is deliberately not a Neo singleton (its spec constructs per-test for isolation), so there is no per-heap instance yet — the wiring must establish ownership.

The Fix

  1. src/ai/admitWrite.mjs (new, pure): admitWrite({context, componentId, parentOf, writeGuard}) → {admitted, reason, conflict} — composes resolveWriteLock (the decision) with writeGuard.requestWrite (the held-lock acquire). Fail-closed: context absent → {admitted:true} (legacy/unguarded); incomplete identity / unresolvable target → deny with reason; missing writeGuard while enforcement is needed → deny (no-write-guard); cross-writer conflict → deny with conflict. writeGuard + parentOf injected → unit-provable in isolation.
  2. src/ai/Client.mjs: the Client (per-heap connect-on-init singleton) owns the per-heap WriteGuard instance (Neo.create(WriteGuard) at init) — the shared lock truth for the heap. WriteGuard stays non-singleton (test isolation preserved).
  3. src/ai/client/InstanceService.mjs: setInstanceProperties / callMethod take context as the 2nd param and call admitWrite({context, componentId: id, parentOf: cid => Neo.getComponent(cid)?.parentId, writeGuard: this.client?.writeGuard}) before mutating; on !admitted they throw a deny (no mutation). A bare/legacy frame (context absent) is unguarded — backward-compatible.

Acceptance Criteria

  • admitWrite returns the documented fail-closed shapes; writeGuard+parentOf injected; exhaustive unit spec (legacy-skip, incomplete-identity deny, unresolvable-target deny, no-write-guard deny, cross-writer conflict deny, grant→admit + lock held).
  • The Client owns a per-heap WriteGuard instance; WriteGuard remains non-singleton.
  • InstanceService.setInstanceProperties / callMethod enforce via admitWrite before mutating; a denied write does not mutate; a legacy (no-context) write is unaffected.
  • Anchor & Echo JSDoc.

Out of Scope

  • The agent_disconnectedWriteGuard.releaseAgent sweep (needs a Bridge disconnect frame not yet on dev) — its own follow-up.
  • The live two-agent cross-write-denial whitebox-e2e integration proof (#13167 umbrella-closure evidence) — a follow-up; this slice ships the unit-proven wiring.
  • #12984 session-id canonicalization.

Related

  • Parent #13167 (Leaf B/C contract-of-record); consumes #13207 (resolveWriteLock), #13134 (WriteGuard), #13138 (deriveSubtreePath). Architecture doc: learn/agentos/MultiWriterEnforcement.md (#13217).

Live latest-open sweep: grep -rl admitWrite src/ ai/ test/ → none; the InstanceService-enforcement wiring has no existing leaf (only the #13167 umbrella).

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

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