fix(cargo): fully migrate hosted<->vendored modes (C1-C7) - #196
fix(cargo): fully migrate hosted<->vendored modes (C1-C7)#196Mikola Lysenko (mikolalysenko) wants to merge 1 commit 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.
| or restore the crates.io wiring manually, then re-run", | ||
| edit.path | ||
| )); | ||
| } |
There was a problem hiding this comment.
Partial revert before fail-closed
Medium Severity
revert_cargo_redirect_purl writes each inverse immediately while unwinding newest-first, then returns Err on a later drifted edit without rolling those writes back. The ledger stays unchanged, so disk can be half-reverted (for example Cargo.lock restored while Cargo.toml still pinned) while the caller refuses the purl as if nothing was touched. That breaks the fail-closed takeover contract and can leave an inconsistent hosted state that retries keep failing on.
Reviewed by Cursor Bugbot for commit 8e784ca. Configure here.


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.