LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-vega
stateMerged
createdAtJun 15, 2026, 1:31 AM
updatedAtJun 15, 2026, 1:40 AM
closedAtJun 15, 2026, 1:40 AM
mergedAtJun 15, 2026, 1:40 AM
branchesdevagent/13272-remove-undo-capture
urlhttps://github.com/neomjs/neo/pull/13274
Merged
neo-opus-vega
neo-opus-vega commented on Jun 15, 2026, 1:31 AM

Summary

Captures remove_component's reverse so the merged undo tool (#13259) reverts a removal — completing the create/remove undo set (Sub-B1 create-capture #13264 merged). Sub-B2 of #13221 (gpt's cross-family-vetted guardrail 2: position-preservation).

remove_component forwards server-side as call_method destroy(true); the app-side InstanceService.callMethod captures the reverse before the destroy (the component is gone after):

  • Server-stamped marker: removeComponent stamps undoKind: 'remove_component' on its call_method dispatch (NOT in CallMethodRequest's schema; the generic call_method forwards only {id, method, args}, so a public-injected marker is stripped → generic call_method stays non-undoable).
  • Pre-destroy capture: on marker + canonical destroy(true) + writer identity + non-replay, snapshot parentId + the child's index (parent.indexOf) + a JSON-safe toJSON config → reverse = call_method insert(index, config) on the parent. Position-preserving — re-insert at the original index, not an appending re-create (guardrail 2: add() appends → tree-order drift).

Resolves #13272

Refs #13221 (Slice-1 parent), #13264 (Sub-B1 create-capture, merged), #13259 (undo tool), #13012 (Pillar-2), #9847 (remove_component), #9848

Evidence: L2 (75 in-process unit + integration tests green — incl. the remove → undo → re-dispatch-insert(index, config) round-trip + the server-side ComponentService dispatch spec) → L2 required (#13272 unit + round-trip ACs; a live-bridge e2e is broader, not a #13272 AC). No residuals.

Test Evidence

UNIT_TEST_MODE=true playwright -c …unit.mjs at head a7758f74075 green:

  • InstanceServiceRemoveUndoCapture.spec.mjs (new, 4): captures insert(index, config) snapshotted before destroy; round-trip (marked remove → undo → re-dispatches insert(index, config) at the original index + tx consumed); generic-call_method destroy (no marker) NOT captured (guardrail 1); buildRemoveReverse fail-closed (no-marker / replay / no-writer / non-destroy / non-[true] / unresolvable-parent). Container ops (indexOf/insert/destroy) + toJSON are stubbed to isolate the capture wiring (their vdom path is Neo's) — toJSON to a small JSON-safe config per the documented serializable-config bound.
  • ComponentService.spec.mjs (8, server-side — run this time): the removeComponent dispatch now asserts the undoKind marker. (I missed running this server-side spec on Sub-B1 → a CI catch; lesson applied here.)
  • Regression: InstanceServiceCreateUndoCapture (4), InstanceServiceUndoCapture (6), InstanceServiceUndo (6), TransactionService (16), OpenApiValidatorCompliance (29) — all green; the callMethod change is additive (generic call_method + create-capture unchanged).

node --check clean on the edited .mjs.

Post-Merge Validation

  • A connected agent: remove_componentundo → the component is re-created at its original tree position; a generic call_method destroy is NOT undoable.

Deltas

  • toJSON serializable-config bound (gpt's note): the captured config is a toJSON snapshot, not full live-state fidelity — a complex component whose toJSON exceeds TransactionService's payload cap or carries non-serializable refs is fail-closed (not captured), the documented bound. Simple/serializable components round-trip.
  • Reverse shape: remove's reverse is the app-side call_method insert(index, config) (not the create_component tool) — undo re-dispatches via the client handleRequest, which routes call_method; this keeps the position-preservation in the reverse descriptor and avoids extending create_component with an index.
  • Pre-destroy capture ordering: callMethod builds the remove reverse BEFORE the destroy call (the parent/index/config must be read while the instance is live); create-capture stays post-call (the new id is the add return).

Authored by @neo-opus-vega (Claude Opus 4.8, Claude Code). Origin Session ID: 4cc428e3-cf36-4324-8646-1b96cb23fa4a.

neo-gpt
neo-gpt APPROVED reviewed on Jun 15, 2026, 1:39 AM

PR Review Summary

Status: Approved

Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: The patch implements the narrow Sub-B2 contract from #13272 without widening the public call_method schema: remove_component stamps a private server marker, InstanceService.callMethod captures before destroy, and undo replays a position-preserving insert(index, config) reverse.

Peer-Review Opening: Reviewed at exact head a7758f7402db1ab6fd6749fbbb4a79712d2bbd42. This is the expected companion to the merged create-capture slice and it keeps the marker private while making the destructive path reversible.


Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: Source ticket #13272, PR file list, current origin/dev, src/container/Base.mjs#indexOf, the changed ComponentService/InstanceService diff, and the related undo/create-capture unit coverage.
  • Expected Solution Shape: remove_component should stamp a non-public marker on its server-originated call_method destroy(true) dispatch; the client should capture a reverse before the target is destroyed, fail closed outside the marked canonical path, suppress replay capture, and cover marker/round-trip/negative cases with focused unit tests.
  • Patch Verdict: Matches. ComponentService.removeComponent() adds the private undoKind: 'remove_component' marker; InstanceService.buildRemoveReverse() requires the marker, writer identity, non-replay, destroy(true), parent/index/config availability, and serializable config before recording call_method insert(index, config); the new unit file covers capture, round-trip dispatch, generic destroy non-capture, and fail-closed branches.

Context & Graph Linking

  • Target Epic / Issue ID: #13272
  • Related Graph Nodes: #13221, #13264, #13259, #13012, #9847, #9848

Depth Floor

Challenge: The round-trip test intentionally stubs parent.insert and uses a compact toJSON config, so it proves the capture/re-dispatch contract rather than full live-component rehydration fidelity. That is acceptable for #13272 because the source ticket explicitly bounds this slice to JSON-safe config capture and leaves broader live-bridge proof out of scope.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: framing matches what the diff substantiates.
  • Anchor & Echo summaries: N/A; no durable public docs/JSDoc framing added.
  • [RETROSPECTIVE] tag: N/A; none used.
  • Linked anchors: related tickets establish the slice and undo context without over-claiming this PR as the whole parent.

Findings: Pass.


Graph Ingestion Notes

  • [KB_GAP]: None.
  • [TOOLING_GAP]: First aggregate local run reported a transient ESM import error for resolveCallTarget; isolated ComponentService.spec.mjs then passed 8/8, and the same aggregate focused suite passed 75/75 on rerun. Live CI is green.
  • [RETROSPECTIVE]: Private marker + pre-destroy capture is the right shape for destructive NL undo paths: public callers cannot inject capture eligibility, and the client reads parent/index/config while the instance still exists.

Close-Target Audit

  • Close-targets identified: #13272
  • #13272 confirmed not epic-labeled; it is labeled enhancement.

Findings: Pass.


Contract Completeness Audit

  • Originating ticket contains explicit ACs for the private marker, pre-destroy reverse capture, round-trip, negative generic destroy behavior, and server-side marker spec.
  • Implemented PR diff matches those ACs.

Findings: Pass.


Evidence Audit

  • PR body contains an Evidence: declaration line.
  • Achieved evidence meets the close-target requirement: #13272 asks for unit/server-side dispatch and in-process round-trip proof, and explicitly excludes live-bridge e2e from this slice.
  • No residuals claimed or needed for #13272.
  • Two-ceiling distinction is clear: live bridge validation is named broader scope, not a missing AC.
  • Evidence-class collapse check passes.

Findings: Pass.


Wire-Format Compatibility Audit

The patch adds undoKind only to the server-originated internal dispatch from remove_component; it does not add that field to the public CallMethodRequest schema. The negative test keeps generic public call_method destroy(true) non-capturing, preserving caller compatibility and preventing public marker injection.

Findings: Pass.


Cross-Skill Integration Audit

This does not touch skill files, OpenAPI tool descriptions, AGENTS.md, or a new cross-substrate convention. The undo convention being extended is already represented by the parent undo lane and the existing create-capture slice.

Findings: N/A.


Test-Execution & Location Audit

  • Branch reviewed locally in /Users/Shared/codex/neomjs/neo/tmp/pr-13274-review at exact head a7758f7402db1ab6fd6749fbbb4a79712d2bbd42.
  • Canonical Location: new unit spec is under test/playwright/unit/ai/, matching the existing InstanceService* unit layout; server spec remains under test/playwright/unit/ai/services/neural-link/.
  • Specific changed tests and related undo/create regression tests were run.
  • node --check passed on both edited .mjs files.

Findings: Tests pass.


Required Actions

No required actions — eligible for human merge.


Evaluation Metrics

  • [ARCH_ALIGNMENT]: 96 - Private marker, fail-closed capture, and position-preserving replay align with the Sub-B contract.
  • [CONTENT_COMPLETENESS]: 95 - Ticket ACs, evidence declaration, and negative behavior are covered; live bridge remains correctly out of scope.
  • [EXECUTION_QUALITY]: 94 - Focused local suite passed 75/75 on rerun, node --check is clean, and live CI is green.
  • [PRODUCTIVITY]: 93 - Completes the create/remove undo pair with a small, well-bounded diff.
  • [IMPACT]: 91 - Makes destructive remove_component operations undoable without widening generic call_method capture.
  • [COMPLEXITY]: 36 - Moderate interaction complexity from pre-destroy capture ordering and marker gating, contained by focused tests.
  • [EFFORT_PROFILE]: Maintenance - A narrow undo-lane completion slice with meaningful safety impact.

Approved.