[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: tracking. Source: review 6.4, 7.3; register C20.
Problem (verified on 045d7ec)
utils/purl.rs has two builder families:
On top of these, 42 production format!("pkg:…") sites outside utils/purl.rs build purls by hand. The review counted 48; corrected here. The largest groups:
vex/product.rs: 13 sites, including versionless purls
vendor/path.rs::leaf_to_purl: 10
vendor/lock_inventory/recover.rs: 6
vex/discover/mod.rs: 5, with their own PyPI/composer/nuget canonicalization in discover/mod.rs L1455-L1468
hosted/engine.rs:622
There are also 24 inline starts_with("pkg:<type>/") checks beside Ecosystem::from_purl.
Drift already present. The families disagree on canonicalization:
composer_purl lowercases, while build_composer_purl, leaf_to_purl and recover.rs don't.
pypi_purl canonicalizes the name, while leaf_to_purl, recover.rs and the hosted skip purl (hosted/engine.rs:622) don't. vex::discover re-canonicalizes afterwards.
vlt.rs:290 alone percent-encodes the npm scope @.
Every consumer that compares purls therefore needs purl_eq/normalize_purl to paper over the differences.
Target design
utils::purl exposes one validated constructor per ecosystem (or Purl::new(Ecosystem, ns, name, version) -> Option<String>) that owns name canonicalization (PyPI PEP 503, composer and nuget lowercase), plus one versionless base_purl. build_* becomes private or is deleted.
- Type checks go through
Ecosystem::from_purl.
Checklist (one PR each, in order)
Dependencies
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: tracking. Source: review 6.4, 7.3; register C20.
Problem (verified on
045d7ec)utils/purl.rshas two builder families:build_gem_purl…build_cargo_purl. There are 7 of them, plainformat!, with 21 production callers.npm_purl…maven_purl. They returnNonefor unsafe coordinates; lockfile discovery and the lock inventory use them.On top of these, 42 production
format!("pkg:…")sites outsideutils/purl.rsbuild purls by hand. The review counted 48; corrected here. The largest groups:vex/product.rs: 13 sites, including versionless purlsvendor/path.rs::leaf_to_purl: 10vendor/lock_inventory/recover.rs: 6vex/discover/mod.rs: 5, with their own PyPI/composer/nuget canonicalization in discover/mod.rs L1455-L1468hosted/engine.rs:622There are also 24 inline
starts_with("pkg:<type>/")checks besideEcosystem::from_purl.Drift already present. The families disagree on canonicalization:
composer_purllowercases, whilebuild_composer_purl,leaf_to_purlandrecover.rsdon't.pypi_purlcanonicalizes the name, whileleaf_to_purl,recover.rsand the hosted skip purl (hosted/engine.rs:622) don't.vex::discoverre-canonicalizes afterwards.vlt.rs:290alone percent-encodes the npm scope@.Every consumer that compares purls therefore needs
purl_eq/normalize_purlto paper over the differences.Target design
utils::purlexposes one validated constructor per ecosystem (orPurl::new(Ecosystem, ns, name, version) -> Option<String>) that owns name canonicalization (PyPI PEP 503, composer and nuget lowercase), plus one versionlessbase_purl.build_*becomes private or is deleted.Ecosystem::from_purl.Checklist (one PR each, in order)
Ecosystem::from_purl(mechanical; can start now).vendor/{gem,maven_repo,nuget_feed,composer_lock}.rs) andredirect/golang_local.rsmove to the validated builders. An unsafe coordinate becomes a refusal instead of a ledger key. Owner: ecosystems area.vendor/path.rs::leaf_to_purlandlock_inventory/recover.rsbuild through the validated builders, canonicalizing PyPI and composer once. Delete the re-canonicalization invex::discover.vex/product.rsversionless/product purls through onebase_purlbuilder.build_*family andpurl_name_version, which only has a test caller.Dependencies
audit-ecosystems), so coordinate with E-rows onvendor/andvex/.formats).