Skip to content

Restore the uv 0.2 pins hosted mode writes - #2014

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
fix/uv-legacy-hosted-unwind
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
fix/uv-legacy-hosted-unwind

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

Problem

Hosted mode rewires uv 0.2 [[distribution]] locks (uv 0.1.45–0.2.34) in all three of their grammars (utils/python_lock.rs allows ArtifactSource::Url there). The hosted unwind (patch/redirect/upstream/uv.rs) refused every such pin: "uv 0.2 [[distribution]] locks changed their artifact grammar across releases; which one this lock follows is not derivable". So rollback, remove and scan could not undo what hosted had written.

Decision: option (a), restore faithfully, and refuse where that can't be proven

The grammar is derivable from the same evidence the hosted rewrite uses to choose it: the entry itself. The rewrite keeps the entry's source spelling ("direct+<url>" for uv ≤ 0.2.17, { url } after) and writes its artifacts in the entry's own grammar ([[distribution.wheel]] sub-tables for uv ≤ 0.2.5, inline wheels from 0.2.6). The unwind reads both back off the hosted entry, and gets the artifact keys and registry from the sibling registry packages.

I checked faithfulness against real uv binaries (uv 0.1.45 through 0.2.35, macOS arm64, CPython 3.11, locking urllib3, markupsafe, python-dateutil and isodate against live PyPI):

  • Every release from 0.1.45 to 0.2.34 records every file of a release, on every platform. All 59 markupsafe 2.1.5 wheels were recorded through 0.2.18, and 40 from a release between 0.2.19 and 0.2.25, which dropped the wheels requires-python excludes. The unwind only restores pure-Python 3 releases, and requires-python can't drop those wheels, so re-deriving every PyPI file is faithful for all three grammars. Supported range: 0.1.45–0.2.34.
  • uv ≤ 0.2.5 writes an entry's sub-tables in the order [distribution.sdist], [[distribution.wheel]], then [[distribution.dependencies]]. My first commit put the wheel after the dependencies. Its synthetic test passed only because it encoded the same wrong assumption.
  • uv 0.2.18–0.2.22 write an inline sdist before inline dependencies, and 0.2.23–0.2.34 after them. Nothing else in the lock tells these releases apart (the locks are otherwise byte-identical), and a hosted entry has lost its sdist. The only evidence is another registry entry that carries both.
  • exclude-newer (uv lock):
    • ≤ 0.2.5 takes none (no flag; it ignores [tool.uv] and the env var, and errors on exclude-newer in uv.toml).
    • 0.2.6 adds --exclude-newer.
    • 0.2.12 adds UV_EXCLUDE_NEWER, [tool.uv] exclude-newer and project or user uv.toml.
    • The cutoff is first recorded at 0.2.28, at the top level of the lock; from 0.2.34 it goes under [options].

So the unwind restores, and it refuses with a precise reason when the lock lacks something it needs.

Change

  • utils/python_lock.rs: shared detectors used by both the rewrite and the unwind:
    • distribution_artifact_tables: the entry's own artifacts decide its grammar, then any other entry's. This replaces the rewrite's whole-text search.
    • distribution_sdist_before_dependencies: reads the order off registry entries only.
    • sets_exclude_newer.
    • UvSource::of_item / of_value.
    • The unused text argument is gone from plan_python_lock_rewrite / PythonLockSession::plan.
  • Hosted rewrite (patch/redirect/mod.rs): refuses (redirect_uv_lock_unsupported) two cases it could not undo:
    • a [[distribution]] entry with an sdist and inline dependencies when no other registry entry shows their order;
    • an inline-artifact [[distribution]] lock whose project (uv.toml, else [tool.uv]) sets exclude-newer.
  • Unwind (patch/redirect/upstream/uv.rs):
    • Restores the source in the entry's spelling through UvSource, including a standard [distribution.source] table.
    • Restores the artifacts in the entry's grammar: sub-tables ahead of the entry's other sub-tables ([[distribution.wheel]] key), or the sdist placed beside inline dependencies as a sibling shows. When no sibling shows that order, it refuses.
    • Points dependents' references back, including [[distribution.dependencies]] tables.
    • Refuses an unrecorded cutoff (project, user or system uv.toml, [tool.uv], or UV_EXCLUDE_NEWER) for the inline grammar, and a top-level exclude-newer (0.2.28–0.2.33) like an [options] one.
    • artifact_tables now reuses table_likes.

