sbom — design
Class:
design· Function axis:f0the emit ·f1the purl (build) ·f1bthe purl parse (the inverse). Strict: only the shape we run. The system view is indesign.md.
f0 — the emit
sbom (module sbom.rs, feature scan) is the emit direction: it serializes a resolved component + vuln set into a standards CycloneDX 1.5 document. The other direction — reading nornir's sbom_components — lives in warehouse.
SbomInput (sbom.rs:35) is { subject, components: Vec<SbomComponent>, vulns: Vec<SbomVuln> }, where SbomComponent (sbom.rs:17) is { ecosystem, name, version, license } and SbomVuln (sbom.rs:25) is { ecosystem, name, version, ids, summary }. to_cyclonedx(input) (sbom.rs:47) returns a serde_json::Value — the CycloneDX 1.5 JSON, multi-ecosystem, with each component keyed by its purl. It is A30's serialization surface and is byte-compatible with nornir's prior cargo output (the nornir cutover delegates to_cyclonedx to this module).
Reuse: sbom.uses = [core]. Pure serialization — no warehouse, no network.
f1 — the package URL
purl(ecosystem, name, version) (sbom.rs:42) builds the multi-ecosystem package URL (pkg:cargo/name@version, pkg:npm/…, pkg:maven/…, …) each component and vuln is keyed by, so the emitted SBOM cross-references cleanly with any purl-aware consumer.
f1b — the inverse: one grammar, both directions
sbom::purl builds the canonical percent-encoded pkg:{type}/{namespace/}{name}@{version}; sbom::parse_purl is its inverse, decoding %XX, stripping the ?qualifiers/#subpath suffixes, preserving a namespace / and folding maven's group/artifact back to the group:artifact coord OSV/modgunn store. Keeping both directions in ONE module is what stops them drifting into a silent join miss (L5): the round-trip is asserted over every awkward shape (scoped npm, maven group, a multi-segment Go module path, an encoded space, a + build metadata version) in purl_parse_is_the_exact_inverse_of_purl_build.
This is the join key the rest of the system leans on: vuln_findings.purl joins sbom_components, and a purl-keyed feed (Sonatype OSS-Index's coordinates) is decoded through this one parser rather than a hand-rolled split.