Skip to content

Fix rollback of pip-written pylock.toml (#804) - #807

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-pylock-wheels-table-array
Oct 5, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-pylock-wheels-table-array

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #804

Summary

rollback, remove and the hosted → vendored takeover always refused a pylock.toml written by pip 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: inline wheels = [{ … }] / sdist = { … }. PEP 751 also allows the standard-table spelling that pip lock uses:

[[packages.wheels]]
name = "idna-3.7-py3-none-any.whl"
url = "https://files.pythonhosted.org/…"

[packages.wheels.hashes]
sha256 = "…"

artifact_tables and the shape probe in lock_shape used Item::as_array / inline tables only, so every pip sibling showed no artifacts, pylock_unindexed_registry returned None, and the restore refused with "no sibling registry package shows the registry and artifact fields this uv release records".

Fix

  • artifact_tables reads both spellings through TableLike (inline arrays, [[packages.wheels]], inline or [packages.sdist]).
  • Shape learns whether siblings use standard tables and a hashes sub-table. The restored entry is written in that spelling ([[packages.wheels]] + [packages.wheels.hashes], or [packages.sdist]).
  • pip lock records only the artifact pip selected. For created-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.
  • The refusal text no longer says "this uv release" (it also covers pip-written locks).
  • CLI_CONTRACT.md documents the restore for both spellings.

Tests (red → green)

Issue variant Test main this PR
#804 rollback/remove of a pip lock, LF and CRLF, byte-exact upstream_restore_golden::pip_pylock_round_trips FAIL (refused, "no sibling registry package…") pass
#804 pip lock of a wheel-less release ([packages.sdist]) upstream_restore_golden::pip_pylock_sdist_only_release_round_trips FAIL pass
#804 not-derivable choice is refused, pin left wired, refusal names pip upstream_restore_golden::pip_pylock_with_several_wheels_is_refused FAIL pass
#804 end to end with real pip lock (pip 26.2.1) + uv 0.8.17: hosted scan → fresh install → VEX → rollback restores pylock.toml byte-identical e2e_redirect_uv_build::hosted_pip_lock_pylock_manifestless_vex (the lane now locks the idna sibling in hosted mode, like the uv pylock lanes, and requires a byte-exact restore instead of accepting a refusal) refused pass

The hosted → vendored takeover goes through the same restore_upstream call, 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. main itself 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 on main because 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_build hosted 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.
  • I couldn't run the full cargo test --workspace locally: building every test binary exceeds the sandbox's disk allowance. CI runs it.

Note: CI's host python3 likely has pip < 25.1, so the pip-lock e2e lane probably reports n/a there. 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.toml files now handles locks written by pip 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 for created-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 in CLI_CONTRACT.md and 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 in vex_consumed.rs are 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

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
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 4, 2026 21:58
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 4, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: marking Ready for review.

  • Head: b33b0ed175
  • CI: 97/97 completed checks green (3 skipped), mergeable, 0 commits behind main
  • Bugbot: reviewed b33b0ed — no findings; no unresolved review threads
  • Reviewer focus: crates/socket-patch-core/src/patch/redirect/upstream/uv.rs — the created-by = "pip" single-artifact restore and the refusal when a release has several pure-Python wheels.

Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
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>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

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
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage on cf8b809 failed in -p socket-patch-cli --lib (and once in -p socket-patch-core --lib). Neither failure comes from this PR.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] native (macos-latest, 2.8.2) (PDM compatibility) failed on 9d1d2b1 in one case: 2.8.2 extras hosted. Its rescanAfterRelockApplies and rollbackAfterRelockPristine checks failed. The other 34 cases passed. I don't think this PR caused it:

  • The case runs the pdm.lock restore (pypi_locks::restore_pdm). This PR only changes the pylock/uv restore in upstream/uv.rs, which the PDM path never calls.
  • The same check passed on this PR's pre-merge head b33b0ed and on main at 792e836 (Fix Poetry/PDM lock splice drift (#694, #695) #703, the last change to the PDM lock code). On 9d1d2b1, native (ubuntu-latest, 2.8.2) passes. Only macOS fails.
  • The per-case logs are in the run artifact, which I can't download from this sandbox, so I can't name the exact error.

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 coverage and test-release failures from the previous head are fixed (see my comment above). Every check that has finished on 9d1d2b1 has passed.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review.

  • Head: 9d1d2b14c22de758eeb80cac069dc30e2da648e0
  • CI: 488/488 check runs green (success/skipped) on this head; mergeable clean
  • Bugbot: reviewed 9d1d2b1, no new issues
  • Reviewer focus: PEP 751 standard-table wheels/sdist parsing in patch/redirect/upstream/uv.rs

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants