Fix lock-only requirements.txt discovery (#412, #523) - #530
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A requirements.txt pin written with spaces around `==` (`six == 1.15.0`), or in the legacy `six (==1.15.0)` form, was not recognized as an exact pin. On a fresh checkout with no venv, lock-only discovery never asked the patch API about the package. scan reported "No patches available" and pip installed the unpatched release. pip treats these forms exactly like `six==1.15.0`, and so does the hosted rewriter. exact_pin now parses the name, extras, optional parentheses and `==` the way pip does. Only options may follow the version, so a range such as `six==1.0,<2` is no longer mistaken for a pin. Lock-only `vex` evidence uses the same rule and gets the fix too. Fixes #523 Assisted-by: Claude Code:claude-opus-5-5
On a fresh checkout, lock-only discovery read only the root requirements.txt and skipped its `-r` include lines. A pin kept in an included file (`-r requirements/base.txt`) was never sent to the patch API. scan reported "No patches available", exited 0, and pip installed the unpatched release, in both hosted and vendored mode. The lock inventory now walks the same in-root include tree the vendored writer edits, using the writer's include parser. The walk runs through the project view, so the in-memory hosted engine sees it too. `-c` constraints and out-of-root includes are still not followed. An index option in any file of the tree now makes hashed pins unverifiable everywhere, matching how pip applies options globally. Fixes #412 Assisted-by: Claude Code:claude-opus-5-5
|
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 acb0abb. Configure here.
|
Ready for review at
Generated by Claude Code |
|
Reviewed No actionable correctness/security regressions found. Checked pin parsing, relative includes/cycle guards, in-memory parity, global index-option handling, and the shared writer/VEX behavior. Validation: |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #412
Fixes #523
Summary
A pip project with no venv yet (a fresh clone, a CI job or a Docker build) is discovered through the lockfile-only requirements.txt inventory. That inventory missed two common ways of writing pins, so those packages never reached the patch API.
scanprinted "No patches available" and exited 0 in both hosted and vendored mode, andpip install -r requirements.txtthen installed the unpatched release. With this PR, discovery reads the same pins pip installs.Root cause
inventory_requirements_txtinvendor/lock_inventory/pypi.rsread requirements differently from both pip and socket-patch's own writers:scanignores pins in requirements.txt-rincludes, so a fresh checkout reports "No patches available" and installs unpatched (hosted and vendored) #412: it read only the rootrequirements.txtand skipped-rlines without following them. The vendored writer, by contrast, already walks every in-root include.six == 1.15.0,six (==1.15.0)), so a fresh checkout reports "No patches available" and pip installs the unpatched release #523: it recognized pins withutils::requirements::exact_pin, which only looked at the first whitespace token. Sosix == 1.15.0,six ==1.15.0,six== 1.15.0,six[x] == 1.15.0and the legacysix (==1.15.0)weren't seen as pins. pip and the hosted rewriter (requirement_version) both accept all of these.Changes
utils::requirements::exact_pinnow parses the requirement the way pip does: name, then optional extras, optional parentheses and==, with whitespace allowed between them. Only options (--hash=…) may follow the version. As a side effect, a range likesix==1.0,<2is no longer mistaken for the pin1.0,<2. Lock-onlyvexdiscovery (vex::discover::pypi_other) uses the same function and gets the fix too.vendor::pypi_requirements::requirements_includesis the include parser pulled out of the vendored writer's walk, now shared.is_in_root_relis nowpub(crate).inventory_requirements_txtreads the root file plus every in-root-r/--requirementinclude through a newrequirements_treewalk. The walk runs over theProjectView, so the in-memory hosted engine reads the same tree. It resolves each include relative to the including file, guards against cycles, never follows-cconstraints, and skips out-of-root or absolute includes, matching the writer. Thepublic_indexrule (an index option makes hashed pins unverifiable) now spans the whole tree, because pip applies options from any file globally.In hosted mode, an installed pin that lives only in an include is documented as rewrite-refused (
redirect_requirements_entry_not_found). Lock-only checkouts now reach that same documented path instead of silently skipping the package.Tests (red → green)
utils::requirements::tests::exact_pin_is_the_shared_registry_pin_rule(spaced, tab, paren and extras forms;==1.*,,<2and stray-paren negatives)vendor::lock_inventory::tests::requirements_spaced_and_parenthesised_pins_are_inventoriedscan_requirements_lock_only::lock_only_scan_discovers_spaced_pins(built binary; hosted + vendored; purls must reach/patches/batch)vendor::lock_inventory::tests::requirements_in_root_includes_are_inventoried(nested and relative includes,--requirement=,-rfile, cycle,-cand../not followed,ProjectView::Memory)vendor::lock_inventory::tests::requirements_index_option_in_an_include_spans_the_treescan_requirements_lock_only::lock_only_scan_discovers_included_pins(built binary; hosted + vendored)Validation
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: 214 targets. 12 tests fail only because this sandbox runs as root, which ignores the read-only or unremovable fixtures they rely on (*_write_failure_*,*unremovable*,relax_loop_must_not_traverse_symlinked_root). All 12 pass when re-run as a non-root user. Everything else passes.cargo test -p socket-patch-cli --test e2e_vex_build -- pip:: --ignored(real pip, every major, hosted + vendored → manifest-less VEX): ok.cargo fmt --all -- --checkis not clean onmainitself with the pinned 1.93.1 toolchain (128 files), and CI doesn't run it. The files this PR touches are rustfmt-clean, and no unrelated files were reformatted.acb0abb: 477 success, 6 skipped (matrix templates and canary/downgrade), 0 failed.native (ubuntu-latest, 2.9.3)failed once in its PDMextras vendoredlane (appliedExactlyOne). That lane readspdm.lockand never reaches the requirements.txt inventory, and it passed on its single re-run.acb0abb: no issues found.Follow-ups
None.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JASDoVJNrZUBy9kd7fWPxN
Generated by Claude Code