feat: purlkit — one home for package-URL behavior - #7
Conversation
Adds the purlkit leaf subpackage (plan 1.1, ADR-0038): structured parse/build/canonicalize with qualifiers and subpath (the legacy root builder passed nil/empty for both), a structural Base that replaces the first-'?'-then-last-'@' string surgery, the single purl-type mapping table with the reverse EcosystemForType join (hex is a pinned refusal: the Hex registry serves both Elixir and Erlang), the one CanonicalEcosystem alias table, and SplitEcosystemName — the ADR-0021-aware inverse of Coordinates.EcosystemName, built on Go 1.27's strings.CutLast. The root helpers delegate with unchanged signatures; an exhaustive parity test drives every historical token pair through both the new table and the frozen legacy switch. ParsePackageURL is deprecated (its signature exposes the third-party type) and carries the one allowlist entry in the new import-boundary guard, which fails any direct packageurl-go import outside purlkit. The two root ecosystem-alias helpers (normEffectiveEcosystem, displayEcosystem) are deliberately not rewired in this PR: their tables disagree on contested rows and their unification rides the identity phase, keeping this PR behavior-neutral. Fuzzing found a real upstream defect before review did: packageurl-go is not idempotent on percent-encoded namespace bytes, so Parse iterates parse-and-render to a stable form and rejects input that will not stabilize — a canonical identity must not depend on how many times it was canonicalized. The crashing input is committed as a corpus seed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reachedNext included review available in 20 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a48858c1d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… errors CanonicalEcosystem now covers the SDK's complete vocabulary: identity rows for every ecosystem token and aliases for every package-manager name (setuppy, pdm, gemspec, luarocks, pear, swipl-pack, and the OS-package ecosystems were missing), with 'multiple' joining 'hex' as a documented refusal. A root-side parity test walks AllPackageManagers and the ecosystem constants and cross-checks each manager against its declared Ecosystem(), so a new vocabulary entry cannot be forgotten in the kit silently. Parse enforces the 1 MiB untrusted-input bound in production, not just the fuzz harness, and every failure shape from Parse and Build — upstream parser errors included — now matches ErrInvalidPURL under errors.Is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c0f60a9d9e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ejected TypeForValues gains explicit rows for managers whose name is not a spec purl type: pnpm/yarn/bun → npm, gradle → maven, pdm/setuppy/setup.py → pypi, gemspec → gem. Manager-only coordinates are a real shape — pnpm and yarn graphs carry no ecosystem token — and the verbatim fallback used to mint non-spec types (pkg:pnpm) for them, which registry-keyed consumers never match. The parity test records these rows as chosen deltas over the frozen legacy switch. Build now rejects a qualifier with a blank key as ErrInvalidPURL instead of silently dropping it: malformed untrusted input must not look identical to a qualifier-free package. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 21de2100fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Structured parts can carry the same renderer-unstable bytes as raw strings, so Build's single-pass rendering is now stabilized through the shared fixed-point core before it is returned — Parse and Build both iterate renderOnce, and parts that will not stabilize are rejected rather than given an identity that canonicalizes to a different key later. New FuzzBuild target asserts the fixed-point property over structured inputs, seeded with the hostile namespace from the committed Parse crasher. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f995661336
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…n core Build's rendered form is now bounded exactly like Parse's raw input: the 1 MiB check moved into stabilize, which both entry points share, so Build can neither return an output the public Parse would reject — which would break the documented fixed-point property — nor feed arbitrarily large structured input through repeated parsing. Regression test on an oversized Build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stack position: S3 (on #6; plan 1.1, ADR-0038). Retarget as predecessors merge.
Adds the
purlkitleaf subpackage — stdlib + packageurl-go only, never the root SDK, so the root delegates without a cycle:nil, ""for both, sopkg:maven/g/a@1?type=jarandpkg:golang/m@v1#subwere unrepresentable.Basereplacing the first-?-then-last-@string surgery (broken for subpath-carrying and version-less PURLs; regression cases pinned).TypeForValues) plus the reverseEcosystemForTypejoin —hexis a pinned refusal with a test, per ADR-0038's "no second table can quietly decide otherwise".CanonicalEcosystem— the one alias table; andSplitEcosystemName, the ADR-0021-aware inverse ofEcosystemName, using Go 1.27'sstrings.CutLastfor multi-segment Go namespaces.purlkit/fails a test; the deprecatedParsePackageURL(third-party type in its signature) is the one allowlisted entry, commented with its removal condition.Behavior-neutral by construction: root wrappers keep signatures and delegate; an exhaustive parity test drives ~3,600 token pairs through both the new table and the frozen legacy switch. The two root ecosystem-alias helpers are deliberately not rewired — their tables disagree on contested rows (e.g.
hex→elixir in one, absent in the other), and unification rides the identity phase.The fuzzer found a real upstream defect before review did: packageurl-go is not idempotent on percent-encoded namespace bytes (
%2000→" 00"→"00").Parsetherefore iterates parse-and-render to a stable form and rejects input that won't stabilize — a canonical identity must not depend on how many times it was canonicalized. Crasher committed as a corpus seed; 10.5M FuzzParse execs clean after the fix.gorelease -base=v0.4.2: compatible, "package added", suggests v0.5.0.🤖 Generated with Claude Code