# sbom — design
> **Class:** `design` · **Function axis:** `f0` the emit · `f1` the purl (build) · `f1b` the purl
> parse (the inverse). Strict: only the
> shape we run. The system view is in [`design.md`](design.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`](warehouse-design.md).
[`SbomInput`](../src/sbom.rs) (`sbom.rs:35`) is `{ subject, components: Vec<SbomComponent>,
vulns: Vec<SbomVuln> }`, where [`SbomComponent`](../src/sbom.rs) (`sbom.rs:17`) is
`{ ecosystem, name, version, license }` and [`SbomVuln`](../src/sbom.rs) (`sbom.rs:25`) is
`{ ecosystem, name, version, ids, summary }`. [`to_cyclonedx(input)`](../src/sbom.rs)
(`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)`](../src/sbom.rs) (`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`](../src/sbom.rs) builds the canonical percent-encoded
`pkg:{type}/{namespace/}{name}@{version}`; [`sbom::parse_purl`](../src/sbom.rs) 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.