Skip to content

fix(sbom): a transformed export mints its own identity and links its source - #461

Merged
bomly-guy merged 3 commits into
mainfrom
fix/transformed-export-identity
Sep 13, 2026
Merged

bomly-guy merged 3 commits into
mainfrom
fix/transformed-export-identity

Conversation

@bomly-guy

Copy link
Copy Markdown
Collaborator

Closes #433.

applySourceAssertions adopted a single ingested document's identity (and creation time, name, comment) whenever exactly one document was read, even when --scope or --enrich had transformed the graph. A filtered subset was published under the full document's namespace or serial: two documents, one identity.

The seam: internal/sbom cannot see the transformation, only the caller can. BuildOptions.RestatesSource is the caller's declaration that nothing filtered, enriched, or degraded the graph between ingest and export; only then does a conversion adopt its source's identity. Default false is the safe side: a transformed conversion behaves as a merge of one (own identity and timestamp, default name, no comment, source linked). Creators and tools still union.

The CLI predicate (sbomRestatesSource in scan_cmd.go) is sbomCompositionAggregate(...) == "complete" && !Enrich, so the completeness and restatement claims stay mechanically consistent. --analyze requires --enrich and nothing under internal/sbom reads reachability, so enrichment covers it. The registry is never nil at the CLI, so it cannot be the signal.

Visible behaviour change: bomly scan --sbom <one document> --scope runtime -f cyclonedx (or --enrich) now emits a fresh serial and this run's timestamp, plus an external reference of type bom with a hash naming the source; SPDX gets an externalDocumentRef with the ingest checksum. Without --scope/--enrich the source serial is kept as before. Verified end to end with the built binary on a self-scan export:

--scope runtime: serial urn:uuid:6acbde59-…, refs [urn:cdx:2089beb2-…/1 (1 hash)]
plain:           serial urn:uuid:2089beb2-… (the source's), refs []
spdx --scope:    namespace https://bomly.dev/spdx/…, externalDocumentRefs [urn:cdx:2089beb2-…/1, SHA256]

Tests: every test asserting adoption now declares RestatesSource: true (TestSingleSourceExportIsAFixedPoint still pins nothing else); the fuzz target gains a pass without the flag asserting the identity is never adopted and is linked whenever it clears the gate; new TestATransformedConversionMintsItsOwnIdentity, TestATransformedConversionLinksItsSource (both formats), TestRestatementFlagIsIgnoredByAMerge, TestSBOMRestatesSourceOnlyForAnUntransformedScan.

Docs: ADR-0042 amended in place (dated blockquote), ADR-0037 cross-references it. A plugin-written source with no ingest checksum is neither adopted nor linked on a transformed SPDX export, which was already the merge behaviour and is now stated.

Gates: make verify passes; smoke TestScan/scan-sbom-spdx, TestScan/scan-sbom-cyclonedx, TestScanSBOMExportGolden, TestScanSBOMExportOrigin pass with no golden change.

Follow-up: bomly-dev/bomly-sdk#61 (source links are not yet read back on ingest), so a transformed export that links its source still loses that link on the next hop.

🤖 Generated with Claude Code

…source

applySourceAssertions adopted a single ingested document's identity -- SPDX
namespace, CycloneDX serial and revision -- and its creation time, name and
comment whenever exactly one document was read, whether or not the graph
being exported still described that document. scan --sbom with --scope
runtime or --enrich hands the exporter a transformed graph beside the
original entry, so a filtered subset was published under the full document's
identity: two documents with different content and one name, which is a
correctness defect in the one field that exists to tell documents apart.

The exporter cannot see the transformation; only the caller knows. So
BuildOptions gains RestatesSource, the caller's declaration that nothing
filtered, enriched, or degraded the graph between ingest and export. Only
then does a conversion adopt its source's identity. The default is false,
the safe side: the document mints its own identity and timestamp, keeps the
default name and no comment, and links the source -- it behaves as a merge
of one. The CLI derives the flag from the same predicate that decides
compositions.aggregate, so a document that cannot claim to be complete
cannot claim to be its source either; --analyze needs --enrich and writes
nothing into the SBOM, so enrichment covers it.

Visible change: a --scope or --enrich export of one SBOM now carries a fresh
serial or namespace and this run's time, with an external reference of type
bom (CycloneDX) or an externalDocumentRef with checksum (SPDX) naming the
source. The fixed point still holds for restating exports and is pinned
without pinning anything else.

ADR-0042 is amended in place; ADR-0037 cross-references it.

Closes #433.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 48 minutes.

Check out review usage here.

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 5346e888-f686-4bbc-a7a8-4a7dfa462974

📥 Commits

Reviewing files that changed from the base of the PR and between 6d3bcb2 and ff53d54.

📒 Files selected for processing (12)
  • dev-docs/adr/0037-sbom-assertions-are-typed-sdk-model-fields.md
  • dev-docs/adr/0042-a-conversion-restates-its-source-a-merge-links-its-sources.md
  • internal/cli/scan_cmd.go
  • internal/cli/scan_cmd_test.go
  • internal/sbom/document_assertions.go
  • internal/sbom/document_assertions_fuzz_test.go
  • internal/sbom/document_assertions_test.go
  • internal/sbom/document_restatement_test.go
  • internal/sbom/document_sources_test.go
  • internal/sbom/model.go
  • internal/sbom/spdx23.go
  • internal/sbom/transform.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.

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Bomly Diff Summary

Compared 6d3bcb2f74136a114ed78b8c203bf28bbfd97aba to ff53d54cb16e2466e566f5c76f8634bc2e8f1e5b.

Overview

Status Manifests Dependencies Findings Duration
✅ Pass +0 / ~0 / -0 0 added / 0 version changed / 0 detail changes / 0 removed 0 introduced / 0 persisted / 0 resolved 1m 27s

Dependency Changes

✅ No dependency changes.

Vulnerabilities

✅ No vulnerability changes.

License Changes

✅ No license changes.

Project Posture

✅ No project posture changes (--matchers +scorecard was not selected).

Policy Findings

✅ No policy differences were identified.

@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: ac389f81a2

ℹ️ 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 internal/cli/scan_cmd.go
…wn its source

Codex review on #461: the degraded argument was computed from any detector
warning being present, while PipelineResult asks consumers concerned with
coverage to filter on DetectorWarning.DegradesCoverage. A package-manager
readiness notice therefore made an unfiltered scan declare its completeness
unknown and mint a new identity for an unchanged source. coverageDegraded
filters on the kind, and feeds both the aggregate claim and restatement.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

@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: 13cbb9fe0b

ℹ️ 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 internal/cli/scan_cmd.go
… whatever the caller declared

Codex review on #461: a directory scan can hold one auto-detected SBOM
entry beside native manifest entries; the source count skipped entries
without a document, saw one source, and adopted its identity over a graph
holding packages the document never named. FromGraphEntries now withdraws
restatement when any entry carries a graph but no document -- decided at
the model boundary, where the entries are visible, rather than by each
caller. A lone document still restates.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@bomly-guy
bomly-guy merged commit 2783818 into main Sep 13, 2026
16 checks passed
@bomly-guy
bomly-guy deleted the fix/transformed-export-identity branch September 13, 2026 07:39
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.

A transformed --sbom export republishes its source's document identity, so two different documents share one namespace

1 participant