Fix rollback of pip-written pylock.toml (#804) - #807
Conversation
Assisted-by: Claude Code:claude-opus-5-5
`pip lock` writes PEP 751's array-of-tables spelling
(`[[packages.wheels]]` with a `[packages.wheels.hashes]` sub-table),
but the hosted upstream restore only read inline `wheels = [{ ... }]`
arrays. Every pip sibling looked artifact-free, so `rollback`, `remove`
and the hosted -> vendored takeover always refused a pip lock with "no
sibling registry package shows ...", leaving users with a hosted patch
they could not undo.
The restore now reads artifacts in either spelling, writes the entry
back in the siblings' spelling, and, since pip records only the one
artifact it selected, restores only the release's wheel (or its sdist
when it has no wheel), refusing a release with several wheels. The
refusal no longer blames "this uv release" for a pip-written lock.
Fixes #804
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Burn-down agent: marking Ready for review.
Generated by Claude Code |
Conflicts: uv.rs lock_shape keeps the PR's TableLike artifact_tables and main's upload_time/upload-time spelling (upload_time_value now takes &dyn TableLike); kept both sides' golden tests, CLI_CONTRACT pylock sentences, and the hosted byte-exact lanes (PipLock + Extras/IncludeGroup). Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
main has been red since #605 taught the npm copy resolver to probe bundled store trees: two vex_consumed alias tests (#738) still assumed the resolver never returns npm-aliased copies, so the CLI lib tests fail on every PR's merge ref. This ports #851's tests-only fix so the PR's CI reflects its own change; it no-ops once #851 lands on main. Assisted-by: Claude Code:claude-opus-5-5
|
[agent]
Generated by Claude Code |
|
BugBot review 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 9d1d2b1. Configure here.
|
[agent]
I've re-run the job once. If it fails again I'll treat it as real and dig into the PDM extras relock/rescan path. The Generated by Claude Code |
|
Burn-down agent: labeled Ready for review.
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #804
Summary
rollback,removeand the hosted → vendored takeover always refused apylock.tomlwritten bypip lock(pip 25.1+), so a hosted patch on such a project could be applied but never undone. They now restore the entry from PyPI, byte for byte.Root cause
The hosted pylock upstream restore (
crates/socket-patch-core/src/patch/redirect/upstream/uv.rs) only read a package's artifacts as uv writes them: inlinewheels = [{ … }]/sdist = { … }. PEP 751 also allows the standard-table spelling thatpip lockuses:artifact_tablesand the shape probe inlock_shapeusedItem::as_array/ inline tables only, so every pip sibling showed no artifacts,pylock_unindexed_registryreturnedNone, and the restore refused with "no sibling registry package shows the registry and artifact fields this uv release records".Fix
artifact_tablesreads both spellings throughTableLike(inline arrays,[[packages.wheels]], inline or[packages.sdist]).Shapelearns whether siblings use standard tables and ahashessub-table. The restored entry is written in that spelling ([[packages.wheels]]+[packages.wheels.hashes], or[packages.sdist]).pip lockrecords only the artifact pip selected. Forcreated-by = "pip"the restore writes only the release's wheel (or its sdist when it has no wheel). A release with several pure-Python wheels is refused, because which one pip picked depends on the interpreter that ran it.Tests (red → green)
rollback/removeof a pip lock, LF and CRLF, byte-exactupstream_restore_golden::pip_pylock_round_trips[packages.sdist])upstream_restore_golden::pip_pylock_sdist_only_release_round_tripsupstream_restore_golden::pip_pylock_with_several_wheels_is_refusedpip lock(pip 26.2.1) + uv 0.8.17: hosted scan → fresh install → VEX →rollbackrestorespylock.tomlbyte-identicale2e_redirect_uv_build::hosted_pip_lock_pylock_manifestless_vex(the lane now locks theidnasibling in hosted mode, like the uv pylock lanes, and requires a byte-exact restore instead of accepting a refusal)The hosted → vendored takeover goes through the same
restore_upstreamcall, so the same tests cover it.Commands run locally (head
b33b0ed):cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt: the changed hunks are rustfmt-clean.mainitself is not rustfmt-clean (many pre-existing diffs; CI has no fmt gate), so I didn't touch unrelated files.cargo test -p socket-patch-core --all-features: 5395 passed, 4 failed. The 4 failures are permission tests (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files). They fail the same way onmainbecause the sandbox runs as root.cargo test -p socket-patch-cli --all-features --test e2e_vex_lockfile --test mode_migration_pypi --test hosted_memory_engine --test in_process_redirect_{pipenv,poetry,pdm}: 344 passed.e2e_redirect_uv_buildhosted pylock lanes (pip-lock,export-pylock,compile-pylock) with real uv 0.8.17 + pip 26.2.1: all restore. I ran them through a local PyPI JSON relay, because this sandbox's Rust TLS client can't reach pypi.org directly. That relay was not committed.cargo test --workspacelocally: building every test binary exceeds the sandbox's disk allowance. CI runs it.Note: CI's host
python3likely has pip < 25.1, so thepip-locke2e lane probably reportsn/athere. The golden tests are the hermetic regression coverage.Note
Medium Risk
Changes PyPI lock upstream-restore logic used by rollback/remove; incorrect behavior could leave hosted pins wired or restore wrong lock bytes.
Overview
Hosted upstream restore for PEP 751
pylock.tomlfiles now handles locks written bypip lock, not only uv’s inline artifact layout.rollback,remove, and hosted→vendored unwind had been refusing pip pylocks because sibling packages’[[packages.wheels]]/[packages.sdist]tables were invisible to the shape probe.The restore path learns table vs inline spelling and
[packages.wheels.hashes]sub-tables from siblings, rewrites restored entries in that form, and forcreated-by = "pip"restores only the single artifact pip recorded (wheel or sdist-only). Releases with multiple pure-Python wheels are refused instead of guessing which wheel pip picked. Docs inCLI_CONTRACT.mdand error wording are updated accordingly.Tests add golden round-trips/refusal cases and extend the uv e2e PipLock lane (hosted sibling
idna, byte-exact revert). Npm hosted tests invex_consumed.rsare adjusted to match the #605 resolver probing bundled trees on its own.Reviewed by Cursor Bugbot for commit 9d1d2b1. Configure here.
Generated by Claude Code