Skip to content

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

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-vex-product-pep440-version
Oct 22, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 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

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 22, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final reviewer (merge-queue backup): dequeued and re-added this PR once at 18:55 UTC because its queue entry was stuck.

  • In the queue since 16:55 (~2h). Its merge_group CI run 39822372512 attempt 1 was fully green at 17:38 (30 success, 18 skipped), but the PR was not merged.
  • Attempt 2 (re-run at 18:40) left job clippy (120015236768) and e2e-build (ubuntu-latest) stuck in_progress, although every step of the clippy job finished by 18:41:50 and the run itself reports completed/success. The required clippy check on the group commit b12ecc1 would never settle.
  • Action: removed it from the queue and re-added it (squash). The PR's own head 9e4f1a7 is still approved, mergeable and green. I won't re-add it again if it gets stuck a second time.

Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

The merge queue removed this PR at 19:41 UTC with CI_FAILURE. The failure was in the runner environment, not in this PR's code.

  • Failing check: yarn-classic 1.22.22 in merge_group run 39840895419 (group 4fcec0a = main + Fix npm 12 pinned allowScripts lost on scan (#1595) #1657 + this PR), and therefore ci-ok. coverage and one e2e shard were cancelled when it failed.
  • What failed: 2 of 40 tests in e2e_redirect_yarn_classic_build: classic_empty_range_workspace_key_is_pinned and classic_file_directory_copy_under_another_name_is_named_and_not_attested. Both panic at tests/common/yarn_classic_vex.rs:106 with "corepack yarn@1.22.22 unavailable (corepack absent or not fetchable)" because SOCKET_PATCH_YARN_E2E_REQUIRED=1 forbids skipping. Earlier in the same job, the runner couldn't reach the network: lookup cache.depot.dev on 9.9.9.9:53: ... i/o timeout. So corepack couldn't download yarn.
  • Why it isn't this PR's: this PR only changes the PEP 440 version of a pyproject.toml VEX product (utils/pep440.rs, vex/product.rs) and touches nothing under yarn. The same job passed on this PR's head 9e4f1a7 and in its earlier merge_group run 39822372512.
  • Fix: none exists in-repo; this is the same runner flakiness tracked in Depot runners lost mid-job evicted 2 merge-queue entries in 27h #1660. Re-queueing should be enough.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[final reviewer] Re-added to the merge queue at 19:49 UTC after the 19:41 eviction. The cause was a runner network failure in yarn-classic 1.22.22 (corepack couldn't fetch yarn; DNS timeout to cache.depot.dev), not this diff, as described above. Head 9e4f1a7 is approved, green and clean.


Generated by Claude Code

Merged via the queue into main with commit b70eb6e Oct 22, 2026
86 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-vex-product-pep440-version branch October 22, 2026 20:02
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

3 participants