Repository navigation
Withhold a vendored Cargo copy a member's other source bypasses - #2024
Draft
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Draft
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Conversation
Mikola Lysenko (mikolalysenko)
force-pushed
the
fix/cargo-mixed-member-pins
branch
from
November 5, 2026 19:57
06d306c to
77a4d2d
Compare
Collaborator
Author
|
Burn-down agent: resolved the merge conflicts with
Generated by Claude Code |
`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>
Mikola Lysenko (mikolalysenko)
force-pushed
the
fix/cargo-vendored-twins-and-split-patches
branch
from
November 6, 2026 05:27
5b08b24 to
64d73a6
Compare
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Stacked on #2019 (
fix/cargo-mixed-member-pins). It closes the vendored-mode Cargo false-attestation gap the #2019 author found.The second gap, a root pinning a crate to one Socket patch while a member pins it to another, is now handled by #2019 itself (
beside_the_pinemits the other patch'sCargo.tomlref, so the CLI gates it aswiring_conflict). This PR no longer touches it. I ran my earlier split-patch tests against #2019's code alone and they pass, so there was nothing left to keep.Vendored copy bypassed by another source
vendorwires a crate through the root's[patch.crates-io], and cargo applies that only to crates.io declarations. A workspace member (or the root) can declare the crate from another registry, aregistry-indexor a repository URL, at a requirement that may select the patched version. That declaration resolves its own unpatched copy. The vendored ref still attested beside it, because the lock copy claim (vendored_copy_claim) only reads sourceless entries.UnattestedKind::CargoPatchBypassed. The detail names the declaration.Cargo.lockentry at exactly the patched version from any registry or repository source, beside the copy, is marked the same way. The detail names that lock entry.DeclSourceclassification:[workspace.dependencies]template no manifest inherits doesn't.[patch.<registry>]table pointing at the same copy redirects it (Decl::patched_by). A git dependency is covered by a[patch."<repository URL>"]table pointing at the copy (see the follow-ups below).a_same_version_fork_beside_the_tagged_copy_is_ignoredalready specifies.socket-patch-*registry dependency is hosted wiring, handled by Stop attesting a Cargo pin a workspace member resolves from crates.io #2019.vex_cargo_patch_bypassed. It's documented in CLI_CONTRACT.md (Unattested references paragraph and the notes table).I added a new variant rather than reusing
CargoUnpinnedDeclaration. That variant's note tells the user to re-runscan, which is the wrong fix for vendored wiring.Member gate. #2019 reads workspace members only when the lock holds a Socket-sourced package (or, with no lock, a manifest names a
socket-patch-registry). A vendored copy's lock entry has no source, so the members are now also read whenever the root manifest or the project config wires a Socket copy through[patch], whatever the lock's state.Cargo follow-ups (second commit)
unresolved_manifest_pins,UnlockedPin,only_this_patch).socket-patch-<uuid>registry besidepath,gitorregistry-indexis no pin, matching the rewriter's pinned check. It no longer feeds anUnlockedPin; it counts as an unpinned declaration that excludes the versions it accepts (all of them, without a requirement).[workspace.dependencies]template no manifest inherits is no declaration.[patch]registry entry no longer counts as an unpinned declaration (it redirects, it declares nothing).DeclSource::Gitkeeps its URL.Decl::patched_bymatches it against a[patch."<repository URL>"]table (a trailing/or.gitignored). A git declaration whose repository table points at the same vendored copy no longer withholds it, while one from another repository still does. Hosted routing gets the same match for a Socket registry pin in such a table.cargo_req_selectssays it may select the locked version. Otherwise the stale lock entry is diagnosed and dropped like any unrouted one.Tests
a_member_from_another_source_bypasses_the_vendored_copy(another registry, a renamed=1.0.4, a repository URL, aregistry-index)a_same_version_lock_entry_from_any_source_bypasses_the_vendored_copya_member_from_git_bypasses_the_config_wired_copymembers_are_read_for_a_vendored_copy_without_a_socket_lock_entry(with and without a lock)declarations_the_patch_redirects_keep_the_vendored_copy_attested. It covers crates.io members in every spelling, includingregistry = "crates-io"and a rename; an excluding requirement;[patch.my-corp]coverage; a path fork; and an un-inherited workspace template.a_vendored_cargo_copy_beside_a_bypassing_source_is_gated.lockless_path_or_git_beside_a_socket_registry_is_no_pin(path / git / registry-index member beside the root's pin, and alone)lockless_uninherited_template_is_not_a_declaration(Socket-pinned template uninherited vs inherited; an uninherited crates.io template beside the root's pin)a_git_patch_table_for_the_copy_covers_that_repository_only(covered with a.git/spelling and a branch; another repository still bypasses)another_patch_pin_excluding_the_locked_version_is_no_conflictcargo test -p socket-patch-core --lib -- vex::discover:: vendor::cargo patch::redirect:: utils::cargo_workspace(includes the golden corpus test): 1564 passed.-p socket-patch-cli --lib vex_sources: 16 passed.--test in_process_redirect cargo,--test in_process_cargo_apply cargo,--test mode_migration_cargo: 6 / 3 / 30 passed.cargo clippy --locked --workspace --all-features -- -D warningsandcargo fmt --all -- --checkare clean.Review findings (
/code-review high, third commit)Each of findings 1–6 was confirmed first by a test that fails on the previous commit and passes now.
False attestation from a shadowed
[patch]entry: confirmed and fixed. The bypass check counted a manifest entry cargo ignores (the config redefines its crates.io key with a git fork) as redirecting crates.io declarations, so the copy's honored[patch.my-corp]entry was attested beside the fork. Only honored entries count now. Test:a_shadowed_manifest_entry_covers_no_declaration.registry-indexcoverage: confirmed and fixed. A[patch."<index URL>"]table now covers a declaration of the same index (aregistry+prefix, host case and a trailing/ignored). Aregistry-indexnaming the crates.io git index is crates.io. Test:a_registry_index_patch_table_covers_that_index.sparse+https://index.crates.io/) is not treated as crates.io.Cargo.lockrecords it as a distinct source string from the git crates.io index, and the files don't show that[patch.crates-io]reaches it. Treating it as covered could attest a copy cargo doesn't build, so it stays withheld. The cost is a missed attestation on a rare hand-written shape.registry = "<name>"declaration is still matched only by a[patch.<name>]table, not by its index URL (the registry's URL may live in$CARGO_HOME).Remedy per evidence: confirmed and fixed. The detail now ends with the remedy for its evidence:
vendordoesn't help;[patch.crates-io]copy: stale, so re-lock (re-runvendororcargo update -p <crate>);cargo tree -i <crate>@<version>names the dependency that declares it.The CLI note's fixed suffix no longer makes that claim. Test:
the_bypass_remedy_follows_its_evidence.Canonical git URLs: confirmed and fixed. URLs are compared as cargo's
CanonicalUrlspells them: one trailing/and.gitdropped, a GitHub URL lowercased whole, any host lowercased. Test:a_git_patch_table_matches_cargos_canonical_url.Unreflected pin to another patch lost its diagnostic: confirmed and fixed. Once the lock held any Socket entry of the crate,
unresolved_manifest_pinsskipped it, so such a pin got no ref and no diagnostic. It now getspatched_ref_unattributableunless the lock resolves the crate from that patch or a ref already carries it. Test:another_patch_pin_excluding_the_locked_version_is_no_conflict(extended).Ineffective Socket registry gets no diagnostic: confirmed and fixed. A well-formed
socket-patch-<uuid>besidepath/git/registry-index, or on a[workspace.dependencies]entry no manifest inherits, is now diagnosedpatched_ref_invalid("pins nothing there"). Test:an_ineffective_socket_registry_is_diagnosed.redirect/cargo/cargo/workspace-member/expectedtree omits the unchanged inheriting membera/, so its pinned template reads as uninherited there and now carries that diagnostic. I regenerated the golden; no other corpus project changed.socket_copiescomputed twice: fixed. It's computed once, before the root manifest moves;vendored = !wired_all.is_empty().Shared helpers: done.
Decl::describeandsame_version_foreign_entryare used by bothbeside_the_pinandpatch_bypassed.One classifier: done without widening
route.routealready takes(table, uuid)pins, so the vendored check calls it with the copy's honored[patch]tables as the pins and the lock's other versions of the crate. That is the same classifier hosted pins use, with no new parameter. The only vendored-specific rule left is that a path declaration (the user's own fork) is ignored, an existing documented rule.Not done
vendor::cargoalready refuses the lock-twin state:locked_multi_source_conflictcovers a same-version entry from two sources, andvendor_source_unsupportedcovers a non-crates.io entry. A member declaration whose other-source copy isn't locked yet is invisible to the planner without re-resolving.Generated by Claude Code