Repository navigation
refactor(detectors): the SDK decides every detector's purl type - #446
Conversation
Fourteen call sites across twelve detectors spelled their ecosystem's package-url type as a string literal in the first argument to sdk.BuildPackageURL. That is a second mapping table -- maintained by whoever happened to be writing a detector, next to purlkit, which the SDK calls the one authority for this mapping (ADR-0038). It is the shape that minted go4.org as pkg:generic (bomly-sdk#67), and the swift pair is the standing trap: CocoaPods and SwiftPM share the ecosystem token "swift" but need the types "cocoapods" and "swift", which only the package manager distinguishes. Every site now passes the ecosystem and package-manager values it already held to sdk.PackageURLTypeForValues. All fourteen emitted types are unchanged -- verified against the SDK for each site's tokens, in both argument orders -- so no node ID, OSV match, or SBOM reference moves. TestDetectorPURLTypesComeFromTheSDK enforces it from here, as a positive requirement rather than a ban on literals: the first argument must be the call to the SDK, so lifting a literal into a const does not escape it. The predicate is pinned on fixtures, because a rule only ever run against a passing tree is a rule nobody has seen fail. Closes the last open half of SDK maturity phase 2.1, criterion 3. Co-Authored-By: Claude Opus 5 <[email protected]>
|
Warning Review limit reachedNext included review available in 33 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: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (13)
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 |
Bomly Diff SummaryCompared Overview
Dependency Changes✅ No dependency changes. Vulnerabilities✅ No vulnerability changes. License Changes✅ No license changes. Project Posture✅ No project posture changes ( Policy Findings✅ No policy differences were identified. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c51b1e00e
ℹ️ 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".
Codex found a signposted way around it. The guard recognized only BuildPackageURL, so a detector could call purlkit.Build with a hand-picked Type and every check stayed green -- and the concatenation guard three rules down recommends purlkit.Build by name, so this is the route the file itself points at, not an obscure one. Reproduced before changing anything: a pub detector minting pkg:generic through purlkit.Build compiles and passes the whole suite. The rule now inspects the Type field of a purlkit.PURL literal with the same positive requirement the mint site carries -- Type must be a PackageURLTypeForValues call, so hoisting the answer into a const does not escape it either. The composite literal's type name is checked, not just the callee, because Build is a common enough name that matching it alone would report unrelated builders that happen to take a struct. Four fixtures added, in both directions: a hand-picked Type is reported, a delegating one is not -- forbidding that would ban the construction the codebase is being moved onto -- a const-hoisted Type is reported, and an unrelated Build taking a struct is not. Mutations: Codex's escape now fails, naming the file and line; dropping the composite type-name check fails on the unrelated builder. Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ceaf18ae28
ℹ️ 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".
Codex again, and again on my own fix. Watching purlkit.Build's arguments only saw the shape written inline: assign the literal to a variable first and the guard read an identifier, found no composite literal, and passed. Reproduced before changing anything. Tracing the value was the obvious repair and it is the wrong one. The type is chosen where the literal is written, not where Build is called, so the rule keys on purlkit.PURL literals wherever they appear -- inline, assigned, or returned by a helper -- and the hop stops mattering. My first attempt did trace, conservatively rejecting arguments it could not read. Its own fixtures caught it reporting a *delegating* value passed through a variable: wrong in both directions, since it would also have missed a literal built one file over. The Build branch is gone; the literal rule alone is shorter and stronger. Field assignment after construction is covered too. Nothing under internal/detectors sets a .Type from a literal today, so the rule costs nothing now and closes the next hop before somebody takes it. Mutations: the identifier bypass fails; the field-assignment path fails; a literal branch that stops reading Type fails. Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09f646cb09
ℹ️ 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".
Three review rounds found three ways a detector could pick a purl type through purlkit -- a literal at the call, a literal assigned first, a field set afterwards -- and each fix added a shape to an AST pattern. A fourth round found a fourth, which we filed rather than patched. Chasing shapes was the defect. Detectors have no business holding purlkit. They ask the SDK for a package URL; purlkit is where one is built, so holding it is holding the ability to choose a type by hand in more shapes than a pattern can enumerate. No detector imports it today, so the rule costs nothing and closes every shape at once, including the ones nobody has thought of. Scoped to detectors. internal/sbom, internal/output and internal/auditors use purlkit legitimately: they read and render package URLs minted elsewhere, and a minting rule has nothing to say about reading. The AST rule keeps only what the import ban cannot cover -- a literal handed to sdk.BuildPackageURL, and a literal handed to the authority itself -- and loses the two branches that leaked, along with their helpers. It went from 88 lines to a third of that. No entitlement was added for this file. It names purlkit in a string and does not import it, which is exactly the distinction the entitlement rules already make. Mutations, all five reported: a literal at the mint site; purlkit.Build inline; the same through a variable; a field set after construction; and a type taken from a const, which is the gap #449 records as needing go/types. It needs no type information at all once the import is gone. Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e490259f83
ℹ️ 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".
The defect
Fourteen call sites across twelve detector files spelled their ecosystem's package-url type as a string literal in the first argument to
sdk.BuildPackageURL:That literal is a mapping decision — ecosystem to purl type — and it is exactly the second mapping table the SDK maturity program set out to delete. The first one is
bomly-sdk/purlkit.TypeForValues, whose own doc comment calls it "the one authority for this mapping (ADR-0038)". The second one had no home, no tests, and no reviewer: it was whatever the person writing a detector typed that day.It is the kind of table that is wrong without looking wrong. Nothing reads oddly about
pkg:genericuntil an advisory fails to match — which is howgo4.orgwent out under that type (bomly-sdk#67).The swift pair is the standing trap and shows why the rule cannot be a review habit.
cocoapods/detector.goandswiftpm/detector.gosit next to each other, both carrysdk.EcosystemSwift, and they need different purl types —cocoapodsandswift. purlkit has deliberately no"swift"case precisely so the package manager is what decides. A detector hand-mapping from its ecosystem alone gets one of the two wrong, silently.The change
Every site now hands the SDK the ecosystem and package-manager values it already held:
Nothing is re-derived — each detector already had both tokens in the same
sdk.Coordinatesliteral.Before / after types — all fourteen sites
Probed against the pinned
bomly-dev/bomly-sdk v0.9.7with each site's actual tokens, in both argument orders, so the result does not depend on the order I happened to pass them in.python/piplock.go:78pypipythonpippypipython/poetrylock.go:89pypipythonpoetrypypicargo/detector.go:472cargorustcargocargocargo/detector.go:590cargorustcargocargocargo/workspace.go:243cargorustcargocargocargo/workspace.go:257cargorustcargocargocomposer/detector.go:308composerphpcomposercomposermix/detector.go:411hexelixirmixhexnuget/detector.go:554nugetdotnetnugetnugetconan/detector.go:328conancppconanconansbt/detector.go:183mavenscalasbtmavenpub/detector.go:199pubdartpubpubcocoapods/detector.go:355cocoapodsswiftcocoapodscocoapodsswiftpm/detector.go:293swiftswiftswiftpmswiftNo emitted purl type changes. No node ID, OSV match, or SBOM reference moves, and no golden shifts —
make verifyconfirms.Two results worth stating rather than assuming, because they were the flagged traps:
swift. purlkit has no"swift"case, so the ecosystem token falls through and the package manager answers:cocoapods→cocoapods,swiftpm→swift. Had either site passed only its ecosystem, CocoaPods would have silently started mintingpkg:swift. It passes both.EcosystemForType("hex")returns unknown by decision). The forward direction is unambiguous:elixirandmixboth map tohex, matching the literal. Nothing changes here.The guard
TestDetectorPURLTypesComeFromTheSDKininternal/detectors/guards_test.go, alongside the existing guards.It is written as a positive requirement — the first argument to
BuildPackageURLmust be the call tosdk.PackageURLTypeForValues— rather than as "no string literal in that position". A negative rule is escaped by lifting the literal into aconstone line up, which renames the defect instead of removing it. A const identifier is not a call to the authority either, so the positive form has no such escape.It is AST-based, not textual, and matches the callee by name with the package qualifier dropped, so an import alias or a local wrapper does not walk past it. A second check reports a string literal passed to
PackageURLTypeForValues, since restating the answer as the question is the same hand-mapping wearing the authority's name.Scoped to
internal/detectors/: that is where the duplicated table lived and where new ones get written. Ingest is a different question — a document underinternal/sbomarrives carrying a type somebody else chose — and a rule about minting has nothing to say about reading.TestPURLTypeGuardSeesTheShapesItForbidspins the predicate on fixtures in both directions, because a rule only ever exercised against a tree that already passes is a rule nobody has seen fail — which is this file's documented failure mode.Mutation results
This file has four recorded cases of a guard passing while the thing it forbade was present, so the guard was mutation-tested against real production code. Each mutation was applied on its own, verified to still compile (
go build ./...), and reverted:BuildPackageURL("cocoapods", …)incocoapods/detector.gococoapods/detector.go:355 mints a package URL with a type sdk.PackageURLTypeForValues did not decideswiftPURLType = "swift"inswiftpm/detector.goswiftpm/detector.go:293 mints a package URL with a type sdk.PackageURLTypeForValues did not decidePackageURLTypeForValues("hex")inmix/detector.gomix/detector.go:411 passes the literal "hex" to sdk.PackageURLTypeForValues instead of the detector's own ecosystem and package-manager valuespubPURL(purlType, …)inpub/detector.gopub/detector.go:284 mints a package URL with a type sdk.PackageURLTypeForValues did not decideM2 is the one that matters most: it is the mutation a regex-based "no literal here" guard would have passed.
Verification
make verifypasses end to end —fmt-check,golangci-lint(0 issues),go vetfor the full, lite and smoke build tags, both builds,go test ./..., andmake generatewith no docs drift.Scope
Closes the open half of
dev-docs/SDK_MATURITY_PLAN.md§6 criterion 3: "zero purl-type string literals in detectors … enforced by a guard test, not a review habit".🤖 Generated with Claude Code