Skip to content

Commit 2149869

Browse files
mikolalysenkoclaude
andcommitted
Drop a superseded hosted generation's wiring on re-pin and restore
A hosted re-pin to a superseding patch moved the pin but left the old generation's wiring behind: - cargo kept [registries.socket-patch-<old>] in the cargo config, and remove never swept it (#864); - Go kept the old gopatch module's go.sum pair, so `go mod tidy -diff` failed after every patch update (#682); - Maven treated an earlier `-socket.<hex8>` literal as a user mismatch and never re-pinned, and a rotated grant token left the stale repository URL in place (#266). Add patch::redirect::generation, the shared owned-pin generation policy: one live socket-owned pin per release, a re-pin replaces the previous generation and drops the wiring only it used, and a restore sweeps every unreferenced generation. The cargo, Go and Maven rewriters and the cargo restore use it; cargo, Maven and NuGet pin names now come from its single hosted_pin_name, and the upstream Maven restore reuses the rewriter's repository lookup/removal instead of its own copy. Adds an owned_pin_generation_matrix test module (cargo, Go new-uuid and republish, Maven supersede and token rotation) asserting that no file names the superseded generation and a re-run is a no-op. The Go equivalence golden is re-blessed: its re-run cases feed the rewriter's own output back in, which now carries no superseded sum lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent ce5474f commit 2149869

8 files changed

Lines changed: 757 additions & 135 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//! Owned-pin generations: the one supersede policy every hosted writer and
2+
//! restore shares (audit B07).
3+
//!
4+
//! A package release (ecosystem, name, version) has at most ONE live
5+
//! socket-owned pin. The patch uuid it carries is that pin's generation: a
6+
//! superseding patch, or the same patch republished, is a new generation of
7+
//! the same pin, never a second pin beside it. So:
8+
//!
9+
//! - a re-pin replaces the previous generation in place, and drops every
10+
//! piece of wiring only that generation used (a cargo `[registries.…]`
11+
//! block, a Go module's go.sum pair, a Maven `<repository>`), the way a
12+
//! fresh pin would never have written it;
13+
//! - a restore (`remove` / `rollback`) unwinds every generation still wired,
14+
//! including residue an older CLI left behind, not only the generation the
15+
//! lockfile names today;
16+
//! - matching a remove/rollback identifier across the stores treats a
17+
//! manifest record, the vendored entry it claims and the hosted pin of the
18+
//! same release as one owned pin ([`crate::ledgers::Ledgers::matching`],
19+
//! [`crate::ledgers::hosted_pins_matching`]).
20+
//!
21+
//! Hosted writers name a generation's wiring `socket-patch-<uuid>` (a cargo
22+
//! registry, a Maven repository id, a NuGet source key); vendored mode's
23+
//! `socket-patch-vendor-<uuid>` names are a different owner and never match.
24+
25+
use std::collections::BTreeSet;
26+
27+
use crate::patch::path_safety::is_canonical_uuid;
28+
29+
const PIN_NAME_PREFIX: &str = "socket-patch-";
30+
31+
/// The name hosted mode gives a generation's wiring: `socket-patch-<uuid>`.
32+
pub(crate) fn hosted_pin_name(uuid: &str) -> String {
33+
format!("{PIN_NAME_PREFIX}{uuid}")
34+
}
35+
36+
/// Every generation (patch uuid) `text` names as `socket-patch-<uuid>`, in
37+
/// any position (a `registry = "…"` value, an `<id>`, a section header). A
38+
/// comment that names one counts too, which only ever keeps wiring alive.
39+
pub(crate) fn named_generations(text: &str) -> BTreeSet<String> {
40+
let mut out = BTreeSet::new();
41+
let mut rest = text;
42+
while let Some(at) = rest.find(PIN_NAME_PREFIX) {
43+
let tail = &rest[at + PIN_NAME_PREFIX.len()..];
44+
if let Some(uuid) = tail.get(..36).filter(|u| is_canonical_uuid(u)) {
45+
// A longer token (`…-<uuid>x`) is not this grammar.
46+
let boundary = tail[36..]
47+
.chars()
48+
.next()
49+
.is_none_or(|c| !(c.is_ascii_alphanumeric() || c == '-' || c == '_'));
50+
if boundary {
51+
out.insert(uuid.to_string());
52+
}
53+
}
54+
rest = tail;
55+
}
56+
out
57+
}
58+
59+
/// The generations the `before` texts name that the `after` texts no longer
60+
/// do: this run moved their pins to another generation, so the wiring only
61+
/// they used is the re-pin's to drop.
62+
pub(crate) fn superseded_generations<'a>(
63+
before: impl IntoIterator<Item = &'a str>,
64+
after: impl IntoIterator<Item = &'a str>,
65+
) -> BTreeSet<String> {
66+
let after: BTreeSet<String> = after.into_iter().flat_map(named_generations).collect();
67+
before
68+
.into_iter()
69+
.flat_map(named_generations)
70+
.filter(|uuid| !after.contains(uuid))
71+
.collect()
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
78+
const A: &str = "aaaaaaaa-0000-4000-8000-000000000001";
79+
const B: &str = "bbbbbbbb-0000-4000-8000-000000000002";
80+
81+
#[test]
82+
fn names_only_canonical_hosted_generations() {
83+
let text = format!(
84+
"x = {{ registry = \"socket-patch-{A}\" }}\n\
85+
[registries.socket-patch-vendor-{B}]\n\
86+
<id>socket-patch-{B}x</id>\n\
87+
socket-patch-not-a-uuid\n"
88+
);
89+
assert_eq!(named_generations(&text), BTreeSet::from([A.to_string()]));
90+
assert_eq!(hosted_pin_name(A), format!("socket-patch-{A}"));
91+
}
92+
93+
#[test]
94+
fn superseded_is_named_before_and_not_after() {
95+
let before = format!("registry = \"socket-patch-{A}\"");
96+
let after = format!("registry = \"socket-patch-{B}\"");
97+
assert_eq!(
98+
superseded_generations([before.as_str()], [after.as_str()]),
99+
BTreeSet::from([A.to_string()])
100+
);
101+
// Still named by any after-text: not superseded.
102+
assert!(
103+
superseded_generations([before.as_str()], [after.as_str(), before.as_str()]).is_empty()
104+
);
105+
}
106+
}

‎crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ fn single_walk_golang_rewrite_matches_golden() {
282282
"redirect_golang_gosum_prune",
283283
"redirect_golang_stale_replace_removed",
284284
"redirect_golang_stale_gosum_removed",
285+
"redirect_golang_superseded_gosum_removed",
285286
] {
286287
assert!(kinds.contains(kind), "no case reached {kind}: {kinds:?}");
287288
}

0 commit comments

Comments
 (0)