fix(cargo): fully migrate hosted<->vendored modes (C1-C7) - #196
Open
Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
Open
fix(cargo): fully migrate hosted<->vendored modes (C1-C7)#196Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
Conversation
A cargo mode takeover used to exit 0 while leaving the project unbuildable in both modes (audit findings C1-C7): - hosted scan over a vendored crate left the vendored [patch.crates-io] entry behind, so every `cargo build --locked` failed (C1); - vendor over a hosted redirect left the Cargo.toml registry = "socket-patch-..." pin, which [patch.crates-io] cannot apply over - unbuildable online and offline (C2), silently via the plain `vendor` command (C7); - a vendored->hosted->vendored round trip overwrote the unrecoverable crates.io lock originals in the vendored ledger with the hosted sparse-index URL + patched checksum, so revert restored a dead grant-tokenized URL (C3); - the takeover classifier could never prove hosted for cargo (lock-inventory resolved is None), so it INVERTED the direction and told users to delete the LIVE redirect ledger (C4/C4b); - the displaced vendored entry was unreclaimable by GC/prune forever (no cargo in-use probe, C5); - the remediation text offered deleting the .socket/vendor/<eco>/ tree while [patch.crates-io] still referenced it - hard resolution failure on every cargo command (C6). A takeover now leaves the project FULLY in the new mode, or refuses: - hosted scan reverts each claimed purl's vendored state per purl (lock originals restored, [patch] entry dropped, tree + ledger entry removed) before redirecting, and refuses the purl when the vendored ledger is missing/corrupt; - every vendored flow (vendor, scan --mode vendored) reverts the purl's hosted edits from the redirect ledger FileEdits first (Cargo.toml pin, Cargo.lock source/checksum, registries block) and drops the ledger record; drifted files refuse fail-closed, and the cargo backend refuses (hosted_redirect_live) when hosted wiring is live with no ledger to revert it; - the classifier gains a cargo lock-shape probe (socket-patch registry index vs detached-with-[patch]), so the direction follows the lock; - persist_vendor_entry carries the cargo lock originals forward on re-vendor, keeping the crates.io fragment as the ledger's only home; - dispatch_in_use_one gains the cargo probe so GC/prune reclaims genuinely stale vendored entries; - both takeover remediation texts now recommend only the safe sequence (vendor --revert / re-run the vendored flow) and warn against hand-deleting ledgers or the vendor tree. Red-then-green: the new real-cargo migration suite (tests/mode_migration_cargo.rs, adapted from the audit probes) fails all four scenarios on the previous code and passes with this change, proving fresh-checkout `cargo build --locked` in the terminal state of every migration direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8e784ca. Configure here.
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Aug 14, 2026
…er-cleanup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-cleanup Resolves the cargo_lock.rs conflict: both sides appended an independent read-only probe to the same spot. Keeps this branch's LockEntryProbe/probe_lock_entry (which mode a crate's lock entry points at, for the takeover logic) alongside main's count_lock_entries from the vendored fail-closed audit (#194). Assisted-by: Claude Code:claude-opus-5
…er-cleanup Picks up #195 (composer redirect confirmation/pinning), which landed on main while this branch was being reconciled. No overlap with the cargo takeover work; merged clean. Assisted-by: Claude Code:claude-opus-5
…er-cleanup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…v/socket-patch into fix/cargo-mode-takeover-cleanup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#174 extracted the re-vendor reconciliation out of the CLI's persist_vendor_entry into vendor::carry_forward_wiring and extended it (wiring union for untouched surfaces, pnpm created-surface merge). This branch had added the cargo lock originals carry-forward to the same inline block, so both sides rewrote it. Resolved by keeping main's extraction and moving this branch's cargo carry-forward into carry_forward_wiring beside the other unconditional, identity-matched reconciliations: a re-vendor over already-detached wiring records lock: None, and the crates.io source/checksum pair it replaced is unrecoverable offline, so the prior entry is its only home. Placed ahead of the same-uuid early return because that loss happens on a new-uuid re-vendor too. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
revert_cargo_redirect_purl unwound the ledger's edits newest-first and
wrote each inverse to disk as it went, so a later drifted edit returned
Err with the earlier ones already written. Reverting a hosted redirect
whose Cargo.toml a third party had re-pinned restored Cargo.lock to
crates.io and then refused on the manifest, leaving the crate pinned to
the socket-patch registry with a lock that no longer resolves there --
a project that builds in neither mode. The ledger still recorded the
redirect as live and the caller reported the package as untouched
("cannot vendor over the live hosted redirect"), so nothing pointed at
the half-reverted files and every retry refused on the same drift.
Each inverse is now resolved against a staged view of the files and
nothing is written until all of them resolve. Reads go through the
staged map, which keeps the two orderings the unwind depends on: a
re-redirect chain still sees the previous step's result, and the
registry block still sees the wiring reverts when it probes whether
anything references it. Only an I/O fault during the final flush can
still stop mid-set, and that surfaces as Err naming the file.
The new unit test drifts only Cargo.toml, which unwinds after
Cargo.lock, and asserts all three files are byte-identical after the
refusal; it fails on the previous code with Cargo.lock already
rewritten.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…er-cleanup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refines the scan/mod.rs conflict resolution in the preceding merge of #187 and #191, which crossed with this branch's takeover work. The vendored-direction remediation had taken #191's text verbatim, which tells the operator to hand-edit .socket/vendor/redirect-state.json and delete each superseded package's `records` entry plus its matching `edits`. #191 could not have known this branch makes that automatic: re-running `socket-patch vendor` (or `scan --mode vendored`) now reverts the stale hosted edits from the ledger and drops both halves of the entry itself. The text leads with that and keeps #191's by-hand procedure as the fallback, along with its records-AND-edits reasoning and its never-delete-the-ledger-file warning. Restores the comment explaining why the ledger is never offered up for hand deletion. The hosted direction keeps #191's per-package `remove <purl>` guidance; its comment now also records why `vendor --revert` is not offered -- it unwinds every vendored package, including ones still live in the lockfile. Both sides' tests pass together: #191's following_the_vendored_remediation_clears_the_warning and hosted_remediation_states_removes_full_blast_radius alongside this branch's cargo classifier and GC reclaim tests. Co-authored-by: Claude Fable 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.

