You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
parse() already extracts each SBOM's identity — name, version, generatedAt, format, and specVersion (src/parser.ts:45-53, 71-80) — and stores it on the SBOM object. But diff() reads only.components and .vulnerabilities and silently discards every identity field (src/diff.ts:10-61). ChangeReport has no place to carry it (src/types.ts:70-88), and the reporter never renders it (src/reporter.ts). So the report that comes out cannot identify the two artifacts it was produced from.
For a package keyworded supply-chain-security whose README sells "auditable SBOM diff reports for compliance evidence," this is a real evidentiary gap: an audit artifact that doesn't record its own inputs is weak evidence. A reviewer looking at a stored report.md or report.json six months later has no way to know it compared my-app 1.2.0 → 1.3.0, which SBOM formats were involved, or when either SBOM was generated — none of it is in the output, even though all of it was parsed and then thrown away.
This is the same "declared/parsed-but-unused signal" pattern already recognized for license (#9) and hashes (#22) — data the parser captures that never reaches the report. It is distinct from every open PR/issue: the in-flight work changes component/CVE comparison semantics (keying #20/#31/#37/#42/#44/#47, downgrades #24, 0.x #48, license #33, CVSS #18, ordering #25, collisions #50, escalation #46, affects #30) or the parser/CLI/CI plumbing. None of them add document-level provenance to ChangeReport or the rendered output.
// src/diff.ts:10-13exportfunctiondiff(a: SBOM,b: SBOM): ChangeReport{constaMap=buildComponentMap(a.components);// only .componentsconstbMap=buildComponentMap(b.components);// ...a.vulnerabilities / b.vulnerabilities used later; nothing else
And ChangeReport (src/types.ts:70-88) has added/removed/upgraded/newCVEs/fixedCVEs/summary — no field naming the compared subjects — so renderReport() has nothing to print.
The rendered report begins straight at # SBOM Diff Report / ## Summary — "my-app", "1.2.0 → 1.3.0", the timestamps, and the formats appear nowhere. The identity the parser recovered is gone by the time the audit artifact is written.
Proposed change
Carry document-level provenance through diff → report and render it as a header. Purely additive; no change to existing fields or default comparison behavior.
1. Types (src/types.ts)
/** Identity of one SBOM in a comparison, for audit provenance. */exportinterfaceSBOMSubject{name?: string;version?: string;format: SBOMFormat;specVersion?: string;generatedAt?: string;}exportinterfaceChangeReport{/** The two documents this report was computed from (old = A, new = B). */subjects?: {old: SBOMSubject;new: SBOMSubject};// ...existing fields unchanged...}
Making subjects optional keeps every existing ChangeReport literal (tests, fixtures) valid, so this doesn't force churn on the many in-flight PRs that construct reports.
2. Diff (src/diff.ts)
Populate it from the two inputs already in hand — one small object, no new logic:
(Missing fields degrade gracefully, e.g. unknown / —; SPDX populates format: 'spdx' and generatedAt from creationInfo.created, which the parser already extracts.)
4. Tests (src/__tests__/)
diff() copies both subjects' name/version/format/specVersion/generatedAt onto report.subjects (diff test).
Reporter renders the old/new header line in text and markdown and includes the subjects in json.
A ChangeReportwithoutsubjects still renders (back-compat guard for existing literal reports).
Why this is high-leverage
Closes an evidentiary gap in the headline use case. "Auditable reports for compliance evidence" requires the artifact to state what it compared; today it cannot, even though the tool parsed exactly that information.
Backward compatible and low-conflict.subjects is optional and additive; it does not touch component/CVE comparison, so it composes cleanly with the diff-semantics PRs in flight and won't invalidate their report literals.
Composable. Once subjects exists, --format markdown PR comments and stored json audit records become self-describing, and downstream tooling can key/deduplicate reports by the compared versions.
Happy to open a focused PR (types + diff + reporter + tests) once the direction is confirmed and whichever ChangeReport-touching PRs the maintainer prefers have landed, to keep diff.ts / reporter.ts conflicts minimal.
Summary
parse()already extracts each SBOM's identity —name,version,generatedAt,format, andspecVersion(src/parser.ts:45-53,71-80) — and stores it on theSBOMobject. Butdiff()reads only.componentsand.vulnerabilitiesand silently discards every identity field (src/diff.ts:10-61).ChangeReporthas no place to carry it (src/types.ts:70-88), and the reporter never renders it (src/reporter.ts). So the report that comes out cannot identify the two artifacts it was produced from.For a package keyworded
supply-chain-securitywhose README sells "auditable SBOM diff reports for compliance evidence," this is a real evidentiary gap: an audit artifact that doesn't record its own inputs is weak evidence. A reviewer looking at a storedreport.mdorreport.jsonsix months later has no way to know it comparedmy-app 1.2.0 → 1.3.0, which SBOM formats were involved, or when either SBOM was generated — none of it is in the output, even though all of it was parsed and then thrown away.This is the same "declared/parsed-but-unused signal" pattern already recognized for license (#9) and hashes (#22) — data the parser captures that never reaches the report. It is distinct from every open PR/issue: the in-flight work changes component/CVE comparison semantics (keying #20/#31/#37/#42/#44/#47, downgrades #24, 0.x #48, license #33, CVSS #18, ordering #25, collisions #50, escalation #46, affects #30) or the parser/CLI/CI plumbing. None of them add document-level provenance to
ChangeReportor the rendered output.Evidence (current
main)Parsed but then dropped:
diff()never reads any of those five fields:And
ChangeReport(src/types.ts:70-88) hasadded/removed/upgraded/newCVEs/fixedCVEs/summary— no field naming the compared subjects — sorenderReport()has nothing to print.Reproduction
The rendered report begins straight at
# SBOM Diff Report/## Summary— "my-app", "1.2.0 → 1.3.0", the timestamps, and the formats appear nowhere. The identity the parser recovered is gone by the time the audit artifact is written.Proposed change
Carry document-level provenance through
diff → reportand render it as a header. Purely additive; no change to existing fields or default comparison behavior.1. Types (
src/types.ts)Making
subjectsoptional keeps every existingChangeReportliteral (tests, fixtures) valid, so this doesn't force churn on the many in-flight PRs that construct reports.2. Diff (
src/diff.ts)Populate it from the two inputs already in hand — one small object, no new logic:
3. Reporter (
src/reporter.ts)Render a provenance header when
subjectsis present (skip cleanly when absent, so old callers are unaffected).jsonis automatic. Examplemarkdown:(Missing fields degrade gracefully, e.g.
unknown/—; SPDX populatesformat: 'spdx'andgeneratedAtfromcreationInfo.created, which the parser already extracts.)4. Tests (
src/__tests__/)diff()copies both subjects'name/version/format/specVersion/generatedAtontoreport.subjects(diff test).textandmarkdownand includes the subjects injson.ChangeReportwithoutsubjectsstill renders (back-compat guard for existing literal reports).Why this is high-leverage
SBOMfor both CycloneDX and SPDX — this only wires existing-but-discarded data throughdiff → report, the same fix pattern as Detect license changes in diff (parsedComponent.licenseis currently extracted but never compared) #9 (license) and Detect component hash/integrity changes in diff (Component.hashes is declared but never parsed or compared) #22 (hashes).subjectsis optional and additive; it does not touch component/CVE comparison, so it composes cleanly with the diff-semantics PRs in flight and won't invalidate their report literals.subjectsexists,--format markdownPR comments and storedjsonaudit records become self-describing, and downstream tooling can key/deduplicate reports by the compared versions.Happy to open a focused PR (types + diff + reporter + tests) once the direction is confirmed and whichever
ChangeReport-touching PRs the maintainer prefers have landed, to keepdiff.ts/reporter.tsconflicts minimal.