Skip to content

Add change-scoped Rollback (active + archived) and opt-in checkpoint retention - #46

Merged
VeryComplexAndLongName merged 5 commits into
mainfrom
feat/change-scoped-rollback
Aug 18, 2026
Merged

VeryComplexAndLongName merged 5 commits into
mainfrom
feat/change-scoped-rollback

Conversation

@VeryComplexAndLongName

Copy link
Copy Markdown
Owner

Summary

  • Rollback previously only ever targeted one process's checkpoint, and nothing offered Rollback for an already-archived change (archiving never touches checkpoint/journal data — it was already unblocked at the data layer, just unexposed).
  • A task-scoped alternative (SQLite-backed per-checklist-item diff store, discussed in review) was explicitly rejected — full reasoning in docs/adr/0008-change-scoped-rollback-and-retention.md: the hard part (attribution) isn't solved by storage choice, a native SQLite dependency carries real packaging risk in a VS Code extension, and "restore with a merge-risk warning" is a more permissive philosophy than every other rollback path in this codebase.
  • rollbackChangeCheckpoints() (core) aggregates every finalized checkpoint for a Change: each file restores to its content from the earliest checkpoint that touched it, conflict-checked against each file's latest known post-run hash — same fail-closed, all-or-nothing semantics as single-process rollback, no new storage. Works identically for active and archived changes.
  • New openspec-ui.rollbackChange command (Changes + Archive trees) and openspec-ui.checkpointRetentionDays setting (default 0 = forever, unchanged from every prior version; a positive value prunes old history once at activation — the Rollback-availability consequence is disclosed in the setting description and README, not silent).
  • REST parity (POST /api/processes/rollback-change) added for API completeness; no new standalone webui button and no automatic server-side retention pruning in this pass (server has no settings system) — same scope pattern repo-bootstrap-snippets already used.
  • Stacked on Archive three shipped OpenSpec changes #42/Withhold Delete Task for done tasks, even in active Changes #43 (already-merged prerequisite branches).

Test plan

  • npm run verify (typecheck/lint/english-policy/test) passes repo-wide — core 162/162, server 39/39, extension 97/97
  • New unit tests across checkpoint.test.ts, workbench-recovery.test.ts, process-scheduler.test.ts, implementation-sessions.test.ts, commands.test.ts, server.test.ts
  • Real Extension Host run (npm run test:integration) — 6/6 passing
  • Real end-to-end smoke check via tsx against real files (not mocks) — two sequential checkpoints correctly restored to the earliest state
  • Real server test: two real checkpoints seeded into a real journal, restored via the actual REST endpoint
  • openspec change validate --strict change-scoped-rollback passes

🤖 Generated with Claude Code

VeryComplexAndLongName and others added 5 commits August 18, 2026 09:21
…retention

Two related gaps: Rollback only ever targeted one process at a time,
and nothing offered Rollback for an already-archived change even
though archiving never touches checkpoint/journal data. A task-scoped
alternative (per-checklist-item rollback via a SQLite diff store) was
discussed and rejected — see ADR 0008 for the full reasoning
(unsolved attribution problem, native-dependency risk, and a
fail-closed-vs-warn-and-restore philosophy mismatch with the rest of
this codebase).

rollbackChangeCheckpoints() aggregates every finalized checkpoint for
a Change into one restore: each file's target is its content from the
earliest checkpoint that touched it, conflict-checked against each
file's latest known post-run hash — same fail-closed, all-or-nothing
semantics as single-process rollback, no new storage. Works
identically for active and archived changes.

Also adds openspec-ui.checkpointRetentionDays (VS Code setting,
default 0 = keep forever, unchanged from every prior version) since
Rollback now reaches further back into a Change's history and
unbounded growth was worth addressing — pruning is opt-in and its
Rollback-availability consequence is disclosed, not silent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…llback

# Conflicts:
#	README.md
#	packages/extension/CHANGELOG.md
#	packages/extension/package.json
…llback

# Conflicts:
#	README.md
#	packages/extension/CHANGELOG.md
#	packages/extension/package.json
CI failed on a fast runner: two checkpoints captured back to back can
share the same millisecond in createdAt (new Date().toISOString()
resolution), and sorting by Date.parse(createdAt) then leaves tied
entries in their original array position via V8's stable sort rather
than a meaningful chronological one — silently aggregating before/
after state from the wrong checkpoint. Caught as a spurious conflict
in the order-independence test; in a real near-simultaneous scenario
it could instead silently restore the wrong content.

Fixed by removing the internal sort: the function now trusts
caller-provided order (earliest-first) instead of re-deriving it from
a lossy timestamp. Both real callers already provide correct order for
free via Map iteration order (insertion order = capture order).
Rewrote the affected test to match: correct order still works,
out-of-order input now fails closed with a conflict instead of
silently restoring the wrong content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…llback

# Conflicts:
#	README.md
#	packages/core/package.json
#	packages/server/package.json
@VeryComplexAndLongName
VeryComplexAndLongName merged commit 29bf192 into main Aug 18, 2026
6 checks passed
@VeryComplexAndLongName
VeryComplexAndLongName deleted the feat/change-scoped-rollback branch August 18, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant