fix(output): emit valid SPDX tag-value for resolved deps and exceptions - #1325
Merged
Conversation
Greptile SummaryFixes SPDX tag-value generation and expands regression validation.
Confidence Score: 5/5The PR appears safe to merge with no eligible blocking follow-up failures identified. No blocking failure remains within the scope of the prior review threads. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A["Shared output schema"] --> B["Plan SPDX packages"]
B --> C["Analyzed subject packages"]
C --> D["File information"]
D --> E["Promoted dependencies"]
E --> F["Relationships"]
A --> G["Normalize license expressions"]
G --> H["Tag-value output"]
G --> I["RDF/XML output"]
Reviews (2): Last reviewed commit: "fix(output): emit valid SPDX tag-value f..." | Re-trigger Greptile |
The checked-in SBOM examples produced SPDX tag-value documents that failed official SPDX validation (spdx-tools/pyspdxtools). Two renderer bugs, both surfaced only by a real scan with resolved dependencies or an unattached license exception (CI's testdata/smoke scan has neither): 1. Promoted-dependency packages (FilesAnalyzed: false) were emitted before the file section. SPDX tag-value binds a file to the most recently declared package, so scanned files bound to a FilesAnalyzed: false package. Emit subject packages before, and dependency packages after, the file section. 2. License exception symbols (e.g. LicenseRef-scancode-generic-exception, LLVM-exception) reached SPDX license fields standalone or AND-joined; SPDX only allows an exception on the right of WITH. Drop the post-WITH token from id lists, strip floating (non-WITH) exceptions from expressions while keeping 'license WITH exception', and drop their unreferenced ExtractedLicensingInfo. Exceptions are identified via license_detection::spdx_lid::is_spdx_exception (now pub(crate)); output.license_references is empty by default. Regression coverage: new golden fixture spdx-dependencies-expected.tv + a golden test asserting dependency-package placement, wired into the official validator set (scripts/validate_output_format_fixtures.py) so CI validates a deps-bearing SPDX document every run; plus unit tests for the exception handling. All four examples/sbom regenerated (1.0.1) and validate clean against the official CycloneDX and SPDX validators. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Maxim Stykow <maxim.stykow@gmail.com>
mstykow
force-pushed
the
fix/spdx-tagvalue-sbom-validity
branch
from
July 23, 2026 22:18
b141519 to
aed009e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The checked-in SBOM examples (and any real scan with resolved dependencies or an unattached license exception) produced SPDX tag-value documents that fail official SPDX validation (
spdx-tools/pyspdxtools). CycloneDX was and is valid; this fixes two SPDX tag-value renderer bugs and adds the regression coverage that would have caught them.Both were surfaced by validating the committed
examples/sbom/*/sbom.spdxagainst the official validator — something CI did not do (itstestdata/smokescan is too small to have resolved-dependency packages or exception detections).Bug 1 — promoted-dependency packages placed before the file section
SPDX tag-value associates a file with the most recently declared package (positional). The writer emitted every package — including promoted resolved dependencies, which are
FilesAnalyzed: falseand own no files — before the## File Informationsection, so the scanned files positionally bound to aFilesAnalyzed: falsepackage. spdx-tools rejects this: "package must contain no elements if files_analyzed is False."Fix (
src/output/spdx.rs): emit scanned-subject packages (FilesAnalyzed: true) before the file section and promoted-dependency packages after it. (Before #1321 there was only the subject package, so this was latent.)Bug 2 — license exception symbols used outside a
WITHA detected exception (e.g. ScanCode's
LicenseRef-scancode-generic-exception, or a listedLLVM-exception) reached SPDX license fields as a standalone id (LicenseInfoInFile,PackageLicenseInfoFromFiles) and inside anAND-joined expression (LicenseConcluded: MIT AND Unlicense AND LicenseRef-scancode-generic-exception). SPDX only permits an exception on the right of aWITH, so spdx-tools rejects it.Fix:
WITHis an exception, not a license id —spdx_ids_from_expressionno longer emits it as a standalone id.strip_unattached_exceptionsdrops floating (non-WITH) exception operands from license expressions while preserving a validlicense WITH exception; applied to file and packageLicenseConcluded/LicenseDeclaredin both the tag-value and RDF writers.ExtractedLicensingInfodeclarations.license_detection::spdx_lid::is_spdx_exception(promoted topub(crate)) — a pure function of the token, sinceoutput.license_referencesis empty by default (ScanCode-compatible; only populated with--license-references).Regression coverage
testdata/output-formats/spdx-dependencies-expected.tvand testtest_spdx_tag_value_emits_promoted_dependency_packages_after_file_section(tests/output_format_golden.rs): a scanned subject package that owns files plus a promoted dependency (the existing CycloneDX dependency sample has no files). The test asserts the subject package precedes, and the dependency package follows, the file section.scripts/validate_output_format_fixtures.py(SPDX_TAGVALUE_FIXTURES), so CI now validates a deps-bearing SPDX document againstpyspdxtoolson every run — closing the gap that let both bugs ship.spdx_ids_from_expression_drops_the_with_exception_symbol,strip_unattached_exceptions_drops_floating_exceptions_but_keeps_with).Examples regenerated
All four
examples/sbom/regenerated (stamped 1.0.1). Validated locally against the official validators: CycloneDX valid ×4, SPDX valid ×4.Release note
This is the fix that the cancelled 1.0.1 was blocked on.
mainis already at the 1.0.1 version bump; nothing was published (no crate, image, release, or Homebrew bump). After merge, re-tagv1.0.1on the new HEAD to re-trigger the release.