Skip to content

Commit b56c9b0

Browse files
committed
Fix wrong takeover warning direction in hosted/vendor mode
The bug: overlapping_ledger_purls correctly returns the intersection of ledger claims, but hosted and vendored flows always assumed the current mode displaced the other without verifying that this run actually confirmed/rewrote those packages. Concrete failure: hosted→vendored takeover leaves both ledgers and lockfile→vendored; a subsequent 'scan --mode hosted --dry-run' (or hosted run with zero redirects) still reported 'redirect_supersedes_vendored' and told the user to remove the vendored ledger—the live one. Fix: Only emit takeover warnings when THIS RUN confirmed those packages: - Hosted: check if confirmed redirects (packages whose URLs landed in lockfile) overlap with vendor ledger - Vendored: check if actually vendored packages (Applied/Rebuilt events) overlap with redirect ledger This prevents false-positive warnings on dry-runs and no-op scans where the lockfile still points at the other mode.
1 parent 44df961 commit b56c9b0

2 files changed

Lines changed: 46 additions & 7 deletions

File tree

crates/socket-patch-cli/src/commands/scan/hosted.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,28 @@ pub(super) async fn run_redirect(
472472
// stale. Detect + warn (JSON `warnings[]` and stderr) WITHOUT deleting the
473473
// other mode's ledger; full reconciliation is deferred (see PR Scope).
474474
// Read after the ledger write above so a non-dry-run reflects this run.
475+
// Only warn about PURLs that THIS RUN confirmed redirected (landed in the
476+
// lockfile) — a dry-run or no-op scan that redirected zero packages must
477+
// not warn, even when both ledgers exist (the lockfile still points at the
478+
// vendored artifacts, not the hosted server).
475479
let mut takeover_warnings: Vec<serde_json::Value> = Vec::new();
476-
let superseded = super::overlapping_ledger_purls(&args.common.cwd).await;
477-
if !superseded.is_empty() {
478-
takeover_warnings.push(serde_json::json!({
479-
"code": super::REDIRECT_SUPERSEDES_VENDORED,
480-
"detail": super::mode_takeover_detail(&superseded, /*current_is_hosted=*/ true),
481-
}));
480+
if !confirmed.is_empty() {
481+
use socket_patch_core::utils::purl::{normalize_purl, strip_purl_qualifiers};
482+
let confirmed_purls: std::collections::HashSet<String> = confirmed
483+
.iter()
484+
.map(|(purl, _)| normalize_purl(strip_purl_qualifiers(purl)).into_owned())
485+
.collect();
486+
let all_overlapping = super::overlapping_ledger_purls(&args.common.cwd).await;
487+
let superseded: Vec<String> = all_overlapping
488+
.into_iter()
489+
.filter(|purl| confirmed_purls.contains(purl))
490+
.collect();
491+
if !superseded.is_empty() {
492+
takeover_warnings.push(serde_json::json!({
493+
"code": super::REDIRECT_SUPERSEDES_VENDORED,
494+
"detail": super::mode_takeover_detail(&superseded, /*current_is_hosted=*/ true),
495+
}));
496+
}
482497
}
483498

484499
// Emit an OpenVEX attestation when `--vex` was requested. The redirected

crates/socket-patch-cli/src/commands/scan/vendor_flow.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,32 @@ async fn preview_vendor_json(cwd: &Path, selected: &[PatchSearchResult]) -> serd
6161
/// at the envelope level (JSON `warnings[]` and stderr), mirroring
6262
/// [`note_classic_migration_risk`]; the stale ledger is NOT deleted here
6363
/// (reconciliation is deferred — see the redirect twin in `hosted.rs`).
64+
/// Only warns about PURLs that THIS RUN actually vendored (have Applied or
65+
/// Rebuilt events) — a no-op vendor run or dry-run that vendored zero packages
66+
/// must not warn, even when both ledgers exist.
6467
async fn note_vendor_supersedes_redirect(env: &mut Envelope, cwd: &Path, common: &GlobalArgs) {
65-
let superseded = super::overlapping_ledger_purls(cwd).await;
68+
use crate::json_envelope::PatchAction;
69+
use socket_patch_core::utils::purl::{normalize_purl, strip_purl_qualifiers};
70+
71+
// Extract PURLs that were actually vendored in this run (Applied or Rebuilt).
72+
let vendored_purls: std::collections::HashSet<String> = env
73+
.events
74+
.iter()
75+
.filter(|e| matches!(e.action, PatchAction::Applied | PatchAction::Rebuilt))
76+
.filter_map(|e| e.purl.as_ref())
77+
.map(|purl| normalize_purl(strip_purl_qualifiers(purl)).into_owned())
78+
.collect();
79+
80+
if vendored_purls.is_empty() {
81+
return;
82+
}
83+
84+
let all_overlapping = super::overlapping_ledger_purls(cwd).await;
85+
let superseded: Vec<String> = all_overlapping
86+
.into_iter()
87+
.filter(|purl| vendored_purls.contains(purl))
88+
.collect();
89+
6690
if superseded.is_empty() {
6791
return;
6892
}

0 commit comments

Comments
 (0)