Graduated from Discussion #13216 (NL mutation-undo Slice-0 design convergence) — §6.2 consensus quorum met 2026-06-14: Claude family [GRADUATION_APPROVED] (@neo-opus-ada) + GPT family [GRADUATION_APPROVED] (@neo-gpt, non-author — signal, version-bound sha256:15cb07d5…); @neo-gemini-pro operator_benched (archived under Unresolved Liveness). This is Slice-1 of #9848 (the Slice-tracking parent): the smallest shippable undo — in-memory, single-level, same live Neural Link session — over the three constrained NL mutation tools.
Pillar-2's conversational-UI thesis (#13012) requires an agent or user to say "undo that" and have the live app revert cleanly. The mutation primitives ship (create_component#9846, remove_component#9847, set_instance_properties); this slice adds the reversibility layer over them.
The Problem
RecorderService persists every NL invocation to nl_action_log, but it is a forward-only audit log ({tool, args, result} for DreamService/RLAIF) — it captures no reverse-ops, so it is not an undo substrate (e.g. remove_component's forward args are just {componentId}; the destroyed config needed to reverse it is never captured). Slice-1 adds an in-memory, per-session transaction stack recording the reverse of each mutation, captured after the multi-writer enforcement grant.
The Architectural Reality
Mutation seam:create_component/remove_component are constrained ai/services/neural-link/ComponentService.mjs tools delegating app-side to call_method (container.add(config) / component.destroy(true)); set_instance_properties is the constrained instance mutation. App-side mutation lands at src/ai/client/InstanceService.mjs.
Enforcement seam (#13167):resolveWriteLock → WriteGuard.requestWrite (heap-side, (agentId, sessionId)-keyed subtree lock). Reverse-capture MUST follow the grant. deriveSubtreePath(componentId, parentOf) derives the absolute path at request time — re-used at undo time.
Capability-security: the App Worker emits only JSON DOM-ops; reverse-ops are data-not-code by construction (each reverse = a re-dispatched validated NL tool).
The Fix
A new in-memory TransactionService (per-session undo stack) + reverse-capture hooks in the InstanceService write-path (post-enforcement-grant) + an undo NL tool (6-site wiring). Reverses: create⁻¹ = remove(boundNewId), set⁻¹ = set(oldValues), remove⁻¹ = create(originatingConfig). Undo re-enters resolveWriteLock/WriteGuard.requestWrite enforced as the current caller (provenance ≠ enforcement identity), re-deriving the subtreePath on the current tree.
Per-session stack keyed on the live {agentId, sessionId} writer pair (the same identity WriteGuard keys locks on; txId = per-transaction handle within the stack, not part of the key); states open→committed→undone / aborted/timedOut
Stores WHO (originWriter, provenance) + WHAT (inverse-op) + targetSubtreePath (audit only) + sequenceId + bounded label; enforcement re-derives path at undo
Unresolvable target → fail-closed undo-denial
class JSDoc
unit (incl. negative: moved/destroyed/unauthorized)
InstanceService capture hook
D#13216 OQ1-B
Post-enforcement-grant capture for the 3 whitelisted tools only; denied write → no reverse record
Non-whitelisted → not captured
inline JSDoc
unit
Decision Record
Not needed (D#13216 graduation classification) — valid only while Slice-1 stays same-session, in-memory, no nl_action_log schema mutation, no Memory Core persistence, no privileged rollback capability. Any of those four flips it to Required in the relevant later slice. Decision Record impact:none — no ADR amended/superseded; aligned-with the #13167 enforcement contract + ADR 0020 (Agent Harness).
OQ4 (security boundary) → AC: reverse via re-dispatched validated tools.
OQ5 (× WriteGuard #13167) → AC: Option D — provenance ≠ enforcement-identity; re-enter enforcement as caller.
Acceptance Criteria
undo NL tool (write-locked tier) reverts the last committed transaction for the requester over the 3 whitelisted tools (create_component, remove_component, set_instance_properties); generic admin call_method is NOT undoable.
Reverse-capture happens only after the forward write is enforcement-granted (resolveWriteLock → WriteGuard.requestWrite succeeds); a denied write creates no reverse record.
The reverse mutation is dispatched as a validated NL tool, enforced as the current requester (re-enters resolveWriteLock/WriteGuard.requestWrite); originWriter is stored as provenance/authorization only and is never impersonated.
Enforcement re-derives the subtreePath at undo via deriveSubtreePath on the current tree (NOT the stored capture-time path). Negative coverage (per @neo-gpt residual): moved target enforces on the new path; destroyed target → null → fail-closed undo-denial (unresolvable-target); unauthorized (different-writer-held overlap) → denied.
create⁻¹ binds the created component id from the successful add result (or an explicit tested read-back contract) before remove(newId). (per @neo-gpt residual — explicit test contract.)
Transaction states open → committed → undone + aborted/timedOut; disconnect/releaseAgent/timeout sweeps the requester's open tx + reconciles held locks (lifecycles can't diverge); denied/aborted/timed-out tx are not undoable + leave no active reverse records.
Explicit caps: stack depth, max ops per transaction, max serialized reverse-payload size.
Undo-of-remove for a component with no captured create-config (pre-existing/non-agent-created) is denied + deferred (documented limitation).
6-site NL-tool wiring complete (openapi path+tier+schema · toolService serviceMapping · the service method · OpenApiValidatorCompliance fixtures · unit spec autoConnect=false · e2e).
e2e proof: agent-created component → undo → reverts cleanly in the live tree.
nl_action_log as the active undo authority — rejected (STEP_BACK #7): it stays forward-audit; the in-memory stack is the active authority.
Pre-image capture of a live config (OQ3-A) for remove⁻¹ — rejected: the create_component module-footgun (class refs/functions don't cross the wire); replay the originating create-config instead.
Related
Graduated from Discussion #13216 (the design convergence, full divergence trail + Signal Ledger).
Parent: #9848 (Slice-tracking parent — the NL Transaction/Undo Stack).
Handoff Retrieval Hints: query_summaries("Neural Link mutation undo Slice-1 transaction stack 13216 graduation") · ask_knowledge_base("NL undo WriteGuard reverse-capture", type='ticket') · the D#13216 convergence trail + Signal Ledger.
Authored by @neo-opus-vega (Claude Opus 4.8, Claude Code).
tobiu referenced in commit 8086fd2 - "fix(ai): align TransactionService stack key to the (agentId, sessionId) writer pair (#13236) (#13238) on Jun 14, 2026, 6:41 PM
tobiu referenced in commit e02a25b - "feat(ai): capture set_instance_properties reverses onto the per-session undo stack (#13248) (#13249) on Jun 14, 2026, 8:54 PM
tobiu referenced in commit 1035769 - "feat(ai): the undo Neural Link tool — peek, re-dispatch reverse, consume (#13257) (#13259) on Jun 14, 2026, 10:02 PM
tobiu referenced in commit d03161f - "feat(ai): capture create_component's reverse for undo — server-stamped marker (#13261) (#13264) on Jun 15, 2026, 1:10 AM
tobiu referenced in commit fbec07f - "feat(ai): capture remove_component's reverse for undo — position-preserving insert (#13272) (#13274) on Jun 15, 2026, 1:40 AM
tobiu referenced in commit 29b45ac - "fix(ai): wire tx-sweep into agent-disconnect (lock/tx coherence) (#13282) (#13284) on Jun 15, 2026, 2:50 AM
Context
Graduated from Discussion #13216 (NL mutation-undo Slice-0 design convergence) — §6.2 consensus quorum met 2026-06-14: Claude family
[GRADUATION_APPROVED](@neo-opus-ada) + GPT family[GRADUATION_APPROVED](@neo-gpt, non-author — signal, version-boundsha256:15cb07d5…); @neo-gemini-prooperator_benched(archived under Unresolved Liveness). This is Slice-1 of #9848 (the Slice-tracking parent): the smallest shippable undo — in-memory, single-level, same live Neural Link session — over the three constrained NL mutation tools.Pillar-2's conversational-UI thesis (#13012) requires an agent or user to say "undo that" and have the live app revert cleanly. The mutation primitives ship (
create_component#9846,remove_component#9847,set_instance_properties); this slice adds the reversibility layer over them.The Problem
RecorderServicepersists every NL invocation tonl_action_log, but it is a forward-only audit log ({tool, args, result}for DreamService/RLAIF) — it captures no reverse-ops, so it is not an undo substrate (e.g.remove_component's forward args are just{componentId}; the destroyed config needed to reverse it is never captured). Slice-1 adds an in-memory, per-session transaction stack recording the reverse of each mutation, captured after the multi-writer enforcement grant.The Architectural Reality
create_component/remove_componentare constrainedai/services/neural-link/ComponentService.mjstools delegating app-side tocall_method(container.add(config)/component.destroy(true));set_instance_propertiesis the constrained instance mutation. App-side mutation lands atsrc/ai/client/InstanceService.mjs.resolveWriteLock→WriteGuard.requestWrite(heap-side,(agentId, sessionId)-keyed subtree lock). Reverse-capture MUST follow the grant.deriveSubtreePath(componentId, parentOf)derives the absolute path at request time — re-used at undo time.The Fix
A new in-memory
TransactionService(per-session undo stack) + reverse-capture hooks in theInstanceServicewrite-path (post-enforcement-grant) + anundoNL tool (6-site wiring). Reverses:create⁻¹ = remove(boundNewId),set⁻¹ = set(oldValues),remove⁻¹ = create(originatingConfig). Undo re-entersresolveWriteLock/WriteGuard.requestWriteenforced as the current caller (provenance ≠ enforcement identity), re-deriving the subtreePath on the current tree.Contract Ledger Matrix
undoNL tool (new, write-locked)TransactionService(new,src/ai/TransactionService.mjs, in-memory){agentId, sessionId}writer pair (the same identityWriteGuardkeys locks on;txId= per-transaction handle within the stack, not part of the key); statesopen→committed→undone/aborted/timedOutreleaseAgent/timeout → abort + reconcile locksoriginWriter, provenance) + WHAT (inverse-op) +targetSubtreePath(audit only) +sequenceId+ bounded label; enforcement re-derives path at undoInstanceServicecapture hookDecision Record
Not needed(D#13216 graduation classification) — valid only while Slice-1 stays same-session, in-memory, nonl_action_logschema mutation, no Memory Core persistence, no privileged rollback capability. Any of those four flips it toRequiredin the relevant later slice. Decision Record impact:none— no ADR amended/superseded; aligned-with the #13167 enforcement contract + ADR 0020 (Agent Harness).Discussion Criteria Mapping (#13216)
InstanceService, post-grant.nl_action_logstays forward-audit.Acceptance Criteria
undoNL tool (write-locked tier) reverts the last committed transaction for the requester over the 3 whitelisted tools (create_component,remove_component,set_instance_properties); generic admincall_methodis NOT undoable.resolveWriteLock→WriteGuard.requestWritesucceeds); a denied write creates no reverse record.resolveWriteLock/WriteGuard.requestWrite);originWriteris stored as provenance/authorization only and is never impersonated.deriveSubtreePathon the current tree (NOT the stored capture-time path). Negative coverage (per @neo-gpt residual): moved target enforces on the new path; destroyed target →null→ fail-closed undo-denial (unresolvable-target); unauthorized (different-writer-held overlap) → denied.create⁻¹binds the created component id from the successfuladdresult (or an explicit tested read-back contract) beforeremove(newId). (per @neo-gpt residual — explicit test contract.){sequenceId, originWriter, inverse-op descriptor, forward descriptor, targetSubtreePath (audit), bounded label/source}.open → committed → undone+aborted/timedOut; disconnect/releaseAgent/timeout sweeps the requester's open tx + reconciles held locks (lifecycles can't diverge); denied/aborted/timed-out tx are not undoable + leave no active reverse records.toolServiceserviceMapping · the service method ·OpenApiValidatorCompliancefixtures · unit specautoConnect=false· e2e).undo→ reverts cleanly in the live tree.Out of Scope (later #9848 children)
nl_action_logschema mutation) — Slice-3.redo+ named/atomic transaction batching — Slice-2.systemRollback(privileged cross-writer rescue) — separate capability with its own security review.call_methodundo (only the 3 whitelisted tools).Avoided Traps
resolveWriteLock's request-time derivation.nl_action_logas the active undo authority — rejected (STEP_BACK #7): it stays forward-audit; the in-memory stack is the active authority.remove⁻¹— rejected: thecreate_componentmodule-footgun (class refs/functions don't cross the wire); replay the originating create-config instead.Related
learn/agentos/MultiWriterEnforcement.md).create_component(#9846),remove_component(#9847),set_instance_properties.Release classification: post-release (Agent Harness Pillar-2 feature; not v13-release-blocking) — boardless.
Origin Session ID: 4cc428e3-cf36-4324-8646-1b96cb23fa4a
Handoff Retrieval Hints:
query_summaries("Neural Link mutation undo Slice-1 transaction stack 13216 graduation")·ask_knowledge_base("NL undo WriteGuard reverse-capture", type='ticket')· the D#13216 convergence trail + Signal Ledger.Authored by @neo-opus-vega (Claude Opus 4.8, Claude Code).