GeezerVison is showing github.com — Stop attesting a Cargo pin a workspace member resolves from crates.io by mikolalysenko · Pull Request #2019 · SocketDev/socket-patch
Conversation
Stacked on #2012 (base fix/cargo-members-liveness-origin).
Problem
#2012 made Cargo VEX discovery read workspace member manifests, but left one case open. When one declaration pins a crate to socket-patch-<uuid> and another declaration of the same crate is not pinned (crates.io, another registry, a path or git) with a requirement that may select the locked version, discovery still emitted an attested Cargo.lock ref. "Some declaration pins the patch" was enough. The other declaration can be the root's or a member's.
Cargo resolves each source on its own. The unpinned declaration builds a separate, unpatched copy, and that copy can be the very version the pin covers. The hosted rewriter pins every declaration in the root and every member (patch/redirect/mod.rs), so this mixed state comes from a partial revert or a hand edit.
Fix
Each declaration of a Socket-locked crate is classified once (route). It is one of three things:
- routed to a Socket patch: by its own
socket-patch-<uuid> registry, or by a Socket [patch.<source>] registry entry in the root manifest or project config that redirects it;
- unpinned: cargo builds its own copy for it;
- saying nothing about the locked version. That covers a requirement that excludes it, a
[patch] entry itself, a [workspace.dependencies] template no manifest inherits, and a malformed socket-patch-* name (diagnosed elsewhere).
What the classification decides when some declaration names the lock's patch A:
- Unpinned declaration: the ref stays (rollback, remove and the ledger liveness gates keep working) but is marked unattested with the new
UnattestedKind::CargoUnpinnedDeclaration.
- The mark names the declaration's manifest, requirement and source.
- It also says whether re-running
scan can pin the declaration. Only a crates.io declaration whose requirement matches no other locked version can be re-pinned; for anything else the rewriter refuses, so the remedy is to change the declaration or roll the patch back.
- What counts as a pin:
registry = "socket-patch-<uuid>" beside path, git or registry-index is not a pin, because cargo builds that other source. This matches the hosted rewriter's own pinned check.
- Another patch B: a declaration routed to patch B that may select the locked version, where the lock does not resolve the crate from B, adds B's
Cargo.toml ref. The CLI then gates the package as wiring_conflict.
- Same-version twin: when the lock already holds a same-version copy from another source, no mark is added. The existing twin rule (Hosted cargo vex attests not_affected while Cargo.lock also builds an unpatched crates.io copy of the same crate@version #679) withholds the ref and names the lock entry.
Version requirements go through patch::redirect::cargo_req_selects, with the lock's other versions of the crate, so "may select" and "ambiguous" agree with the rewriter.
CLI: a new note arm vex_cargo_unpinned_declaration in vex_sources.rs. Its remedy is generic ("not attested while that declaration stands"); the per-case remedy is in the detail. Both places in CLI_CONTRACT.md are documented.
Vendored mode
Vendored mode does not have this bug. Its wiring is a root [patch.crates-io] entry, which cargo applies workspace-wide to every crates.io declaration the copy satisfies. The related gap (a member declaring the crate from another registry or git) is handled in #2024, which is stacked on this PR.
Review findings (/code-review high)
All nine were confirmed and fixed. For the behaviour bugs I wrote the test first and saw it fail; 7 tests failed before the fix.
- Fixed. A crates.io declaration that a Socket
[patch] registry entry redirects was marked unpinned. Redirected declarations now count as pins. Added out.unattested.is_empty() to patch_table_registry_pin_counts_as_the_declaration and config_patch_registry_pin_counts_for_a_declared_crate, plus a case where a declaration from another registry, not redirected by [patch.crates-io], is still marked.
- Fixed. A socket registry beside
path / git / registry-index counted as pinned. Test: a_socket_registry_beside_another_source_is_not_a_pin. Next to a real pin, it marks the ref. As the only declaration, it keeps the ref because Socket's wiring is present, but marks it.
- Fixed. The remedy now depends on the case (
the_remedy_says_whether_a_rescan_can_pin_it). A requirement that also matches another locked version names that version and says scan cannot pin it.
- Fixed. In the same-version twin case the mark is skipped, and the Hosted cargo vex attests not_affected while Cargo.lock also builds an unpatched crates.io copy of the same crate@version #679 diagnostic stands (
a_locked_same_version_twin_is_left_to_the_twin_rule).
- Fixed. A
[workspace.dependencies] template counts only when inherited (an_unpinned_workspace_template_counts_only_when_inherited).
- Fixed. A
registry-index declaration is reported as "the registry index ", with the roll-back remedy.
- Fixed. Root pins A and a member pins B: B's ref is emitted beside A's (
a_member_pinned_to_another_patch_is_a_conflict_pair). No conflict is emitted when B's requirement excludes the locked version, or when the lock resolves the crate from B at another version (a separate, patched package with its own ref). This overlaps with the split-patch part of Withhold a vendored Cargo copy a member's other source bypasses #2024; see the note there.
- Fixed. The code uses
cargo_req_selects with the lock's other versions; Decl::accepts is gone.
- Fixed.
Decl carries a DeclSource enum and a live flag. The origin text is formatted only for messages, and [patch] pins use Decl::patch, which has no dead version parameter.
Tests
cargo test -p socket-patch-core --lib -- vex::discover:: vendor::cargo patch::redirect:: utils::cargo_workspace: 1555 passed. The golden corpus test passes with no golden changes.
cargo test -p socket-patch-cli --lib vex_sources: 15 passed.
cargo test -p socket-patch-cli --test in_process_redirect cargo: 6 passed.
cargo clippy --locked --workspace --all-features -- -D warnings and cargo fmt --all -- --check are clean.
Not done
- The lockless path (
unresolved_manifest_pins, from Cargo VEX: read member manifests, match liveness by origin, withhold mixed and vendored bypasses #2012) still treats a socket-patch-<uuid> registry beside path / git as pinning, and an uninherited template as a declaration. It is unchanged here.
- In the old "no declaration pins the lock's patch" branch, the conflict pair is still emitted without a requirement check.
- I did not read the lock's per-package
dependencies lists to tell which copy a declaration actually resolved.
Brought current with #2012, plus round-2 review findings (cloud routine)
Merged the updated #2012 in. It merged cleanly, and the targeted core tests pass (688).
Review findings, round 2 (/code-review high on 7837fb4)
- The remedy treated
registry = "crates-io" as another registry: fixed. The hosted rewriter re-pins that declaration (Hosted cargo redirect refuses a crates.io dependency declared with an explicit registry = "crates-io", treating crates.io as "another registry" #386), so the note now says a re-scan pins it. Test: the_remedy_says_whether_a_rescan_can_pin_it (extended).
- FALSE ATTESTATION: a shadowed
[patch] Socket pin counted as pinning declarations. Confirmed. Cargo ignores a [patch.crates-io] entry when a crates.io URL table replaces it, or when the config redefines the key. patch_pins still counted such an entry as honored, so a member's crates.io declaration was taken as routed to the patch. Fixed in Withhold a vendored Cargo copy a member's other source bypasses #2024, where the Shadow classifier lives (the next PR in this chain). Test there: a_shadowed_patch_pin_pins_no_declaration.
Not changed:
Tests (cloud run): cargo test -p socket-patch-core --lib -- vex::discover:: gives 465 passed, golden included. clippy and fmt are clean.
Round 3: merged #2012's fail-closed change, /code-review high (0c83105)
Merged fix/cargo-members-liveness-origin at cb98b9b: liveness now fails closed on unconfigured patch servers, per the owner decision on #2012. The merge was clean, and core and CLI vex tests pass on it.
Fixed:
- The re-scan remedy was wrong when the rewriter refuses over another declaration.
plan_cargo_toml skips the whole dependency on its first refusal. That can be the pin's own requirement matching another locked version, or a path template nobody inherits, and neither is in unpinned. Discovery now runs the rewriter's own planner dry, before it promises a re-scan. The planner is plan_cargo_tomls, extracted from rewrite_cargo_with with no behavior change. Test: the_remedy_says_whether_a_rescan_can_pin_it (extended).
- The detail misread a Socket registry beside
path/git/registry-index. It now says the source "overrides its registry = …". With several unpinned declarations it says "those declarations". Test: a_socket_registry_beside_another_source_is_not_a_pin (extended).
- The
inherited lookup was quadratic. It's now a BTreeSet.
Fixed by the stacked #2024, which lands right after this PR (the owner chose in-order, back-to-back landing):
Declined:
- "A Socket registry
B beside git/path no longer makes a conflict pair." That declaration routes nothing to B: cargo builds the git/path source, which is the hosted rewriter's own pinned check. So the stale A lock entry is correctly dropped as patched_ref_invalid, and A's ledger record correctly dies.
- "An Ambiguous unpinned declaration is withheld although the lock resolves it to another version." This is intended and documented in CLI_CONTRACT.md ("a version requirement that may select the locked patched version"). The lock records no per-declaration source edge discovery reads, and a relock may move it, so we fail closed. The cost is a missed attestation, never a false one.
- "Use
CargoLock::locked_versions" and "compute the lock's Socket uuids once". These are cleanups on different lock types and small loops, with no behavior change.
Tests: core vex:: origin cargo hosted:: patch::redirect:: (2266, golden included), CLI --lib (868), e2e_vex_lockfile (322), e2e_vex_redirect (33), in_process_redirect (135) and mode_migration_cargo (8). All pass. clippy and fmt are clean.
Note Medium Risk
Changes Cargo VEX attestation rules for workspaces; wrong classification could omit or over-attest patches, but management commands and ledgers still treat wiring as live.
Overview
Cargo VEX discovery no longer attests a hosted Cargo.lock pin when another manifest declaration (root or workspace member) can still build an unpatched copy of the same crate version — for example a member on crates.io beside a Socket-pinned root.
Discovery now classifies every declaration with route / beside_the_pin: Socket registry/[patch] redirects count as pins; path/git/registry-index override a socket-patch-* registry; [workspace.dependencies] only matters when inherited. Conflicting pins to another Socket patch still emit both refs for wiring_conflict; same-version lock twins stay on the existing #679 rule.
Unattested refs get vex_cargo_unpinned_declaration (CLI note + CLI_CONTRACT.md), with detail naming the offending manifest and whether scan can re-pin (crates.io-only, unambiguous version) vs roll back / edit the declaration.
Extensive new tests cover mixed workspaces, remedies, and edge cases.
Reviewed by Cursor Bugbot for commit b126508. Configure here.
Generated by Claude Code
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Nov 4, 2026
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 <[email protected]>
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 <[email protected]>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Nov 6, 2026
`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 <[email protected]>
…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 <[email protected]>
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Nov 6, 2026
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 <[email protected]>
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
left a comment
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
- 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 <[email protected]>
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
left a comment
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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 <[email protected]>
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b126508. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…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 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[agent] Approving: no false-attestation paths found by two independent passes (Claude Opus, codex). This PR also closes #2012's root-reverted/member-pinned attestation, so #2012 should not land on main without it.
Non-blocking:
- Codex: a git-source dependency redirected through
[patch."<git URL>"] to socket-patch-<uuid> is now refused by patched_by (cargo.rs:521). Fail-safe, but a lost attestation vs base if that shape is supported.
- Shadowed root/config
[patch] entries still count as redirecting (cargo.rs:1033); #2024 covers it.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
|
Folded into #2012 at the owner's request: fix/cargo-members-liveness-origin was fast-forwarded to this stack's top, so #2012 now carries the whole stack. Review it there. The branch is kept.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Nov 9, 2026
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 <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|