Skip to content

Fix requirements pin spellings not contesting VEX (#1602) - #1632

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-requirements-pin-contest-pep440
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-requirements-pin-contest-pep440

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1602

Summary

A requirements.txt line that pins the wired release as ===1.2.3, ==1.2.3.0, ==v1.2.3 (or six==1.16 for 1.16.0) installs the same upstream wheel as ==1.2.3. Before this PR, only the canonical spelling contested another lock's Socket wiring, so vex attested not_affected (exit 0) for a project whose pip install -r requirements.txt ships the unpatched release. Now every one of those spellings withholds the statement with patched_ref_unattributable, the same way ==1.2.3 already did.

Root cause

The requirements reader (vex/discover/pypi_other.rs, registry_pin) took evidence only through utils::requirements::exact_pin. That function rejects === and versions that don't start with a digit, and it returns 1.2.3.0 verbatim. The cross-lock contest (ResolvedElsewhere::covers) then compared canonical purl strings, so @1.2.3.0 never matched @1.2.3.

Fix

  • registry_pin records any requirement that can only select one release:
    • an == pin in any valid PEP 440 spelling;
    • an === pin, recorded with the new ResolvedElsewhere::as_spelled flag because pip compares === as a string.
  • ResolvedElsewhere::covers compares PyPI versions under PEP 440 equality (purl_key::pypi_same_release), except for as_spelled entries. It checks names first, so the PEP 440 parse runs only for pairs with the same name.
  • The contest's "this tree wires the release itself" exclusion uses the same equality. Without that, a tree that wires six@1.16.0 while another lock wires six@1.16 would now be counted as a contester.
  • Ranges and wildcards (~=1.2.3, ==1.2.*) are still not evidence. What they install depends on the index, which the issue also calls a design question.
  • CLI_CONTRACT.md "Contested locks" now documents this. The exact_pin comment in the lock inventory says how the two readers differ.

The npm, PyPI and gem wrappers don't need changes: this is Rust discovery logic only.

Tests

Issue case Test Without fix With fix
#1602 ===X, ==X.0, ==vX, padded + marker (real CLI, Pipfile.lock wired) e2e_vex_lockfile pipenv::requirements_pin_spellings_contest_the_lock_wiring FAILED (vex exit 0, at vex_pipenv_pip_common/mod.rs:1385) ok
#1602 every spelling (===, ==1.16.0.0, ==v1.16.0, ==1.16, case/marker/extras/hash) contests; ==1.16.1, ~=, ==1.16.*, ===1.16, other name don't socket-patch-core vex::discover::pypi_other::tests::issue_1602_requirements_pin_spellings_contest_wiring (new, same file as the fix) ok
A tree that wires another spelling of the release isn't a contester ...::issue_1602_a_tree_wiring_another_spelling_contests_nothing FAILED with the old exact-string wiring check ok

Commands run locally:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core --all-features --lib -- vex::discover production_code_classifies_purls: 416 passed on 1b96ed7
  • cargo test --workspace --all-features --no-fail-fast (on 65efed5): 232 test targets ok. The 10 failures are sandbox-only. Nine are read-only-permission / write-failure tests that can't fail when running as root (uid 0): copy_tree::relax_loop_must_not_traverse_symlinked_root ("removal cannot succeed under a read-only parent"), vlt_heal::an_unremovable_hidden_lock…, pypi_poetry::wire_write_failure…, pypi_requirements::wire_failure_rolls_back…, 3 in in_process_redirect, 2 in repair. The tenth, e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed, failed because the sandbox proxy blocked patches-api.socket.dev. The one real failure, the purl-type lint, is fixed in 1b96ed7.

/code-review high

Fixed: the wiring exclusion compared strings exactly; === was read under PEP 440; duplicate helper; the hard-coded version in the e2e case; the stale exact_pin comment.
Not fixed, with reasons:

  • A public ==X pin also matches X+local under PEP 440. That gap already existed before this PR (a canonical ==2.1.0 never contested a wired 2.1.0+cpu). Lock entries resolve exact versions, so folding local labels into covers would be wrong for them. It needs a requirements-only rule, which is a separate follow-up.
  • Move PEP 440 identity into PurlKey. PurlKey keys maps across every ecosystem, and PyPI is kept spelling-exact there on purpose (pypi_equivalent_purls exists for this). Changing it would touch much more than this fix needs.
  • Regex cost in the contest loop. The names are compared first, so the parse runs only for ref and entry pairs of the same package.
  • Scan can't rewrite an === line, so the contest persists. That's correct: pip installs upstream for that line, so the attestation must stay withheld until the user changes it. === now matches only its exact string.

https://claude.ai/code/session_01FuB4p2UQANHrpx4dHRwddS


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A requirements.txt line that pins the wired release as `===1.2.3`,
`==1.2.3.0` or `==v1.2.3` installs the same upstream wheel as
`==1.2.3`, but only the canonical spelling contested another lock's
Socket wiring. `vex` attested `not_affected` for a project whose
`pip install -r requirements.txt` ships the unpatched release.

The requirements reader now records any pin that can only select one
release (an `==` pin in any valid PEP 440 spelling, or `===`), and
the cross-lock contest compares PyPI versions under PEP 440 equality,
the way pip resolves the pin. Ranges and wildcards are still not
evidence, since what they install depends on the index.

Fixes #1602

Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the pin-spelling contest:

- pip compares a `===X` pin as a string, so it now contests only a
  wiring of exactly that version string. `===1.16` no longer withholds
  an attestation for release 1.16.0, which pip can't install from it.
- A lock tree that wires the release itself, under another PEP 440
  spelling, is no longer counted as a contester of the other lock.
- Reuse purl_key::pypi_same_release instead of a second copy of it.

Assisted-by: Claude Code:claude-opus-5-5
The repo's lint test requires purl types to be read through
Ecosystem::from_purl rather than an inline prefix check.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 17, 2026 17:42
@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 1b96ed7. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Labeled Ready for review (burn-down agent).

  • Head: 1b96ed7
  • CI: 86/86 green (36 success, 50 skipped). No main-wide failures.
  • Bugbot: reviewed 1b96ed7, no findings. 0 open threads.
  • Mergeable with no conflicts. CHANGELOG.md is not touched.
  • Reviewer focus: requirements pin spelling normalization in vex/discover/pypi_other.rs and vendor/lock_inventory/pypi.rs, plus the CLI_CONTRACT.md wording.

Slack announcement not sent this run (the Slack send tool was unavailable). The next run will retry.


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

2 participants