Skip to content

Decide whether a hosted patch is pinned through lockfile discovery alone - #1058

Open
Mikola Lysenko (mikolalysenko) wants to merge 8 commits into
mainfrom
arch-fix/pinned-check
Open

Mikola Lysenko (mikolalysenko) wants to merge 8 commits into
mainfrom
arch-fix/pinned-check

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Audit §3.B, "is this pinned": four places decided whether a patch uuid is pinned, and they disagreed.

  • engine::confirm used the rewriters' reports plus substring needles.
  • rollout::mark_pinned counted any uuid-shaped token in any candidate text.
  • The in-memory engine's memory_recorded counted any mention of an offered uuid in any file.
  • vex::discover / HostedInventory (what vex, list, rollback, remove and vendor read) parses each format.

What that caused:

Change

Lockfile discovery is now the only answer to "is this pinned". Three commits:

  1. Read lockfile discovery through any project view. Behavior-preserving.
    • Discovery used to read the disk directly in 10 places. Content reads and listings now go through ProjectView.
    • Probes that need an installed tree sit behind DiscoverCtx::disk_root() and see nothing in memory.
    • New entry point: discover_patched_refs_view().
    • The hosted engine's private Python-lock lister is deleted in favor of ProjectView::python_lock_paths().
  2. Decide recorded hosted pins through lockfile discovery.
    • mentioned_uuids and the mention scan in memory_recorded are deleted.
    • Both engines' recorded view is now HostedPin::discover. Two behavior changes in the in-memory engine: a pin now counts even when the API no longer offers its patch (as on disk, so a re-scan re-confirms it as ALREADY instead of spending a NEW slot), and pins wired only by files under a nested root are still excluded, as the old mention scan did.
    • A pin on a patch server the run's references name but that isn't configured is recognized by re-running discovery with those origins (foreign_dep_origins). This replaces mark_pinned's reason to exist.
    • The disk scan makes that check before its lock decision. The late lock taken after an unlocked read is gone (B58).
    • Lockless pins now carry ContestedWiring.lockless. The refusal names the lockfile to create (dotnet restore --use-lock-file / cargo generate-lockfile) instead of a scan re-run.
    • A lockless pin's own grant token is no longer reported as a second contested patch (UnlockedPin.index_url).
  3. Never write hosted pins lockfile discovery would contest.

Kept as is, and documented in CLI_CONTRACT ("Attribution gate"):

  • A pin in a file discovery does not read keeps the rewriter's verdict. Example: a pre-2.6 bundler Gemfile, which the next bundle install locks.

  • Deliberate partial redirects the run already reports keep their behavior:

    • bundled or bun patch-ed copies (bundled_skipped_uuids)
    • bundled copies in vlt's store
    • deps withheld from the vlt rewrite while a sibling lock takes them

    Which unreachable copies should block a redirect is the copy-source policy (audit B16).

  • A vendored→hosted takeover is never vetoed. A wet run has already reverted its vendored wiring and saved the ledger before the rewrite, so dropping it would leave the package on the unpatched registry release (exit 1) while the dry run reported success. Its pin keeps the rewriters' verdict, as before this PR, and the dry run and wet run agree (RewriteOptions::takeover_uuids).

  • Lockless NuGet / Cargo pins are still written. They now carry a redirect_pin_lockless warning.

Duplicate copies deleted (before → after)

What Before After
"Is this uuid pinned" oracles 4: confirm needles, mark_pinned/mentioned_uuids, memory_recorded mention scan, discovery 1 deciding oracle (discovery, via HostedPin::discover / HostedInventory). confirm()'s needles stay as a "did the write land" pre-check that discovery can veto, and they still decide pins in files discovery does not read. The in-run --vex RedirectState carrier is deferred to E45. Counted strictly, 4 → 2.
Python-lock listers over a view 2: hosted::engine::python_lock_paths and the discovery disk call 1: ProjectView::python_lock_paths
Requirements include walkers Disk-only, with a separate disk path for discovery 1 view-based walk; the disk API delegates to it
Discovery origin builders 2 identical copies: commands::discover_options and rollback::patch_server_origins 1. The vlt heal's private list, which also counts --api-url, is renamed vlt_heal_origins and documented, so the two names no longer collide.

Testing

Failing-first: each new regression test was run against code without its fix and failed there.

Commands run (through heavy-job.sh, CARGO_INCREMENTAL=0, -j4), on head e25c17d over origin/main 431b818:

  • cargo test -p socket-patch-core --no-fail-fast: 6238 passed, 0 failed.
  • cargo test -p socket-patch-cli --no-fail-fast: 5129 passed, 3 failed. All 3 are unrelated and fail the same way before this PR:
    • mode_migration_npm::berry_vendored_then_hosted_takeover_leaves_pure_hosted fails on the base commit too.
    • e2e_vendor_cargo_build::cargo_vendored_{manifest_patch_builds_on_old_toolchains,two_versions_are_refused_by_cargo_below_1_45} fail locally because the old x86_64 rustup toolchains can't run on arm64 ("Bad CPU type").
  • cargo clippy -p socket-patch-core -p socket-patch-cli --all-features -- -D warnings: clean apart from the macOS-only unused_variables in python_crawler.rs, which already fails on main on this host (checked with -A unused-variables).
  • cargo clippy --all-targets: no findings in touched files.
  • rustfmt was applied only to the lines this branch changed.

Linux and docker suites are left to CI.

Deferred

Fixes #567
Fixes #260

🤖 Generated with Claude Code


Note

Medium Risk
Changes hosted scan rewrite, rollout budgeting, and lock acquisition in ways that alter which patches get written and how caps apply; behavior is fail-closed with broad regression coverage but affects production lockfile edits.

Overview
Hosted mode now treats lockfile discovery as the single source of truth for whether a patch is pinned, replacing substring / uuid-mention heuristics in rollout recording, in-memory scans, and rewrite confirmation.

Before writing redirects, the hosted engine runs an attribution gate: it simulates the post-rewrite project (via DiskSnapshot::overlay or in-memory writes) and drops any candidate whose pin would be contested wiring for vex / rollback / remove / vendor. Those show up as redirect.skipped[] with redirect_unattributable and no lockfile changes (exit 0). Vendored→hosted takeovers stay exempt via takeover_uuids so a wet run cannot strand a package on the unpatched registry after reverting vendor state.

Rollout --max-new-patches no longer treats stale uuid mentions (e.g. in package.json metadata) as “already pinned”; only attributable HostedPin::discover results count. Foreign patch-server origins from grant URLs are discovered before the apply lock is taken (fixing late-lock races). Lockless NuGet/Cargo pins still write but emit redirect_pin_lockless and clearer contested-wiring remedies.

Discovery and requirements/Rush paths are refactored to read through ProjectView / discover_patched_refs_view, with CLI discovery origins centralized on rollback::patch_server_origins.

Reviewed by Cursor Bugbot for commit 5f0b71c. Configure here.

erification rules.

Reviewed by Cursor Bugbot for commit e9a33b8. Configure here.


Generated by Claude Code

Discovery read the disk directly in ten places (the requirements -r
walk, the Python lock listing, cargo's config resolution, Rush subspace
listing, nuget's spelling and feed probes, maven's vendored jars, sbt's
resolution evidence and hashes, vlt's bundled-store walk), so it could
only run over a real checkout. Route the content reads and listings
through ProjectView, keep the probes that need an installed tree behind
DiscoverCtx::disk_root() (they see nothing in memory), and add
discover_patched_refs_view() so the in-memory hosted engine can ask the
same "is this pinned" question the disk commands ask.

The view-based Python lock listing replaces the hosted engine's private
copy, the requirements include walk and cargo's effective-config probe
take a view, and the discovery future is boxed as Send (the in-memory
engine's future must be Send; vlt's bundled-store pairs are collected
first for the same reason). Behavior on disk is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rollout had three answers to "is this patch already pinned": the
disk scan's discovery pins, plus a second pass (mark_pinned) that
counted any uuid-shaped token in any candidate text, and the in-memory
engine's memory_recorded, which counted any mention of an offered uuid
in any file. A stale hosted URL in a package.json field, an inactive
pdm.lock or a comment therefore read as ALREADY, and the row rode past
the --max-new-patches cap. Delete the mention scan (mentioned_uuids)
and decide both engines' recorded view with HostedPin::discover, the
discovery the management commands read. A pin on a patch server that is
not configured is recognized once the run's references name it: both
engines re-run discovery with those origins (foreign_dep_origins).

The disk scan now makes that check before it decides whether to take
the apply lock, so a run that writes a row found pinned this way locks
before it reads what it writes. The late lock taken after an unlocked
read is gone (audit B58).

Lockless NuGet / Cargo pins (an exclusive Socket source without
packages.lock.json, a registry pin without Cargo.lock) are contested
wiring that nothing can attribute to a version. Their refusal no longer
tells the user to re-run the hosted scan, which only writes the same pin
again: it names the lockfile to create, and the pin's own grant token no
longer shows up as a second contested patch (audit B13; whether such
pins should be written at all stays with the E45 decision).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A hosted run confirmed a redirect when the rewriters' own report or a
substring probe said its URL landed, while vex, list, rollback, remove
and vendor read the same files through lockfile discovery. When the two
disagreed the run reported success and wrote wiring every later command
refused as contested, with a remedy (re-run the scan) that changed
nothing: a Pipfile.lock rewired beside a requirements -r include that
still pins the registry release (#567), a Maven pin inside a <profile>
(#260).

engine::rewrite now runs discovery over the project as the rewrite
would leave it (a DiskSnapshot overlaid with the pending writes, or the
in-memory project plus them) and reads it as the management commands do
(HostedInventory). A confirmed candidate whose pin would be contested
wiring is dropped and the rest rewritten without it; it is reported in
redirect.skipped[] as redirect_unattributable, nothing is written for
it, and the exit code is unchanged. Both the disk scan and the in-memory
engine go through this one gate.

Kept as they were, and documented: a pin in a file discovery does not
read (a pre-2.6 bundler Gemfile, locked by the next bundle install), a
deliberate partial redirect the run already reports (a bundled or
bun-patched copy left on the registry, a dep withheld from the vlt
rewrite while a sibling lock takes it; which unreachable copies should
block a redirect is the audit's copy-source policy, B16), and lockless
NuGet / Cargo pins, which are written with a new redirect_pin_lockless
warning naming the lockfile to create (E45 decides whether they should
be written at all).

The engine's unit fixtures now use real uuids in their hosted urls, so
discovery recognizes the pins they land.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) added the arch-refactor PR opened by the scheduled architecture refactor routine label Oct 7, 2026
A wet `scan --mode hosted` reverts a purl's vendored wiring and saves the
ledger before the rewrite runs. The attribution gate then dropped such a
purl when discovery contested its pin (the #567 shape: an unreached
requirements -r include beside Pipfile.lock), so the run left the package
on the unpatched registry release and exited 1, while the dry run, whose
takeover previews never reach the gate, reported success.

A takeover's uuid is now exempt from the veto (RewriteOptions::
takeover_uuids) and keeps the rewriters' verdict, as before the gate. The
dry run and the wet run agree again. CLI_CONTRACT no longer claims the
gate runs before anything is written.

Also:
- describe_skip_reason has human text for redirect_unattributable
  instead of blaming the server.
- New tests: the takeover is never stranded (failed before this fix),
  the --json skip contract (reason, detail, redirected 0, exit 0), the
  redirect_pin_lockless warning for a lockless NuGet pin, and exit-code
  assertions on the #567 / #260 tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
memory_recorded now reads discovery's pins, which can include files under
a nested root that discovery reaches through a requirements include or a
rush subspace. Those pins are that root's own, as they were under the
mention scan this replaced, so they are filtered out again. Pins to
patches the API no longer offers now count (as on disk); the doc comment
says so.

Also document that DiskSnapshot::overlay does not change directory
listings, and rename the vlt heal's private origin list to
vlt_heal_origins so it is not confused with rollback's
patch_server_origins (it also counts --api-url).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 7, 2026 17:30

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Gate omits configured patch-server origins
    • Added patch_server_origins field to RewriteOptions and related types to pass the --patch-server-url allowlist through to the attribution gate's discovery, ensuring it uses the same origins as management commands.

Create PR

Or push these changes by commenting:

@cursor push 4947219719
Preview (4947219719)
diff --git a/crates/socket-patch-cli/src/commands/list.rs b/crates/socket-patch-cli/src/commands/list.rs
--- a/crates/socket-patch-cli/src/commands/list.rs
+++ b/crates/socket-patch-cli/src/commands/list.rs
@@ -431,7 +431,10 @@
                 detail: detail.clone(),
             });
         } else if !args.common.silent {
-            eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail));
+            eprintln!(
+                "Warning: {}",
+                crate::commands::rollback::capitalize_first(detail)
+            );
         }
     }
     let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent);
@@ -773,12 +776,18 @@
         let listings = HostedListing::from_pins(
             &[
                 pin("pkg:npm/minimist@1.2.2", &record.uuid),
-                pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"),
+                pin(
+                    "pkg:npm/other@1.0.0",
+                    "33333333-3333-4333-8333-333333333333",
+                ),
             ],
             Some(&legacy),
         );
         assert_eq!(listings[0].record, record);
-        assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333");
+        assert_eq!(
+            listings[1].record.uuid,
+            "33333333-3333-4333-8333-333333333333"
+        );
         assert!(listings[1].record.vulnerabilities.is_empty());
         assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]);
     }

diff --git a/crates/socket-patch-cli/src/commands/mod.rs b/crates/socket-patch-cli/src/commands/mod.rs
--- a/crates/socket-patch-cli/src/commands/mod.rs
+++ b/crates/socket-patch-cli/src/commands/mod.rs
@@ -1,7 +1,7 @@
 pub mod apply;
 pub(crate) mod bun_preflight;
+pub(crate) mod composer_hints;
 pub(crate) mod context;
-pub(crate) mod composer_hints;
 pub(crate) mod fetch_stage;
 pub mod get;
 pub mod hosted_bundle;
@@ -9,11 +9,11 @@
 pub(crate) mod lock_cli;
 pub mod remove;
 pub mod repair;
-pub(crate) mod vendored_backend;
 pub mod rollback;
 pub mod scan;
 pub mod update;
 pub mod vendor;
+pub(crate) mod vendored_backend;
 pub mod vex;
 pub(crate) mod vex_consumed;
 pub(crate) mod vex_sources;
@@ -136,9 +136,11 @@
     common: &crate::args::GlobalArgs,
     root: &Path,
 ) -> socket_patch_core::patch::redirect::RedirectState {
-    hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
-        &discover_wiring(common, root).await,
-    ))
+    hosted_state_from_pins(
+        &socket_patch_core::patch::redirect::upstream::HostedPin::all(
+            &discover_wiring(common, root).await,
+        ),
+    )
 }
 
 /// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
@@ -148,10 +150,8 @@
 ) -> socket_patch_core::patch::redirect::RedirectState {
     let mut state = socket_patch_core::patch::redirect::RedirectState::new();
     for pin in pins {
-        state
-            .records
-            .entry(pin.purl.clone())
-            .or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
+        state.records.entry(pin.purl.clone()).or_insert_with(|| {
+            socket_patch_core::manifest::schema::PatchRecord {
                 uuid: pin.uuid.clone(),
                 exported_at: String::new(),
                 files: Default::default(),
@@ -159,7 +159,8 @@
                 description: String::new(),
                 license: String::new(),
                 tier: String::new(),
-            });
+            }
+        });
     }
     state
 }
@@ -186,4 +187,3 @@
         }
     }
 }
-

diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs
--- a/crates/socket-patch-cli/src/commands/scan/discovery.rs
+++ b/crates/socket-patch-cli/src/commands/scan/discovery.rs
@@ -168,29 +168,32 @@
     }
     // `(ledger key, base purl, entry)`; the artifact fallback has no
     // entries to probe, so it never reports unwired keys.
-    let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> =
-        match state {
-            Ok(state) => state
-                .entries
-                .iter()
-                .map(|(key, entry)| {
-                    (
-                        key.clone(),
-                        strip_purl_qualifiers(&entry.base_purl).to_string(),
-                        Some(entry),
-                    )
-                })
-                .collect(),
-            // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
-            // recover the vendored set from the committed artifacts, or
-            // `scan --prune` (whose ledger exemption also degrades to empty)
-            // would delete still-vendored packages' manifest entries and blobs.
-            Err(_) => vendored_purls_from_artifacts(common)
-                .await
-                .into_iter()
-                .map(|base| (base.clone(), base, None))
-                .collect(),
-        };
+    let candidates: Vec<(
+        String,
+        String,
+        Option<&socket_patch_core::vendor::VendorEntry>,
+    )> = match state {
+        Ok(state) => state
+            .entries
+            .iter()
+            .map(|(key, entry)| {
+                (
+                    key.clone(),
+                    strip_purl_qualifiers(&entry.base_purl).to_string(),
+                    Some(entry),
+                )
+            })
+            .collect(),
+        // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above):
+        // recover the vendored set from the committed artifacts, or
+        // `scan --prune` (whose ledger exemption also degrades to empty)
+        // would delete still-vendored packages' manifest entries and blobs.
+        Err(_) => vendored_purls_from_artifacts(common)
+            .await
+            .into_iter()
+            .map(|base| (base.clone(), base, None))
+            .collect(),
+    };
     // Composer by release identity: a ledger `@3.0.2.0` is the crawled
     // `@3.0.2`, not a second package to supplement.
     let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned());
@@ -1045,7 +1048,9 @@
             ..GlobalArgs::default()
         };
         let state = socket_patch_core::vendor::load_state(root).await;
