Skip to content

feat: purlkit — one home for package-URL behavior - #7

Merged
bomly-guy merged 7 commits into
mainfrom
claude/s3-purlkit
Aug 28, 2026
Merged

feat: purlkit — one home for package-URL behavior#7
bomly-guy merged 7 commits into
mainfrom
claude/s3-purlkit

Conversation

@bomly-guy

Copy link
Copy Markdown
Contributor

Stack position: S3 (on #6; plan 1.1, ADR-0038). Retarget as predecessors merge.

Adds the purlkit leaf subpackage — stdlib + packageurl-go only, never the root SDK, so the root delegates without a cycle:

  • Structured parse/build/canonicalize with qualifiers and subpath — the legacy root builder passed nil, "" for both, so pkg:maven/g/a@1?type=jar and pkg:golang/m@v1#sub were unrepresentable.
  • Structural Base replacing the first-?-then-last-@ string surgery (broken for subpath-carrying and version-less PURLs; regression cases pinned).
  • The single purl-type table (TypeForValues) plus the reverse EcosystemForType join — hex is a pinned refusal with a test, per ADR-0038's "no second table can quietly decide otherwise".
  • CanonicalEcosystem — the one alias table; and SplitEcosystemName, the ADR-0021-aware inverse of EcosystemName, using Go 1.27's strings.CutLast for multi-segment Go namespaces.
  • Import-boundary guard: any direct packageurl-go import outside purlkit/ fails a test; the deprecated ParsePackageURL (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"). Parse therefore 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

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>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 20 minutes.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a6306e60-c79c-49dd-a4e6-9ffc55785d9e

📥 Commits

Reviewing files that changed from the base of the PR and between f1009b0 and fbf7bd2.

📒 Files selected for processing (12)
  • importboundary_test.go
  • purl.go
  • purlkit/doc.go
  • purlkit/fuzz_test.go
  • purlkit/purlkit.go
  • purlkit/purlkit_test.go
  • purlkit/split.go
  • purlkit/split_test.go
  • purlkit/table.go
  • purlkit/table_test.go
  • purlkit/testdata/fuzz/FuzzParse/35e7667007e925c3
  • purlkit_delegation_test.go

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from claude/s2-go-1.27 to main August 28, 2026 05:14
@bomly-guy
bomly-guy marked this pull request as draft August 28, 2026 05:14
@bomly-guy
bomly-guy marked this pull request as ready for review August 28, 2026 05:14

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread purlkit/table.go
Comment thread purlkit/purlkit.go
Comment thread purlkit/purlkit.go Outdated
… 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>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread purlkit/table.go
Comment thread purlkit/purlkit.go Outdated
…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>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread purlkit/purlkit.go
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>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread purlkit/purlkit.go
…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>
@bomly-guy
bomly-guy merged commit 1ab50fc into main Aug 28, 2026
3 checks passed
@bomly-guy
bomly-guy deleted the claude/s3-purlkit branch August 28, 2026 05:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant