Repository navigation
Compare purls through one PurlKey type - #1045
Merged
Merged
Conversation
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
Review of #1045 found purl equality checks that still bypassed the key: - hosted memory batch_search matched response packages to their asking roots by spelling, so a `Newtonsoft.Json` / `typing_extensions` / composer `@3.0.2.0` answer fell back to every root in the chunk; - lock_inventory::lookup had its own matcher (PEP 503 only, exact version), so a composer API purl `@3.0.2.0` missed the lock's `3.0.2`; - the vendored blob harvest and apply's mismatch-blob record filter compared qualifier-stripped strings; - rollback's superseded_by_hosted (new on main) used the deleted canonical_base_purl / composer_purls_equivalent pair; - rollout::stage::qualified_key duplicated PurlKey::qualified (deleted); RecordedIndex now keys by PurlKey; - Gradle scan keys use canonical_base_purl (Maven is case-sensitive, so the canonical spelling is the identity there). Regression tests: a mixed-case NuGet batch answer is credited only to its asker; lookup matches composer padding and PEP 503 spellings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
force-pushed
the
arch-fix/purl-key
branch
from
October 7, 2026 16:47
583e93c to
af4a4ed
Compare
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 7, 2026 16:47
One type answers "do these two purls name the same package release": qualifiers and subpath stripped, components percent-decoded, the type lowercased, PyPI names PEP 503-folded, NuGet names and versions case-folded, Composer names case-folded and Composer versions keyed by release identity (3.0.2 = v3.0.2 = 3.0.2.0). PurlKey::qualified keeps release variants apart. canonical_base_purl (moved here from vex::discover) stays the display spelling the key is built from. Property tests check that every spelling variant of a release shares one key, that no other release does, and that the Composer half equals release equivalence over the whole shared vector file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Six "same package" relations disagreed, so a NuGet or PyPI spelling
difference between the API purl, the crawl and the ledger could prune a
live manifest entry, skip a takeover or miss a remove target. Every one
now compares PurlKeys, and the copies are gone:
- deleted utils::purl::purl_eq, composer_version::{purl_identity_key,
composer_purl_identity, composer_purls_equivalent,
composer_bases_equivalent}, rollout::canonical_base_purl,
policy::canon, vex::discover::{canonical_base_purl, same_package},
vex_sources::same_package and policy's canonical_pypi_purl;
- the ledger matchers (covers_purl, purl_keys/purl_keys_cover,
lookup_entry_kv), the hosted/vendored overlap, the hosted pin lookup,
apply --check's Go set, the takeover ledger drop and every local
normalize_purl(strip_purl_qualifiers(..)) closure used as a key now
build PurlKeys; vendored key sets are HashSet<PurlKey>.
Fixes B20: scan --prune no longer GCs a NuGet entry the API spelled
Newtonsoft.Json while the global-cache crawl reports newtonsoft.json
(update detection and redirect candidates use the same key). Fixes B73:
remove/rollback identifiers fold PEP 503 and NuGet case. canonical_purl
keeps only its display role and its doc no longer claims identity.
Policy filter specs stay case-insensitive (they compare the folded key
lowercased), and policy/rollout report purls are the PurlKey spelling,
so PyPI/NuGet names there appear folded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The wiremock fixture had to serve the crawler's lowercase purl so scan's GC pass would not prune the manifest entry. Serve the API's real mixed-case Newtonsoft.Json spelling for the patch instead; scan --sync must now keep it, and agent VEX names the manifest key's spelling. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review of #1045 found purl equality checks that still bypassed the key: - hosted memory batch_search matched response packages to their asking roots by spelling, so a `Newtonsoft.Json` / `typing_extensions` / composer `@3.0.2.0` answer fell back to every root in the chunk; - lock_inventory::lookup had its own matcher (PEP 503 only, exact version), so a composer API purl `@3.0.2.0` missed the lock's `3.0.2`; - the vendored blob harvest and apply's mismatch-blob record filter compared qualifier-stripped strings; - rollback's superseded_by_hosted (new on main) used the deleted canonical_base_purl / composer_purls_equivalent pair; - rollout::stage::qualified_key duplicated PurlKey::qualified (deleted); RecordedIndex now keys by PurlKey; - Gradle scan keys use canonical_base_purl (Maven is case-sensitive, so the canonical spelling is the identity there). Regression tests: a mixed-case NuGet batch answer is credited only to its asker; lookup matches composer padding and PEP 503 spellings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- `scan --prune --dry-run` previewed the vendor GC's manifest drop with a qualifier-strip relation while the wet pass used PurlKey, so NuGet case, PEP 503 and composer padding variants were pruned for real but not in the preview. Both now call vendor::unused_vendored_manifest_keys. - LockfileSupplement.purls is a HashSet<PurlKey> built once, so the lockfile-only predicate is one hash lookup instead of re-keying the whole set on every miss. - Ledgers::hosted_vendored_overlap deduplicates by PurlKey, so two spellings of one release give one takeover warning. - get's hosted-claim set (rebased onto #940's new code) is a HashSet<PurlKey>; the new gem takeover pin lookup uses PurlKey::same. - composer_version: pin that the sentinel-free key PurlKey uses never lets a rejected spelling collide with an accepted one, and document it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
force-pushed
the
arch-fix/purl-key
branch
from
October 7, 2026 17:01
af4a4ed to
5953eb1
Compare
Collaborator
Author
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Lockfile-only skips installed spelling variants
- Changed lockfile_supplement to use PurlKey normalization for exclusion checks, matching the membership check behavior and preventing spelling variants from being incorrectly classified as not installed.
Or push these changes by commenting:
@cursor push aae44b9bf5
Preview (aae44b9bf5)
diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs
--- a/crates/socket-patch-cli/src/commands/scan/discovery.rs
+++ b/crates/socket-patch-cli/src/commands/scan/discovery.rs
@@ -75,7 +75,7 @@
if entries.is_empty() {
return out;
}
- let crawled_purls: HashSet<&str> = crawled.iter().map(|p| p.purl.as_str()).collect();
+ let crawled_purls: HashSet<PurlKey> = crawled.iter().map(|p| PurlKey::new(&p.purl)).collect();
let in_scope = |purl: &str| {
only.is_none_or(|list| {
socket_patch_core::crawlers::Ecosystem::from_purl(purl)
@@ -83,7 +83,7 @@
})
};
for entry in entries {
- if crawled_purls.contains(entry.purl.as_str()) || !in_scope(&entry.purl) {
+ if crawled_purls.contains(&PurlKey::new(&entry.purl)) || !in_scope(&entry.purl) {
continue;
}
let Some(pkg) = crawled_from_purl(&entry.purl, &common.cwd) else {You can send follow-ups to the cloud agent here.
Tanmay Singla (Tanmay182003)
approved these changes
Oct 7, 2026
lockfile_supplement excluded installed packages by literal purl but keyed the remainder by PurlKey, so a lock spelling that differed from the crawl only in NuGet case, PEP 503 form or composer padding was recorded as lockfile-only and lockfile_only_contains then flagged the live install package_not_installed (agent apply skip, vendor baseline pre-verify, [NOT INSTALLED] marker). Exclude crawled packages by PurlKey, the same relation the lookup uses, via a testable lockfile_only_packages helper. unused_vendored_manifest_keys only keyed the ledger key, but a golang ledger key can keep the module proxy's !x case encoding while the manifest holds the decoded spelling, which PurlKey does not bridge. The earlier wet GC also matched entry.base_purl; restore that through VendorEntry::covers_purl in both the wet GC and the scan --prune --dry-run preview so a reverted !burnt!sushi entry no longer leaves its BurntSushi manifest record and blobs behind. Co-Authored-By: Claude <noreply@anthropic.com>
Collaborator
Author
|
bugbot run 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 daa5ef8. Configure here.
Collaborator
Author
|
[agent] Ready for review at
Generated by Claude Code |
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Oct 8, 2026
Resolve conflicts against main's rustfmt pass and the containment-helper refactor (#1042): keep main's formatting, drop the removed `canon` / composer-identity helpers in favour of PurlKey, and route main's new apply-failure purl matching in get.rs (#955) through PurlKey::qualified / PurlKey::same so it agrees with the rest of the purl identity. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pick up #1093 so PR CI runs without the macOS legs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Oct 8, 2026
Resolve conflicts with #1035 (supersede lifecycle), #1038 (shared repo-root walk), and #1033 (unwired VEX copies): - #1035 added `vex::discover::same_release` and a second `canonical_base_purl` there. `PurlKey::same` already treats composer version spellings as one release, so every `same_release` call (vex_sources, rollback, discover, and `ledgers::hosted_pins_matching`) now uses `PurlKey::same`, and the duplicate helpers are dropped. - Both sides' new ledgers tests are kept. - The repo-root lookup in `policy` takes main's `utils::repo_root` version. `canonical_pypi_purl` stays deleted (nothing calls it now). - `vex` re-exports main's `UnattestedKind`. `UnwiredCopy::covers` compares purls with `PurlKey::same` rather than raw string equality. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Oct 8, 2026
Resolve the apply `--check` conflict with #1029. Main hoisted the vendored key set into `run_check`'s outer scope (`vendored_purl_keys`, already a `PurlKey` set via `purl_keys_cover`). The Go redirect check now reuses that set instead of loading the vendor ledger a second time. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
enabled auto-merge
October 8, 2026 09:02
Upstream moved setup-php's `v2` tag to d52fc211, so the `# v2` comment on the f3e473d1 pin no longer matches. zizmor's ref-version-mismatch now fails the org-required "Audit GitHub Actions" check on every PR. f3e473d1 is tag 2.37.2, the label composer-compatibility.yml already uses. This is the same one-line change as #1118, carried here so this PR can merge; whichever lands first, the other merges cleanly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
Resolve the hosted.rs conflict with #1045: this branch moved the takeover out of hosted.rs into hosted/takeover.rs, so keep that layout and port #1045's change there. The takeover's ledger drop now compares PurlKeys and the local canonical_purl key helper is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
Resolve get.rs and scan/vendor_flow.rs against #1045's PurlKey: the hosted takeover set is now a HashSet<PurlKey> shared by the fetch gate and the dry-run preview. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
get.rs: keep this branch's split (the download engine lives in agent_download.rs, short_uuid tests in ui/text.rs) and carry main's changes to the moved code over to agent_download.rs: store_verified_blob in write_blob_entry (#726), PurlKey comparisons in lock_text_refusals_for / apply_key_covers / fold_apply_failures (#1045), and the base64 engine. main's new get.rs tests (verified blob writes, linked blob dirs, base64 tolerance, the pnpm-lock FIFO guard) are kept. list.rs: keep ui::sentence_case (main only reformatted the line). scan/hosted.rs: add main's yarn_classic_outer closure (#1083). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
Conflicts resolved: - vex discover: main's UnattestedKind (#1033) replaces this branch's parallel UnattestedWhy; the #899 shrinkwrap case becomes UnattestedKind::NpmShrinkwrapOnly, mapped through vex_sources' unattested_note like the other kinds. - scan/policy.rs: the #812 shared-copy keys use PurlKey (#1045) instead of the removed canon(). - CLI_CONTRACT.md: main's contested/unattested bullets, plus this branch's #828 withheld-ref sentence, #899 shrinkwrap bullet and npm warning rows. - redirect npm.rs / scan mod.rs: imports and key types from both sides. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
Conflicts: - ruby_crawler.rs: keep the branch's bundler_config_files (the read-set paths of the snapshot probe) alongside main's BundlerEnv / BUNDLE_LOCKFILE; main's new gems.rb stat in bundler_loaded_manifest_with_env is added to those paths so a reused discovery re-checks it. - vex/discover/mod.rs: DiscoverCtx keeps both the branch's view-based constructor and hosted_hosts memo and main's per-ecosystem read log; Discovery keeps vlt_bundled_copies plus main's read and withheld. - testing/golden.rs: ignore all three bookkeeping fields. canonical_base_purl moved to utils::purl_key on main (#1045); the branch's three call sites now use it from there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 8, 2026
Resolve the conflicts with main's move of vendored in-use detection into discovery (#1050) and its PurlKey rename (#1045): - npm_flavor.rs: drop the branch's vendored_entry_in_use, now that Discovery::vendor_entry_in_use replaces it. Bun discovery already picks the live lock through bun_text_lock_drives, so the #735 regression test now runs through the shared in_use helper with a minimist entry. - lock_inventory/tests.rs: keep both sides' new tests. - vendor.rs: key the Bun reinstall dedupe on PurlKey. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 8, 2026
The merge of main (#1045) moved lock_text_refusals_for onto PurlKey, so canonical_purl is no longer referenced in agent_download.rs and the workspace clippy job fails on -D unused-imports. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
Architecture audit §3.B (purl equality). At least six relations answered "do these two purls name the same package release", and they disagreed:
purl_eqfolded percent-encoding and composer only;purl_identity_keystripped qualifiers and did composer only;canonical_base_purl(one invex::discoverand one inrollout);policy::canonandpackage_spec_matcheseach had their own folding;covers_purl,purl_keys_cover,lookup_entry, apply's Go--checkset, thehosted_pin_of/hosted_vendored_overlapkeys);normalize_purl(strip_purl_qualifiers(..))closures used as keys.The
canonical_purldoc comment also claimed an identity it doesn't provide.User-visible effects:
scan --prune/--syncdropped a live manifest entry, and GC'd its blobs, when the API spelled the purlpkg:nuget/Newtonsoft.Json@13.0.3and the NuGet global-cache crawl reportedpkg:nuget/newtonsoft.json@13.0.3. Update detection and redirect-candidate matching missed the same entry.docker_e2e_nuget.rsworked around this by serving a lowercase purl from the fixture. A PEP 503 spelling (typing_extensionsvstyping-extensions) was pruned the same way.remove/rollback pkg:pypi/typing_extensions@4.12.2found nothing when the recorded key wastyping-extensions. The same happened for a NuGet case variant.Related: #553 (the takeover ledger lookup now uses the same key, but there's no e2e for it yet), #748 / C20 (this is the identity half; the builders are untouched), #484 (Go
+vs%2Bis folded by the key).Change
utils::purl_keyPurlKeyis the release identity. It strips qualifiers and subpath, percent-decodes, lowercases the type, PEP 503-folds PyPI names, case-folds NuGet names and versions, case-folds Composer names, and keys Composer versions by release identity (3.0.2=v3.0.2=3.0.2.0).PurlKey::qualifiedkeeps release variants apart.canonical_base_purlmoved here. It is the display spelling the key is built from, and VEX product purls still use it.PurlKeys (the exceptions are listed under Deferred):notInstalledcheck now calls it);getpresence;lock_inventory::lookup, the vendored blob harvest, apply's mismatch-blob record filter, rollback'ssuperseded_by_hosted, and the rolloutRecordedIndex(review round, see below).HashSet<PurlKey>, so the compiler routes every construction through the key.package_spec_matchescompares the folded key case-insensitively. User filter specs stay as lenient as before, and a versioned Composer spec still matches by exact release identity.canonical_purlkeeps only its display role. Its doc no longer claims identity.docker_e2e_nuget.rsnow serves the API's real mixed-case spelling for the patch, andscan --syncmust keep that entry.Duplicate copies deleted
Purl equality relations went from 11 to 1 (
PurlKey, withcanonical_base_purlas its display spelling). Deleted:utils::purl::purl_eqcomposer_version::{purl_identity_key, composer_purl_identity, composer_purls_equivalent, composer_bases_equivalent}rollout::canonical_base_purlpolicy::canonvex::discover::{canonical_base_purl, same_package}rollout::stage::qualified_key(a copy ofPurlKey::qualified)lock_inventory::lookup's own PEP 503 / exact-version matchervex_sources::same_packagecanonical_pypi_purlThe ledger matchers (
covers_purl,purl_keys_cover,lookup_entry_kv) are now one-linePurlKeycomparisons. The ad-hoc key closures inscan/mod.rs,ledgers.rs,list.rs,hosted.rs,get.rsandapply.rsare gone.Behavior notes
filtered[]/retained[]purls and rolloutbase_purls are thePurlKeyspelling, so PyPI and NuGet names there now appear folded. Composer already showed its identity form.dev-branch name keeps its case incanonical_base_purl. Before this PR it was lowercased.Review round (rebased onto main @ 431b818)
removeandrollbackdon't PEP 503-normalise PyPI purl identifiers, soremove pkg:pypi/typing_extensions@4.7.1exits 1 "No patch found" whilegetaccepts the same identifier #1024, the PyPI half of B73) had brought backutils::purl::purl_eqwith a PEP 503 rule. This branch deletes it again:patch_matchesalready folds PEP 503 throughPurlKey. Fix remove/rollback missing PyPI name spellings (#1024) #1025's unit test now checks equality withPurlKey::qualified, and itsremove/pypi_name_spellings.rsCLI tests pass unchanged. Theget.rsconflict was resolved so Fix VEX attesting beside an unpatched same-lock copy (#935, #938, #939) #940's hosted-claim set is aHashSet<PurlKey>(notcanonical_purlstrings). The new gem-takeover pin lookup invendor.rsusesPurlKey::same, and the newrollback::superseded_by_hostedusesPurlKeyinstead of the deleted helpers.scan --prune --dry-runnow shares the wet vendor GC's manifest-key drop (vendor::unused_vendored_manifest_keys). Before this, the preview used a qualifier-strip relation, so NuGet case, PEP 503 and composer padding variants were pruned for real but missing from the preview.hosted::memory::discover::batch_searchmatches response packages to their asking roots byPurlKey. Before this, a respelled answer (such asNewtonsoft.Json) was credited to every root in the chunk.lock_inventory::lookupmatches byPurlKey, so a composer API@3.0.2.0finds the lock's3.0.2.LockfileSupplement.purlsis aHashSet<PurlKey>built once. The lockfile-only check is now one hash lookup, not an O(n) re-key on every miss.hosted_vendored_overlapdeduplicates byPurlKey, so one release spelled two ways gives one takeover warning.PurlKeykeeps the sentinel-freecomposer_version_keyso its string stays printable in policy and rollout reports. A new test over the vector file checks that a rejected spelling can never key the same as an accepted one: every accepted key is itself accepted, bare and behind av. The module docs now say so.Core API break (for the release notes; CHANGELOG untouched)
The following
socket-patch-corepublic items were removed or moved:utils::purl::purl_eqvex::canonical_base_purl(re-export) androllout::canonical_base_purl; useutils::purl_key::canonical_base_purlcomposer_version::{composer_purl_identity, purl_identity_key, composer_purls_equivalent}rollout::stage::qualified_keyThese signatures changed from
HashSet<String>toHashSet<PurlKey>:vendor::vendored_purl_keysVendorState::purl_keysvendor::purl_keys_coverlock_inventory::lookupnow also matches composer padding and NuGet/composer name case. Nothing else in the workspace references the removed items.Testing
Failing first: on
mainplus only the new tests, all three regression tests failed. On this branch they pass.cargo test -p socket-patch-core --lib -- folds_pep503_and_nuget_case nuget_case_spellings_are_one_vendored_package: 2 failed.cargo test -p socket-patch-cli --lib -- detect_prunable_keeps_case_and_pep503: failed. It pruned bothNewtonsoft.Jsonandtyping_extensions.New tests:
purl_keyproperty-style table tests (hand-written loops, not proptest):composer-version-vectors.json;gc.rs).purl.rs).scan --mode vendoredover a hosted pin skips the takeover (purl case mismatch), keeps the hosted feed wired, writes no vendor ledger and still reports success #553-shape ledger test (state.rs).Commands run on this branch (macOS):
cargo test -p socket-patch-core -p socket-patch-cli --libdigest::production_digests_go_through_the_helpers, which was already red onmainand is fixed by Fix main CI red on stale digest pending-list entries #1016 (now merged; I rebased onto it after the run).cargo test -p socket-patch-core --tests --no-fail-fast: all 36 integration suites pass. The lib failure is the same pre-existing one.cargo test -p socket-patch-cliwith these integration tests: scan, remove, rollback, apply, get, vendor, cli, e2e_scan, e2e_nuget, e2e_composer, e2e_composer_version_identity, e2e_socket_yml_policy, scan_rollout_e2e, hosted_memory_{rollout,engine,parity}, covgap_commands_{scan_mod,scan_hosted,rollback,get,vendor,vex}, global_scope_project_state, in_process_{remove_repair_lifecycle,rollback_vendored,vendor}, e2e_vex, e2e_vex_vendor, e2e_vex_redirect, e2e_embedded_vex, coverage_fix_scan_hosted_dryrun_vendored. All passed.cargo clippy -p socket-patch-core -p socket-patch-cli --all-targets -- -D warnings: no findings in any touched file.jvm_jar.rs,prebuilt_common/mod.rsand a macOS-only unused variable inpython_crawler.rs.mainkeep their old formatting.cargo build -p socket-patch-core -p socket-patch-cli --all-targetscargo clippy -p socket-patch-core -p socket-patch-cli --all-targets: 0 findings in touched files. The pre-existing lints in untouched files listed above remain.cargo test -p socket-patch-core --lib: 5641 passed (the digest failure is gone now that Fix main CI red on stale digest pending-list entries #1016 is on main).cargo test -p socket-patch-cli --lib --test scan --test vendor --test rollback --test apply --test get --test remove --test gradle_agent_cli --test in_process_rollback_vendored --test in_process_vendor: all passed (lib 872, scan 118, vendor 91, rollback 38, apply 106, get 81, remove 94 (incl. Fix remove/rollback missing PyPI name spellings (#1024) #1025's PyPI spelling tests), gradle_agent_cli 34, in_process_rollback_vendored 16, in_process_vendor 121).batch_search_credits_a_respelled_response_only_to_its_asker,lookup_matches_by_purl_identity,overlap_reports_one_entry_per_release_across_spellings,covers_every_spelling_of_the_release(unused_vendored_manifest_keys), andsentinel_free_key_keeps_rejected_spellings_apart. The first three fail against the pre-fix code: a spelling-keyed owner falls back to every chunk root, exact-version lookup misses3.0.2.0, and spelling dedup gives 4 entries.docker_e2e_nuget(Linux/Docker). It is the end-to-end proof for B20 (mixed-case manifest key against the lowercase global-cache crawl, throughscan --sync, GC, apply and agent VEX). It must pass in CI before this lands.Deferred
canonicalize_pypi_name(a) == canonicalize_pypi_name(b)) are left alone. They compare names, not purls.vendor.rs/apply.rs(variant_groups,vendored_basesover manifest keys) is left as-is, since both sides already share one spelling.Ecosystemandcanonicalize_pypi_nameto core (E39 / Move canonicalize_pypi_name and the PEP 508 name scanner out of crawlers and vendor into one PyPI name module #883) is a separate refactor.--package, get fuzzy, remove/rollback, UUID shortcut) are untouched.get.rs's failure dedup onnormalize_purlis display-only.ecosystem_dispatch::merge_qualifiedmatches a crawler's echo of the exact base purl it was asked for, so both sides are the same string.canonical_base_purl. Maven coordinates are case-sensitive, so the canonical spelling is the identity, and the lock-file GAVs they are checked against are built as strings.PurlKey::new(base_purl), notlookup_entry. It keeps the old qualifier-insensitive match for manifest keys, whereaslookup_entryis qualifier-sensitive.🤖 Generated with Claude Code
Note
Medium Risk
Wide refactor of identity comparisons across apply, scan, GC, rollback, and policy paths; behavior changes are intentional but any missed call site could still mis-match or over-prune patches.
Overview
Introduces
PurlKeyas the single release-identity for package URLs (encoding, qualifiers, NuGet/PyPI/Composer spelling variants) and routes prune GC, lockfile-only detection, vendor ownership, rollout, policy, VEX, and hosted batch search through it instead of a dozen overlapping helpers (purl_eq,policy::canon, duplicatecanonical_base_purl, composer purl identity helpers, etc.).User-visible fixes:
scan --prune/--syncno longer drops live manifest entries when the API uses mixed-case NuGet or alternate PyPI spellings vs the crawl;remove/rollbackidentifiers now match those recorded keys. Vendored key sets becomeHashSet<PurlKey>; dry-run prune preview shares wet vendor GC manifest-key logic viaunused_vendored_manifest_keys; hosted memory batch search credits respelled API purls only to the asking root.Several removed/moved socket-patch-core public APIs (
purl_eq, oldcanonical_base_purllocations, composer identity exports); vendored helpers now takeHashSet<PurlKey>.Reviewed by Cursor Bugbot for commit daa5ef8. Configure here.
Generated by Claude Code