Skip to content

Fix VEX product purl using raw PEP 440 version (#1589) - #1653

Open
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
mainfrom
agent/fix-vex-product-pep440-version
Open

Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
mainfrom
agent/fix-vex-product-pep440-version

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1589

Summary

The OpenVEX product purl detected from pyproject.toml now 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 versioned 1.0.0-RC1 got the product pkg:pypi/my-service@1.0.0-RC1, which matches nothing in an SBOM that lists 1.0.0rc1, so consumers silently ignored every not_affected statement.

Root cause

vex/product.rs parse_pyproject passed the raw [project].version / [tool.poetry].version / [tool.pdm].version string (only trimmed) into pypi_purl. Nothing normalized it.

Fix

  • utils::pep440::normalize prints a version the way str(packaging.version.Version(v)) does: leading v and surrounding whitespace dropped, a zero epoch dropped, leading zeros dropped from every number (trailing .0 release segments kept), canonical a/b/rc/.post/.dev spellings, and a lowercased, dot-separated local label.
  • parse_pyproject uses it. A version that isn't valid PEP 440 keeps its current verbatim spelling.
  • pep440::parse now keeps the release as written. Equality still ignores trailing .0 segments by trimming in versions_equal. Local labels are lowercased with Unicode rules (str.lower()), since the case-insensitive regex accepts U+212A KELVIN SIGN, which packaging prints as k.

Tests (red → green)

Issue case Test Without fix With fix
#1589 PEP 621 spellings (1.0.0-RC1, v1.2, 01.02.0, 1.0.0+Local.1, 1.0.post0) vex::product::tests::pyproject_normalizes_pep621_version FAILED ok
#1589 Poetry comment (1.0.0-beta.1, -rc.2, RC1, 2026.01.05, -post.1, -dev.3, +Local.Build) vex::product::tests::pyproject_normalizes_tool_poetry_version FAILED ok
Normalizer table, every spelling cross-checked against packaging 's output utils::pep440::tests::normalize_matches_packaging FAILED ok
CLI vex auto-detected product (PEP 621 + [tool.poetry]) e2e_vex::auto_detect_pyproject_normalizes_pep440_version FAILED (left: "pkg:pypi/my-service@1.0.0-RC1") ok
Invalid PEP 440 stays verbatim vex::product::tests::pyproject_keeps_non_pep440_version_verbatim ok ok

Commands run locally:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core --lib --all-features: 6569 passed, 4 failed. The same 4 fail on origin/main in 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_untouched and pypi_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 passed
  • The full cargo test --workspace --all-features couldn'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/ and gem/ only dispatch to the binary.

/code-review high: findings not fixed

  • Raw + / ! in the purl for local and epoch versions (@1.0.0+local.1): this predates the PR. Every purl builder in utils::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.json version not semver-cleaned (v1.2.3): this is a different ecosystem with a different cause, so it's out of scope here.
  • Rust \s vs Python \s on U+001C–U+001F: packaging strips these ASCII separator controls and Rust's \s doesn't, so such a version keeps today's verbatim behaviour. The path is far-fetched, and a pyproject.toml version with control characters isn't realistic.
  • Fixed: the duplicate regex pass, Unicode lowercasing of local labels, and the module docs.

Generated by Claude Code

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
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 21, 2026 05:05
@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 9e4f1a7. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review.

  • Head: 9e4f1a7
  • CI: 36/36 checks green (50 skipped)
  • Bugbot: reviewed this head (check Cursor Bugbot success), no findings, no open review threads
  • Mergeable, no conflicts; no CHANGELOG.md change

Slack announcement not sent: this run has no Slack send tool.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at 9e4f1a7.

  • CI: 36/36 checks green on the head (50 skipped), no main-wide failures.
  • Bugbot: reviewed 9e4f1a7, no findings; no open review threads.
  • Mergeable against main, no CHANGELOG changes.
  • Slack announcement not sent this run (no Slack send tool available); the next run will retry.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (for the approver)

What it does. socket-patch vex auto-detects the product from pyproject.toml; its purl now carries the PEP 440 normalized version (1.0.0-RC1 → 1.0.0rc1, v01.02 → 1.2), the spelling uv.lock, wheel METADATA, PyPI and SBOMs record. Before, a non-canonical version produced a product purl that matched nothing in an SBOM, so consumers silently ignored the VEX statements (#1589). A version that isn't valid PEP 440 keeps its spelling.

Risk: low. New pep440::normalize plus one call site. The one shared-code change: parse now keeps trailing .0 release segments and versions_equal trims them itself, so equality is unchanged for its callers (pipenv, requirements, hatch, purl_key); equivalent_release_spellings doesn't use parse. Local labels now lowercase with Unicode rules, which only matters for U+212A KELVIN SIGN, matching packaging.

Look here

Verified. Read the full diff and every pep440:: caller to confirm equality semantics are unchanged; spot-checked the table against packaging rules (epoch, -N post, rev/r, preview/c → rc, numeric local parts). Ran cargo test -p socket-patch-core --lib -- pep440 vex::product locally on 9e4f1a7: 156 passed, 0 failed. CI 86/86 settled on 9e4f1a7 (36 success, 50 path-skipped), ci-ok and clippy green. Bugbot reviewed 9e4f1a7: no findings; no review threads. Mergeable, CHANGELOG.md untouched.

Changes I made. None.

Open questions. None. Note for consumers: VEX documents for projects with non-canonical versions will now carry a different product @id than before; that is the fix.

Auto-merge is armed, so approving sends it straight to the merge queue.


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