Repository navigation
Fix PyPI hosted takeover un-vendoring before refusal (#723, #945) - #946
Mikola Lysenko (mikolalysenko) merged 14 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Add regression tests for the vendored -> hosted takeover of a uv project whose lock resolved another version than the patch (#723, direct and transitive) and of a Poetry 0.12 lock (#945). Each case runs wet and --dry-run and expects the takeover to be refused before the revert, keeping the vendored patch. On main they fail: the wet run leaves the package unpatched and the dry run reports a clean takeover. Assisted-by: Claude Code:claude-opus-5-5
`scan --mode hosted` over a vendored uv or Poetry project reverted the vendored wiring first and only then asked the hosted rewriter to pin the package. When the rewriter could not, the package ended up in neither mode and the next install got the unpatched release, while --dry-run promised a clean takeover. The PyPI takeover gate now checks the hosted rewriter's reach before the revert, wet and dry run alike, and keeps the package vendored: - uv: vendored mode pins the lock entry down to the patch's version. If the recorded pre-vendor entry is at another version, the revert brings it back and hosted mode can't pin it. Refused with redirect_uv_takeover_version_unreachable (#723). - Poetry: hosted mode refuses every Poetry 0.x lock. Refused with redirect_poetry_lock_unsupported (#945). The requirements.txt check moves into the same core preflight. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The uv and Poetry takeover refusals told the user to re-lock while the package was still vendored. That can't clear the uv refusal (the recorded pre-vendor entry never changes) and makes the later revert see drift. Both remedies now start with `socket-patch vendor --revert`, say that it reverts every vendored package, and only then re-lock and re-run hosted mode, matching the requirements.txt refusal. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Generated by Claude Code |
|
[burn-down agent] Ready for review at head
Generated by Claude Code |
Conflict in crates/socket-patch-cli/CLI_CONTRACT.md: both sides edited the same one-line scan --mode hosted paragraph. Main added the gem_mirror_overrides_source text; this branch added the uv/Poetry takeover-refusal sentence. Kept main's paragraph and inserted this branch's sentence at its original spot (after the requirements takeover refusal). #963 on main covers the hosted -> vendored direction; this branch's preflight covers vendored -> hosted, so both stay. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
[burn-down agent] Merge commit Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wk9XBZsw4FpBuPb4DpKRPv
…-takeover-preflight # Conflicts: # crates/socket-patch-cli/src/commands/scan/hosted.rs # crates/socket-patch-cli/tests/mode_migration_pypi.rs
|
[agent] Removed the stray BugBot review Generated by Claude Code |
Ledger purls keep the API's percent-encoding, so a PEP 440 local version (+) arrives as %2B and never equals the lock's version, refusing a reachable takeover. Decode it as matching_package does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wk9XBZsw4FpBuPb4DpKRPv
|
BugBot review Generated by Claude Code |
Port of #1016. main routes the Gradle cache crawler, jar comparator and Maven sidecar through utils::digest but still lists them as pending, so production_digests_go_through_the_helpers fails on main and every branch off it. No-op once #1016 lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wk9XBZsw4FpBuPb4DpKRPv
|
[agent] Generated by Claude Code |
|
BugBot review Generated by Claude Code |
…-takeover-preflight # Conflicts: # crates/socket-patch-cli/CLI_CONTRACT.md
|
BugBot review Generated by Claude Code |
|
[agent] I don't think this PR caused it:
I couldn't reproduce it locally because the sandbox can't reach Generated by Claude Code |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 10c2237. Configure here.
|
[agent] Ready for review at 10c2237.
Generated by Claude Code |
Resolve conflicts with main's #946 (PyPI takeover pre-gate), #1042 (containment helper) and #1077 (classic berry-migration warning): - hosted.rs: keep this PR's staged takeover; main's new `preflight_pypi_takeover` pre-gate inside the deleted `vendored_takeover` is dropped. The staged takeover's `explain` now calls `preflight_pypi_takeover` (instead of only the requirements check), so a retracted uv pin-down (#723) or Poetry 0.x (#945) takeover is still skipped with `redirect_uv_takeover_version_unreachable` / `redirect_poetry_lock_unsupported`, as main's tests expect. - socket_dir.rs: use main's `containment::ensure_unlinked` guard, then this PR's deferred removal. - redirect/mod.rs: take main's `pinned_any` (a mirror-refused entry counts only when it already carries our hosted pin), which subsumes this PR's mirror fix. - CLI_CONTRACT.md: describe the uv/Poetry cases as retractions. - mode_migration_pypi.rs: keep both sides' tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LLM Description written by Claude Code:claude-opus-5-5
Fixes #723
Fixes #945
Summary
On a uv or Poetry project that socket-patch vendored,
scan --mode hostedcould delete the vendored patch and then fail to pin the hosted one. The package ended up in neither mode, and the next install got the unpatched release.--dry-runpromised a clean takeover in the same situation. With this fix the takeover is refused up front, in the dry run and the wet run alike, and the package stays vendored and patched (exit 0).Root cause
The vendored → hosted takeover reverts a purl's vendored wiring first, then asks the hosted rewriter to pin it. The
takeover_refusalgate incrates/socket-patch-cli/src/commands/scan/hosted.rsis supposed to refuse, before the revert, any purl the hosted rewriter can't reach. Forpkg:pypi/it only ranpreflight_requirements_takeover, which covers requirements.txt. Nothing checked the uv or Poetry rewriter:redirect_uv_entry_not_found).redirect_poetry_lock_unsupported), but that refusal only ran after the revert.Fix
patch::redirect::preflight_pypi_takeover(root, entry)inpypi_takeover.rs. It dispatches on the ledger entry's flavor:preflight_requirements_takeoverredirect_uv_takeover_version_unreachablewhen a recordeduv_lock_packageoriginal unit has aversiondifferent from the patch's. The comparison is exact, the same as the hosted planner'smatching_package.poetry.lockand refuses withredirect_poetry_lock_unsupportedwhen it's a 0.x lock. The revert never changes the lock format.scan/hosted.rscomputes these refusals once per PyPI takeover purl, before any revert. A refused purl is never dispatched, so the dry run reports the same refusal as the wet run, and the package keeps its wiring, ledger entry and wheel.CLI_CONTRACT.md: documents the new code and the Poetry takeover refusal.The npm/PyPI/gem wrappers only dispatch to the binary, so they need no change.
Also in this PR
mainis red oncoverage/testbecause of that guard, and this port becomes a no-op once Fix main CI red on stale digest pending-list entries #1016 lands. (The earlier Route Gradle digests through utils::digest #878 port, d11b01c, now matchesmainand adds nothing to the diff.)+arrives as%2B).socket-patch vendor --revert, because re-locking while still vendored can't clear the uv gate and makes the revert see drift.Per-issue checklist
New
mode_migration_pypie2e tests (they run in plaincargo test). Each test runs a realvendorand then a hostedscan:bb3a25d)8be3951)six>=1.15uv_pinned_down_direct_takeover_is_refused_before_revertredirected: 0,redirect_takeover_unpatched)dry_run_predicts_uv_pinned_down_direct_takeover_refusalredirected: 1)uv_pinned_down_transitive_takeover_is_refused_before_revertredirect_takeover_unpatched)dry_run_predicts_uv_pinned_down_transitive_takeover_refusalredirected: 1)poetry_0_lock_takeover_is_refused_before_revertredirect_takeover_unpatched)dry_run_predicts_poetry_0_lock_takeover_refusalredirected: 1)Core unit tests in
pypi_takeover::tests(6 passed): a pinned-down uv entry is refused, a uv entry at the patch version is admitted, an unparseable original is admitted (the revert's own drift handling owns that case), a Poetry 0 lock is refused, a Poetry 2.1 lock is admitted, and other flavors are admitted.Local verification
cargo test -p socket-patch-cli --all-features --test mode_migration_pypi: 33 passed. That includes the controlsuv_vendored_to_hostedandpoetry_vendored_to_hosted(where the lock already resolves the patch version) and the existing requirements refusals.cargo test -p socket-patch-cli --all-features --test coverage_fix_scan_hosted_dryrun_vendored --test covgap_commands_scan_hosted: 9 + 52 passed.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --lib: everything passes except 5 tests that fail the same way onmainin this sandbox:utils::digest::tests::production_digests_go_through_the_helpers: the Gradle inline-hash guard that Route Gradle digests through utils::digest #878 fixes.copy_treerelax loop,vlt_healunremovable lock, poetry and requirements wire-failure rollback): they can't fail a write when run as root (uid 0 here).cargo test --workspace --all-featurescouldn't finish locally because the sandbox ran out of disk (about 21 GB of test binaries). CI ran the full set on0874e9d: all 547 check runs passed or were skipped, 0 failures.cargo fmt: the new and changed hunks are rustfmt-clean.cargo fmt --all -- --checkfails onmainitself (466 pre-existing diffs; CI doesn't run it), so I didn't reformat unrelated files.🤖 Generated with Claude Code
Note
Medium Risk
Changes hosted scan takeover ordering for PyPI projects; incorrect preflight could block valid migrations or still allow bad ones, but behavior is covered by new e2e and unit tests and only affects vendored→hosted transitions.
Overview
Vendored → hosted PyPI takeovers no longer revert wiring first when hosted mode cannot complete the redirect. A new
preflight_pypi_takeoverruns before any revert (wet and--dry-run), using the vendor ledger and on-disk locks.For uv, takeover is refused with
redirect_uv_takeover_version_unreachablewhen the recorded pre-vendoruv.lockunit version differs from the patch (revert would restore a version hosted mode will not pin). For Poetry, Poetry 0.x locks are refused up front withredirect_poetry_lock_unsupportedinstead of after un-vendoring. requirements.txt keeps the existing unreachable-pin check, now routed through the same preflight.Hosted scan builds these refusals once per PyPI takeover candidate and skips dispatch when refused, so the package stays vendored (exit 0) instead of landing unpatched with
redirect_takeover_unpatched.CLI_CONTRACT.mddocuments the uv code and Poetry takeover behavior; integration tests cover uv (direct/transitive) and Poetry 0.12 refusals.Reviewed by Cursor Bugbot for commit 10c2237. Configure here.
Generated by Claude Code