Repository navigation
Cargo VEX: read member manifests, match liveness by origin, withhold mixed and vendored bypasses - #2012
Conversation
Redirect-ledger liveness step 1 kept a record alive when any lock inventory entry's resolved url merely CONTAINED the patch uuid, so a url on any host spelling the uuid (or a Socket url for another patch whose grant token is that uuid) kept a dead hosted record alive. Step 1 now classifies the url with the one origin classifier, `patch::redirect::hosted_patch_uuid`, over Socket's host and the run's patch-server origins, and requires the classified uuid to equal the record's. Discovery records the origins it was run with (`Discovery::patch_server_origins`, set in `discover_with_ctx`), so liveness classifies with exactly the origins discovery did; every `LedgerLiveness` caller already holds a discovery built from the run's `DiscoverOptions`. Socket-host and configured-origin urls for the record's uuid stay live; anything else falls to step 2 (resolves elsewhere: dead). The uv script-lock regression test relied on the substring match for an unconfigured internal host; it now shows that host dead without the origin and live with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosted mode pins a patched crate in the root Cargo.toml AND every workspace-member manifest that declares it, and vendored mode's live-redirect refusal reads member manifests too. Discovery read only the root manifest, so a crate the root does not declare was "the lock is the only word": a member whose declaration was reverted to crates.io (no `registry = "socket-patch-<uuid>"`) left a ref attested although cargo re-resolves that dependency from crates.io. Discovery now enumerates the members the way the hosted rewriter does (`cargo_workspace::member_manifests_in`, filtered by the rewriter's own `is_cargo_member_manifest_key`, so nothing under `.socket/` or `target/`) and applies its manifest-declaration check across root + members. Each declaration carries its file: diagnostics, the conflict-pair Cargo.toml ref and lockless `UnlockedPin`s name the member manifest that holds the pin. `[patch]` stays root-only (cargo ignores a member's). The member walk over a `DiskSnapshot` now notes every manifest it reads and directory it probes or lists with the snapshot instead of asking for its raw root, so a discovery run under a read-set recording (the re-scan cache) keeps the recording usable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iveness Cargo member walk (review 4, 6, 7): - `cargo_workspace::member_manifests_in` is now async over the ProjectView: manifests are read with `read_text` (concurrently, bounded, ordered), globs list with `list_dir`, and directory probes use the view's own mediated `is_dir` / `is_symlink`. A snapshot's overlays and content cache apply and its recording notes the walk, so fresh manifests (a `.socket/` copy followed through `[patch]` included) no longer leave a racy read set. `DiskSnapshot::touch` / `touch_listing` are private again; `SnapshotTree` is gone. The in-memory engine keeps the sync walk (`member_manifests_in_memory`); disk-only callers keep `member_manifests`. A test pins the view walk to the disk walk over globs, `**`, excludes, path dependencies, `[patch]` copies, dot / `target` dirs and a symlinked member. Discovery (review 5, 8): - Members are walked only when they can matter: a lock with a Socket-sourced package, or no lock (then parsed only when some manifest carries a `socket-patch-` pin). The walk's texts are adopted into the ctx's read log and rule-11 recognition instead of being read again. - The root `DocumentMut` is moved into the manifest list, not cloned. Lockless pins (review 1, 3): - `UnlockedPin` gains `excluded_reqs`: the requirements of the crate's unpinned declarations. The rewriter leaves a declaration alone only when its requirement excludes the patched version, so a member's `serde = "0.9"` beside a pinned `serde = "1"` no longer suppresses the pin; a ledger version must satisfy every pinned requirement and no excluded one. An unpinned declaration with no requirement still blocks. - One pin and one diagnostic per (crate, patch): the union of the pinning declarations' requirements, every pinning manifest listed once. Liveness (review 2): - A lock entry whose url has the hosted shape for the record's uuid on an origin this run was not given is `RecordLiveness::Undecided`: never attested (`redirect_record_live` / `LedgerLiveness::redirect_record` stay attestation-grade) and never judged dead. `scan`'s takeover classifier and hosted-wiring-retained advisory use the new `LedgerLiveness::redirect_record_retained`, so a record written against a patch server a later run is not pointed at keeps its wiring. A url that merely contains the uuid (file name, query) is still dead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cargo resolves each source on its own. When one declaration of a crate pins it to `socket-patch-<uuid>` but another (the root's or a workspace member's) names no Socket registry (crates.io, another registry, a path or git) with a requirement the locked patched version satisfies, that declaration builds a separate, unpatched copy, possibly of the very version the pin covers. Discovery still attested the Cargo.lock ref because "some declaration pins the patch" was enough. The hosted rewriter pins every declaration, so this mixed state is a partial revert or a hand edit. The ref now stays (rollback, remove and the ledgers' liveness still see the pin) but is marked unattested with the new `UnattestedKind::CargoUnpinnedDeclaration`, naming the unpinned declaration's manifest. When Cargo.lock already locks a same-version copy from another source beside the Socket entry, the detail names that lock entry as direct evidence. A declaration whose requirement excludes the patched version, and a `[patch]` registry entry (which redirects declarations rather than adding one), do not withhold the statement. The CLI omits the ref as `vex_cargo_unpinned_declaration`; documented in CLI_CONTRACT.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for the unpinned-declaration mark. Each declaration of a Socket-locked crate is now classified once (`route`): routed to a Socket patch (its own `socket-patch-<uuid>` registry, or a Socket `[patch.<source>]` registry entry that redirects it), unpinned (cargo builds its own copy), or saying nothing about the locked version. - A crates.io (or registry) declaration that a Socket `[patch.<source>]` registry entry in the root manifest or project config redirects is no longer counted as unpinned. - A declaration naming `registry = "socket-patch-<uuid>"` beside `path`, `git` or `registry-index` is not a pin (the hosted rewriter's own pinned check): cargo builds that other source, so it now marks the ref. - Requirements go through `patch::redirect::cargo_req_selects` with the lock's other versions of the crate, and the detail says whether a re-scan can pin the declaration: only a crates.io declaration whose requirement matches no other locked version; otherwise the rewriter refuses (`redirect_cargo_toml_dep_unrewritable`), so the remedy is to change the declaration or roll back. The CLI note's remedy is now generic and the per-case remedy lives in the detail. - `registry-index` declarations are reported as such, not as crates.io. - A `[workspace.dependencies]` template counts only when some manifest inherits it (`workspace = true`). - When the lock already holds a same-version copy from another source, no mark is added: the twin rule (#679) withholds the ref and names it. - Root pinned to patch A with a member pinned to patch B (for the locked version, and B not resolved by the lock) now emits B's Cargo.toml ref beside A's, so the CLI's wiring-conflict gate runs. - `Decl` carries a `DeclSource` enum and a `live` flag; the origin text is formatted only for messages, and `[patch]` pins are built with `Decl::patch` (no dead version parameter). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`vendor` wires a crate through the root's `[patch.crates-io]`, which cargo applies only to declarations from crates.io. A workspace member (or the root) that declares the crate from another registry, a `registry-index` or git, at a requirement that may select the patched version, resolves its own unpatched copy. The vendored ref stayed attested beside it because the lock copy claim only reads sourceless entries. The ref now stays (rollback, remove and the ledgers still see the wiring) but is marked unattested with the new `UnattestedKind::CargoPatchBypassed`, naming the declaration. A same-version Cargo.lock entry from any registry or git source beside the copy is marked the same way, naming the lock entry. The check reads the declarations through #2019's classification: live declarations only, a registry dependency counts as covered when a `[patch.<registry>]` table pointing at the same copy redirects it (`Decl::patched_by`), a path declaration (the user's own fork) keeps the existing "ignored" rule, and a `socket-patch-*` registry dependency is hosted wiring. The CLI omits the ref as `vex_cargo_patch_bypassed`; documented in CLI_CONTRACT.md. The workspace member walk ran only when the lock held a Socket-sourced package (or, with no lock, a manifest named a `socket-patch-` registry). A vendored copy's lock entry has no source and it has no such pin, so the walk now also runs whenever the root manifest or the project config wires a Socket copy through `[patch]`, whatever the lock's state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three Cargo follow-ups to the declaration classification: Lockless pins read declarations like locked ones. A `socket-patch-<uuid>` registry beside `path`, `git` or `registry-index` is no pin (cargo builds that source, and the rewriter's own pinned check agrees), so it no longer adds its requirement to an `UnlockedPin` or makes one on its own. It counts as an unpinned declaration instead, excluding the versions it accepts (every version, without a requirement). A `[workspace.dependencies]` template no manifest inherits is no declaration: a Socket-pinned one makes no lockless pin, and an unpinned one excludes nothing from the root's pin. A non-Socket `[patch]` registry entry no longer counts as an unpinned declaration either (it redirects declarations, it adds none). `DeclSource::Git` keeps its URL, and `Decl::patched_by` matches a `[patch."<repository URL>"]` table against it (a trailing `/` or `.git` ignored). So a git declaration whose repository table points at the same vendored copy no longer withholds that copy, while one from another repository still does. Hosted routing gains the same match for a Socket registry pin in such a table. When no declaration pins the lock's patch, a pin to another patch now makes the conflict pair only when it is live and its requirement may select the locked version (`cargo_req_selects`). One that excludes it is about another version; the stale lock entry is then diagnosed and dropped like any unrouted one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
foreign_hosted_uuid parsed the raw lock url, while hosted_patch_uuid first unescapes \/, percent-decodes and strips a Cargo sparse+ / registry+ prefix. A hosted url on an unconfigured origin that only parses after that normalization was never Undecided and could be marked Dead, dropping live wiring. Share the normalization. Addresses one of the two Bugbot findings on this PR. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
Burn-down agent: this needs an owner decision. Head The failure.
Cause. The PR intends this change. Since 43fd5aa ("Match ledger liveness uuids by origin") and 650143e (
Question for the owner. Should a pre-v5 redirect-ledger record whose lock url sits on an unconfigured host (a staging server used without
I didn't change either side, because either way the documented user-facing contract changes, and I won't rewrite a test's expectations just to get it green. Minor, for whoever picks this up: in 94af3e4, Bugbot reviewed Generated by Claude Code |
False attestation: the vendored bypass check counted a declaration as redirected by any root or config `[patch]` entry pointing at the copy, including a manifest entry cargo ignores (the project config redefines its crates.io key, or a crates.io URL table replaces `[patch.crates-io]`). Cargo then builds whatever replaced it for those declarations, while the copy's other, honored entry was attested. Only honored entries count now: the root's `[patch]` entries are collected once with their shadow reason (a `Shadow` built from the root manifest and the config), and the bypass check sees only the unshadowed ones. `vendored` (the member gate) is derived from the same list instead of re-reading every `[patch]` table. One classifier: the bypass check runs each declaration through `route` with the copy's honored `[patch]` tables as the pins, as hosted pins do, passing the lock's other versions of the crate. A path declaration (the user's own fork) is still left alone. `patched_by` now covers a `registry-index` declaration with a `[patch."<index URL>"]` table for the same index (a `registry+` prefix, host case and a trailing `/` ignored; `sparse+` is another source), and treats a `registry-index` naming the crates.io git index as crates.io. The sparse crates.io index stays a separate source. Git URLs compare as cargo's `CanonicalUrl` spells them (one trailing `/` and a `.git` suffix dropped, a GitHub URL lowercased whole, any host lowercased). The bypass detail ends with the remedy for its evidence: a declaration (re-running `vendor` does not help), a crates.io lock entry beside a `[patch.crates-io]` copy (stale: re-lock), or another lock entry (`cargo tree -i` names its declarer). The CLI note no longer claims re-vendoring never helps. Diagnostics: a Socket pin to another patch that the lock does not reflect (its requirement excludes every locked version) used to vanish once any Socket entry of the crate was locked; it is now diagnosed `patched_ref_unattributable`. A well-formed `socket-patch-<uuid>` registry that pins nothing (beside `path` / `git` / `registry-index`, or on a `[workspace.dependencies]` entry no manifest inherits) is diagnosed `patched_ref_invalid`. The committed `workspace-member` redirect golden gains that diagnostic: its `expected/` tree omits the unchanged inheriting member, so the pinned template reads as uninherited there. `Decl::describe` and `same_version_foreign_entry` are shared by the hosted and vendored checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review round on Cargo member liveness and origin matching. - `read_members` returned nothing for a parsed lock with no Socket-sourced package or an unparseable one, so a member's `socket-patch-` pin beside a stale lock lost its "stale lock, or a server outside the allowlist" diagnostic and its uuid was no longer recognized (rule 11). Members are now parsed whenever the lock is Socket-sourced or a `socket-patch-` name appears in a member's text, the root's or the config's, whatever the lock says; a root or config `[patch]` pin now counts too (it governs members' declarations). - `foreign_hosted_uuid` read any url on any host ending in the record's uuid as a foreign patch server's, so an artifact store's `/blobs/<uuid>/` url kept a dead hosted record retained (and raised a takeover note). It now needs a patch server's path shape (a `patch`, `patch-registry` or `gopatch` segment). - Two inserted functions split their neighbours' doc comments (`hosted_patch_url_uuids`, `log_read`); restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n' into fix/cargo-mixed-member-pins
Second review round. The unpinned-declaration remedy sent every declaration with an explicit `registry` key to "it names another registry ... roll the patch back", including `registry = "crates-io"`, which the hosted rewriter re-pins (#386); it now says a re-scan pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o fix/cargo-vendored-twins-and-split-patches # Conflicts: # crates/socket-patch-core/src/vex/discover/cargo.rs
FALSE ATTESTATION (found reviewing #2019; fixed here, where `Shadow` lives): a root `[patch.crates-io]` Socket registry pin cargo ignores (a `[patch."<crates.io URL>"]` table replaces it, or the config redefines the key) still counted as pinning every crates.io declaration of the crate, so a member's plain declaration was taken as routed to the patch and the hosted ref attested while cargo builds crates.io. Such an entry stays a declaration (Socket's wiring is present, the ref stays) but is not live, and `route` only weighs live `[patch]` pins, as the vendored bypass check already weighs only honored entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cargo replaces a manifest [patch] item with a project-config item of the same source and key, whatever the source table and whether either wires a path. Shadow only looked at crates.io tables and treated two path-less items (a registry pin and a git fork) as the same wiring, so a config fork left the manifest's Socket pin live and its stale lock row could be attested. Only an identical path is now the same wiring, and named and URL tables are compared too (URLs as an index key). The ineffective-registry diagnostic also called a shadowed [patch] pin an uninherited [workspace.dependencies] template; it now says cargo ignores the [patch] entry. Addresses Bugbot 4501098254, 4501098256 and 4501098258. Co-Authored-By: Claude <noreply@anthropic.com>
Shadow compared a config's [patch."<url>"] table with the manifest's through index_key, which keeps a trailing .git and a GitHub path's case. Cargo canonicalizes both, so a config item under a canonical-equivalent git URL replaced the manifest's Socket copy while discovery still counted it. Non-sparse URL tables now compare through git_key; sparse+ ones stay index keys (another source). Addresses Bugbot 4501313607. Co-Authored-By: Claude <noreply@anthropic.com>
Main's #2002 moved the hosted-origin helpers out of patch::redirect into crate::origin, and DiscoverCtx now holds an OriginPolicy instead of the raw origin list. Carry this PR's normalized_hosted_url split into origin.rs, point its liveness and foreign-origin checks at crate::origin::hosted_patch_uuid, and read the run's origins from ctx.origin.origins(). Co-Authored-By: Claude <noreply@anthropic.com>
|
Burn-down agent: merged
New head Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix prepared a fix for 1 of the 2 issues found in the latest run.
- ✅ Fixed: Member pin attests unreverted lock
- Added check in hosted_from_lock to detect when unpinned declarations from different files (root vs member) match the locked version and correctly skip attestation.
⚠️ Could not fix: Foreign host skips VEX verification- Implemented logic to handle Undecided verdict and proceed to hash verification, but test still fails because redirect_record_verdict returns Dead instead of Undecided for foreign hosts.
Or push these changes by commenting:
@cursor push e8b393275a
Preview (e8b393275a)
diff --git a/crates/socket-patch-cli/src/commands/vex_sources.rs b/crates/socket-patch-cli/src/commands/vex_sources.rs
--- a/crates/socket-patch-cli/src/commands/vex_sources.rs
+++ b/crates/socket-patch-cli/src/commands/vex_sources.rs
@@ -82,7 +82,7 @@
use socket_patch_core::utils::purl_key::{canonical_base_purl, PurlKey};
use socket_patch_core::vendor::state::{VendorArtifact, VendorEntry, VendorState};
use socket_patch_core::vex::discover::{
- vendor_ref, Discovery, LedgerLiveness, PatchedRef, WiringMode,
+ vendor_ref, Discovery, LedgerLiveness, PatchedRef, RecordLiveness, WiringMode,
};
use socket_patch_core::vex::{FailedPatch, Unattested, UnattestedKind};
@@ -657,15 +657,19 @@
continue;
}
} else if cand.redirected {
- let live = assumed.contains(strip_purl_qualifiers(&cand.key))
- || liveness.redirect_record(&cand.key, &cand.uuid).await;
- if live {
+ let verdict = if assumed.contains(strip_purl_qualifiers(&cand.key)) {
+ RecordLiveness::Live
+ } else {
+ liveness.redirect_record_verdict(&cand.key, &cand.uuid).await
+ };
+ if verdict == RecordLiveness::Live {
Basis::Hosted {
lockfile_basis: false,
}
- } else if cand.manifest_owned {
- // The hosted claim is dead but the manifest still owns the
- // purl: verify it as the agent-mode patch it now is.
+ } else if verdict == RecordLiveness::Undecided || cand.manifest_owned {
+ // Undecided: foreign host, verify by hash. Manifest-owned:
+ // the hosted claim is dead but the manifest still owns the
+ // purl, verify it as the agent-mode patch it now is.
Basis::Installed
} else {
notes.extend(dead_claim_notes(
@@ -784,7 +788,13 @@
continue;
}
match basis {
- Basis::Installed => {}
+ Basis::Installed => {
+ // Foreign-host candidates from a redirect ledger are verified
+ // by hash but still marked as "(redirected)" for provenance.
+ if cand.redirected && !cand.manifest_owned {
+ plan.redirected.push(cand.key.clone());
+ }
+ }
Basis::Vendored(entry) => {
plan.vendor_entries.insert(cand.key.clone(), *entry);
}
diff --git a/crates/socket-patch-core/src/vex/discover/cargo.rs b/crates/socket-patch-core/src/vex/discover/cargo.rs
--- a/crates/socket-patch-core/src/vex/discover/cargo.rs
+++ b/crates/socket-patch-core/src/vex/discover/cargo.rs
@@ -629,10 +629,70 @@
out.push(lock_ref);
continue;
};
- if regs
+ let pinned: Vec<&Decl> = regs
.iter()
- .any(|d| d.registry.as_deref() == Some(reg.as_str()))
- {
+ .filter(|d| d.registry.as_deref() == Some(reg.as_str()))
+ .collect();
+ if !pinned.is_empty() {
+ // Check if there are unpinned declarations from different files
+ // that would resolve independently. A [patch] entry applies
+ // globally, but a member dependency with a registry does not
+ // affect the root's declaration (or vice versa).
+ let unpinned: Vec<&Decl> = regs
+ .iter()
+ .filter(|d| d.registry.as_deref() != Some(reg.as_str()) && !d.socket_shaped())
+ .collect();
+ // Only check if unpinned declarations are from different files than
+ // the pinned ones (e.g., root vs member), indicating independent
+ // resolution. If all declarations are from the same file (e.g., a
+ // [patch] in Cargo.toml plus a regular dep in Cargo.toml), the
+ // [patch] applies globally.
+ let different_files = !unpinned.is_empty()
+ && unpinned.iter().any(|u| {
+ pinned.iter().all(|p| {
+ // Consider declarations from different files, but treat
+ // Cargo.toml and .cargo/config.toml as the "root" scope
+ // since [patch] entries from either apply globally.
+ let p_is_root = p.file == CARGO_TOML
+ || p.file == ".cargo/config"
+ || p.file == ".cargo/config.toml";
+ let u_is_root = u.file == CARGO_TOML
+ || u.file == ".cargo/config"
+ || u.file == ".cargo/config.toml";
+ // Different if one is root-scope and the other isn't,
+ // or if both are non-root and from different files.
+ (p_is_root != u_is_root) || (!p_is_root && p.file != u.file)
+ })
+ });
+ if different_files {
+ use crate::patch::redirect::{cargo_req_selects, CargoReqMatch};
+ let matches_locked = unpinned.iter().any(|d| {
+ let req = d.version.as_deref();
+ matches!(
+ cargo_req_selects(req, &pkg.version, &[]),
+ CargoReqMatch::Ours | CargoReqMatch::Ambiguous
+ )
+ });
+ if matches_locked {
+ let files = decl_files(unpinned.iter().copied());
+ let verb = if files.contains(", ") {
+ "declare"
+ } else {
+ "declares"
+ };
+ out.diag(
+ DIAG_REF_INVALID,
+ CARGO_LOCK,
+ format!(
+ "{CARGO_LOCK}: {purl} resolves from Socket patch {uuid}, but {files} \
+ {verb} {} without registry = \"{reg}\" — cargo re-resolves it from the \
+ declared source; not counted",
+ pkg.name
+ ),
+ );
+ continue;
+ }
+ }
out.push(lock_ref);
continue;
}You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit ca08f29. Configure here.
- vex/discover/mod.rs: keep both sides' UnattestedKind variants;
CargoUnpinnedDeclaration stays after MavenClassifierUnpatched, followed
by the base's pnpm/npm/sbt/NuGet/Go kinds and its file_scoped() impl
(Cargo is not file-scoped).
- vex_sources.rs: keep both sides' note constants and unattested_note()
arms; keep the base's new tests (npm-family tags, Go workspace marks)
and re-add the PR's Cargo gating test after them.
- CLI_CONTRACT.md: take the base's "Unattested references" paragraph,
bump its count to nine ("one of them" for the sbt case listed
elsewhere) and append the PR's Unpinned Cargo declarations case; keep
both sides' rows in the vex reasons table.
Co-Authored-By: Claude <noreply@anthropic.com>
…plit-patches The base picked up #2012's main merge, which added the npm-family, sbt, NuGet and Go UnattestedKind variants and their vex notes. This branch adds CargoPatchBypassed. The two sides only add entries, so the merge keeps both: - vex/discover/mod.rs: CargoPatchBypassed comes after CargoUnpinnedDeclaration, then the base's variants and file_scoped(). Cargo stays non-file-scoped. - vex_sources.rs: keeps both sides' note constants and unattested_note() arms. - CLI_CONTRACT.md: keeps the base's paragraph, appends this branch's vendored-Cargo case, and changes the case count from "Nine" to "Ten". The reasons table keeps both sides' rows. Co-Authored-By: Claude <noreply@anthropic.com>
Cargo reads an explicit `registry = "crates-io"` the same as no registry, so a Socket pin under `[patch."https://github.com/rust-lang/ crates.io-index"]` redirects such a declaration. `patched_by` only compared the key to the table name, so it missed that case and marked the lock ref CargoUnpinnedDeclaration. Route the explicit spelling through `is_crates_io_source` like a bare declaration (Bugbot on #2019). Co-Authored-By: Claude <noreply@anthropic.com>
…plit-patches Resolve the patched_by conflict: keep this branch's per-source match (registry, registry-index, git) and apply the base's fix that reads an explicit registry = "crates-io" as crates.io in the Registry arm. Co-Authored-By: Claude <noreply@anthropic.com>
patch_bypassed only withheld the vendored ref for declarations route() finds Unpinned. A member declaring the crate from another socket-patch registry routes to Pin(other) and was skipped. When the lock resolves the crate from that patch, its hosted ref sits beside the vendored one and the CLI's wiring_conflict gate refuses both. When the lock is stale and holds no entry from that patch, only a patched_ref_unattributable diagnostic was left, and patch A was attested even though the member re-resolves to patch B. Mark the vendored copy CargoPatchBypassed in that case instead. Co-Authored-By: Claude <noreply@anthropic.com>
The exemption for a member pinned to another Socket patch trusted any lock line from that patch. hosted_from_lock drops a line whose project registry definition disagrees, and a line at another version is no ref for this package, so wiring_conflicts never fired and the vendored copy stayed attested. Exempt only when the hosted pass emitted a ref for that patch at this very purl that is not itself withheld. Co-Authored-By: Claude <noreply@anthropic.com>
Owner decision on #2012: a pre-v5 redirect-ledger record whose wiring resolves the package from a hosted-shaped url on an origin this run was not given (a staging server used without --patch-server-url, or a look-alike host) is never attested. vex reports it as redirect_unwired, whatever is installed and even under --no-verify. Step 1 of redirect_record_verdict already returned Undecided for a lock inventory entry carrying such a url. The formats whose inventory has no url (yarn berry __archiveUrl bindings, Cargo.lock registry sources, pom repositories, Gemfile source blocks, go.mod replace module paths) fell through to the recorded-wiring step 3 (or the gem step 0) and attested. Those steps now return Undecided when any recorded file spells a hosted-shaped url for the uuid on an unconfigured origin. A Socket-host spelling elsewhere in the files does not clear it. foreign_hosted_uuid drops userinfo before reading the origin, so a https://patch.socket.dev@evil.example/... look-alike is Undecided in every step rather than Dead in step 1 and Live in step 3. vex adds a vex_claim_unwired note for such a record naming the cause and --patch-server-url. CLI_CONTRACT.md "Patch hosts (manifest-less VEX)" and the vex_sources module docs state the new rule. The yarn, cargo and maven e2e cells that asserted the old staging fallback now expect redirect_unwired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The owner settled the Changes in cb98b9b:
Review threads:
|
…n' into fix/cargo-mixed-member-pins
Review on #2019 (/code-review high): - The vex_cargo_unpinned_declaration remedy said "re-run socket-patch scan" whenever every unpinned declaration was a crates.io one the rewriter would pin. The rewriter skips the whole dependency on its first refusal, including one over a declaration discovery doesn't count as unpinned (the pin's own requirement matching another locked version, a path template nobody inherits). Discovery now runs the rewriter's own Cargo.toml planner dry over the same manifests (plan_cargo_tomls, extracted from rewrite_cargo_with) before it promises a re-scan. - A declaration naming registry = "socket-patch-<uuid>" beside a path, git or registry-index read as if the registry were missing. The detail now says the source overrides it, and says "those declarations" when there are several. - manifest_declarations looks up inherited template keys in a set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plit-patches Brings #2012's fail-closed liveness and #2019's round-3 review fixes. Conflict in beside_the_pin's declaration text: kept this branch's Decl::describe and moved #2019's 'which overrides its registry' clause into it, so every caller says it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #2024 (/code-review high), round 4: - The "resolves it only from other Socket patches" diagnostic fired beside a same-version crates.io twin; it now needs a lock with no non-Socket entry of the crate. - Shadow resolves a [patch.<registry>] table through the config's [registries] index, so a config [patch."<index>"] item shadows it. - An identical path-less Socket registry [patch] pin in the manifest and the config is the same wiring, not a shadow. - patched_by (registry-index) and same_patch_source share patch_source_key, with CanonicalUrl rules on both. - patch_bypassed's conflict-gate check matches the CLI's bypassed_ref (file-scoped marks only); it routes each declaration once; its detail also names other-patch declarations; a git lock entry a git [patch] table redirects gets the stale/re-lock remedy. - Reuse patch::redirect::is_crates_io_source for lock sources. - Move the manifests comment back above its vector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wenxin Jiang (Wenxin-Jiang)
left a comment
There was a problem hiding this comment.
[agent] Review findings (Claude Opus + codex, independent passes):
- Root-reverted pin is attested (
crates/socket-patch-core/src/vex/discover/cargo.rs:634, both reviewers).hosted_from_locknow pools root and member declarations and attests if any one carriesregistry = "socket-patch-<uuid>". Input: rootserde = "=1.0.1"(pin reverted),crates/astillserde = { version = "=1.0.1", registry = "socket-patch-<uuid>" }, stale lock holding only the Socket-sourced serde → ref attested, but the root builds crates.io serde unpatched. On main the root alone decided this (DIAG_REF_INVALID). #2019 fixes it, so this PR should not land onmainwithout #2019. The PR body's "main behaves the same" for mixed states is true only in the root-pinned/member-unpinned direction. - Lowercase percent escapes bypass the foreign-origin guard (
vex/discover/mod.rs:2710, codex). A Berry__archiveUrl=https%3a%2f%2fevil.example%2fpatch%2f<U>%2fx-1.0.0.tgzis not normalized; since Berry inventory omits the resolved URL, the recorded-file fallback returnsLiveand a legacy record for U attests undervex --no-verify.
Minor: cargo_workspace.rs:136-147 silently drops a symlinked/unreadable member manifest (not a regression).
Wenxin Jiang (Wenxin-Jiang)
left a comment
There was a problem hiding this comment.
[agent] Approving per maintainer decision; see the findings in the review above. Land together with #2019, which fixes finding 1.
Behavior-preserving cleanup of #2012 after #2019 and #2024 were folded in: - One `distinct<T>` replaces four hand-written order-preserving dedups; `describe_all` and one `other_declared` replace repeated describe/join chains. - `DeclSource::patched_by` is the one redirect rule; the bypass check's stale-entry test maps the lock source to a `DeclSource` instead of re-deriving crates.io and git matching. `index_key` is folded into `patch_source_key`. - `beside_the_pin` collects the lock's Socket uuids once; the manifests are serialized for the rewriter's dry planner once per discovery (`ManifestTexts`), not per crate. `vendored_from_patches` takes the lock from `Bypass`; the two `decls` passes in `extract` are one. - `foreign_hosted_uuid` moves to `origin::hosted_shape_uuid`, so `normalized_hosted_url` is private again; the cheap accepted-origin check runs first in `foreign_hosted_url_in_files`. - Comments and docs no longer narrate review history; the cargo module doc points at `beside_the_pin` / `patch_bypassed` instead of restating them; CLI_CONTRACT.md states the registry-index rule once and the `apply` sentence parses. - Tests share `workspace_root`, `member`, `one_mark` and `shadow` instead of respelling manifests and config parsing; the e2e fail-closed comments point at CLI_CONTRACT.md. Review of the simplify diff (/code-review high): the stale-lock remedy named `[patch.crates-io]` and `socket-patch vendor` for a git entry too; it now names the table that redirects the entry and offers `vendor` only for crates.io. CLI_CONTRACT.md keeps the member-walk and uninherited- template rules the trim had dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Final reviewer: the merge queue removed this PR at 22:11Z because #2018 merged and Generated by Claude Code |
Brings #2018 (never patch a user-patched pnpm/yarn/vlt dependency). Conflict in CLI_CONTRACT.md "Unattested references": kept main's patchedDependencies wording and its new pnpm/Yarn/patch-package paragraph, and this branch's two Cargo cases (count Eight -> Ten). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I merged There was one conflict, in
Ran locally: core |


Problem
Cargo VEX and ledger liveness attested wiring the build doesn't use:
Cargo.tomlwas read, so a member reverted to crates.io still attested.path,git,registry-index).[patch]copy attested when another source, a same-version lock entry or a shadowed[patch]entry bypassed it.Fix
ProjectView, only when Socket wiring could need them.routeclassifies each declaration. An unpinned one marks the refvex_cargo_unpinned_declaration, with re-scan advice from the rewriter's own planner run dry. A pin to another patch becomes awiring_conflictpair.vex_cargo_patch_bypassedwhen a declaration or lock entry escapes every honored[patch]table (Shadowmodels cargo's precedence; URLs compare asCanonicalUrl).crate::origin: Live / Undecided / Dead. Undecided is never attested;scanretains it.Owner decisions
--no-verify. It's reported asredirect_unwired, with a note naming--patch-server-url.Where to look
__archiveUrl,Cargo.lock, pom, Gemfile and go.mod (foreign_hosted_url_in_files,origin::hosted_shape_uuid). Look-alike userinfo hosts (patch.socket.dev@evil.example) count as foreign. The yarn, cargo and maven e2e cells expectredirect_unwired.cargo.rs(route,beside_the_pin,patch_bypassed,Shadow),discover/mod.rs(redirect_record_verdict), andplan_cargo_tomls(extracted fromrewrite_cargo_withunchanged).Testing
vex:: origin cargo hosted:: patch::redirect:: vendor::(4962 tests, golden included) andredirect_golden.--libplus the vex, scan, redirect and cargo-migration suites.Folds #2019, #2024.
Review history
Fixed:
excluded_reqs).[patch]pin.[patch]-redirected andregistry = "crates-io"declarations aren't unpinned.path/git/registry-indexisn't a pin, and the message says so.vendoroffered only for crates.io.[patch]precedence: shadowed entries, named vs URL tables, an identical config pin.registry-indexand git tables, canonical URLs in one helper.Declined:
git/pathmakes no conflict pair: it routes nothing to B.workspace-memberdiagnostic:expected/holds only changed files.[patch]Socket pin as a transitive crate's only wiring; ambiguouscargo update -pbeside a tagged copy; no re-lock advice for a stale other-registry entry. Sparse URLs: cargo'sCanonicalUrlalso drops the trailing/, so no change.🤖 Generated with Claude Code