What
Makes a cargo mode migration (vendored ↔ hosted) a real takeover: the flow reverts the displaced mode's wiring per purl BEFORE applying the new mode, so the project ends FULLY in the new mode — or the purl is refused with an actionable error. Fixes audit findings C1–C7.
run_redirectnow runs the exact per-purlvendor --revertmachinery first (restore the Cargo.lock originals from the vendored ledger, drop the[patch.crates-io]entry, remove the committed tree and the ledger entry), persisting the ledger per purl, and only then redirects. A purl whose vendored state cannot be cleanly reverted (revert failure, ledger unreadable/unwritable) is refused — skipped withredirect_vendored_revert_failed— never half-migrated. Dry runs preview the takeover withredirect_would_revert_vendored.vendorcommand andscan --mode vendorednow revert the purl's hosted edits from the redirect ledger's recordedFileEdits first — Cargo.toml loses itsregistry = "socket-patch-…"pin, Cargo.lock gets the original crates.iosource/checksumback, the now-unused[registries.socket-patch-…]block is dropped — then drop the ledger record and vendor. New core modulepatch/redirect/takeover.rs(revert_cargo_redirect_purl) unwinds re-redirect chains newest-first and fails closed on drifted files (neither the recorded redirected fragment nor the original matches ⇒Err⇒ the purl is refused withredirect_revert_failed). The plainvendorcommand also emits the samevendor_supersedes_redirectenvelope advisory the scan flow does (C7).vendor_cargo_crateitself refuses (hosted_redirect_live) when live hosted wiring is present with no redirect-ledger record to revert it — with manual remediation steps — instead of baking the hosted registry values into the vendor ledger as "originals".persist_vendor_entrycarries the cargo lock originals forward on re-vendor (entry.lockwas previously dropped whenever the re-detach found nothing to detach), so an A→B→A round trip can no longer overwrite the ledger's only copy of the unrecoverable crates.io source/checksum, andvendor --revertrestores the true pre-vendor lock.classify_overlap_takeovergains a cargo-specific probe (classify_cargo_overlap) that reads the Cargo.lock entry's actual shape —sourcematching a config-declared[registries.socket-patch-*]sparse index (orpatch.socket.dev) proves hosted; a detached entry with[patch.crates-io]pointing into the entry's committed.socket/vendor/cargo/<uuid>/copy proves vendored; anything else stays silent. The previous generic probe could never prove hosted for cargo (lock_inventoryrecordsresolved: None), so it inverted the direction and told users to delete the live redirect ledger.dispatch_in_use_onegains the cargo probe (vendored_entry_in_use): a detached lock entry routed through the entry's[patch]copy is in use; a registry-sourced or missing entry is reclaimable, so a vendored entry displaced by a hosted takeover no longer survives everyscan --pruneforever.vendor --revert/ re-run the vendored flow) and explicitly warn against hand-deleting.socket/vendor/(the[patch.crates-io]entry may still reference it — deleting it hard-fails every cargo command) orredirect-state.json(it may hold the only revert data and VEX records for other still-redirected packages).Why
Before this change, every cargo mode migration exited 0 while leaving the project broken — the exact fail-open bug class the audit flagged:
[patch]entry, so every CIcargo build --lockedfails (C1) — withwarnings: []because the classifier could not see hosted-liveness for cargo (C4);[patch.crates-io]cannot patch over — unbuildable online and offline (C2), and completely silent via the primaryvendorentry point (C7);Testing
New real-binary + real-cargo migration suite
crates/socket-patch-cli/tests/mode_migration_cargo.rs(adapted from the audit probes inmode_migration_cargo_audit.rs), red on the pre-fix code and green here. Each scenario proves the terminal state with a fresh-checkoutcargo build --locked:vendored_then_hosted_takeover_leaves_pure_hosted— vendored state fully gone (no[patch], no tree, no ledger entry), redirect live, fresh checkout builds--locked(C1/C4);hosted_then_vendored_takeover_leaves_pure_vendored— registry pin and[registries]block gone, redirect record dropped, fresh checkout builds--locked --offline(C2/C7);double_takeover_a_b_a_preserves_lock_originals— ledger keeps the crates.io fragment through A→B→A, andvendor --revertrestores the pristine lock byte-identically (C3);vendor_over_hosted_without_ledger_is_refused— live hosted wiring with a deleted ledger: vendor refuses (hosted_redirect_live), project untouched (fail-closed).Plus a GC unit test (
vendor_gc_reclaims_cargo_entry_displaced_by_hosted_takeover, C5) and classifier/remediation coverage in the scan tests.Gates run locally, all green:
cargo test -p socket-patch-cli --all-features --test mode_migration_cargo(4/4, no skips)cargo test -p socket-patch-cli --all-features --test in_process_redirect --test rollback_invariants --test repair_invariants --test e2e_vendor_cargo_buildcargo clippy --workspace --all-features -- -D warningscargo test --workspace --all-featuresNote
High Risk
Changes critical cargo wiring, lockfile originals, and ledger persistence across hosted/vendored migrations; incorrect revert or classifier logic could leave projects unbuildable or destroy unrecoverable lock state.
Overview
Cargo hosted ↔ vendored mode switches are now takeovers: the displaced wiring is reverted per package before applying the new mode, so the repo ends fully in one mode—or that package is refused with an actionable error instead of reporting success on a broken
--lockedbuild.Vendored → hosted:
scan --mode hostedruns the same per-purl revert asvendor --revert(lock originals,[patch.crates-io], committed tree, ledger) before redirecting; corrupt/missing vendored ledger with live wiring is skipped.Hosted → vendored:
vendorand vendored scan call newrevert_cargo_redirect_purlto unwind hosted edits from the redirect ledger, then vendor; drift or missing ledger refuses the purl (hosted_redirect_livein the cargo backend).Classifier & GC: Cargo overlap direction uses
Cargo.lockshape (sparse index vs detached + patch path), not genericpatch.socket.devresolution; cargo vendored entries displaced by hosted wiring are reclaimable on prune/GC.Originals chain: Re-vendor carries forward cargo lock originals in the ledger; remediation text warns against hand-deleting vendor trees or
redirect-state.json.New e2e
mode_migration_cargo.rsproves fresh-checkoutcargo build --lockedafter each scenario.Reviewed by Cursor Bugbot for commit 8e784ca. Configure here.