Repository navigation
Fix VEX product purl using raw PEP 440 version (#1589) - #1653
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A pyproject.toml version spelled 1.0.0-RC1, v1.2 or 01.02.0 went into the OpenVEX product purl verbatim, while uv.lock, the built wheel, PyPI and SBOMs all record the PEP 440 normalized form (1.0.0rc1, 1.2, 1.2.0). Consumers match products by exact purl, so every not_affected statement was silently ignored. The product now carries the normalized version for PEP 621, [tool.poetry] and [tool.pdm] projects; a version that isn't valid PEP 440 keeps its spelling. Fixes #1589 Assisted-by: Claude Code:claude-opus-5-5
Parse each version once for normalize, keep trailing-zero trimming in the equality check, and lowercase local labels the way Python does so a non-ASCII letter the regex accepts prints as packaging prints it. Also documents the normalized product version and adds a CLI test for the vex command's detected product. Refs #1589 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 9e4f1a7. Configure here.
|
Burn-down agent: labeled Ready for review.
Slack announcement not sent: this run has no Slack send tool. Generated by Claude Code |
|
Burn-down agent: labeled Ready for review at
Generated by Claude Code |
Final review brief (for the approver)What it does. Risk: low. New Look here
Verified. Read the full diff and every Changes I made. None. Open questions. None. Note for consumers: VEX documents for projects with non-canonical versions will now carry a different product Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1589
Summary
The OpenVEX product purl detected from
pyproject.tomlnow carries the PEP 440 normalized version, the one uv.lock, the built wheel's METADATA, PyPI and SBOMs (uv's CycloneDX export, syft) record. Before, a project versioned1.0.0-RC1got the productpkg:pypi/my-service@1.0.0-RC1, which matches nothing in an SBOM that lists1.0.0rc1, so consumers silently ignored everynot_affectedstatement.Root cause
vex/product.rsparse_pyprojectpassed the raw[project].version/[tool.poetry].version/[tool.pdm].versionstring (only trimmed) intopypi_purl. Nothing normalized it.Fix
utils::pep440::normalizeprints a version the waystr(packaging.version.Version(v))does: leadingvand surrounding whitespace dropped, a zero epoch dropped, leading zeros dropped from every number (trailing.0release segments kept), canonicala/b/rc/.post/.devspellings, and a lowercased, dot-separated local label.parse_pyprojectuses it. A version that isn't valid PEP 440 keeps its current verbatim spelling.pep440::parsenow keeps the release as written. Equality still ignores trailing.0segments by trimming inversions_equal. Local labels are lowercased with Unicode rules (str.lower()), since the case-insensitive regex accepts U+212A KELVIN SIGN, whichpackagingprints ask.Tests (red → green)
1.0.0-RC1,v1.2,01.02.0,1.0.0+Local.1,1.0.post0)vex::product::tests::pyproject_normalizes_pep621_version1.0.0-beta.1,-rc.2,RC1,2026.01.05,-post.1,-dev.3,+Local.Build)vex::product::tests::pyproject_normalizes_tool_poetry_versionpackaging's outpututils::pep440::tests::normalize_matches_packagingvexauto-detected product (PEP 621 +[tool.poetry])e2e_vex::auto_detect_pyproject_normalizes_pep440_versionleft: "pkg:pypi/my-service@1.0.0-RC1")vex::product::tests::pyproject_keeps_non_pep440_version_verbatimCommands run locally:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-core --lib --all-features: 6569 passed, 4 failed. The same 4 fail onorigin/mainin this container because it runs as root, so the permission-denied setups don't deny anything: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_untouchedandpypi_requirements::wire_failure_rolls_back_already_written_files.cargo test -p socket-patch-cli --all-features --test e2e_vex --test e2e_vex_redirect --test e2e_embedded_vex: 43 + 42 + 53 passedcargo test --workspace --all-featurescouldn't link every integration-test binary because the session's disk allowance ran out (21 GB target dir). CI runs the full suite.No wrapper changes are needed.
npm/,pypi/andgem/only dispatch to the binary./code-review high: findings not fixed
+/!in the purl for local and epoch versions (@1.0.0+local.1): this predates the PR. Every purl builder inutils::purl(pypi_purl,simple_purl, …) emits these characters unencoded, and so do the document's own component purls. Percent-encoding them belongs in its own change at that shared boundary. This PR leaves the product consistent with its components.package.jsonversion not semver-cleaned (v1.2.3): this is a different ecosystem with a different cause, so it's out of scope here.\svs Python\son U+001C–U+001F:packagingstrips these ASCII separator controls and Rust's\sdoesn't, so such a version keeps today's verbatim behaviour. The path is far-fetched, and apyproject.tomlversion with control characters isn't realistic.Generated by Claude Code