You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Vendored uv repair pairs a hashless pure wheel with another wheel's hash, because ledger recovery re-parses uv.lock with its own scanner #1079
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: E89.
Kind: bug (with a duplicated predicate). Source: new finding; register E89.
Problem
Verified on main 05ecc6e. Two parts of vendor/lock_inventory pick "the hash-pinned pure wheel" of a uv.lock [[package]], and they parse it differently.
Ledger recovery (repair / re-vendor of a missing artifact): pure_wheel_from_uv_unit, called at recover.rs#L239, is a string scanner. It finds url = ", then takes the firsthash = "sha256: anywhere after it (rest.find(...)), even when that hash belongs to a later wheel. Its doc and its test claim the opposite ("fail-closed, never a guessed pairing").
The "is this a pure wheel" rule is also written four times:
On main the suffix rule and the shared classifier agree on well-formed names. Open PR #1053 (fix for #1048) changes only the shared classifier: cp311-none-any, pp310-none-any and py2-none-any become non-portable. The three inventory copies will keep choosing those wheels as "pure", so the modes drift as soon as it lands.
Repro. A throwaway test in vendor/lock_inventory/tests.rs, run twice on 05ecc6e. One uv [[package]] unit whose pure wheel has no hash, followed by a hashed platform wheel:
RECOVER: Some(("https://files.example/six-1.16.0-py3-none-any.whl", "bbbb…")) ← the platform wheel's digest
INVENTORY: resolved=None integrity=None ← correctly no pure pinned wheel
The same test showed that pure_wheel_from_uv_unit also accepts cp311-none-any, which the shared rule will reject after #1053.
Symptoms
None filed. Impact: low severity and fail-closed. The fetch layer verifies the digest, so repair fails with a hash mismatch for the wrong wheel instead of the honest NO_URL / "no hash-pinned pure wheel" message. The structural cost is a third uv.lock reader, and portability drift between hosted/vendored and inventory/recovery once #1053 lands. The fixture is realistic for uv locks from --find-links or flat indexes, which carry no hashes.
Proposed change
Add pub(crate) fn is_portable_wheel(file_name: &str) -> bool beside wheel_platform_from_filename, or use !wheel_platform_from_filename(..).0. Use it at the three inventory sites, stripping ?/# once in a shared helper.
In recover.rs, parse the recorded uv_lock_package / pdm_lock_package fragment as TOML (it is a [[package]] unit) and reuse python_package_archive.
Deleted:pure_wheel_from_uv_unit (about 25 lines) and the three ends_with("-none-any.whl") literals.
The repro unit recovers to the NO_URL / "no hash-pinned pure wheel" error, not to a mismatched pair.
Inventory and recovery give the same answer for the same [[package]] unit. Add a table test over py3-none-any, py2.py3-none-any, cp311-none-any, *-abi3-*, a hashless pure wheel and a #sha256= URL.
Priority: P1 → P3. Digest verification rejects the mismatched recovery wheel. This is a fail-closed recovery/diagnostic issue, explicitly low severity; keep active #1121.
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: E89.
Kind: bug (with a duplicated predicate). Source: new finding; register E89.
Problem
Verified on main
05ecc6e. Two parts ofvendor/lock_inventorypick "the hash-pinned pure wheel" of a uv.lock[[package]], and they parse it differently.python_package_archivereads the lock withtoml_editthrough the sharedutils::python_lock::package_artifacts. It pairs each artifact'surlwith that artifact'shash.repair/ re-vendor of a missing artifact):pure_wheel_from_uv_unit, called atrecover.rs#L239, is a string scanner. It findsurl = ", then takes the firsthash = "sha256:anywhere after it (rest.find(...)), even when that hash belongs to a later wheel. Its doc and its test claim the opposite ("fail-closed, never a guessed pairing").The "is this a pure wheel" rule is also written four times:
pypi_distribution::wheel_platform_from_filenamevendor_platform_locked) and hosted (redirect_pypi_platform_wheel,redirect/mod.rs#L580).lock_inventory/pypi.rs#L313url.split(['?','#'])…ends_with("-none-any.whl")lock_inventory/pypi.rs#L393(poetry)file.ends_with("-none-any.whl")recover.rs#L464url.ends_with("-none-any.whl"), with no?/#stripOn main the suffix rule and the shared classifier agree on well-formed names. Open PR #1053 (fix for #1048) changes only the shared classifier:
cp311-none-any,pp310-none-anyandpy2-none-anybecome non-portable. The three inventory copies will keep choosing those wheels as "pure", so the modes drift as soon as it lands.Repro. A throwaway test in
vendor/lock_inventory/tests.rs, run twice on05ecc6e. One uv[[package]]unit whose pure wheel has no hash, followed by a hashed platform wheel:The same test showed that
pure_wheel_from_uv_unitalso acceptscp311-none-any, which the shared rule will reject after #1053.Symptoms
None filed. Impact: low severity and fail-closed. The fetch layer verifies the digest, so repair fails with a hash mismatch for the wrong wheel instead of the honest
NO_URL/ "no hash-pinned pure wheel" message. The structural cost is a third uv.lock reader, and portability drift between hosted/vendored and inventory/recovery once #1053 lands. The fixture is realistic for uv locks from--find-linksor flat indexes, which carry no hashes.Proposed change
pub(crate) fn is_portable_wheel(file_name: &str) -> boolbesidewheel_platform_from_filename, or use!wheel_platform_from_filename(..).0. Use it at the three inventory sites, stripping?/#once in a shared helper.recover.rs, parse the recordeduv_lock_package/pdm_lock_packagefragment as TOML (it is a[[package]]unit) and reusepython_package_archive.pure_wheel_from_uv_unit(about 25 lines) and the threeends_with("-none-any.whl")literals.Size and scope
vendor/lock_inventory/{pypi,recover,tests}.rs,vendor/pypi_distribution.rs.vendor/(theredirect → vendorimport is E20/Tracking: move the pure lock codecs and neutral lock types into formats/ so formats imports nothing from vendor or redirect #833 territory) and the PEP 425 rule itself (Fix interpreter-bound wheels treated as portable (#1048) #1053).Acceptance criteria
NO_URL/ "no hash-pinned pure wheel" error, not to a mismatched pair.[[package]]unit. Add a table test overpy3-none-any,py2.py3-none-any,cp311-none-any,*-abi3-*, a hashless pure wheel and a#sha256=URL.pure_wheel_rejects_short_hash_missing_hash_and_non_http_url,uv_lock_inventories_pure_wheels,uv_lock_one_line_wheels_array_pairs_the_pure_wheel_with_its_own_hashandpoetry_lock_carries_the_pure_wheel_sha256_when_listedstay green.Dependencies
formats/vendor/redirectlayering.Backlog review — 2026-10-08
Priority: P1 → P3. Digest verification rejects the mismatched recovery wheel. This is a fail-closed recovery/diagnostic issue, explicitly low severity; keep active #1121.