SBOM: publish each element's model path, parent path and repository URL - #174
Merged
villelaitila merged 11 commits intoAug 11, 2026
Merged
Conversation
Publishes each SBOM's model location as a native CycloneDX 'group' (the parent element's full path) plus a 'softagram:elementPath' property, and adds the repository URL as a vcs externalReference. Additive only: bom-ref, serialNumber, purl and version are untouched.
A detached element would have published its bare name as elementPath - a value that looks like a path but resolves to nothing. Raising at the call site is the better failure.
Swept in twice by a careless 'git add docs/'. It is pre-existing local scratch output, unrelated to this change.
Adds a CycloneDX section to docs/data-formats.md: what group and softagram:elementPath hold, the nearest-ancestor vcs rule, and the caveats a consumer needs (unstable estate root, group holds a path rather than a package namespace, inheritance by proximity). Comments three decisions that a future reader would otherwise reasonably 'fix': why 3rd-party components stay unlocated, why the ancestor walk reaches past the repository, and why the legacy path keeps its own vcs logic instead of adopting _add_vcs_reference. Excludes docs/superpowers/ from the Jekyll build - internal design notes must not publish to the GitHub Pages site.
Softagram Impact Report for pull/174 (head commit: 1d855d4)TL;DR Changed code files: 4 | Directly impacted code files: 1⭐ Change Overview
⭐ Details of Dependency Changes (diagram)
🤖 AGENTS - machine-readable impact data (4 files changed, 1 impacted, +58/-0 deps)Change overviewHead Added dependencies (56, showing 50)
6 more omitted. Complete data: https://opensource.softagram.com/cdn/impact/d20d45f2-ea4e-4ae4-a9ca-0f350bd8fac5_sgraph_174_impact_change_graph_TObH6sOkJXoEatCBiWrPSbQaF5a5V9.png_change_info.json Removed dependencies (0)None. Impacted files (1)Unchanged files that directly depend on files changed in this PR - check them for behavioral impact. Grouped by changed file:
Complete data
[] 📄 Full report
Impact Report explained. Give feedback on this report to support@softagram.com |
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.


The problem
At
--level 3a per-repository SBOM identifies its subject by name only:{ "bom-ref": "repoa", "name": "repoA", "type": "application", "version": "", "purl": "", "externalReferences": [] }Nothing records that the element lives at
/OrgName/GroupA/repoA. Since the bulk export isthe only endpoint usable at scale, a consumer that needs the parent group has to download the
whole model XML — hundreds of megabytes on a large estate — purely to rebuild a tree the SBOM
set already implicitly contains. One consumer today infers the group heuristically from
sourceCodeReferencespaths, which is collected model-wide and therefore ambiguous; for arepository mirrored into two groups the inference is unresolvable.
The path is not expensive to publish. The generator already computes it, and already depends
on it — the serial number is derived from it.
The identity that was missing is the only stable one
Mirrored repositories do not share a
bom-ref. The collision suffix gives them distinct refs,assigned in traversal order:
Delete GroupA's copy and GroupB's silently becomes
shared. Sobom-refdistinguishes the twodocuments but identifies neither across time,
nameidentifies neither, andserialNumberisan opaque hash. After this change
softagram:elementPathis the only stable, human-legibleper-element identity in the document.
What this adds
Every component that describes a model element — the metadata component of each document,
and the internal components inlined by
--transitive— now publishes its location:{ "bom-ref": "repoa", "name": "repoA", "group": "/OrgName/GroupA", "type": "application", "version": "", "purl": "", "externalReferences": [ { "url": "https://example.org/org/repoA.git", "type": "vcs" } ], "properties": [ { "name": "softagram:elementPath", "value": "/OrgName/GroupA/repoA" } ] }groupproperties[softagram:elementPath]additionalProperties: false. It is also the exact stringdeterministic_serial()hashes, so a consumer can verify a document's identity offline.externalReferences[type=vcs]repo_urlof the element or its nearest ancestor carrying a non-blank oneComponents describing 3rd-party packages get none of these; their identity is the
purl.Guarantees
deterministic_serial(elementPath) == serialNumbergroup + '/' + name == elementPathbelow the top level;groupis omitted at the topgroupandelementPathDeliberately unchanged
bom-ref— referenced fromdependencies[].dependsOnand from BOM-Link URNs in otherdocuments. Changing it would break cross-SBOM resolution for everything already ingested.
serialNumber— already derived from the path; must stay stable so re-ingesting a modelupdates projects rather than creating new ones.
purlandversion— a repository has no package identity and no version. A path is nota valid purl; placing one there would make purl-parsing consumers reject or mis-parse the
component, the same failure class as the
pkg:???placeholder removed in 1.7.1.UNKNOWN-REPOSITORY_LOCATIONfallback inanalyze_component_section— fabricating aURL for a repository whose remote is unknown is a defect, but removing it changes output for
existing consumers and is its own decision. That path therefore keeps its own vcs logic and
gets only the location field; the new code paths never fabricate. The asymmetry is commented
at the call site.
Verification
# --- Element location tests ---section.purl,version,bom-refandserialNumberdo not move.src/are one deliberately restructuredblock, and no removed line touches a
bom-ref,serialNumber,purlorversionvalue.yapf --diffhunk count is unchanged.
transitive modes.
A new fixture
modelfile_for_sbom_mirrored_tests.xmlcovers one repository name under twogroups — the case the feature exists to disambiguate, which no existing fixture covered. In
transitive mode it produces a document holding two components named
shared, one of which isits own metadata component, separated only by their published locations.
Consumer impact
Additive — no existing field changes value, so nothing already ingested breaks. Consumers can
drop the model download for tree reconstruction, and the group-inference heuristic can go away
along with the mirrored-repository ambiguity it could not resolve. Transitive-mode consumers
additionally gain the location of every link in the exposure chain, not just its root.
Two caveats, both documented in
docs/data-formats.md: the first path segment is the estateroot and is not stable across model generations, so read it from the path rather than
hardcoding it; and
groupholds a slash-delimited path where the CycloneDX specificationsuggests a package-namespace-shaped value, so tools that render
groupas a package coordinatewill display the path.
Documentation
docs/data-formats.mdgains a CycloneDX section describing the output contract, since it is nowparsed by downstream tools.
docs/superpowers/is excluded from the Jekyll build — it holds aninternal design document that belongs in the repository for contributors but must not publish to
the GitHub Pages site.