Skip to content

Commit d5e1815

Browse files
feat!: scan↔rollback duality — full-state rollback default, --preserve-state, path targeting (#231)
* wip: rollback duality — core replay module, RevertOpts threading, rollback multi-leg orchestration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * wip: rollback duality — scan PATHS, remove preserve/hosted legs, contract doc, suite green (163/163) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * wip: rollback duality — hosted-only remove path, 36 new behavior tests, suite 168/168 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: review-driven hardening of the rollback duality - P0: line-aware fragment removal (gem DEPENDENCIES/CHECKSUMS indent corruption) - dry-run hosted previews compose like wet runs (per-purl reverts claim the in-memory ledger; replay never refuses their edits) - remove hosted legs persist the mutated ledger before partial-failure exits - all three state stores loaded under apply.lock (pre-lock = existence probes) - vendoredFailed envelope key; vendored reserved-empty; any drift-keep makes remove a partialFailure; wiring-unknown guards apply under --preserve-state - vacuous anchor-original probe refuses; gem section-move record fails closed - FIFO/regular-file guards on replay reads+writes; error-class prints escape --silent; leftover-edit replays are prompted - hosted-only remove path (manifest-less redirect projects) + contract sync Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): clippy lints + extend GEM_UUIDS with the fifth activestorage 6.0.3 patch The hosted-e2e pin drifted independently of this branch: production published a fifth free-tier activestorage@6.0.3 patch on 2026-08-24 (GHSA-xr9x-r78c-5hrm / CVE-2026-66066, the Active Storage libvips variant-processing advisory) and the server-ranked selection now wires it. Live-verified via the public proxy /patch/view before pinning. Suite green locally (16 passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): classify rooted-but-driveless path patterns as absolute Path::is_absolute() is false on Windows for /global/store (rooted, no drive letter), so PathScope::parse classified it relative and it could never match an out-of-cwd path. A rooted pattern can never be cwd-relative — classify on has_root() (identical to is_absolute on Unix). Fixes path_scope::absolute_pattern_matches_paths_outside_cwd on the windows-latest runner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 3a2b06d commit d5e1815

58 files changed

Lines changed: 8422 additions & 340 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,96 @@ into the new version's section — see docs/releasing.md.
1717

1818
## [Unreleased]
1919