-        vendored_ledger_supplement(&args, crawled, &state).await.packages
+        vendored_ledger_supplement(&args, crawled, &state)
+            .await
+            .packages
     }
 
     /// A ledger entry vendored as `@3.0.2.0` is the crawled composer
@@ -1080,7 +1085,9 @@
             out.iter().map(|p| &p.purl).collect::<Vec<_>>()
         );
 
-        let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages;
+        let out = vendored_ledger_supplement(&args, &[], &Ok(state))
+            .await
+            .packages;
         assert_eq!(
             out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
             vec!["pkg:composer/psr/log@3.0.2.0"]
@@ -1183,7 +1190,10 @@
             let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await;
             let out = vendored_ledger_supplement(&args, &[], &state).await;
             assert_eq!(
-                out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(),
+                out.packages
+                    .iter()
+                    .map(|p| p.purl.as_str())
+                    .collect::<Vec<_>>(),
                 vec!["pkg:npm/left-pad@1.3.0"],
                 "lock={lock:?}"
             );

diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs
--- a/crates/socket-patch-cli/src/commands/scan/hosted.rs
+++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs
@@ -1023,7 +1023,11 @@
             .map(|c| c.dep.patch_uuid.clone())
             .collect()
     };
-    let rewrite_options = || RewriteOptions {
+    // The `--patch-server-url` allowlist: extra patch-server origins the
+    // attribution gate recognizes when discovering attributable pins.
+    let patch_server_origins = crate::commands::rollback::patch_server_origins(common);
+    let rewrite_options = || {
+        RewriteOptions {
         dry_run: common.dry_run,
         targets_pipenv_lock,
         pipenv_major,
@@ -1036,6 +1040,8 @@
         npm_outer: &npm_outer,
         blocking: true,
         takeover_uuids: takeover_uuids.clone(),
+        patch_server_origins: patch_server_origins.clone(),
+    }
     };
     // The rollout gate plans again without its deferred rows: keep what
     // the second pass needs.
@@ -4787,19 +4793,43 @@
         use super::npm_allow_remote_one_line;
         let hosts = ["patch.socket.dev"];
         let cases = [
-            (npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
-            (npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
-            (npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
-            (npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
-            (npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
-            (npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
+            (
+                npm_allow_remote_configured_detail(&hosts, true, false),
+                "Note: set",
+            ),
+            (
+                npm_allow_remote_configured_detail(&hosts, false, false),
+                "Note: set",
+            ),
+            (
+                npm_allow_remote_configured_detail(&hosts, true, true),
+                "Note: would set",
+            ),
+            (
+                npm_allow_remote_already_detail(&hosts),
+                "Note: .npmrc already",
+            ),
+            (
+                npm_allow_remote_user_set_detail(&hosts, "none"),
+                "Warning: npm >=12",
+            ),
+            (
+                npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
+                "Warning: npm >=12",
+            ),
             (npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
-            (npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
+            (
+                npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
+                "Warning: npm >=12",
+            ),
         ];
         for (detail, start) in cases {
             let line = npm_allow_remote_one_line(&detail);
             assert!(line.starts_with(start), "{line}");
-            assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
+            assert!(
+                !line.contains('\n') && line.ends_with("(details: --verbose)."),
+                "{line}"
+            );
         }
     }
 }

diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs
--- a/crates/socket-patch-cli/src/commands/scan/policy.rs
+++ b/crates/socket-patch-cli/src/commands/scan/policy.rs
@@ -11,9 +11,9 @@
 use socket_patch_core::api::types::PatchSearchResult;
 use socket_patch_core::manifest::schema::PatchManifest;
 use socket_patch_core::policy::{
-    canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name,
-    DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy,
-    PATCHES_DISABLED,
+    canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked,
+    sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError,
+    PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED,
 };
 use socket_patch_core::utils::purl::normalize_purl;
 
@@ -42,12 +42,18 @@
 /// Load the policy for `args` (4.5): `--global` scans have no repo and read
 /// no file; everything else reads the repo root's socket.yml.
 pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy, PolicyLoadError> {
-    let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?;
+    let overrides = args
+        .socket_yml
+        .overrides()
+        .map_err(PolicyLoadError::Usage)?;
     let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone());
     if args.common.is_global() {
-        let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides)
-            .map_err(PolicyLoadError::Policy)?
-            .0;
+        let policy = SelectionPolicy::load(
+            &socket_patch_core::policy::MemoryPolicyFs::default(),
+            &overrides,
+        )
+        .map_err(PolicyLoadError::Policy)?
+        .0;
         return Ok(InvocationPolicy {
             policy,
             repo_root: cwd,
@@ -56,8 +62,8 @@
         });
     }
     let (repo_root, mut warnings) = find_repo_root_with_warnings(&cwd);
-    let (policy, load_warnings) =
-        SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides).map_err(PolicyLoadError::Policy)?;
+    let (policy, load_warnings) = SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides)
+        .map_err(PolicyLoadError::Policy)?;
     warnings.extend(load_warnings);
     Ok(InvocationPolicy {
         policy,
@@ -141,7 +147,12 @@
 
 impl ScanPolicy {
     /// The policy for the project rooted at `root_dir`.
-    pub(crate) fn for_root(invocation: &InvocationPolicy, root_dir: &Path, explicit: bool, global: bool) -> Self {
+    pub(crate) fn for_root(
+        invocation: &InvocationPolicy,
+        root_dir: &Path,
+        explicit: bool,
+        global: bool,
+    ) -> Self {
         let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf());
         let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default();
         let root_verdict = if global {
@@ -174,7 +185,9 @@
                 severity: None,
             });
         }
-        let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed);
+        let announce_warnings = !invocation
+            .warned
+            .swap(true, std::sync::atomic::Ordering::Relaxed);
         Self {
             policy: invocation.policy.clone(),
             warnings,
@@ -227,7 +240,10 @@
     /// exclude stays in the query (so `upgradeAvailable` can be reported)
     /// but joins the retained set, which never reaches a writer.
     pub(crate) fn admit_crawled(&self, purl: &str) -> bool {
-        let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl));
+        let verdict = self
+            .root_verdict
+            .clone()
+            .and_then(|()| self.policy.admits_purl(purl));
         let reason = match verdict {
             Ok(()) => return true,
             Err(reason) => reason,
@@ -337,7 +353,8 @@
             // (not when a lower-ranked admitted patch simply wins).
             let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0]));
             if let Err(reason) = top_withheld {
-                let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
+                let upgrade_withheld =
+                    chosen.is_some() && chosen == recorded_at && recorded_at != Some(0);
                 if chosen.is_none() || upgrade_withheld {
                     report.filtered.push(FilteredEntry {
                         purl: Some(canon(&purl)),
@@ -525,17 +542,20 @@
         let verdict = if !policy.enabled() {
             Err(FilterReason::Disabled)
         } else {
-            root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| {
-                // The floor only hides a package when none of its patches pass.
-                match group
-                    .iter()
-                    .map(|p| policy.admits_severity(patch_severity_order(p)))
-                    .find(Result::is_ok)
-                {
-                    Some(ok) => ok,
-                    None => policy.admits_severity(patch_severity_order(group[0])),
-                }
-            })
+            root_verdict
+                .clone()
+                .and_then(|()| policy.admits_purl(purl))
+                .and_then(|()| {
+                    // The floor only hides a package when none of its patches pass.
+                    match group
+                        .iter()
+                        .map(|p| policy.admits_severity(patch_severity_order(p)))
+                        .find(Result::is_ok)
+                    {
+                        Some(ok) => ok,
+                        None => policy.admits_severity(patch_severity_order(group[0])),
+                    }
+                })
         };
         if let Err(reason) = verdict {
             out.push((

diff --git a/crates/socket-patch-cli/src/commands/scan/rollout.rs b/crates/socket-patch-cli/src/commands/scan/rollout.rs
--- a/crates/socket-patch-cli/src/commands/scan/rollout.rs
+++ b/crates/socket-patch-cli/src/commands/scan/rollout.rs
@@ -4,8 +4,10 @@
 
 use std::collections::{BTreeMap, BTreeSet, HashSet};
 
-use socket_patch_core::rollout::{canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan};
 pub(crate) use socket_patch_core::rollout::stage::*;
+use socket_patch_core::rollout::{
+    canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan,
+};
 
 use super::discovery::UpdateInfo;
 
@@ -208,11 +210,11 @@
 mod tests {
     use super::*;
     use socket_patch_core::api::types::PatchSearchResult;
+    use socket_patch_core::api::types::VulnerabilityResponse;
     use socket_patch_core::manifest::schema::PatchManifest;
-    use std::path::Path;
-    use socket_patch_core::api::types::VulnerabilityResponse;
     use socket_patch_core::manifest::schema::PatchRecord;
     use std::collections::HashMap;
+    use std::path::Path;
 
     fn offer(purl: &str, uuid: &str, published: &str, severities: &[&str]) -> PatchSearchResult {
         PatchSearchResult {
@@ -357,13 +359,21 @@
         let stored = manifest(&[("pkg:composer/psr/log@3.0.2.0", "old")]);
         let recorded = RecordedIndex::new(Some(&stored), &[]);
         let offers = offers_from_results(
-            &[offer("pkg:composer/psr/log@v3.0.2", "new", "2026-02-01T00:00:00Z", &["high"])],
+            &[offer(
+                "pkg:composer/psr/log@v3.0.2",
+                "new",
+                "2026-02-01T00:00:00Z",
+                &["high"],
+            )],
             false,
         );
         let rows = classify(&offers, &recorded, "");
         let plan = socket_patch_core::rollout::plan_rollout(
             rows.into_iter().map(|row| row.candidate).collect(),
-            &MaxNew { value: Some(0), source: MaxNewSource::Flag },
+            &MaxNew {
+                value: Some(0),
+                source: MaxNewSource::Flag,
+            },
             false,
             &BTreeSet::new(),
         );

diff --git a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs
--- a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs
+++ b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs
@@ -1,7 +1,6 @@
 //! `scan --max-new-patches` (see the rollout guide,
 //! `docs/configuration.md#gradual-rollout`).
 
-
 use clap::Args;
 pub(crate) use socket_patch_core::rollout::stage::RolloutCarry;
 use socket_patch_core::rollout::{resolve_max_new, MaxNew};
@@ -77,7 +76,6 @@
     }
 }
 
-
 #[cfg(test)]
 mod tests {
     use super::*;

diff --git a/crates/socket-patch-cli/src/commands/vendor.rs b/crates/socket-patch-cli/src/commands/vendor.rs
--- a/crates/socket-patch-cli/src/commands/vendor.rs
+++ b/crates/socket-patch-cli/src/commands/vendor.rs
@@ -442,10 +442,7 @@
 /// entry (fail-safe): ecosystems other than npm, cargo and pypi (whose
 /// probe covers the requirements flavor only) have no in-use probe yet,
 /// and a missing/unreadable lockfile proves nothing.
-pub(crate) async fn dispatch_in_use_one(
-    entry: &VendorEntry,
-    project_root: &Path,
-) -> Option<bool> {
+pub(crate) async fn dispatch_in_use_one(entry: &VendorEntry, project_root: &Path) -> Option<bool> {
     match entry.ecosystem.as_str() {
         "npm" => vendor::npm_flavor::vendored_entry_in_use(entry, project_root).await,
         // Cargo probes the lock entry's shape: detached + `[patch]` pointing
@@ -1237,8 +1234,7 @@
     // know (the ledger was ignored or dropped from the commit along with the
     // manifest) leaves every fresh install failing; the manifest keys above
     // cannot see it, so the references are read from the wiring itself.
-    let references =
-        crate::commands::vendored_backend::repair::scan_vendor_references(root).await;
+    let references = crate::commands::vendored_backend::repair::scan_vendor_references(root).await;
     for (eco, uuid, rel) in references {
         let ledgered = state
             .entries

diff --git a/crates/socket-patch-cli/tests/apply/apply_network.rs b/crates/socket-patch-cli/tests/apply/apply_network.rs
--- a/crates/socket-patch-cli/tests/apply/apply_network.rs
+++ b/crates/socket-patch-cli/tests/apply/apply_network.rs
@@ -940,7 +940,10 @@
         "a legacy package archive must not cover the patch; stdout={stdout}\nstderr={stderr}"
     );
     let content = std::fs::read(tmp.path().join("node_modules/pkgcache/index.js")).unwrap();
-    assert_eq!(content, before, "the file must not be patched from the legacy archive");
+    assert_eq!(
+        content, before,
+        "the file must not be patched from the legacy archive"
+    );
 
     let requests = mock.received_requests().await.unwrap_or_default();
     let blob_path = format!("/v0/orgs/{ORG_SLUG}/patches/blob/{after_hash}");
@@ -1043,10 +1046,7 @@
         v["summary"]["applied"], 1,
         "the drifted nested copy must be warn-overwritten.\nstdout={v:#}"
     );
-    assert_eq!(
-        v["summary"]["failed"], 0,
-        "no copy may fail.\nstdout={v:#}"
-    );
+    assert_eq!(v["summary"]["failed"], 0, "no copy may fail.\nstdout={v:#}");
 
     // The nested copy's blob was fetched on demand…
     let requests = mock.received_requests().await.unwrap();

diff --git a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs
--- a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs
+++ b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs
@@ -201,7 +201,9 @@
         "non-silent stderr must carry the {CODE} warning; got:\n{stderr}"
     );
     assert_eq!(
-        stderr.matches("Warning: bundler app config BUNDLE_PATH").count(),
+        stderr
+            .matches("Warning: bundler app config BUNDLE_PATH")
+            .count(),
         1,
         "exactly ONE warning line (not one per discovery call); got:\n{stderr}"
     );

diff --git a/crates/socket-patch-cli/tests/cli/covgap_output.rs b/crates/socket-patch-cli/tests/cli/covgap_output.rs
--- a/crates/socket-patch-cli/tests/cli/covgap_output.rs
+++ b/crates/socket-patch-cli/tests/cli/covgap_output.rs
@@ -168,9 +168,8 @@
         .expect("spawn socket-patch in PTY");
     drop(pair.slave);
 
-    let reader_handle = crate::pty_io::PtyOutput::spawn(
-        pair.master.try_clone_reader().expect("clone reader"),
-    );
+    let reader_handle =
+        crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader"));
 
     // Watchdog: detached kill after `timeout`; a no-op if the child exits
     // naturally first.
@@ -261,7 +260,10 @@
         "\n",
         Duration::from_secs(15),
     );
-    assert_eq!(code, 0, "remove with bare Enter must succeed; got: {output}");
+    assert_eq!(
+        code, 0,
+        "remove with bare Enter must succeed; got: {output}"
+    );
     // The interactive confirm MUST have run — otherwise this test passes
     // vacuously against a regression that drops the TTY gate and
     // auto-proceeds. Match the distinctive prompt verbatim (the loose

diff --git a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs
--- a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs
+++ b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs
@@ -112,9 +112,8 @@
     // closed. The previous design used a chunked read+mpsc loop
     // because it interleaved with a try_wait poll; the simplified
     // design serializes wait → drop master → read_to_end joins.
-    let reader_handle = crate::pty_io::PtyOutput::spawn(
-        pair.master.try_clone_reader().expect("clone reader"),
-    );
+    let reader_handle =
+        crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader"));
 
     // Watchdog: detach a thread that kills the child after `timeout`.
     // The cloned ChildKiller is independent of the main `child`

diff --git a/crates/socket-patch-cli/tests/cli_config_fallback.rs b/crates/socket-patch-cli/tests/cli_config_fallback.rs
--- a/crates/socket-patch-cli/tests/cli_config_fallback.rs
+++ b/crates/socket-patch-cli/tests/cli_config_fallback.rs
@@ -59,8 +59,7 @@
     let mut cmd = Command::new(BINARY);
     // Human mode: core's proxy advisory (the oracle below) is muted under
     // `--json`/`--silent`.
-    cmd.args(["scan", "-e", "npm", "--cwd"])
-        .arg(project);
+    cmd.args(["scan", "-e", "npm", "--cwd"]).arg(project);
     for (key, _) in std::env::vars_os() {
         let name = key.to_string_lossy();
         if name.starts_with("SOCKET_") {
@@ -298,7 +297,9 @@
     json_cmd.arg("--json");
     let json_out = run(json_cmd);
     assert!(
-        json_out.stderr.contains("could not parse socket-cli config"),
+        json_out
+            .stderr
+            .contains("could not parse socket-cli config"),
         "the parse warning must reach stderr under --json too; got:\n{}",
         json_out.stderr
     );

diff --git a/crates/socket-patch-cli/tests/cli_get_silent.rs b/crates/socket-patch-cli/tests/cli_get_silent.rs
--- a/crates/socket-patch-cli/tests/cli_get_silent.rs
+++ b/crates/socket-patch-cli/tests/cli_get_silent.rs
@@ -25,10 +25,7 @@
     for var in GLOBAL_ARG_ENV_VARS {
         cmd.env_remove(var);
     }
-    for var in [
-        "SOCKET_SAVE_ONLY",
-        "SOCKET_ALL_RELEASES",
-    ] {
+    for var in ["SOCKET_SAVE_ONLY", "SOCKET_ALL_RELEASES"] {
         cmd.env_remove(var);
     }
     cmd.env("SOCKET_TELEMETRY_DISABLED", "1");

diff --git a/crates/socket-patch-cli/tests/cli_parse_list.rs b/crates/socket-patch-cli/tests/cli_parse_list.rs
--- a/crates/socket-patch-cli/tests/cli_parse_list.rs
+++ b/crates/socket-patch-cli/tests/cli_parse_list.rs
@@ -370,7 +370,11 @@
     let out = run_list_binary(tmp.path(), &["--json"]);
     let v: serde_json::Value = serde_json::from_str(String::from_utf8_lossy(&out.stdout).trim())
         .expect("stdout must be valid JSON envelope");
-    assert_eq!(out.status.code(), Some(0), "missing manifest is an empty list");
+    assert_eq!(
+        out.status.code(),
+        Some(0),
+        "missing manifest is an empty list"
+    );
     assert_eq!(v["status"], "success", "envelope: {v}");
     assert_eq!(v["summary"]["discovered"], 0, "envelope: {v}");
 }
@@ -1313,7 +1317,10 @@
     assert_eq!(v["status"], "success", "envelope={v}");
     let warnings = v["warnings"].as_array().expect("warnings[] present");
     assert_eq!(warnings.len(), 1, "envelope={v}");
-    assert_eq!(warnings[0]["code"], "redirect_ledger_corrupt", "envelope={v}");
+    assert_eq!(
+        warnings[0]["code"], "redirect_ledger_corrupt",
+        "envelope={v}"
+    );
     assert!(
         out.stderr.is_empty(),
         "--json must keep stderr clean: {}",

diff --git a/crates/socket-patch-cli/tests/cli_parse_rollback.rs b/crates/socket-patch-cli/tests/cli_parse_rollback.rs
--- a/crates/socket-patch-cli/tests/cli_parse_rollback.rs
+++ b/crates/socket-patch-cli/tests/cli_parse_rollback.rs
@@ -366,7 +366,11 @@
 /// relied on the rejection get a test-visible flip instead of a silent one.
 #[test]
 fn multiple_targets_parse_in_order() {
-    let args = parse_rollback(&["pkg:npm/foo@1", "packages/api/**", "b0630680-4da6-45f9-bba8-b888e0ffd58c"]);
+    let args = parse_rollback(&[
+        "pkg:npm/foo@1",
+        "packages/api/**",
+        "b0630680-4da6-45f9-bba8-b888e0ffd58c",
+    ]);
     assert_eq!(
         args.targets,
         vec![

diff --git a/crates/socket-patch-cli/tests/cli_parse_scan.rs b/crates/socket-patch-cli/tests/cli_parse_scan.rs
--- a/crates/socket-patch-cli/tests/cli_parse_scan.rs
+++ b/crates/socket-patch-cli/tests/cli_parse_scan.rs
@@ -898,7 +898,11 @@
         ("NONE", None),
     ] {
         let args = parse_scan(&["--max-new-patches", raw]);
-        assert_eq!(args.rollout.max_new_patches, Some(MaxNewPatches(want)), "{raw}");
+        assert_eq!(
+            args.rollout.max_new_patches,
+            Some(MaxNewPatches(want)),
+            "{raw}"
+        );
     }
 }
 
@@ -989,20 +993,33 @@
     assert_eq!(parse_scan(&[]).socket_yml.min_severity, None);
     assert_eq!(overrides(&[], &[]).unwrap().min_severity, None);
     assert_eq!(
-        overrides(&["--min-severity", "High"], &[]).unwrap().min_severity,
+        overrides(&["--min-severity", "High"], &[])
+            .unwrap()
+            .min_severity,
         Some((Some(1), OverrideSource::Flag))
     );
     assert_eq!(
-        overrides(&["--min-severity", "none"], &[("SOCKET_MIN_SEVERITY", "critical")]).unwrap().min_severity,
+        overrides(
+            &["--min-severity", "none"],
+            &[("SOCKET_MIN_SEVERITY", "critical")]
+        )
+        .unwrap()
+        .min_severity,
         Some((None, OverrideSource::Flag))
     );
     assert_eq!(
-        overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")]).unwrap().min_severity,
+        overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")])
+            .unwrap()
+            .min_severity,
         Some((Some(2), OverrideSource::Env))
     );
-    assert_eq!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "")]).unwrap().min_severity, None);
+    assert_eq!(
+        overrides(&[], &[("SOCKET_MIN_SEVERITY", "")])
+            .unwrap()
+            .min_severity,
+        None
+    );
     assert!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "severe")]).is_err());
     assert!(try_parse_scan(&["--min-severity", "severe"]).is_err());
     assert!(overrides(&["--no-socket-yml"], &[]).unwrap().bypass);
 }
-

diff --git a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs
--- a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs
+++ b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs
@@ -149,7 +149,9 @@
     );
     let chatter = stderr_chatter(&stderr);
     assert!(
-        chatter.iter().any(|l| l.contains("could not be downloaded")),
+        chatter
+            .iter()
+            .any(|l| l.contains("could not be downloaded")),
         "--silent must keep the download-failure error (errors only, \
          never nothing); stderr was: {stderr:?}"
     );

diff --git a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs
--- a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs
+++ b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs
@@ -566,8 +566,7 @@
         "the human skipped line must name purl + reason; stderr=\n{stderr}"
     );
     assert!(
-        stderr.contains("Warning: ")
-            && stderr.contains("could not be reverted"),
+        stderr.contains("Warning: ") && stderr.contains("could not be reverted"),
         "the takeover pre-warning must reach human stderr; stderr=\n{stderr}"
     );
 }
@@ -814,7 +813,10 @@
     let lock_before = std::fs::read(root.join("package-lock.json")).unwrap();
 
     let assert_ignored = |code: i32, doc: &Value, label: &str| {
-        assert_eq!(code, 0, "{label}: a pre-v5 ledger is never an error: {doc:#}");
+        assert_eq!(
+            code, 0,
+            "{label}: a pre-v5 ledger is never an error: {doc:#}"
+        );
         assert_eq!(doc["status"], "success", "{label}: {doc:#}");
         assert!(
             !doc.to_string().contains("redirect-state.json"),
@@ -1017,7 +1019,10 @@
 
     for extra in [&[][..], &["--silent"][..]] {
         let (code, stdout, stderr) = scan_hosted(root, &server.uri(), extra, &[]);
-        assert_eq!(code, 0, "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}");
+        assert_eq!(
+            code, 0,
+            "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}"
+        );
         if extra.is_empty() {
             assert!(
                 stdout.contains("No patches available for installed packages."),
@@ -1404,16 +1409,22 @@
         ],
         &env,
     );
-    assert_eq!(code, 1, "a binary bun.lockb pin is refused: {stdout}\n{stderr}");
+    assert_eq!(
+        code, 1,
+        "a binary bun.lockb pin is refused: {stdout}\n{stderr}"
+    );
     let doc: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{e}: {stdout}"));
... diff truncated: showing 800 of 6730 lines

You can send follow-ups to the cloud agent here.

Comment thread crates/socket-patch-core/src/hosted/engine.rs
Comment thread crates/socket-patch-core/src/hosted/memory/mod.rs
same_file returned false on every non-Unix platform, so on Windows'
case-insensitive filesystem the one nuget.config was also recognized as
NuGet.config and NuGet.Config. A contested-wiring refusal then named the
file three times, and its `git checkout --` remedy listed three paths for
one file, which lockless_nuget_pin_refusal_names_the_lockfile_remedy
caught on windows-latest.

Compare volume serial + file index on Windows through the same-file crate
the core already depends on. Unix keeps its stat-only dev + inode check so
a FIFO under a config name is never opened.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5f0b71c. Configure here.

Comment thread crates/socket-patch-core/src/vendor/nuget_config.rs
…grades

The attribution gate discovered with only the hosts this run's grants
name, not the operator's --patch-server-url allowlist that vex, list,
rollback, remove and vendor read. An existing pin on a configured server
was invisible to the gate whenever this run's grants lived on another
host, so a write those commands would refuse as contested could land.
RewriteOptions now carries the configured origins and the gate discovers
over them plus the grants' hosts.

The second rollout pass (mark_pinned) only flipped a NEW row to ALREADY
when the selected uuid was pinned on a server only this run's references
name. An older patch pinned there stayed NEW, so an upgrade spent a NEW
cap slot (in both engines; in memory always, since it has no configured
origins). It now marks such a row Superseded.

Addresses Bugbot findings on #1058.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Blocked: CI still pending on 8da8a1a, so this is not marked ready yet.

  • Head already contained current origin/main (05ecc6e). No merge was needed.
  • Fixed the two open Bugbot findings from e25c17d in 8da8a1a and replied on both threads:
    • The attribution gate now also discovers over the operator's --patch-server-url origins (RewriteOptions::patch_server_origins), the same list the management commands use.
    • mark_pinned now marks a NEW row as Superseded when an older patch for the same package is pinned on a server only this run's references name. Before, that upgrade spent a NEW cap slot.
  • Local checks on 8da8a1a: clippy -D warnings is clean (lib/bin targets). core --lib 5634/5634 and hosted_inventory pass. The cli lib and hosted_memory_*, in_process_redirect_pipenv, in_process_get_hosted_ecosystems, mode_migration_pypi and scan_rollout_e2e tests all pass.
  • Bugbot: its auto-run on 8da8a1a is pending. The two threads are replied to but not resolved, because resolving needs GraphQL and the GraphQL budget was 0.
  • "Ready for review" label: not present.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] scan performance failure on 8da8a1a is a real regression. Reproduced locally, no push yet.

Local perf-profile bench (socket-patch-bench compare --runs 10) against origin/main:

scenario main this PR change
npm/hosted 282 ms 359 ms +25.9%
npm/rescan 257 ms 320 ms +25.1%
pnpm/rescan 211 ms 231 ms +12.8%

CPU is up about 20% and peak memory about 10%.

Cause: engine::rewrite (hosted/engine.rs) now loops rewrite_once + unattributed_pins. Each pass that confirms pins runs a full discover_patched_refs_view, even on a rescan that writes 0 files. That is a third full discovery per rewrite, on top of scan's ctx.discovery() (scan/mod.rs) and hosted_state_from_lockfiles. The CLI view is ProjectView::Disk, so the cost is parsing, not file reads, and overlaying a cached snapshot alone will not recover it.

Planned fix (next pass):

  1. Thread scan's pre-rewrite Discovery and the origins it was computed with into RewriteOptions as an Option. get and the in-memory engine pass None.
  2. In unattributed_pins, reuse it only when all of these hold:
    • nothing was written (ignoring synthetic sbt keys)
    • no binary files
    • the merged origin list matches the one the prior discovery used
    • no takeover or migration wrote first
  3. Have rewrite_once borrow CandidateFiles and python_metadata instead of cloning them.

redirect_unattributable and redirect_pin_lockless keep working, because the same discovery result feeds HostedInventory::of.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Needs a human decision: is the scan performance regression on this PR acceptable?

Two fix attempts this run. The second reuses scan's pre-rewrite discovery in the unattributed_pins gate when a pass writes nothing and the origins match. It is parked, not pushed here, on branch agent/wip-1058-discovery-reuse (f27cf57). Clippy and tests were not re-run after its last edit.

scenario before the fix with the fix
npm/rescan +25% +5%
pnpm/rescan +13% +2.5% (noise)
npm/hosted +26% +26%

npm/hosted still regresses because a rewrite that writes files needs a full fresh discovery over the overlay, and the cost is lockfile parsing. Closing that gap means also dropping the post-write hosted_state_from_lockfiles discovery in favor of the gate's result. That is safe only when origins match and nothing writes after the rewrite (vlt heal, .npmrc). It is a larger behavioral refactor.

Question: should we

  • (a) do that refactor on this PR,
  • (b) take the parked rescan fix and accept the npm/hosted cost with performance-regression-accepted, or
  • (c) rethink the unattributed_pins gate?

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:needs-human arch-refactor PR opened by the scheduled architecture refactor routine

Projects

None yet

2 participants