Skip to content

Commit da19b80

Browse files
mikolalysenkoclaude
andcommitted
Document and test remove/rollback of a superseded generation by uuid
`remove <uuid A>` and `rollback <uuid A>` of a manifest record that a hosted scan superseded with patch B now unwind B's lockfile pin too (one owned pin per release). CLI_CONTRACT says so for both commands, and that the hosted restore can then refuse offline. The in-process test reuses the #933 fixture and drives both commands by the superseded uuid. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 29e30b1 commit da19b80

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,12 @@ worse, lets a warm cache silently serve unpatched bytes):
833833
(purl or patch uuid; v5 keeps no hosted ledger) restores each matched pin to its default
834834
upstream registry entry — the same restore as `rollback` (see "Hosted unwind coverage"), for every
835835
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,
837842
`bun.lockb`, …) or a failed write is the top-level `hosted_revert_failed` error BEFORE the
838843
manifest mutation (exit 1, manifest not modified; message `could not restore <purl> to its
839844
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):
888893

889894
### Targets
890895

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):
892897

893898
* **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.
894899
* **Path targets select installed copies; entries with no installed copy are reachable only by identifier or unscoped runs.**

‎crates/socket-patch-cli/tests/in_process_rollback_hosted.rs‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,8 +1144,9 @@ async fn a_git_pattern_hosted_pin_is_refused_not_restored_to_the_registry() {
11441144
assert!(
11451145
envelope["hosted"]["failed"][0]["error"]
11461146
.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+
),
11491150
"{envelope}"
11501151
);
11511152
assert_eq!(
@@ -1568,3 +1569,33 @@ async fn remove_unhosts_a_package_whose_agent_record_is_superseded() {
15681569
"the superseded record is reported:\n{envelope:#}"
15691570
);
15701571
}
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+
}

0 commit comments

Comments
 (0)