Repository navigation
Fix PyPI hosted takeover un-vendoring before refusal (#723, #945) - #946
Mikola Lysenko (mikolalysenko) wants to merge 5 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 |
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 0874e9d. Configure here.
|
[burn-down agent] Ready for review at head
Generated by Claude Code |
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
utils::digest).mainis red oncoverage/testbecause of the digest guard, and this port becomes a no-op once Route Gradle digests through utils::digest #878 lands.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 vendored→hosted migration gates for PyPI (uv/Poetry), which can alter whether takeovers proceed but is designed to prevent leaving packages unpatched; includes contract and e2e coverage.
Overview
Vendored → hosted PyPI takeovers no longer revert wiring first when hosted mode cannot pin afterward. A new
preflight_pypi_takeoverruns once perpkg:pypi/candidate (requirements, uv, Poetry) from the ledger and on-disk locks; refused packages stay vendored, exit 0, and--dry-runmatches the wet run.uv (#723): emits
redirect_uv_takeover_version_unreachablewhen vendored mode pinneduv.lockto the patch version but the recorded pre-vendor unit resolved another version—revert would leave hosted with nothing to pin.Poetry (#945): refuses takeover on Poetry 0.x locks with
redirect_poetry_lock_unsupportedbefore revert (hosted already refuses those locks post-revert).Docs/tests:
CLI_CONTRACT.mdandmode_migration_pypie2e cover the new refusal paths; requirements takeover tests share a generalizedassert_takeover_refusedhelper.Minor: Gradle/JVM/Maven code paths use shared
utils::digestsha1_hex_of/sha256_hex_ofinstead of inline hashing.Reviewed by Cursor Bugbot for commit 0874e9d. Configure here.
Generated by Claude Code