20+
> **Semver note:** this entry changes `rollback`'s default behavior and
21+
> narrows the meaning of its existing `vendored: []` JSON key — both MAJOR
22+
> per CLI_CONTRACT.md's semver policy — so it ships as the next major
23+
> release (v5.0).
24+
25+
### Changed (BREAKING)
26+
27+
- **`rollback` is now the full-state dual of `scan`.** `scan` and `rollback`
28+
are the batch primaries (`get``remove` stay the single-patch duals): a
29+
bare `rollback` restores the SYSTEM to unpatched across all three modes —
30+
in-place file restore (agent), vendored unwire + artifact deletion +
31+
ledger-entry drop, hosted lockfile-redirect unwind + redirect-record drop
32+
— then removes the rolled-back entries from `.socket/manifest.json` and
33+
GCs the now-unused blobs plus diff/package archives. No `--mode` needed:
34+
state is inferred from the manifest, the vendor ledger, and the redirect
35+
ledger, and rollback now runs manifest-less when a ledger holds work
36+
(hosted-only and detached-vendored projects; the truly-empty project
37+
keeps the "Manifest not found" exit 1, and a wired-but-ledgerless project
38+
errors naming `socket-patch repair`). Wet non-preserve runs confirm once
39+
("Roll back N patch(es), remove them from the local manifest, and delete
40+
M vendored artifact(s)?" — auto-accepted under `--yes`/`--json`/non-TTY;
41+
declining prints "Rollback cancelled." and exits 0). Drift-keeps, hosted
42+
refusals/unsupported targets, corrupt ledgers, and a failed manifest
43+
write exit 1 `partial_failure`; not-installed entries still exit 0.
44+
- **`rollback --json`'s `vendored: []` array narrows** to vendor-owned purls
45+
the run did NOT act on (today: the corrupt-vendor-ledger skip). Acted-on
46+
entries move to the new always-present `vendoredReverted` /
47+
`vendoredPreserved` / `vendoredKept` arrays; the envelope also gains
48+
always-present `warnings[]` (`{code, detail}`, now populated), `hosted`
49+
(`{reverted, failed, unsupported, editedFiles}`), `manifest`
50+
(`{removedEntries, preserved}`), `gc`, and `paths` keys.
51+
52+
### Added
53+
54+
- **Path targeting on `scan` and `rollback`.** `scan [PATHS]...` scopes
55+
discovery to packages with an installed copy under a matching glob
56+
(ancestor rule: `scan packages/foo` covers the subtree; `*` never crosses
57+
`/`; absolute patterns are the only way to reach `--global` stores); the
58+
prune universe is never narrowed (`scan PATHS --prune` prunes exactly
59+
what an unscoped run would), lockfile-only/vendor-ledger supplements are
60+
excluded with a `path_scope_excluded_supplements` warning, an empty match
61+
is a normal empty scan (exit 0, no GC), and PATHS is rejected with
62+
`--mode hosted|vendored` (exit 2). `rollback [TARGET]...` accepts
63+
PURLs, UUIDs, and path globs (variadic, unioned); only path-SHAPED tokens
64+
(separator, glob metachar, `./` prefix, absolute) become globs, so a
65+
mistyped identifier stays a safe exit-1 error. A path target selecting
66+
nothing is an error on rollback (exit 1) and an empty scan on scan
67+
(exit 0); path targets select installed copies, and rollback restores
68+
EVERY installed copy of a selected patch (`out_of_scope_copies_restored`
69+
warning when copies live outside the patterns).
70+
- **`--preserve-state` on `rollback` and `remove`** (env
71+
`SOCKET_PRESERVE_STATE`): fully unpatch the system but keep the local
72+
state for a later re-apply — manifest entries, vendored artifacts +
73+
ledger entries (kept byte-identical; re-vendor re-wires from the live
74+
lock) — and skip all GC. Hosted redirects have no preservable state:
75+
they are unwound and their records dropped either way
76+
(`hosted_state_not_preservable` warning). On `remove`, combining it with
77+
`--skip-rollback` is a usage error (exit 2, flag- or env-sourced): the
78+
combination would be a no-op — one flag keeps the tree and drops the
79+
state, the other restores the tree and keeps the state.
80+
- **Hosted redirect unwind.** Per-purl reverts for cargo + the npm family,
81+
plus a whole-ledger reverse replay (core `patch/redirect/replay.rs`) that
82+
runs whenever the scope covers every redirect record: a per-kind inverse
83+
table, staged all-or-nothing per ecosystem group, covering gem, golang,
84+
pypi, composer, bun, and the non-package rideshare edits (pnpm
85+
`trustLockfile` auto-config — pristine scaffold deleted, modified
86+
scaffold keeps the file and loses only the owned line). The bun.lockb
87+
migration is unrestorable by design (warning names git history);
88+
maven and nuget fail closed with `hosted_revert_unsupported` guidance
89+
(their structured-metadata edits keep their ledger records; re-run
90+
`scan --mode hosted` or restore from VCS). Refused groups keep their
91+
edits AND records — the coherent ledger a retry needs.
92+
- **`remove` gains the hosted leg and full archive GC**: an identifier
93+
matching hosted redirect-ledger records unwinds those redirects (per-purl
94+
or via the replay when it covers the full record set; works manifest-less
95+
on hosted-only projects; unsupported ecosystems fail closed with
96+
`hosted_revert_unsupported` before the manifest mutation), and remove's
97+
default GC extends from blobs-only to blobs + diff + package archives
98+
(parity with rollback/repair/`scan --prune`).
99+
100+
### Fixed
101+
102+
- **`remove` no longer drops the manifest entry of a drift-kept vendored
103+
purl.** When the vendored revert keeps the artifact (`kept_artifact`
104+
the lockfile drifted), the manifest entry is now kept too
105+
(`skipped`/`vendor_revert_kept`), matching the core RevertOutcome
106+
contract; previously the entry was deleted, stranding a live ledger
107+
entry with no backing record. An all-kept run exits 1 `partialFailure`
108+
with `summary.removed: 0` (never `not_found` — the identifier matched).
109+
20110
### Changed
21111

22112
- **Release publishing decomposed into per-registry workflows.** The

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dialoguer = "=0.11.0"
2828
indicatif = "=0.17.11"
2929
tempfile = "=3.26.0"
3030
regex = "=1.12.3"
31+
glob = "=0.3.4"
3132
toml_edit = "=0.25.12"
3233
once_cell = "=1.21.3"
3334
qbsdiff = "=1.4.4"

0 commit comments

Comments
 (0)