Vendored still refuses [[distribution]] locks (no portable local wheel), unchanged. Discovery and the lock inventory already read all three grammars.

Review findings (/code-review high)

  1. exclude-newer (confirmed; fixed by refusing). uv 0.2.12–0.2.27 apply a cutoff from [tool.uv], a project or user uv.toml, or UV_EXCLUDE_NEWER without recording it.
    • The unwind refuses an inline-grammar lock when any of those is set. For the env var it follows the pylock path: the variable as set in the restoring environment is the best evidence of the one uv ran in.
    • The rewrite refuses to wire such a lock when its project settings set a cutoff.
    • A cutoff that 0.2.28–0.2.33 recorded at the top level now refuses like [options] (the old check read only [options]).
    • Locks from uv ≤ 0.2.5 (sub-table grammar) can't carry a cutoff, so they still round-trip.
    • I chose refusal over filtering by upload time to stay consistent with the existing [options] refusal (Hosted uv rollback / remove ignore exclude-newer-package: on uv 0.12+ the restored uv.lock gains an sdist the per-package cutoff had excluded, so the restore isn't the original lock #1604). Date-only cutoffs are in local time, so filtering wouldn't be exact either.
    • Undetectable residual: a cutoff passed only as uv lock --exclude-newer to 0.2.6–0.2.27 leaves no trace in the lock, the project or the environment. A wrong restore then needs the cutoff to land between the upload times of one release's own files.
  2. Universal file recording (refuted, with evidence). Measured with real binaries as above. Every 0.1.45–0.2.34 lock records all platforms' files, so no grammar needs refusing on these grounds.
    • The probe surfaced two real ordering bugs instead: the uv ≤ 0.2.5 sub-table order, and the 0.2.23 sdist/dependencies flip. Both are fixed.
    • The tests now round-trip real uv lock output from 0.1.45, 0.2.5, 0.2.15, 0.2.16, 0.2.18 and 0.2.34 (tests/fixtures/upstream/uv-0.2/).
  3. Grammar chosen by different rules (confirmed; fixed). One shared distribution_artifact_tables is used by both directions; the entry's own artifacts come first. New test uv_distribution_entry_grammar_wins_over_the_first_sibling (first sibling inline, hit in sub-tables), plus a unit test of both detectors.
  4. Contract said the source spelling follows the siblings (confirmed; doc fixed). The code is right, since hosted writes the entry's own spelling. CLI_CONTRACT.md now says so. The test above uses a hit with a string source and a sibling with an inline-table source.
  5. artifact_tables duplicated table_likes (confirmed; fixed).
  6. restored_source hand-parsed the source (confirmed; fixed). It now goes through UvSource::of_value / of_item and UvSource::url, the same parser lock_hits uses.
  7. Misleading error, [distribution.source] table unhandled (confirmed; fixed). A standard source table is restored in that spelling (test uv_distribution_source_table_is_restored). A missing source and a source that no longer names the patch each get their own message.
  8. uv-compatibility doc (fixed). The [[distribution]] row and the limits section now cover the unwind, the tested versions and the measured boundaries.

Tests

  • New in tests/upstream_restore_golden.rs. Each one fails on the previous commit (234b65e) and passes now:
    • uv_distribution_locks_round_trip: six real uv locks, pins urllib3 and urllib3 + python-dateutil; paired pyproject in LF and CRLF, plus lock-only. All byte-exact.
    • uv_distribution_sdist_order_without_evidence_is_refused: covers both the rewrite and the unwind.
    • uv_distribution_entry_grammar_wins_over_the_first_sibling
    • uv_distribution_exclude_newer_is_refused: covers pyproject, uv.toml, a top-level lock key and UV_EXCLUDE_NEWER; 0.2.5 still round-trips.
    • uv_distribution_source_table_is_restored
  • New unit test utils::python_lock::tests::distribution_grammar_reads_the_entry_before_its_siblings.
  • cargo test -p socket-patch-core --lib -- patch::redirect:: utils::python_lock vex::discover:: vendor::pypi: 1857 passed. That includes the python_lock_equivalence goldens, which are unchanged.
  • committed_fixture_corpus_matches_golden passes. Golden change: vex-discover-golden/upstream.json gains entries for the six new pristine fixtures (no refs; registry elsewhere evidence only).
  • --test upstream_restore_golden: 58 passed. --test uv_hosted: 4 passed. CLI in_process_rollback_hosted (uv override and pypi filters): 6 passed.
  • cargo clippy --locked --workspace --all-features -- -D warnings and cargo fmt --all -- --check are clean.

Not done

  • No filtering of restored files by upload time (see finding 1).
  • Hosted still wires a [[package]] lock whose [options] records a cutoff. Done in round 2 (be7384f).
  • No real-uv install backtest of the restored locks. The fixtures pin real uv lock bytes instead.

Review findings, round 2 (/code-review high on ab4f0f8+80180b8, cloud routine), and the exclude-newer follow-up

Fixed in be7384f. Each test below failed on 80180b8.

  1. Hosted wired locks whose recorded cutoff the unwind always refuses: confirmed and fixed (this was also the known follow-up). Three cases are now refused by the rewrite (redirect_uv_lock_unsupported): a uv lock with [options] exclude-newer, a top-level exclude-newer (uv 0.2.28–0.2.33), or an exclude-newer-package naming the package (unless it exempts it with false). This covers [[package]] and [[distribution]] locks alike. One classifier, upstream::lock_cutoff, is now shared by the rewrite and the unwind, replacing the unwind's inline copy of sets_exclude_newer. Tests: patch::redirect::tests::uv_lock_recording_a_cutoff_is_not_wired, and uv_distribution_exclude_newer_is_refused ("recorded" case).
    • Behavior change: such locks were wired before. Rollback then refused every pin and left it stuck. The unwind tests uv_refusals and uv_exclude_newer_package_refuses_only_its_package now wire before the cutoff is recorded, and also assert that hosted refuses a lock that already records it.
  2. The project-settings cutoff check missed the settings file above the root: confirmed and fixed. distribution_project_cutoff re-implemented uv settings discovery. It now reads pylock_uv_settings (that is, root_uv_settings, including UV_ANCESTOR_CONFIG_KEY), the same files the unwind reads. An invalid uv.toml stops the lookup as it does in uv. The check is computed once per run, not once per dep. Test: uv_distribution_exclude_newer_is_refused ("ancestor" case).
  3. The sdist / inline-dependencies refusal ran before the not-applicable checks: confirmed and fixed. An entry the rewrite doesn't touch (a non-registry, non-direct string source) now gets redirect_uv_entry_not_found again, not a hard refusal. Test: utils::python_lock::tests::a_not_applicable_distribution_entry_is_not_refused_for_its_sdist.
  4. The compatibility doc contradicted itself: fixed. docs/testing/uv-compatibility.md now matches the module docs: 0.2.x locks record each file that requires-python admits.

Declined:

  • The user/system uv.toml and UV_EXCLUDE_NEWER. The rewrite reads only project files, and the scan can't see the machine that will lock or roll back. The unwind still refuses on them, which fails closed, and the refusal names the setting.
  • The wheel_targets probe fetches metadata for deps the rewrite then refuses. That costs only network time and doesn't affect correctness.
  • restore_uv_entry's duplicated tables flag. A refactor only; both values are set together today.

Tests (cloud run): cargo test -p socket-patch-core --lib -- patch::redirect:: utils::python vex::discover:: vendor::pypi hosted:: gives 2061 passed. --test upstream_restore_golden gives 58 passed. CLI mode_migration_pypi gives 79 passed, and in_process_redirect gives 149 passed. clippy and fmt are clean. The 3 lib failures and 3 in_process_redirect failures are write-failure tests that chmod a directory read-only, which root (this container) ignores. They're unrelated to this PR.


Note

Medium Risk
Changes hosted rollback/remove for legacy uv locks and PyPI hash re-derivation, with explicit refusals when metadata is ambiguous; scope is lockfile editing rather than auth or runtime install paths.

Overview
Hosted rollback/remove no longer blanket-refuses uv 0.2 [[distribution]] locks (0.1.45–0.2.34). The upstream restore path rewrites pins back to PyPI in the same grammar the hosted entry used—string vs inline source, sub-table vs inline artifacts, sdist placement relative to inline dependencies, and dependent references including [[distribution.dependencies]].

Shared logic in python_lock.rs (distribution_artifact_tables, distribution_sdist_before_dependencies, sets_exclude_newer) drives both the hosted rewrite and unwind so shape detection stays aligned. PythonLockSession::plan no longer takes lock text; planning uses the parsed document only.

New refusals (rewrite and/or unwind) when rollback cannot be proven faithful: entries with both sdist and inline dependencies but no sibling registry entry showing uv’s 0.2.18–0.2.34 ordering; inline-artifact locks when project/user exclude-newer or UV_EXCLUDE_NEWER applies without being recorded (plus top-level lock cutoffs for 0.2.28–0.2.33). Vendored [[distribution]] behavior is unchanged.

Docs (CLI_CONTRACT.md, uv-compatibility.md) and golden tests cover the new restore rules; six real uv lock fixtures round-trip through hosted rewrite and unwind.

Reviewed by Cursor Bugbot for commit 80180b8. Configure here.


Generated by Claude Code

Hosted mode rewires uv 0.2 `[[distribution]]` locks (uv < 0.2.35) in all
three of their grammars, but the hosted unwind refused every such pin
("which grammar this lock follows is not derivable"), so `rollback`,
`remove` and `scan` could not undo what hosted wrote.

The grammar is derivable: it is the same evidence the rewrite follows.
The entry's own hosted source keeps its spelling (`"direct+<url>"` vs
`{ url }`), and the lock's other registry packages show the registry,
the artifact shape (`[distribution.sdist]` / `[[distribution.wheel]]`
sub-tables vs inline `sdist` / `wheels`) and the artifact keys. So the
unwind now restores these entries like `[[package]]` ones:

- the source goes back to `"registry+<R>"` or `{ registry = R }` in the
  entry's spelling;
- artifacts are re-derived from PyPI in the siblings' shape, with uv
  <= 0.2.5's `wheel` key and its sub-table order (sdist before the
  entry's `[[distribution.dependencies]]`, wheels after);
- dependents' references, inline or `[[distribution.dependencies]]`
  tables with string sources, are pointed back.

The paired pyproject is restored as before. Vendored still refuses these
locks (no portable local wheel spelling), unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 5, 2026 16:48
@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.

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

Copy link
Copy Markdown
Collaborator Author

Labeled Ready for review (burn-down agent).

  • Head: 234b65ea86cc12dce123262e3aee5a7a5550f877
  • CI: 80/82 checks green; the only red is the e2e_vex_build pdm leg (+ its ci-ok rollup), which is main-wide: hishel 2.0.0 (released 2026-11-05) breaks pdm lock on every PR. Fix pdm e2e red on every PR: pin hishel<2 #2028 fixes it; merge main in after it lands.
  • Bugbot: reviewed this head, no issues found.
  • Mergeable, no CHANGELOG.md change.
  • Slack announcement not sent this run (no Slack send tool available); the next run will retry.

Generated by Claude Code

Review fixes for the uv 0.2 `[[distribution]]` hosted unwind, checked
against real `uv lock` output from uv 0.1.45 to 0.2.35:

- Every one of those releases records every file of a release on every
  platform, so a pure-Python 3 release re-derives faithfully in all
  three grammars. The tests now round-trip real 0.1.45, 0.2.5, 0.2.15,
  0.2.16, 0.2.18 and 0.2.34 locks (tests/fixtures/upstream/uv-0.2).
- uv <= 0.2.5 write an entry's sdist and wheel sub-tables before its
  dependency tables; the unwind had the wheel after them.
- uv 0.2.18-0.2.22 write an inline sdist before inline dependencies,
  0.2.23-0.2.34 after them, and nothing else in the lock tells them
  apart. The order is read off another registry entry carrying both;
  without one the rewrite refuses to wire the entry and the unwind
  refuses to restore it.
- uv 0.2.12-0.2.27 apply an exclude-newer cutoff from [tool.uv], a
  project or user uv.toml, or UV_EXCLUDE_NEWER without recording it;
  0.2.28-0.2.33 record it at the lock's top level. The unwind refuses
  on any of them for the inline grammar (and on the top-level key), and
  the rewrite refuses a lock whose project sets one.
- One grammar detector in utils/python_lock.rs serves both directions:
  the entry's own artifacts decide, then any other entry's (the
  rewrite's whole-text search is gone).
- Sources are restored through UvSource in their own spelling, a
  standard [distribution.source] table included, with accurate errors;
  artifact_tables reuses table_likes.

Docs: CLI_CONTRACT.md and docs/testing/uv-compatibility.md describe the
restore, the refusals and the measured boundaries.

Co-Authored-By: Claude Opus 5.5 <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.

sets_exclude_newer matched every key starting with exclude-newer, so a
project setting exclude-newer-package (a per-package cutoff, or a false
exemption) made hosted rewrite and the uv 0.2 unwind refuse every inline
[[distribution]] pin. uv 0.2.12-0.2.27, the releases whose unrecorded
cutoff this guards against, predate that key and ignore it, so only the
global exclude-newer cutoff blocks those locks. The pyproject [options]
path in upstream/uv.rs already excluded the key the same way.

Found by Cursor Bugbot on ab4f0f8.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011f9aeQKNTJYztmYaJaAEsc
@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 6, 2026
@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.

✅ 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 80180b8. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Labeled Ready for review again (burn-down agent).

  • Head: 80180b84
  • CI: 29 passed, 19 skipped, 2 failed. Both failures are the e2e_vex_build pdm leg and its ci-ok rollup. hishel 2.0.0 broke pdm lock on every PR, so this is not caused by this PR. Fix pdm e2e red on every PR: pin hishel<2 #2028 fixes it.
  • Bugbot on ab4f0f8 found one real issue (medium): sets_exclude_newer treated exclude-newer-package as a global uv 0.2 cutoff, so hosted mode refused every inline [[distribution]] pin when that key was set. Fixed in 80180b8 and covered by a unit test. Bugbot then re-reviewed 80180b8 and found nothing new.
  • Reviewers: the ab4f0f8 commit (proving the uv 0.2 unwind faithful) landed after the first label, so please look at it too.
  • Slack: not announced. This run had no Slack send tool.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (for the human approver)

What it does. Hosted mode already rewires uv 0.2 [[distribution]] locks (uv 0.1.45–0.2.34), but rollback/remove/scan refused to undo them. This PR restores those entries in the grammar the entry itself carries: string vs table source, sub-table vs inline artifacts, and the uv 0.2.18–0.2.22 vs 0.2.23+ sdist/dependencies order. When the lock doesn't hold enough evidence to restore, it refuses on both sides: the rewrite won't wire such an entry, and the unwind won't guess.

Risk: medium. It changes how lockfiles are written on the unwind path, but every gap it can't prove fails closed with a precise refusal. Round trips are checked byte for byte against real uv lock output from six uv releases, with LF and CRLF.

Look here

  • python_lock.rs:191-244: the shared grammar, order and cutoff detectors. Both directions now use them; the rewrite used to search the whole text.
  • python_lock.rs:938-951 and redirect/mod.rs:6798-6812, 6905-6930: the new rewrite-side refusals (no order evidence; a project exclude-newer).
  • upstream/uv.rs:361-427:```` top-level cutoff (uv 0.2.28–0.2.33), per-hit grammar, and the unrecorded-cutoff refusal (746-762).`````
  • upstream/uv.rs:1558-1670:`````` restore_uv_entry (source spelling, sub-table sort, `move_before`) and `[[distribution.dependencies]]` refs.

Verified

  • Read the full diff and the PR description. The code matches the description, there's no CHANGELOG.md change and no debug leftovers. The touched code doesn't read untrusted paths.
  • Ran locally on 80180b841b: cargo test -p socket-patch-core --test upstream_restore_golden (58 pass, including uv_distribution_locks_round_trip over 0.1.45/0.2.5/0.2.15/0.2.16/0.2.18/0.2.34 × 1–2 pins × LF/CRLF/lock-only) and the core lib python_lock/uv tests (253 pass).
  • Checked by reading the code: with two pins in one lock, the rewrite takes order evidence only from entries that are still registry entries, and the unwind skips every hit. So one hosted entry is never used as evidence for another.
  • CI on 80180b841b: 29 passed, 19 path-skipped, 2 failed. Both failures are the e2e_vex_build pdm leg and its ci-ok rollup: hishel 2.0 broke pdm lock on every PR, and Fix pdm e2e red on every PR: pin hishel<2 #2028 fixes it. Unrelated to this PR.
  • Bugbot: its ab4f0f8 finding (exclude-newer-package treated as a global cutoff) is fixed in 80180b8 with a unit test. Its re-review of 80180b8 found nothing new.

Changes I made: none.

Open questions (non-blocking)

  • The two directions check for a cutoff differently. The rewrite refuses only on a project exclude-newer (uv.toml/[tool.uv]). The unwind also refuses on a user or system uv.toml and on UV_EXCLUDE_NEWER. So on a machine with a user-level cutoff, hosted mode can wire a lock whose rollback is then refused. That fails safe, but the pin would have to be unwound by hand. Acceptable?
  • Known residual (stated in the PR): a cutoff passed only as uv lock --exclude-newer to uv 0.2.6–0.2.27 leaves no trace.

Auto-merge is not armed yet because ci-ok is red on the pdm leg. Once #2028 lands and this PR is green, the next run arms it, so approving will send it straight to the merge queue.


Generated by Claude Code

Second review round, plus the known follow-up.

- A uv lock that records an `exclude-newer` cutoff for the package (a
  global one under `[options]` or, for uv 0.2.28-0.2.33, at the top
  level, or an `exclude-newer-package` naming it) was still wired by
  hosted mode, though the unwind always refuses such a pin. The rewrite
  now refuses it (`redirect_uv_lock_unsupported`), for `[[package]]` and
  `[[distribution]]` locks alike. One classifier, `upstream::lock_cutoff`,
  serves the rewrite and the unwind.
- The rewrite's project-settings cutoff check for `[[distribution]]`
  locks re-implemented uv settings discovery and missed the settings file
  above the root (`UV_ANCESTOR_CONFIG_KEY`) the unwind reads. It now uses
  `pylock_uv_settings` / `root_uv_settings`, computed once per run
  rather than per dep.
- The sdist / inline-dependencies refusal ran before the not-applicable
  checks, turning an entry the rewrite does not touch (a non-registry,
  non-direct source) into a hard refusal. It now runs after them.
- docs/testing/uv-compatibility.md no longer contradicts itself on which
  release files a 0.2.x lock records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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

Development

Successfully merging this pull request may close these issues.

2 participants