Frontmatter
| title | >- |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Jun 15, 2026, 1:31 AM |
| updatedAt | Jun 15, 2026, 1:40 AM |
| closedAt | Jun 15, 2026, 1:40 AM |
| mergedAt | Jun 15, 2026, 1:40 AM |
| branches | dev ← agent/13272-remove-undo-capture |
| url | https://github.com/neomjs/neo/pull/13274 |

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_methodschema:remove_componentstamps a private server marker,InstanceService.callMethodcaptures before destroy, and undo replays a position-preservinginsert(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 changedComponentService/InstanceServicediff, and the related undo/create-capture unit coverage. - Expected Solution Shape:
remove_componentshould stamp a non-public marker on its server-originatedcall_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 privateundoKind: 'remove_component'marker;InstanceService.buildRemoveReverse()requires the marker, writer identity, non-replay,destroy(true), parent/index/config availability, and serializable config before recordingcall_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 forresolveCallTarget; isolatedComponentService.spec.mjsthen 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 labeledenhancement.
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-reviewat exact heada7758f7402db1ab6fd6749fbbb4a79712d2bbd42. - Canonical Location: new unit spec is under
test/playwright/unit/ai/, matching the existingInstanceService*unit layout; server spec remains undertest/playwright/unit/ai/services/neural-link/. - Specific changed tests and related undo/create regression tests were run.
-
node --checkpassed on both edited.mjsfiles.
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 --checkis clean, and live CI is green.[PRODUCTIVITY]: 93 - Completes the create/remove undo pair with a small, well-bounded diff.[IMPACT]: 91 - Makes destructiveremove_componentoperations undoable without widening genericcall_methodcapture.[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.
Summary
Captures
remove_component's reverse so the mergedundotool (#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_componentforwards server-side ascall_methoddestroy(true); the app-sideInstanceService.callMethodcaptures the reverse before the destroy (the component is gone after):removeComponentstampsundoKind: 'remove_component'on itscall_methoddispatch (NOT inCallMethodRequest's schema; the genericcall_methodforwards only{id, method, args}, so a public-injected marker is stripped → genericcall_methodstays non-undoable).destroy(true)+ writer identity + non-replay, snapshotparentId+ the child's index (parent.indexOf) + a JSON-safetoJSONconfig → reverse =call_methodinsert(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), #9848Evidence: L2 (75 in-process unit + integration tests green — incl. the remove → undo → re-dispatch-
insert(index, config)round-trip + the server-sideComponentServicedispatch 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.mjsat heada7758f740— 75 green:InstanceServiceRemoveUndoCapture.spec.mjs(new, 4): capturesinsert(index, config)snapshotted before destroy; round-trip (marked remove →undo→ re-dispatchesinsert(index, config)at the original index + tx consumed); generic-call_methoddestroy(no marker) NOT captured (guardrail 1);buildRemoveReversefail-closed (no-marker / replay / no-writer / non-destroy/ non-[true]/ unresolvable-parent). Container ops (indexOf/insert/destroy) +toJSONare stubbed to isolate the capture wiring (their vdom path is Neo's) —toJSONto a small JSON-safe config per the documented serializable-config bound.ComponentService.spec.mjs(8, server-side — run this time): theremoveComponentdispatch now asserts theundoKindmarker. (I missed running this server-side spec on Sub-B1 → a CI catch; lesson applied here.)InstanceServiceCreateUndoCapture(4),InstanceServiceUndoCapture(6),InstanceServiceUndo(6),TransactionService(16),OpenApiValidatorCompliance(29) — all green; thecallMethodchange is additive (genericcall_method+ create-capture unchanged).node --checkclean on the edited.mjs.Post-Merge Validation
remove_component→undo→ the component is re-created at its original tree position; a genericcall_methoddestroyis NOT undoable.Deltas
toJSONserializable-config bound (gpt's note): the captured config is atoJSONsnapshot, not full live-state fidelity — a complex component whosetoJSONexceedsTransactionService's payload cap or carries non-serializable refs is fail-closed (not captured), the documented bound. Simple/serializable components round-trip.call_method insert(index, config)(not thecreate_componenttool) —undore-dispatches via the clienthandleRequest, which routescall_method; this keeps the position-preservation in the reverse descriptor and avoids extendingcreate_componentwith an index.callMethodbuilds the remove reverse BEFORE thedestroycall (the parent/index/config must be read while the instance is live); create-capture stays post-call (the new id is theaddreturn).Authored by @neo-opus-vega (Claude Opus 4.8, Claude Code). Origin Session ID: 4cc428e3-cf36-4324-8646-1b96cb23fa4a.