GeezerVison is showing github.com — Document and test remove/rollback of a superseded generation by uuid · SocketDev/socket-patch@da19b80
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: crates/socket-patch-cli/CLI_CONTRACT.md +7-2Lines changed: 7 additions & 2 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -833,7 +833,12 @@ worse, lets a warm cache silently serve unpatched bytes): | 833 | 833 | (purl or patch uuid; v5 keeps no hosted ledger) restores each matched pin to its default | 834 | 834 | upstream registry entry — the same restore as `rollback` (see "Hosted unwind coverage"), for every | 835 | 835 | ecosystem. A hosted-only match works with no manifest at all (mirroring the manifest-less | 836 | | - vendored escape). A pin the restore refuses (`--offline`, a registry that does not answer, | | 836 | + vendored escape). **One owned pin per release**: an identifier that matches a manifest entry | | 837 | + also selects the vendored ledger entry that entry claims and every hosted pin wiring the same | | 838 | + package release, whatever patch uuid (generation) each recorded — so `remove <uuid A>` of a | | 839 | + record a later scan superseded with patch B (vendored or hosted) unwinds B's wiring too. That | | 840 | + hosted restore needs the registry lookups below, so such a remove can refuse offline where the | | 841 | + manifest entry alone would not. A pin the restore refuses (`--offline`, a registry that does not answer, | 837 | 842 | `bun.lockb`, …) or a failed write is the top-level `hosted_revert_failed` error BEFORE the | 838 | 843 | manifest mutation (exit 1, manifest not modified; message `could not restore <purl> to its | 839 | 844 | upstream registry entry: … (`git checkout -- <files>`)`). v4's `hosted_revert_unsupported` is no | @@ -888,7 +893,7 @@ worse, lets a warm cache silently serve unpatched bytes): | 888 | 893 |
| 889 | 894 | ### Targets | 890 | 895 |
| 891 | | -`rollback [TARGET]...` — zero or more targets, unioned. `pkg:` tokens are PURLs (base purl matches every release variant; qualified purl exact), other identifier-shaped tokens are UUIDs, and only **path-shaped** tokens (separator, glob metachar `*?[`, `./` prefix, or absolute) are path globs — see the per-subcommand args table for the safety rationale. Identifier matching runs across ALL THREE sources (a hosted pin matches by purl or by the patch uuid in its hosted URL); an identifier matching nothing anywhere is the familiar exit-1 error. Path globs use the same matcher as `scan [PATHS]` (ancestor rule, `require_literal_separator`, absolute-only outside `--cwd`, Windows case-insensitive): installed copies of every candidate purl are discovered and purls with ≥ 1 matching copy are selected. Scoping sentences (shared with scan): | | 896 | +`rollback [TARGET]...` — zero or more targets, unioned. `pkg:` tokens are PURLs (base purl matches every release variant; qualified purl exact), other identifier-shaped tokens are UUIDs, and only **path-shaped** tokens (separator, glob metachar `*?[`, `./` prefix, or absolute) are path globs — see the per-subcommand args table for the safety rationale. Identifier matching runs across ALL THREE sources (a hosted pin matches by purl or by the patch uuid in its hosted URL); an identifier that matches a manifest entry also selects the vendored ledger entry that entry claims and every hosted pin of the same package release under any patch uuid (one owned pin per release — `remove` parity); an identifier matching nothing anywhere is the familiar exit-1 error. Path globs use the same matcher as `scan [PATHS]` (ancestor rule, `require_literal_separator`, absolute-only outside `--cwd`, Windows case-insensitive): installed copies of every candidate purl are discovered and purls with ≥ 1 matching copy are selected. Scoping sentences (shared with scan): | 892 | 897 |
| 893 | 898 | * **A target that selects nothing is an error on `rollback` (exit 1) and an empty scan on `scan` (exit 0).** Each rollback path pattern must select at least one patched package; the error names the pattern and the reachability rule. | 894 | 899 | * **Path targets select installed copies; entries with no installed copy are reachable only by identifier or unscoped runs.** | |
Collapse file Copy file name to clipboardExpand all lines: crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +33-2Lines changed: 33 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1144,8 +1144,9 @@ async fn a_git_pattern_hosted_pin_is_refused_not_restored_to_the_registry() { | 1144 | 1144 | assert!( | 1145 | 1145 | envelope["hosted"]["failed"][0]["error"] | 1146 | 1146 | .as_str() | 1147 | | - .is_some_and(|e| e.contains("installs from git") | 1148 | | - && e.contains("`git checkout -- yarn.lock`")), | | 1147 | + .is_some_and( | | 1148 | + |e| e.contains("installs from git") && e.contains("`git checkout -- yarn.lock`") | | 1149 | + ), | 1149 | 1150 | "{envelope}" | 1150 | 1151 | ); | 1151 | 1152 | assert_eq!( | @@ -1568,3 +1569,33 @@ async fn remove_unhosts_a_package_whose_agent_record_is_superseded() { | 1568 | 1569 | "the superseded record is reported:\n{envelope:#}" | 1569 | 1570 | ); | 1570 | 1571 | } | | 1572 | + | | 1573 | +/// B07: a remove/rollback identifier that names the superseded record's | | 1574 | +/// uuid (A) selects the whole owned pin, so the hosted pin of the same | | 1575 | +/// release under the superseding uuid (B) is unwound too, instead of being | | 1576 | +/// left live with no record. | | 1577 | +#[tokio::test] | | 1578 | +#[serial] | | 1579 | +async fn remove_and_rollback_by_superseded_record_uuid_unhost_the_release() { | | 1580 | + for command in ["remove", "rollback"] { | | 1581 | + let server = MockServer::start().await; | | 1582 | + let tmp = tempfile::tempdir().unwrap(); | | 1583 | + let pristine = write_superseded_agent_fixture(tmp.path(), &server, B_PATCHED_INDEX).await; | | 1584 | + | | 1585 | + let (code, envelope) = if command == "remove" { | | 1586 | + run_remove_subprocess_online(tmp.path(), &server, SUPERSEDED_UUID) | | 1587 | + } else { | | 1588 | + run_rollback_subprocess_online(tmp.path(), &server, &[SUPERSEDED_UUID]) | | 1589 | + }; | | 1590 | + assert_eq!(code, 0, "{command}:\n{envelope:#}"); | | 1591 | + let restored = std::fs::read_to_string(tmp.path().join("package-lock.json")).unwrap(); | | 1592 | + assert_eq!( | | 1593 | + restored, pristine, | | 1594 | + "{command} {SUPERSEDED_UUID} must unwind the hosted pin of the same release:\n{envelope:#}" | | 1595 | + ); | | 1596 | + assert!( | | 1597 | + manifest_patch_keys(tmp.path()).is_empty(), | | 1598 | + "{command}:\n{envelope:#}" | | 1599 | + ); | | 1600 | + } | | 1601 | +} |
|