Skip to content

SBOM fragments

See also: Command line for every other subcommand, and Configuration for [tool.pitloom.fragment].

A fragment is an SPDX 3 JSON-LD document that adds to an SBOM: an enrichment (loom enrich), a training run (pitloom.loom), or one you write. A project merges the fragments its [tool.pitloom.fragment] lists; loom merge merges a directory of them into a document of their own.

Merge fragments

loom merge .spdx3-fragments/ -o combined.spdx3.json

Merges every *.json file directly in the directory, in file-name order, into one SPDX 3 document: one SpdxDocument with profileConformance (the profiles its elements use, plus those each fragment's document declared), an import entry per fragment document, rooted at what each fragment's own SpdxDocument/Sbom rooted. Its id derives from the fragments' names and content, not the directory, and created is SOURCE_DATE_EPOCH, else the latest created of the fragments, never the current time: the same fragments give the same bytes. The Python API is pitloom.assemble.generate_merged_sbom().

Equal elements unify, here and in a project build with [tool.pitloom.fragment]: the same spdxId, the same SHA-256 (files and packages), the same agent or tool, and the same licence -- an expression or a text with the same canonical value, so mit and MIT are one element, while an expression and a text stay two. Elements are never unified by name alone. The earlier one is kept (the project's own, then the earlier fragment, by file name for loom merge), and an Annotation records each unification. A kept licence keeps its own name and comment. To make independent runs agree on ids in the first place, see Loom ID registry.

In a project build, fragment envelopes are dropped and duplicate relationships removed. The document's profileConformance gains ai/dataset as appropriate, and a second software_Sbom rooted at the merged ai_AIPackage is added, so the wheel ships one connected AI-pipeline graph: the packaged training script generates the model, which was trainedOn datasets that trace back via hasInput to the raw data.

A configured fragment that is the document being merged into (an earlier SBOM of the same project, with the same SpdxDocument id) is skipped with a WARNING:, and fails the build when required = true.

Exits non-zero (with an ERROR: line, after a WARNING: naming each offending reference) if any element or root in the merged result references an id absent from the merge and not imported by the merged document (a fragment's own import is not carried over) -- most commonly a fragment merged against a stale base SBOM (see the note in Enrich an SBOM). Regenerate the base SBOM and re-run the fragment-producing step before merging again.

merge, fragment, and id each take only their own small flag set, not the common options -- e.g. --offline/-v/--config/--enrich don't apply to any of them (id generate/id import do take -o/--id-registry, as an alias for their own target-file flag -- not in the common-options sense). merge's own --pretty also defaults to True (pretty-printed), the opposite of every other subcommand's compact default.

Validate fragments

loom fragment validate combined.spdx3.json
loom fragment validate base.spdx3.json fragment.spdx3.json  # + merged-graph check

Checks JSON Schema and SHACL conformance via spdx3-validate's library API (requires the validate extra, see Installation). Works on any SPDX 3 JSON document, not just Pitloom's own output. Passing more than one path also validates the graph formed by merging them, which catches type errors across ExternalMap references -- pass --no-merge to skip that and check each document only in isolation. Non-zero exit reports every finding to stderr with every line ERROR:-tagged -- a SHACL violation's Severity/Source Shape/Focus Node breakdown spans several ERROR: lines, not just one.

List configured fragments

loom fragment list
loom fragment list --project-dir path/to/project

Reads [tool.pitloom.fragment] from that directory's pyproject.toml (default: cwd) and prints one line per configured fragment:

PATH=fragments/model.spdx3.json ROLE=ai_model REQUIRED=false EXISTS=true ELEMENTS=42 SHA256=match MODIFIED=2026-09-10T12:00:00+00:00 SAME_DOCUMENT=false

ELEMENTS is the fragment's @graph entry count -- 0 for valid JSON with no @graph key (a real, valid empty fragment), - if the file is missing, unreadable, or not valid JSON at all; SHA256 is -/unknown/match/mismatch depending on whether a sha256 is configured and, if so, whether the file could be checked -- display only, not yet enforced before merge; SAME_DOCUMENT is true when the fragment is the document a loom project build of the directory would merge it into (an earlier SBOM of the project), - if the file could not be read or the project's own metadata cannot be (one WARNING:). A missing or broken fragment logs the same WARNING: wording a real build would log for it. Exits non-zero only when a required = true fragment is missing, unreadable, fails to parse as valid SPDX3 JSON-LD, or is that same document -- the same conditions that would also fail an actual build (see Merge fragments above); a non-required missing fragment or a SHA256 mismatch is informational only.