Skip to content

Key NuGet purls by the normalized version in PurlKey (#1202) - #1230

Merged
Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
arch-refactor/1202-nuget-purl-identity
Oct 9, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
arch-refactor/1202-nuget-purl-identity

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Refs #1202 (this PR is the PurlKey slice. The issue stays open for the crawler directory lookup and for moving the normalizer into formats::nuget.)

Summary

PurlKey now keys a NuGet purl by its normalized version, using the same normalize_nuget_version that the vendored backend and upstream restore already use. Before this change, a NuGet entry vendored at a non-normalized version, such as pkg:nuget/Newtonsoft.Json@13.0.3.0 (the 4-part packages.config spelling), was wired against the lock's resolved: "13.0.3". Every liveness reader then compared it through PurlKey, which only lowercased the version, and judged it unused. As a result, scan --prune silently reverted a live patch, vendor --check reported the wiring as lost, and vex did not attest it.

Why (leverage)

  • B 1: the prune, check and VEX half of #1202.
  • D 1: NuGet release identity had two rules, the vendored normalize_nuget_version and PurlKey's lowercase-only arm. Now there is one.
  • U 1: the remaining crawler lookup and the formats::nuget move now build on a single identity.
  • R L.
  • Register row E93 (register/10-audit-ecosystems.md). Living document: doc/06-discovery-vex.md, the "NuGet version identity is defined twice" bullet.
  • crawlers/nuget_crawler.rs and vendor/nuget_feed.rs are changed by open PRs Classify purls through Ecosystem::from_purl in free files (#747) #1126 and Resolve the org once per run and route every API call through it (#648) #1041, so this slice doesn't touch them. PurlKey imports the existing pub(crate) normalizer, the same way upstream/nuget.rs already does.

What changed

  • utils/purl_key.rs: PurlKey::new goes through release_identity, which handles composer as before and adds a new nuget_identity (pkg:nuget/<id>@<normalize_nuget_version(v)>). An empty id or version keys as its canonical spelling. canonical_base_purl is unchanged and keeps the as-written version.
  • New utils/purl_key_nuget_vendor_tests.rs (a child test module).

Nothing was deleted: the lowercase-only NuGet version rule is now the spelling layer only.

+ −
production 37 (about half are doc comments) 7
tests 242 1

Behavior

Two NuGet purls that differ only in version spelling (1.0.0.0 vs 1.0.0, 1.02.3 vs 1.2.3, +build metadata, pre-release case) now share one key. Any report that prints a PurlKey string (rollout and policy purl) shows the normalized NuGet version, the same way composer keys already show 3.0.2.0. JSON shapes, error codes and exit codes are unchanged.

Test evidence

  • nuget_version_spellings_share_the_normalized_key: the issue's acceptance vectors.
  • nuget_key_agrees_with_the_vendored_version_match: a 30×30 sweep checking that PurlKey::same equals "case-insensitive id equal ∧ normalize_nuget_version equal", the rule locked_at and upstream restore apply.
  • nuget_vendored_at_a_four_part_version_stays_live: test_support::vendor_nuget("…@13.0.3.0") against a lock resolving 13.0.3. It passes assert_fresh_vendor_in_use, which is the prune GC's vendor_entry_in_use, and asserts vendor_entry_in_use == Some(true) and vendor_entry_live.
  • Red → green: with only the production line reverted to main's rule, all 3 tests fail. The vendor test fails at test_support.rs:498: "the prune GC would revert a freshly vendored nuget entry". With the change, all 3 pass.
  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo test -p socket-patch-core --lib: 5890 passed. The 4 failures are the known root-only sandbox tests (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, pypi_requirements::wire_failure_rolls_back_already_written_files). They fail on main too.
  • CLI suites: in_process_scan 27, in_process_vendor 130, e2e_vex_redirect 31 and in_process_remote_ecosystems_apply 12, all passing.
  • I didn't add the CLI scan --prune regression test the issue asks for. No CLI fixture vendors NuGet through a mocked service yet, and run_vendor_gc's verdict is exactly vendor_entry_in_use, which the core test pins. No dotnet in the sandbox, so e2e_nuget_dotnet_build was not run.

Risk

Low. The change only widens equality for NuGet version spellings that NuGet itself treats as one package. Non-NuGet keys are byte-identical, which the existing purl_key and rollout tests pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLYGeuZVwXNvU9MGhqEeQR


Note

Medium Risk
Widens NuGet PurlKey equality across the codebase (GC, VEX, policy maps); behavior matches NuGet’s own identity rules but any caller that assumed byte-exact NuGet versions could see different grouping.

Overview
PurlKey now treats NuGet package releases as the same when their versions normalize identically, aligning liveness checks (VEX, vendor --check, scan --prune) with the vendored backend and lock resolved fields that already use normalize_nuget_version.

PurlKey::new routes through a shared release_identity helper (Composer unchanged, new NuGet path). Equivalent spellings such as 13.0.3.0 vs 13.0.3, padded segments, pre-release case, and +build metadata now share one key; canonical_base_purl still keeps the as-written version for display. Reported PurlKey strings for NuGet show the normalized version, similar to Composer’s release identity.

New unit and integration tests cover version equivalence, agreement with the vendored version matcher, and that an entry vendored at a four-part purl stays “live” against a lock resolving the normalized version (#1202).

Reviewed by Cursor Bugbot for commit 8c93493. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko Mikola Lysenko (mikolalysenko) added refactor Structural change: duplicated code or logic, missing abstraction, layering, dead code arch-refactor PR opened by the scheduled architecture refactor routine labels Oct 9, 2026
A NuGet package vendored under a non-normalized purl version, such as
the 4-part pkg:nuget/Foo@1.0.0.0 spelling packages.config projects
use, was wired against the lock's normalized `resolved` version but
judged by PurlKey, which only lowercased NuGet versions. VEX, vendor
--check and the scan --prune GC then saw the live entry as unused, so
prune silently reverted a working patch.

PurlKey now folds a NuGet version through the one
normalize_nuget_version the vendored backend and upstream restore
already use, the same way it folds composer release spellings. The
canonical spelling is unchanged. Tests pin the issue's vectors, sweep
PurlKey against the vendored version match, and vendor at @13.0.3.0
against a lock resolving 13.0.3, which must stay in use and live.

Refs #1202

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 9, 2026 06:14
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 9, 2026
Assisted-by: Claude Code:claude-opus-5-5

@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 8c93493. Configure here.

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 9, 2026
@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 9, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head 8c9349309ec2abc20d9a4a49010a86d3902315c3.

  • CI: all checks green on this head (success/skipped only).
  • Bugbot: reviewed 8c934930, no new issues; no unresolved review threads.
  • Mergeable against main, no CHANGELOG.md change.
  • Slack announcement not sent this run (Slack send unavailable); the next run will retry.

Generated by Claude Code

Merged via the queue into main with commit 8439e55 Oct 9, 2026
294 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the arch-refactor/1202-nuget-purl-identity branch October 9, 2026 07:41
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 9, 2026
…elease notes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-refactor PR opened by the scheduled architecture refactor routine Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review refactor Structural change: duplicated code or logic, missing abstraction, layering, dead code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants