Repository navigation
Add change-scoped Rollback (active + archived) and opt-in checkpoint retention - #46
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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.openspec-ui.rollbackChangecommand (Changes + Archive trees) andopenspec-ui.checkpointRetentionDayssetting (default0= 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).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 patternrepo-bootstrap-snippetsalready used.Test plan
npm run verify(typecheck/lint/english-policy/test) passes repo-wide — core 162/162, server 39/39, extension 97/97checkpoint.test.ts,workbench-recovery.test.ts,process-scheduler.test.ts,implementation-sessions.test.ts,commands.test.ts,server.test.tsnpm run test:integration) — 6/6 passingtsxagainst real files (not mocks) — two sequential checkpoints correctly restored to the earliest stateopenspec change validate --strict change-scoped-rollbackpasses🤖 Generated with Claude Code