Skip to content

Commit 2390c5f

Browse files
Wire vendored cargo patches through Cargo.toml and tag the copy (#254)
* feat(vendor): wire cargo patches through Cargo.toml [patch] Vendored cargo patches are now wired through a Socket-owned [patch.crates-io] key in the workspace-root Cargo.toml instead of .cargo/config, so the patch uuid is visible from the manifest alone. Legacy config wiring is migrated on re-run, repair and uuid bumps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(vendor): tag vendored cargo copies with +socket.<uuid> A vendored cargo copy's own Cargo.toml version is now rewritten to <version>+socket.<uuid> (existing build metadata is kept: 2.0.1+zstd.1.5.2.socket.<uuid>), and the detached Cargo.lock entry records that tagged version, with every reference that spells the old version rewritten, in lock formats v1-v4. This is the lock cargo itself writes for the tagged copy, so the patch uuid of the copy that actually builds can be read from Cargo.lock alone. The patched crate now sees the tag in CARGO_PKG_VERSION. A lock that cannot be kept consistent is refused before anything is written (cargo_lock_untaggable). Revert restores the original lock byte for byte. A uuid bump re-tags the copy and lock, and a re-run or repair tags projects vendored before this change (cargo_version_tagged). VEX discovery treats the tagged lock version as the primary identity signal. The old-toolchain e2e tests prefer the rust:1.41-slim and rust:1.56-slim docker images. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(vendor): harden tagged cargo copies after review - Keep a recorded whole-tree inventory when repair tags a pre-tag copy instead of re-baselining it over unverified bytes; the inventory check accepts the copy's Cargo.toml only with exactly this uuid's tag dropped, the cargo inventory carries forward for the same copy, and repair refreshes it only from a member-verified rebuild. - Select Cargo.lock entries by rank (this uuid's tagged copy, another tagged copy, a registry entry, then an untagged sourceless one), so a user's same-version path fork locked beside the copy is never restored, retagged or counted as a multi-source conflict; the revert spells the restored registry entry by its full id, exactly as cargo writes it. - GC keeps an entry whose lock tag is stale while the manifest still wires its copy (cargo re-locks to it; the next re-run retags). - Revert drops the tag the first build locked when no lock originals were recorded (vendored before any Cargo.lock), so a hosted takeover finds the crate again. - Dry runs preview "would tag" and the cargo_lock_untaggable / cargo_copy_untaggable refusals. - lock_inventory lists a sourced Socket-tagged entry with no verifier. - Docs: semver Version equality/ordering sees the tag; 1.56 is built in CI docker; an untagged detached entry counts only beside an untagged copy. - Tests: fork-shape lock units (v1/v4, cargo's exact lock), vendor-flow tests for revert/dry-run/untaggable copies, tag-aware inventory and carry-forward units, VEX discovery copy-tag units, VEX e2e split and ledger leg, and real-cargo cells for a transitive registry dependent, the repair inventory refresh and a lockless vendor + hosted takeover. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(vendor): cover cargo tag rollbacks and 1.41 index - A failed Cargo.lock retag write (read-only project root) untags the copy again, through both the vendor re-run and repair's migration. - repair's migration over a copy whose Cargo.toml cannot take the tag reports cargo_version_untagged and writes nothing. - The cargo 1.41 two-version leg now proves the documented remedy: a populated crates.io index in $CARGO_HOME builds and runs both patched copies with no network, instead of only accepting the failure. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(vendor): see every hosted pin shape before vendoring The cross-mode guard that refuses to wire [patch.crates-io] on top of a live hosted redirect read Cargo.toml with a single-line regex, so it saw only `<crate> = { ... registry = "socket-patch-<uuid>" }`. The hosted rewriter also writes a standalone `registry = ...` line under a `[dependencies.<crate>]` header, pins renamed declarations by their `package` key, and accepts a quoted key — all read as "not redirected". With a Cargo.lock restored from version control (so the lock probe sees crates.io again) and no redirect ledger, vendor then reported success on a project pinned to the hosted registry and patched through [patch.crates-io] at once: cargo resolves the crate from a non-crates.io source, which [patch.crates-io] does not cover, so the build either fails or silently links the unpatched copy. The probe now reuses the rewriter's own manifest reader, so detection and writing stay in step, and the refusal names the registry it found. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(vendor): pin the cargo tag allowance to this patch A cargo patch that edits the crate's own Cargo.toml verifies with the Socket version tag dropped. The whole-tree inventory check drops only THIS entry's tag; the per-file check dropped whatever Socket tag it found, so a copy under <uuidA>'s path whose manifest is tagged for <uuidB> — a hand edit, a merged vendored tree, a half-applied uuid bump — verified clean and repair reported it healthy, while VEX discovery refused the same bytes as dead wiring. Cargo builds it as the version the tag names, which is not the version the detached lock pins. Cargo entries record no file inventory, so this per-file path is the live one for every vendored crate, not a legacy fallback. It now applies the same uuid pin, with the four-arm test the inventory path already had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(vendor): assert the old-toolchain index constraint The two-version old-toolchain test only printed whether a build without --offline worked, and reached the documented 1.41 remedy (a populated crates.io index in $CARGO_HOME) only when the plain --offline build happened to fail. Both arms run with no network, so the unasserted run could only fail, for a reason nothing checked — and a cargo that stopped needing the index would have skipped the remedy and still passed. Both directions are now asserted: the run without --offline must fail on the unreachable crates.io index, and below 1.56 the empty-CARGO_HOME --offline run must fail with the missing registry index before the seeded index is required to build and run both patched copies. The docs said 1.56 needs --offline; what 1.56 needs is --offline or a reachable index, exactly like 1.41, which additionally needs the index populated. Both the changelog and the ecosystem notes now say that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(purl): decode build-metadata versions before matching The patches API serves canonical PURLs, so a semver build-metadata version arrives percent-encoded — pkg:cargo/wasi@0.11.0%2Bwasi-snapshot- preview1. The parsers compared that raw spelling against Cargo.lock's 0.11.0+wasi-snapshot-preview1, never matched, and refused the package with vendor_fetched_missing and then locked_version_mismatch. It failed closed, but no cargo crate with build metadata could be vendored at all, and wasi is in most Rust dependency graphs. Every ecosystem's parse now percent-decodes the namespace, name and version exactly once, after the / and @ split and before the callers' path-safety guards, so an escaped separator still cannot introduce a path segment and an already-decoded %2B cannot become a second +. The crawler and the ledger revert paths that decoded by hand, or fed an already-canonical PURL back in, drop their extra pass. Hosted mode already went through a decoding parse and is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(vendor): keep the Cargo.lock line endings toml_edit renders LF only and drops the \r of every CRLF line, including the ones no edit touched, so vendoring rewrote a lock committed with Windows line endings as all-LF. vendor --revert then "restored" that file, so the rollback differed from the original in every line ending and was not byte-identical. The copy's Cargo.toml already mapped its rendering back onto the original endings; the lock did not. The lock edits — detach, retag and restore — now go through the same reconciliation, in lock formats v1 through v4. A CRLF lock stays CRLF, a missing trailing newline stays missing, and every line a mixed-ending lock's edit leaves alone keeps its own ending. A line the edit rewrites, removes or re-inserts still takes the file's dominant ending, because its original one is not recoverable from the text, so a mixed lock is promised an unchanged remainder rather than a byte-identical revert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(vendor): state the real cargo floor for two versions Cargo before 1.56 resolves every source-less Cargo.lock entry for a crate through ONE [patch.crates-io] path — the entry whose key sorts last — so with two vendored versions one lock entry is pinned to the other version's copy and cargo build --locked fails closed with "patch for <crate> did not resolve to any crates". The docs claimed older cargo needed only a populated crates.io index; the index does not help. The old-toolchain e2e passed because its fixture uuids happened to sort so that the lower version's key came last, the one order 1.41 can take. Verified on the rust:1.41-slim and rust:1.56-slim images with two crates (cfg-if 1.0.5/0.1.10, lazy_static 1.5.0/0.2.11) and with three versions (bitflags 1.2.1/0.9.1/0.8.2): only the key sort order decides, the lock entry order does not, and 1.56 builds either way. The e2e fixture now uses uuids whose keys sort adversarially, asserts that order so a future uuid cannot quietly make the test toothless, and pins the refusal below 1.56 while requiring 1.56 to build both copies. Vendoring a second version of a crate warns with cargo_multi_version_old_cargo unless the project's rust-version or rust-toolchain[.toml] promises cargo 1.56 or newer; socket-patch never runs cargo, so those files are the only signal it has. A single vendored version still builds on 1.41. A key-naming scheme that puts the lowest version's key last does make 1.41 resolve every entry correctly, but it would replace the documented <name>-socket-<uuid8> key with a version-rank encoding in every project's committed manifest, and rest correctness on an undocumented cargo iteration order. Left out deliberately; the failure is loud, not silent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(vendor): check the wasi oracle at compile time The build-metadata e2e vendored `wasi` and then ran a consumer binary calling the patched marker fns. `wasi` declares 46 `extern "C"` WASI syscalls that only resolve on wasm targets; GNU and Apple linkers dead-strip the never-called wrappers, but MSVC refuses the executable (`LNK1120: 46 unresolved externals`), so the test failed on the windows-latest leg. The fresh-checkout `cargo build` of the same binary consumer would have failed identically. Keep `wasi` as the fixture (it is the real-world build-metadata crate the bug hit) and make the consumer a library whose oracle is compile-time: the patch suffix exposes `SOCKET_PATCHED` and `SOCKET_PKG_VERSION` constants and the consumer's `src/lib.rs` holds a `const fn bytes_eq` plus two `const _: () = assert!(...)` items, so `cargo build --locked --offline` of the library proves both that the patched bytes are what cargo compiled and that `CARGO_PKG_VERSION` carries the exact `<version>.socket.<uuid>` tag, without linking anything on any OS. The byte-identical revert leg and the zero-downloads assertion are unchanged. Negative controls, run by hand against the vendored tree: removing the suffix from the copy fails with E0425 (`SOCKET_PATCHED` not found in `wasi`); stripping the tag from the copy's manifest and lock entry fails with E0080 (the const assertion). Verified under cargo 1.93.1 and 1.83.0 and all four lock formats. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(vex): label cargo lock shapes in assert messages CodeQL's default setup raised 14 new `rust/cleartext-logging` alerts on this branch, all in `assert_attested`'s panic messages. The SARIF code flows start at the `uuid` reads inside `cargo_tag::tag_version`'s `format!` (CodeQL's name heuristics classify any `uid`/`uuid`-named identifier as sensitive), pass through the test's `tagged_version(U)` wrapper into the `copy`/`locked` strings of `cargo_vendored_a_attests_in_every_lock_version`, and end in the `what` label interpolated into every assertion message of the helper. A Socket patch uuid is a public identifier, so the alerts are false positives, but they gate the PR and no CodeQL config file exists to exclude tests. Break the path where the value entered the messages: the loop now labels its two cases `tagged` / `untagged` instead of embedding the tagged version strings, and `assert_attested` documents that `what` is a shape label so the next test does not reintroduce the flow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Fix ten hosted cargo redirect bugs found by a real-cargo matrix (#255) * fix(redirect): pin and revert each cargo version separately A project that locks two versions of one crate (cfg-if 1.0.4 beside a renamed cfg-if-legacy at 0.1.10) had every same-named declaration pinned to the last patch's registry: the manifest planner matched the crate name only, so `^0.1.10` pointed at a registry that serves 1.0.4 and `cargo fetch --locked` failed. `get <uuid>` for one version broke it the same way. The planner now pins a declaration only when its version requirement selects the patched version. A requirement that also matches another locked version refuses the dep (nothing written); `workspace = true` inheritors of an entry naming another version are skipped. `remove` had the matching defect: manifest edits are keyed by crate name, so removing one version also reverted the other version's pin (leaving its lock entry hosted) and dropped its registry edit from the ledger, so the created `.cargo/config.toml` survived the second removal. Manifest edits whose pin names a sibling version's registry lineage are no longer claimed. Golden: cargo/cargo/multi-version (the depscan TS twin lags; list it in TS_LAGGING on the next submodule bump). Real-cargo regression: e2e_redirect_cargo_shapes multi-version (fresh `cargo fetch --locked` + offline build links both patched copies; removing both purls restores every byte). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): restore a cargo config's bytes on hosted remove `remove` of a hosted cargo patch deleted the `[registries.socket-patch-…]` block it had appended to an existing config but kept the blank separator line it inserted before it, so a legacy `.cargo/config` holding `[net]\nretry = 2\n` came back as `[net]\nretry = 2\n\n`. It also collapsed every run of three newlines anywhere in the file, rewriting the user's own spacing. The block now leaves through the replay path's fragment removal, which takes the block's own separator and nothing else. Real-cargo regression: e2e_redirect_cargo_shapes legacy-config. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): pin cargo workspace members' own declarations Hosted mode pinned only the root Cargo.toml. In a workspace whose member declares the patched crate itself (`cfg-if = "1.0.4"` beside siblings that inherit `[workspace.dependencies]`), the member stayed on crates.io while Cargo.lock was repointed at the patch registry, so `cargo fetch --locked` failed — and the dep was still reported redirected and attested. `scan`/`get --mode hosted` now read every workspace member manifest (`[workspace] members` globs minus `exclude`, plus in-root path dependencies) and the rewriter plans them all in the dep's single transaction: each direct declaration is pinned, `workspace = true` inheritors resolve against the root's entry, and a member that cannot be pinned refuses the dep everywhere. `remove` already reverts edits by path; its registry-block reference probe now covers member manifests too. Golden: cargo/cargo/workspace-member (the depscan TS twin is handed no member manifests and lags; list it in TS_LAGGING on the next submodule bump). Real-cargo regression: e2e_redirect_cargo_shapes workspace-direct-member. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(redirect): attest every cargo shape post-install The real-cargo shape suite now also runs `socket-patch vex` over each fresh checkout (ledger kept, patch server admitted) and requires exactly the shape's patches to be attested — multi-version and workspace-member redirects included. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): redirect CRLF cargo projects in hosted mode Every hosted cargo planner matched LF text, so a Windows checkout with CRLF Cargo.toml / Cargo.lock was refused outright (redirect_cargo_toml_dep_unrewritable / redirect_cargo_lock_pkg_not_found). A manifest, lock or config whose every line break is CRLF is now planned as LF and written back as CRLF, and its recorded edit fragments are stored CRLF so `remove` and rollback find them in the file. Files with mixed endings are left alone and keep refusing where the grammar does not match. The shared fragment remover (used for the appended registry block) inverts CRLF files as LF so it no longer strands a `\r` line. Golden: cargo/cargo/crlf (CRLF manifest, lock and legacy config; the depscan TS twin lags — list it in TS_LAGGING on the next submodule bump). Real-cargo regression: e2e_redirect_cargo_shapes crlf (endings kept, fresh `cargo fetch --locked` + offline build, byte-identical remove). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): say why a transitive-only crate is not redirected A hosted cargo redirect pins a crate with `registry = "…"` on its manifest declaration, which reaches only that declaration: a crate the project gets purely through another dependency cannot be redirected in hosted mode. That stays a refusal — nothing is written, recorded or attested, and a requested VEX fails the run — but the warning said only "no [dependencies] entry for X in Cargo.toml", which reads like a discovery bug. When Cargo.lock resolves the crate at the patched version the warning now says it is a transitive-only dependency, that it was NOT redirected and stays unpatched, and that `scan --mode vendored` (whose `[patch.crates-io]` covers the whole graph) or a direct declaration can patch it. The code (`redirect_cargo_toml_dep_not_found`) is unchanged. Golden: cargo/cargo/transitive-refusal (pins the refusal and its warning code). In-process: the refusal writes nothing, is not attested, and the envelope carries the transitive-only warning. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(redirect): run the cargo shape suite in every lock format The multi-version, workspace-member, legacy-config and CRLF shapes ran only against the toolchain's own v4 lock, so nothing in-repo checked them against a committed v1 lock (full-id dependency edges, checksums in `[metadata]`) or v2/v3. The shape suite now honours SOCKET_PATCH_CARGO_E2E_LOCK_VERSION like the other real-cargo suites and joins the cargo-vex-matrix CI job. The shared lock re-encoder kept only dependency names, which cannot tell two locked versions of one crate apart; it now keeps each edge's package id and writes the shortest form cargo writes for that format (`name`, `name version`, or the full id; always the full id in v1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): refuse a cargo crate other crates depend on A hosted cargo redirect pins a crate with `registry = "…"` on each manifest declaration and repoints its one Cargo.lock entry. When a crates.io (or git) crate in the lock also depends on it — cfg-if, libc and serde usually are both direct and transitive — that edge keeps resolving from crates.io. The repointed lock then fails `cargo fetch --locked`, a plain build adds the crates.io copy back beside the patched one, and the dependent crate compiles the unpatched code, while scan reported the crate redirected and VEX attested it. The same happened for a path package whose manifest the rewriter never saw (outside the project root, or behind a symlink). Before committing a dep, every Cargo.lock package that depends on it (any edge spelling: `name`, `name version`, or the full v1 id) must be a source-less package whose manifest was planned and pinned. Otherwise the dep is skipped with the new additive warning `redirect_cargo_transitive_dependents`, which names the dependents, says the crate was NOT redirected and stays unpatched, and points to `scan --mode vendored`. Nothing is written, recorded or attested. Golden: cargo/cargo/shared-dependency (the depscan TS twin lags — list it in TS_LAGGING on the next submodule bump). Unit: registry, git, v1 full-id and unplanned-path dependents refuse; another version's edge and a planned member do not. Real cargo: e2e_redirect_cargo_shapes direct-and-transitive (cfg-if beside crc32fast) is refused and the untouched project still fetches `--locked`; before this change the same shape reported redirected: 1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): never follow a symlink to a cargo member Workspace member discovery checked the project root only lexically. A literal `members = ["linked"]` entry or a path dependency through a symlinked directory was returned as a rewrite target, and `scan --mode hosted` then wrote the registry pin into a Cargo.toml outside the project (the whole-run symlink guard checks only the file itself, not its parent directories). The same link matched by a `crates/*` glob was skipped instead, because the glob does not follow links. Discovery now returns only manifests reached without crossing a symbolic link: a symlinked member directory (literal or glob), a symlinked path dependency, a symlinked intermediate directory and a symlinked Cargo.toml are all left out. Cargo still reads those manifests, so a crate one of them depends on is refused by the Cargo.lock dependents check (redirect_cargo_transitive_dependents), exactly like a member or path dependency outside the root. Unit: symlinked literal/glob/nested members, a symlinked path dependency and manifest, and out-of-root members/path dependencies are not returned. In-process: an out-of-root path dependency and a glob or literal symlinked member each refuse the crate loudly and leave every file, the one outside the project included, untouched (the literal case reported redirected: 1 and wrote outside the project before). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): remove v1-lock cargo patches in any order A v1 Cargo.lock names every dependency by its full id, so repointing a crate also rewrites its dependents' references. Each dependent's whole `[[package]]` block was recorded as one edit. When one block referenced two patched packages (the root, for two cfg-if versions or any two patched crates it declares) the second edit's `original` was the first edit's `new`, and removing the first-applied purl alone found neither fragment: `remove`, scoped rollback and the hosted-to-vendored takeover refused as "drifted" unless purls went in exact reverse apply order. The dependents' references are now one `redirect_cargo_lock_reference` edit holding just the quoted full id `"name version (source)"`. It names that package exactly, so its inverse puts back every occurrence, independently of any other package's edits; the per-purl revert and the whole-ledger replay both handle it. The v1 whole-block edits were never released, so no existing ledger carries them. Unit: two cfg-if versions and two crates sharing a dependent block each remove in both orders byte-for-byte (both refused as drifted before); a full id named by two members replays clean. Real cargo: e2e_redirect_cargo_shapes now removes every multi-patch shape in apply order and in reverse, from the same post-scan state, and passes with SOCKET_PATCH_CARGO_E2E_LOCK_VERSION=1, 2 and 4. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): restore a cargo config's exact trailing bytes The earlier bug-H fix removed the appended `[registries.…]` block with the shared fragment remover, which collapses the whole trailing newline run at EOF to one newline. That fixed a config ending in exactly one newline but lost a config's trailing blank line (restored byte-for-byte before that change) and still added a newline to a config without one. The block removal now inverts exactly what the rewriter appended: the recorded fragment plus the single blank separator before it. The newline a config without a final one needs rides in the recorded fragment, so that case is exact too; content the user appended after the block is kept; a config the rewrite created still ends empty and is deleted. `remove` and the whole-ledger replay share the helper (replay no longer leaves an emptied created config behind). All-CRLF configs are inverted as LF, and a fragment recorded with the other line endings still matches. Existing ledgers keep reverting as before. Unit: the bug-H revert test covers no final newline (LF and CRLF), trailing blank lines (LF and CRLF) and a whitespace-only config through both `remove` and the replay; helper pins for every append shape. Real cargo: e2e_redirect_cargo_shapes config-unterminated and config-trailing-blank restore the config byte-for-byte. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): remove cargo patches across line-ending changes The CRLF support records a CRLF project's edit fragments with CRLF, and the ledger is JSON, so git line-ending normalization never touches them while it does rewrite the committed Cargo.toml / Cargo.lock / .cargo/config. A hosted redirect scanned on Windows (core.autocrlf) therefore could not be removed on a Linux checkout, nor an LF scan on a CRLF checkout: `remove`, rollback and the vendored takeover matched fragments byte-for-byte and refused as "drifted", and the suggested re-scan was a no-op that kept the same fragments. The cargo per-purl revert and the whole-ledger replay now match fragments regardless of CRLF/LF: an all-CRLF file is matched as LF and written back CRLF, any other file is tried with the recorded fragments and then with their LF forms. The file keeps its current line endings. The registry-block checks and removals use the same matching. Drift of the text itself still refuses exactly as before. Unit: a ledger recorded CRLF reverts LF files and a ledger recorded LF reverts CRLF files, each through `remove` and through the replay, with the ledger round-tripped through JSON (all four refused before). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): remove each cargo version from older ledgers Manifest edits are keyed by crate name, so the per-version revert told two redirected versions' edits apart by the registry each pin names. Ledgers an older CLI wrote with its name-only matcher break that rule: each version's run pinned BOTH declarations to its own registry, and a project it mis-pinned is later repaired by superseding that pin. On such a ledger `remove pkg:cargo/cfg-if@1.0.4` claimed half of a declaration's re-pin chain (or the superseded mis-pin) and refused as "drifted" — for exactly the users bug B had already hurt — and the other removal order dropped a still-pinned registry edit, leaving the created .cargo/config.toml behind. With another version of the crate still redirected, each manifest edit is now attributed to the version its declaration's requirement selects (the planner's own rule); a line without a readable requirement falls back to the registry it pins. A sibling version's registry is never claimed, and a still-referenced block keeps its ledger edit when another recorded wiring edit still pins to it, so the removal that retires that pin removes the block. A block kept for a hand pin still leaves the ledger, and the whole-ledger replay now keeps such a block too instead of deleting it from under the hand pin. Unit: a name-only two-version ledger and a repaired mis-pin ledger each remove in both orders back to the pristine project with no config and an empty ledger (removing 1.0.4 first refused before); the replay keeps a hand-pinned block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): keep identical hosted edits from one scan The hosted flow appends a run's edits to the redirect ledger, skipping any edit already in it. The check also ran against the edits appended earlier in the same run, so identical edits from one rewrite collapsed into one. The cargo rewriter records one edit per occurrence, and a Cargo.toml that declares the crate with the same line in two sections (`[dependencies]` and `[dev-dependencies]`) yields two identical edits. With only one in the ledger, `remove` reverted one pin, kept the other and the registry block it references, and still reported success. Edits are now deduplicated only against the ledger as the run found it, so a re-run still records nothing twice. Real cargo: e2e_redirect_cargo_shapes two-sections (scan, fresh `--locked` build, VEX, byte-identical `remove`); it failed on the leftover pin before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: describe the hosted cargo redirect fixes CLI_CONTRACT.md still said the hosted rewriter reads only root candidate files, but cargo now also reads and pins workspace-member and in-root path-dependency manifests, which can show up in `rewrittenFiles`. The contract now covers that (members globs minus `exclude`, no symlinks, never `.socket/`), the new `redirect_cargo_transitive_dependents` refusal, the transitive-only and multi-version refusals, and CRLF handling across a checkout conversion. CHANGELOG [Unreleased] gains Fixed entries for this branch's user-visible hosted cargo changes, and docs/ecosystems.md notes that hosted cargo reaches direct dependencies only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(redirect): refuse cargo requirements the patch misses A crate declared only with version requirements the patched version does not satisfy resolves those declarations to another version, so a pin cannot reach the locked crate. Hosted cargo now refuses it with redirect_cargo_toml_dep_unrewritable, the code the Socket backend uses for the same shape, instead of reporting it not found. Shared golden fixtures pin both refusals the backend and CLI must agree on: requirement-excludes-patched-version, path-dependency and transitive-dependents-git (a git crate also depending on the patched crate). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(vendor): see a hosted pin in a workspace member The hosted rewriter pins the patched crate in every manifest that declares it, workspace members included, but the guard that refuses to vendor on top of a live hosted redirect read the root Cargo.toml alone. A member-only pin was invisible, so with the redirect ledger gone vendor wired [patch.crates-io] beside a member still resolving the crate from the hosted registry — where [patch.crates-io] does not reach — and reported success. The guard now reads the root manifest and every member manifest the rewriter itself discovers, and the refusal names the file it found. Real cargo: mode_migration_cargo's takeover refusal gains the shape the guard exists for — the lock restored from version control while a table-form manifest pin survives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(redirect): repoint a v1 lock's [root] table A v1 Cargo.lock written before cargo dropped [root] keeps the root package in that table, whose dependencies spell full package ids. The reference rewrite walked the [[package]] array only, so a redirected crate left [root] naming the crates.io id of a package the lock no longer held: cargo build --locked then fails, an unlocked build discards the lock and re-resolves, and the scan reported the crate redirected either way. The walk now covers that table too, recorded by the same every-occurrence reference edit, so remove still restores the lock byte for byte in any order. The vendored backend has read [root] since it learned v1 locks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(redirect): spend one cargo edit per occurrence One scan records one cargo edit per occurrence, so a manifest that declares the crate with the same line in two sections leaves two identical edits in the ledger, and the per-purl revert unwinds one occurrence per edit. The whole-ledger replay — the path a run whose patch-record fetch failed leaves behind, records empty — instead refused as soon as the fragment appeared more than once, and that refusal dropped every other cargo edit with it: the project stayed fully redirected, with a suggested remedy (re-run the scan) that cannot change a duplication the manifest itself carries. The replay now allows as many occurrences as there are identical edits left to spend on them, and still refuses a surplus no edit accounts for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(redirect): never pin a manifest under target/ Member discovery skipped cargo's build directory only while expanding a glob: a literal `members = ["target/gen"]` entry, or a path dependency pointing into target/, was returned and pinned — which the key predicate's own doc comment said could not happen. The next `cargo clean` deletes that manifest, and a recorded hosted edit whose file no longer exists refuses the rollback of every other cargo edit. A `target` segment is now excluded wherever a member path is accepted, in discovery and in the key predicate both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(redirect): refuse a lockless cargo pin A `registry = …` pin reaches only the declarations it sits on, so a crate another package also pulls in is refused — but that check reads Cargo.lock, and a project without one was pinned unconditionally. Rust libraries routinely gitignore their lock, so the silent shape was the common one: the scan reported the crate redirected, vex attested it, and the next build resolved the patched copy for the root and an unpatched crates.io copy for whatever else depends on the crate. Without a lock the dependents question is now answered from the manifests instead: a crate declared beside any other dependency — or beside a workspace member this run could not read (a members glob, or a member outside the project or behind a symbolic link) — is refused `redirect_cargo_lockless_dependents`, whose detail names both remedies. A path dependency on a manifest the same run pins is not company, and a project whose only dependency is the patched crate has nothing that could pull it in and still redirects. Real cargo: e2e_redirect_cargo_shapes lockless-other-dependencies, the direct-and-transitive project with its lock deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 1bfe532 commit 2390c5f

107 files changed

Lines changed: 18137 additions & 2889 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1455,9 +1455,45 @@ jobs:
14551455
SOCKET_PATCH_CARGO_E2E_LOCK_VERSION: ${{ matrix.lock }}
14561456
run: |
14571457
set -euo pipefail
1458-
cargo test -p socket-patch-cli --test e2e_redirect_cargo_build --test e2e_vendor_cargo_build --test mode_migration_cargo
1458+
cargo test -p socket-patch-cli --test e2e_redirect_cargo_build --test e2e_redirect_cargo_shapes --test e2e_vendor_cargo_build --test mode_migration_cargo
14591459
cargo test -p socket-patch-cli --test e2e_safety_cargo_build -- --ignored
14601460
1461+
# Manifest `[patch]` + the tagged detached lock (the v5 vendored cargo
1462+
# wiring) on cargo 1.41 and 1.56 — below / at the 1.56 floor of
1463+
# config-file `[patch]`. The old-toolchain tests prefer the local
1464+
# `rust:1.41-slim` / `rust:1.56-slim` docker images (run `--network none`;
1465+
# they build AND run the consumer) and skip when no old cargo is
1466+
# available; this leg pulls both images and requires them.
1467+
cargo-old-toolchains:
1468+
name: cargo old toolchains (manifest [patch])
1469+
needs: test
1470+
runs-on: ubuntu-latest
1471+
timeout-minutes: 30
1472+
steps:
1473+
- name: Checkout
1474+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1475+
with:
1476+
persist-credentials: false
1477+
- name: Install Rust
1478+
run: rustup show
1479+
- name: Pull the old cargos under test
1480+
run: |
1481+
docker pull rust:1.41-slim
1482+
docker pull rust:1.56-slim
1483+
- name: Cache cargo
1484+
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
1485+
with:
1486+
key: cargo-old-toolchains
1487+
save-if: ${{ github.ref == 'refs/heads/main' }}
1488+
- name: Vendored manifest [patch] on old cargo
1489+
shell: bash
1490+
env:
1491+
SOCKET_PATCH_CARGO_E2E_REQUIRED: '1'
1492+
SOCKET_PATCH_CARGO_OLD_TOOLCHAINS_REQUIRED: '1'
1493+
run: |
1494+
set -euo pipefail
1495+
cargo test -p socket-patch-cli --test e2e_vendor_cargo_build -- old_toolchain --nocapture
1496+
14611497
# ----------------------------------------------------------------------
14621498
# Experimental `setup`-flow matrix (NON-BLOCKING).
14631499
#

‎CHANGELOG.md‎

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,126 @@ into the new version's section — see docs/releasing.md.
1919

2020
> **Semver note:** this entry changes `rollback`'s default behavior, narrows
2121
> the meaning of its existing `vendored: []` JSON key, makes vendored mode
22-
> manifest-free, turns a plain non-TTY `scan` report-only, and makes `vex`
22+
> manifest-free, moves vendored cargo wiring from `.cargo/config*` into
23+
> `Cargo.toml`, tags vendored cargo copies' versions with `+socket.<uuid>`
24+
> (visible to the patched crate as `CARGO_PKG_VERSION`), turns a plain
25+
> non-TTY `scan` report-only, and makes `vex`
2326
> refuse to attest stale ledger records and corrupt vendor ledgers — all
2427
> MAJOR per CLI_CONTRACT.md's semver policy — so it ships as the next major
2528
> release (v5.0).
2629
2730
### Changed (BREAKING)
2831

32+
- **Vendored cargo copies carry a tagged version: `<version>+socket.<uuid>`.**
33+
The vendored copy's own `Cargo.toml` `[package] version` is rewritten to
34+
the patch-tagged version (`1.0.4+socket.<uuid>`; a version that already
35+
has build metadata keeps it: `2.0.1+zstd.1.5.2.socket.<uuid>`), and the
36+
detached `Cargo.lock` entry records that tagged version with no
37+
`source` / `checksum` — exactly the lock cargo itself writes when it
38+
resolves the `[patch]` against the tagged copy (verified by building on
39+
cargo 1.41 in docker and on current stable, and by the CI
40+
`cargo-old-toolchains` leg on the 1.41 / 1.56 docker images — a local
41+
run without those images only type-checks on rustup toolchains:
42+
`--locked` builds, the patched bytes compile, a registry crate that
43+
depends on the patched one (`^1`) resolves to the copy too, since cargo
44+
ignores build metadata when matching requirements, and `cargo metadata`
45+
reports the tagged version). Every lock reference that spells the old
46+
version (`"cfg-if 1.0.4"`, v1's `"cfg-if 1.0.4 (registry+…)"`) is
47+
rewritten to the tagged version, in lock formats v1–v4; a lock the edit
48+
cannot keep consistent (a leftover reference in another spelling, a v1
49+
`replace`, an entry already at the tagged version) refuses before any
50+
write with `cargo_lock_untaggable`, and a copy manifest whose version
51+
literal cannot be rewritten byte-exactly fails the package with
52+
`cargo_copy_untaggable`. The patch uuid of the copy cargo actually
53+
builds is therefore recoverable from `Cargo.lock` alone (a config-level
54+
`[patch]` override pointing elsewhere changes the locked version). **The
55+
patched crate sees the tag in `CARGO_PKG_VERSION`** (and in
56+
`env!("CARGO_PKG_VERSION")`-derived strings such as `--version` output
57+
of a vendored binary crate): requirement matching on it
58+
(`semver::VersionReq::matches`) is unaffected, but string comparisons
59+
AND equality / ordering on a parsed `semver::Version` see the tag
60+
(`semver` 1.x compares build metadata: `1.0.4+socket.<uuid>` is not
61+
`== Version::new(1, 0, 4)` and sorts above it). Re-runs are idempotent
62+
(a dry run previews the tag as "would tag", and the wet run's
63+
`cargo_lock_untaggable` / `cargo_copy_untaggable` refusals); a uuid bump
64+
re-tags the copy and the lock; `vendor --revert` / `rollback` /
65+
`remove` / GC / the hosted takeover restore the original lock byte for
66+
byte (tag dropped with the `source` / `checksum`; a crate vendored
67+
before any `Cargo.lock` existed has no originals, so only the tag its
68+
first build locked is dropped). A user's own same-version path crate
69+
that cargo later locks beside the tagged copy (an untagged sourceless
70+
entry) is never mistaken for it: re-runs stay in sync, and the revert
71+
restores the registry entry — spelled by its full id while the fork
72+
shares its name+version, exactly as cargo writes it — without touching
73+
the fork. GC keeps an entry whose lock tag is stale (another uuid) while
74+
the manifest still wires this entry's copy: cargo re-locks any unlocked
75+
build to the wired copy, and the next re-run retags. Projects vendored
76+
before tagged versions (the pre-v5 `.cargo/config*` wiring, or an
77+
untagged manifest wiring) are tagged by the next re-run or `repair`
78+
(`cargo_version_tagged` note; a tag `repair` cannot write is the
79+
`cargo_version_untagged` warning); a whole-tree file inventory recorded
80+
for the copy is kept, and still verifies through exactly this uuid's
81+
tag, so tagging never re-baselines it over unverified bytes. VEX
82+
discovery treats the tagged lock version as the primary identity
83+
(`pkg:cargo/<name>@<version>` is the tag stripped): a detached entry
84+
tagged for a different uuid than the wiring's copy path is dead wiring
85+
(not attested), and so is a copy whose own `Cargo.toml` is tagged for
86+
another uuid than its path; a tagged entry no visible wiring names is
87+
diagnosed unattributable; an untagged detached entry counts only beside
88+
an untagged copy (the pre-tag vendored shape) — beside a tagged copy it
89+
is some other crate cargo built, not attested. A patch that edits the
90+
crate's own `Cargo.toml` verifies with the tag dropped — the tag being
91+
this copy's own uuid, the same pin the inventory check applies, so a copy
92+
tagged for another patch stays a mismatch instead of verifying clean
93+
while VEX refuses it.
94+
- **Vendored cargo wiring moved to `Cargo.toml`.** `vendor` / `scan` /
95+
`get --mode vendored` write the `[patch.crates-io]` path entry into the
96+
workspace-root `Cargo.toml` (beside the `Cargo.lock` it detaches) instead
97+
of `.cargo/config.toml` / `.cargo/config`, so Socket scanners can recover
98+
the patch uuid from the manifest alone and single-version wiring builds
99+
on cargo older than 1.56 (the floor of config-file `[patch]`; proven on
100+
cargo 1.41 with no network). TWO vendored versions of one crate need
101+
cargo 1.45 or newer: from 1.45 `--offline` from an empty `$CARGO_HOME` is
102+
enough (without `--offline` it first tries to update the crates.io index
103+
and fails when that is unreachable), while cargo before 1.45 resolves
104+
every source-less lock entry for a crate through one `[patch]` path and
105+
fails closed on the other — see the `cargo_multi_version_old_cargo`
106+
entry under Fixed. The edit is
107+
format-preserving (comments, ordering, CRLF / mixed line endings, a
108+
UTF-8 BOM and the trailing-newline state survive; a revert restores the
109+
manifest byte for byte and keeps a user's own `[patch]` /
110+
`[patch.crates-io]` headers). The key is always the Socket-owned
111+
`<name>-socket-<uuid8>` with `package = "<name>"`, never the bare crate
112+
name: cargo lets a config-file `[patch]` item (project, ancestor
113+
directory or `$CARGO_HOME`) replace the manifest item with the same key
114+
whatever its version, so a crate-named key could be silently shadowed —
115+
and two vendored versions of one crate get distinct keys instead of
116+
clobbering each other (the config wiring keyed by crate name let the
117+
second overwrite the first). The ledger's `cargo_patch_entry` record now
118+
names `Cargo.toml` (its `key` is the TOML key). New refusals, each before
119+
any write: `cargo_manifest_unreadable`, `cargo_manifest_unparseable`,
120+
`cargo_manifest_symlink_unsupported` (vendor and revert),
121+
`cargo_manifest_not_workspace_root` (run from a workspace member, whose
122+
`[patch]` cargo ignores) and `cargo_manifest_patch_source_alias` (the
123+
manifest spells crates.io by URL in `[patch."https://github.com/rust-lang/crates.io-index"]`,
124+
which replaces `[patch.crates-io]` wholesale);
125+
`user_authored_patch_entry` now covers user entries in `Cargo.toml` and
126+
in every cargo config file cargo merges (project, ancestors,
127+
`$CARGO_HOME`) and matches by crate (`package` or key), sparing a path
128+
patch that is provably another version. **Old wiring migrates
129+
automatically**: a re-run or `repair` moves a Socket-owned
130+
`.cargo/config*` entry into `Cargo.toml` (`cargo_wiring_migrated` note; a
131+
migrating vendor re-run reports the package `applied`) and cleans a
132+
config file / `.cargo/` the move emptied — a legacy entry that cannot be
133+
removed fails the run and unwinds it (`cargo_legacy_wiring_kept`) — while
134+
`rollback` / `remove` / `vendor --revert` / GC / hosted takeover remove
135+
both spellings. Projects hit by the pre-v5 multi-version overwrite (a
136+
detached lock entry nothing wired) are healed by a re-run or `repair`
137+
(`cargo_wiring_restored`). User config entries are never touched. VEX
138+
discovery reads the manifest first (key-agnostic), skips a manifest entry
139+
that cargo ignores (a same-key project-config item or a URL-spelled
140+
crates.io table replaces it), and still honors pre-v5 config wiring. The
141+
hosted takeover's missing-ledger guard is now per version.
29142
- **Binary Bun lockfiles are patched natively in place.** Hosted and vendored
30143
modes read and rewrite `bun.lockb` formats 1–3 directly, including mode
31144
changes, repair, and scoped rollback. Binary-to-text conversion, migration
@@ -614,6 +727,46 @@ into the new version's section — see docs/releasing.md.
614727
`setup --remove` could not land byte-identical on the pre-setup file.
615728
`package.json` is now written in its own layout (BOM, indent, line ending,
616729
trailing-newline shape), the same helper the vendored backends use.
730+
- **Two vendored versions of one cargo crate are documented — and now
731+
warned about — as needing cargo 1.45.** The docs said older cargo (1.41)
732+
only needed a populated crates.io index. It needs more than that: cargo
733+
before 1.45 resolves every source-less `Cargo.lock` entry for a crate
734+
through ONE `[patch.crates-io]` path — the entry whose KEY sorts last —
735+
so one of the two versions is pinned to the other's copy and `cargo build
736+
--locked` fails closed with ``patch for `<crate>` … did not resolve to
737+
any crates``, index or no index. The old-toolchain e2e passed only
738+
because its fixture uuids happened to sort the other way; it now uses the
739+
adversarial order, and the floor was measured rather than assumed — on
740+
one two-version fixture in both key orders, 1.41.1, 1.42, 1.43 and 1.44
741+
refuse the adversarial order while 1.45, 1.49, 1.53, 1.56 and current
742+
stable resolve either order, each lock entry to its own copy. Vendoring a
743+
second version of a crate warns with `cargo_multi_version_old_cargo`
744+
unless the project's `rust-version` or `rust-toolchain[.toml]` promises
745+
cargo 1.45 or newer (socket-patch never runs `cargo`, so those files are
746+
the only signal it has). A SINGLE vendored version still builds on cargo
747+
1.41, as before.
748+
- **A CRLF `Cargo.lock` stays CRLF, and reverts byte-for-byte.** Vendoring
749+
rewrote every line of a lock committed with Windows line endings as LF
750+
(`toml_edit` renders LF only), and `vendor --revert` then "restored" the
751+
all-LF file — a whole-file diff on a Windows checkout and a rollback that
752+
was not byte-identical. The lock edits (detach, retag, restore) now map
753+
the rendering back onto the file's own line endings, the way the copy's
754+
`Cargo.toml` already did, in lock formats v1–v4: a CRLF lock stays CRLF,
755+
a missing trailing newline stays missing, and every line a mixed-ending
756+
lock's edit leaves alone keeps its own ending.
757+
- **Vendored mode can patch a package whose version carries build
758+
metadata.** The patches API serves canonical PURLs, so a semver build
759+
metadata version arrives percent-encoded
760+
(`pkg:cargo/wasi@0.11.0%2Bwasi-snapshot-preview1`). The PURL parsers
761+
compared that raw spelling against the lockfile / install directory's
762+
`0.11.0+wasi-snapshot-preview1`, never matched, and refused the package
763+
(`vendor_fetched_missing`, then `locked_version_mismatch`) — so no cargo
764+
crate with build metadata (`wasi` is in most Rust dependency graphs)
765+
could be vendored at all. Every ecosystem's PURL parse now
766+
percent-decodes the namespace, name and version once, after the
767+
`/`-and-`@` split and before the path-safety guards, so an escaped
768+
separator still cannot introduce a path segment. Hosted mode was already
769+
correct.
617770
- **A vendoring-service outage no longer re-vendors packages.** An npm
618771
re-run (every lock flavor, `bun.lockb` included) re-acquired its tarball
619772
from whichever source answered — the service's prebuilt, or a local pack
@@ -814,6 +967,41 @@ into the new version's section — see docs/releasing.md.
814967
vendored rewrites now follow the `[metadata]` checksum table and rewrite
815968
dependents' full-id references, so `cargo --locked` accepts the lock (and
816969
the revert stays byte-identical).
970+
- **Hosted cargo pins every declaration of the patched version.** Each
971+
version of a multi-version crate is pinned only in the declarations whose
972+
requirement selects it (a requirement matching several locked versions is
973+
refused `redirect_cargo_toml_dep_unrewritable`), and workspace-member and
974+
in-root path-dependency manifests are pinned beside the root, so
975+
`cargo --locked` accepts the redirected lock. Member discovery never
976+
follows a symbolic link, so nothing outside the project is rewritten.
977+
- **Hosted cargo refuses crates a pin cannot reach.** A crate another
978+
`Cargo.lock` package also depends on (a crates.io or git crate, or a path
979+
package outside the project) now warns
980+
`redirect_cargo_transitive_dependents` and is skipped instead of being
981+
reported redirected while that package compiled the unpatched copy; a
982+
transitive-only crate's `redirect_cargo_toml_dep_not_found` detail now
983+
says so and points to `--mode vendored`. A crate declared only with
984+
requirements the patched version does not satisfy (cargo resolves those
985+
declarations to another version) is refused
986+
`redirect_cargo_toml_dep_unrewritable`, the Socket backend's code for the
987+
same shape, instead of `redirect_cargo_toml_dep_not_found`. A project with
988+
NO `Cargo.lock` has no resolved graph to ask, so a crate declared beside
989+
any other dependency — anything but a path dependency on a manifest the
990+
same run pins — or beside a workspace member this run did not read (a
991+
glob, a member outside the project or behind a symbolic link) is refused
992+
`redirect_cargo_lockless_dependents` (commit a lockfile, or use `--mode
993+
vendored`); a project whose only dependency is the patched crate has
994+
nothing that could pull it in and still redirects.
995+
- **CRLF cargo projects redirect in hosted mode.** All-CRLF `Cargo.toml`,
996+
`Cargo.lock` and cargo configs are rewritten with their endings kept
997+
(they were refused), and `remove` / rollback still find the recorded
998+
edits after a checkout converts the line endings.
999+
- **Hosted cargo `remove` restores every byte, in any order.** An appended
1000+
registry block leaves the user's config exactly as it was (trailing blank
1001+
lines or a missing final newline included) and a created config is
1002+
deleted with the last block; v1-lock and multi-version patches, ledgers
1003+
written by older CLIs included, can be removed in any order; and a crate
1004+
declared with the same line in two sections gets both pins reverted.
8171005
- **yarn 4.0.x checksums keep the lock's own spelling.** Vendored and hosted
8181006
berry rewrites write bare-hex `cacheKey: 10c0` checksums when the lock
8191007
does, so `yarn install --immutable` no longer fails with YN0028.

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ Behavior worth knowing:
12461246
| pnpm | `pnpm-lock.yaml` (all generations), `shrinkwrap.yaml` (pnpm 1/2), Rush locks | Aliased / nested `resolution` shapes are diagnosed, not attested; `overrides` alone prove nothing |
12471247
| yarn | `yarn.lock` (classic + berry) | Berry vendored entries also need the root `package.json` `resolutions` mapping; member locks are not read |
12481248
| bun | `bun.lock`, else `bun.lockb` | A hosted entry that Bun < 1.3.10 re-saved without its sha512 attests only after install |
1249-
| cargo | `Cargo.lock`, `Cargo.toml`, `.cargo/config[.toml]` | Root manifest + project config only (no `$CARGO_HOME` / parent configs); a lockless hosted pin needs the redirect ledger's record |
1249+
| cargo | `Cargo.lock`, `Cargo.toml`, `.cargo/config[.toml]` | Root manifest + project config only (no `$CARGO_HOME` / parent configs); vendored `[patch.crates-io]` entries are read from `Cargo.toml` first (v5), the project config for pre-v5 projects, and must agree with the detached lock entry's tagged version `<version>+socket.<uuid>` (a tag for another uuid — in the lock or in the copy's own `Cargo.toml` — is dead wiring; an untagged detached entry counts only beside an untagged, pre-tag copy); a manifest entry cargo ignores (a same-key project-config item, or a URL-spelled crates.io `[patch]` table) is not attested; a lockless hosted pin needs the redirect ledger's record |
12501250
| golang | `go.mod`, `go.work`, `go.sum`, `go.work.sum` | A replace that `require` no longer selects is inert; `vendor/modules.txt` is not read |
12511251
| pypi | `uv.lock`, `*.py.lock`, `pylock*.toml`, `poetry.lock`, `pdm.lock`, `Pipfile.lock`, `requirements.txt` (+ `-r` includes), `pyproject.toml` / `hatch.toml` | A `uv.lock` beside a `pyproject.toml` must agree with its `[tool.uv.sources]`; PDM 3.1 / 4.0–4.2 locks are refused; a Pipenv project needs `--product` (or a git remote) |
12521252
| gem | `Gemfile.lock`, `gems.locked` | Platform gems unsupported; a Gemfile-only (pre-bundler-2.6, not yet locked) wiring needs the redirect ledger |

0 commit comments

Comments
 (0)