Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 39 additions & 26 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,26 +315,30 @@ pub(super) async fn run_redirect(
}
};

// Cross-mode takeover (cargo): a purl this run is about to redirect may
// still be VENDORED — a committed `[patch.crates-io]` path entry, a
// Cross-mode takeover: a purl this run is about to redirect may still be
// VENDORED — for cargo a committed `[patch.crates-io]` path entry, a
// detached Cargo.lock entry, a committed copy, and a vendored ledger
// entry. The hosted rewriters know nothing about that wiring, so
// redirecting on top of it would leave BOTH wirings in place and cargo
// then refuses every `--locked` build over the now-unused `[patch]`
// entry while this run reports success. A takeover must leave the
// project FULLY hosted: revert each such purl's vendored state first
// (the exact per-purl machinery `vendor --revert` runs — restore the
// lock originals from the ledger, drop the `[patch]` entry, remove the
// committed tree and the ledger entry), and only then redirect. This
// ordering also hands the redirect the PRISTINE crates.io lock fragment
// to record as its own revert original, keeping the originals chain
// intact across repeated mode migrations. A purl whose vendored state
// cannot be cleanly reverted (revert failure, or vendored wiring with a
// missing/corrupt ledger) is REFUSED — skipped with an actionable
// error — never half-migrated.
// entry; for the npm family a `file:./.socket/vendor/…` lock resolution
// (plus a berry `resolutions` pin) and its committed tarball. The hosted
// rewriters know nothing about that wiring: cargo then refuses every
// `--locked` build over the now-unused `[patch]` entry while this run
// reports success, and the npm rewriters either hijack the vendored
// resolution while the vendored ledger still claims it (yarn classic)
// or fail-closed refuse the `file:` protocol entirely (yarn berry). A
// takeover must leave the project FULLY hosted: revert each such purl's
// vendored state first (the exact per-purl machinery `vendor --revert`
// runs — restore the lock originals from the ledger, drop the vendored
// wiring, remove the committed artifact and the ledger entry), and only
// then redirect. This ordering also hands the redirect the PRISTINE
// registry lock fragment to record as its own revert original, keeping
// the originals chain intact across repeated mode migrations. A purl
// whose vendored state cannot be cleanly reverted (revert failure, or
// vendored wiring with a missing/corrupt ledger) is REFUSED — skipped
// with an actionable error — never half-migrated.
let takeover_capable = |p: &str| p.starts_with("pkg:cargo/") || p.starts_with("pkg:npm/");
let mut takeover_pre_warnings: Vec<serde_json::Value> = Vec::new();
if !candidates.iter().any(|(p, ..)| p.starts_with("pkg:cargo/")) {
// No cargo candidates — nothing to reconcile.
if !candidates.iter().any(|(p, ..)| takeover_capable(p)) {
// No takeover-capable candidates — nothing to reconcile.
} else {
use socket_patch_core::utils::purl::{normalize_purl, strip_purl_qualifiers};
let canon = |p: &str| normalize_purl(strip_purl_qualifiers(p)).into_owned();
Expand All @@ -343,7 +347,7 @@ pub(super) async fn run_redirect(
socket_patch_core::vendor::cargo_config::read_patch_entries(&args.common.cwd).await;
let mut refused: Vec<String> = Vec::new();
for (purl, _uuid, ..) in &candidates {
if !purl.starts_with("pkg:cargo/") {
if !takeover_capable(purl) {
continue;
}
let stripped = strip_purl_qualifiers(purl);
Expand Down Expand Up @@ -433,7 +437,13 @@ pub(super) async fn run_redirect(
// this crate is nevertheless present, the ledger is missing or
// corrupt — the originals needed to revert are unrecoverable,
// so redirecting on top would wedge the project. Refuse.
let name = parse_purl_simple(purl).map(|(_, name, _)| name);
// (Cargo-only probe: `.cargo/config.toml` `[patch]` entries.
// An npm purl in this state falls through to the rewriters'
// own per-flavor diagnostics.)
let name = purl
.starts_with("pkg:cargo/")
.then(|| parse_purl_simple(purl).map(|(_, name, _)| name))
.flatten();
let wired = name
.as_deref()
.is_some_and(|n| patch_entries.get(n).is_some_and(|i| i.socket_owned));
Expand All @@ -460,17 +470,20 @@ pub(super) async fn run_redirect(
}));
}
}
let refused_names: std::collections::HashSet<(String, String)> = candidates
let refused_names: std::collections::HashSet<(String, String, String)> = candidates
.iter()
.filter(|(p, ..)| refused.contains(p))
.filter_map(|(p, ..)| {
parse_purl_simple(p).map(|(_, name, version)| (name, version))
})
.filter_map(|(p, ..)| parse_purl_simple(p))
.collect();
candidates.retain(|(p, ..)| !refused.contains(p));
overrides.retain(|o| {
o.ecosystem != "cargo"
|| !refused_names.contains(&(o.name.clone(), o.version.clone()))
// Overrides built here carry the full coordinate in `name`
// (namespace unset) — the same shape parse_purl_simple emits.
let coord = match o.namespace.as_deref() {
Some(ns) if !ns.is_empty() => format!("{ns}/{}", o.name),
_ => o.name.clone(),
};
!refused_names.contains(&(o.ecosystem.clone(), coord, o.version.clone()))
});
}
}
Expand Down
50 changes: 31 additions & 19 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -920,10 +920,11 @@ pub(crate) async fn vendor_records(
// it still claims must revert the hosted edits FIRST (see the hook in the
// dispatch loop below). Loaded once; mutated + persisted per reverted
// purl. A MALFORMED ledger is held as the hard error it is: this loop
// WRITES the ledger for cargo takeovers, and with its records unreadable
// a claimed purl is indistinguishable from an unclaimed one — so every
// cargo purl fails closed with the corruption surfaced (non-cargo purls
// never touch the redirect ledger here and proceed).
// WRITES the ledger for takeovers, and with its records unreadable a
// claimed purl is indistinguishable from an unclaimed one — so every
// purl of a takeover-capable ecosystem (cargo, npm) fails closed with
// the corruption surfaced (other purls never touch the redirect ledger
// here and proceed).
let (mut redirect_ledger, redirect_ledger_corrupt) =
match socket_patch_core::patch::redirect::load_redirect_state(&common.cwd).await {
Ok(state) => (state, None),
Expand Down Expand Up @@ -979,18 +980,24 @@ pub(crate) async fn vendor_records(
}
matched.insert(candidate.clone());

// Cross-mode takeover (cargo): vendoring over a LIVE hosted
// redirect must first revert the hosted edits from the redirect
// ledger — `[patch.crates-io]` only patches crates-io-sourced
// Cross-mode takeover: vendoring over a LIVE hosted redirect
// must first revert the hosted edits from the redirect ledger.
// Cargo: `[patch.crates-io]` only patches crates-io-sourced
// deps, so vendoring on top of the `registry = "socket-patch-…"`
// pin leaves the project unbuildable in BOTH modes while this
// run reports success — and the pre-revert also hands the vendor
// detach the PRISTINE crates.io lock fragment to record as the
// ledger's unrecoverable originals (not the hosted values). A
// purl whose hosted edits cannot be cleanly reverted is REFUSED;
// the backend's own fail-closed guard (`hosted_redirect_live`)
// run reports success. npm family: the vendor rewire happens to
// succeed either way, but without the pre-revert the vendor
// ledger records the grant-tokenized HOSTED lock fragment as its
// unrecoverable pre-vendor original (so `vendor --revert` lands
// back on an expiring hosted URL with no CLI path to registry
// state) and the superseded redirect records/edits survive
// forever as a stale-ledger replay hazard. In every ecosystem
// the pre-revert hands the vendor detach the PRISTINE registry
// lock fragment to record as the ledger's originals. A purl
// whose hosted edits cannot be cleanly reverted is REFUSED; the
// cargo backend's own fail-closed guard (`hosted_redirect_live`)
// backstops states with no usable ledger at all.
if candidate.starts_with("pkg:cargo/") {
if socket_patch_core::patch::redirect::redirect_revert_supported(candidate) {
if let Some(corrupt) = &redirect_ledger_corrupt {
has_errors = true;
env.record(
Expand Down Expand Up @@ -1028,7 +1035,7 @@ pub(crate) async fn vendor_records(
);
} else if claimed {
let ledger = redirect_ledger.as_mut().expect("claimed implies Some");
match socket_patch_core::patch::redirect::revert_cargo_redirect_purl(
match socket_patch_core::patch::redirect::revert_redirect_purl(
&common.cwd,
ledger,
candidate,
Expand Down Expand Up @@ -1060,17 +1067,22 @@ pub(crate) async fn vendor_records(
);
continue;
}
let reverted_what = if candidate.starts_with("pkg:cargo/") {
"the hosted edits (Cargo.toml registry pin, Cargo.lock \
source/checksum, registries block)"
} else {
"the hosted lockfile edits back to their pre-redirect \
registry values"
};
record_warning(
env,
candidate,
&VendorWarning::new(
"vendor_takeover_reverted_redirect",
format!(
"{} was hosted-redirected; reverted the hosted \
edits (Cargo.toml registry pin, Cargo.lock \
source/checksum, registries block) and dropped \
the redirect-ledger record before vendoring \
(mode takeover)",
"{} was hosted-redirected; reverted {reverted_what} \
and dropped the redirect-ledger record before \
vendoring (mode takeover)",
normalize_purl(candidate)
),
),
Expand Down
Loading
Loading