.nornir/assets/sbom-flow.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 860 400" font-family="ui-sans-serif,system-ui,sans-serif" font-size="13">
<defs>
<marker id="arw" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L7,3 L0,6 Z" fill="#64748b"/>
</marker>
<style>
.box{fill:#0f172a;stroke:#334155;stroke-width:1.5}
.lbl{fill:#e2e8f0}
.sub{fill:#94a3b8;font-size:11px}
.store{fill:#1e293b;stroke:#38bdf8;stroke-width:2}
.model{fill:#065f46;stroke:#10b981}
.write{fill:#7c2d12;stroke:#f97316}
.read{fill:#312e81;stroke:#818cf8}
.edge{stroke:#64748b;stroke-width:1.5;fill:none}
@media (prefers-color-scheme: light){
.box{fill:#f1f5f9;stroke:#cbd5e1}
.lbl{fill:#0f172a}.sub{fill:#475569}
.store{fill:#e0f2fe;stroke:#0284c7}
.model{fill:#d1fae5;stroke:#059669}
.write{fill:#ffedd5;stroke:#ea580c}
.read{fill:#e0e7ff;stroke:#4f46e5}
}
</style>
</defs>
<text x="20" y="26" class="lbl" font-size="15" font-weight="700">SBOM hosting — one coordinate-keyed store, attach (write) + serve (read)</text>
<!-- Write surfaces -->
<rect class="write" x="20" y="56" width="200" height="40" rx="8"/>
<text x="120" y="81" class="lbl" text-anchor="middle">gRPC AttachSbom (writer/admin)</text>
<rect class="write" x="20" y="104" width="200" height="40" rx="8"/>
<text x="120" y="129" class="lbl" text-anchor="middle">CLI sbom attach --file</text>
<!-- Read surfaces -->
<rect class="read" x="20" y="228" width="200" height="40" rx="8"/>
<text x="120" y="253" class="lbl" text-anchor="middle">gRPC FetchSbom (open read)</text>
<rect class="read" x="20" y="276" width="200" height="40" rx="8"/>
<text x="120" y="301" class="lbl" text-anchor="middle">HTTP GET /-/sbom</text>
<rect class="read" x="20" y="324" width="200" height="40" rx="8"/>
<text x="120" y="349" class="lbl" text-anchor="middle">CLI sbom fetch --out</text>
<!-- The store -->
<rect class="store" x="330" y="150" width="210" height="120" rx="8"/>
<text x="435" y="178" class="lbl" text-anchor="middle" font-weight="700">SbomStore</text>
<text x="435" y="199" class="sub" text-anchor="middle"><data_root>/holger-sboms/</text>
<text x="435" y="216" class="sub" text-anchor="middle"><repo>/<coord>.sbom.json</text>
<text x="435" y="238" class="sub" text-anchor="middle">key = repo + namespace/name/version</text>
<text x="435" y="255" class="sub" text-anchor="middle">path-safe (reused promote sanitizers)</text>
<!-- write edges -->
<path class="edge" d="M220,76 C270,90 285,140 330,175" marker-end="url(#arw)"/>
<path class="edge" d="M220,124 C270,140 290,160 330,185" marker-end="url(#arw)"/>
<!-- read edges (from store back to surfaces) -->
<path class="edge" d="M330,240 C280,250 270,250 220,248" marker-end="url(#arw)"/>
<path class="edge" d="M330,250 C285,275 280,290 220,296" marker-end="url(#arw)"/>
<path class="edge" d="M330,258 C285,300 275,330 220,344" marker-end="url(#arw)"/>
<!-- modgunn model -->
<rect class="model" x="620" y="70" width="210" height="86" rx="8"/>
<text x="725" y="96" class="lbl" text-anchor="middle" font-weight="700">modgunn::sbom (L5)</text>
<text x="725" y="117" class="sub" text-anchor="middle">SbomInput · SbomComponent · SbomVuln</text>
<text x="725" y="134" class="sub" text-anchor="middle">to_cyclonedx → CycloneDX 1.5</text>
<text x="725" y="150" class="sub" text-anchor="middle">consumed, never twinned</text>
<path class="edge" d="M620,140 C580,150 570,160 540,178" marker-end="url(#arw)"/>
<text x="620" y="176" class="sub">build_cyclonedx() = the one serialization seam</text>
<text x="20" y="388" class="sub">Attach is write-gated (RBAC fail-closed, scoped to the repo); fetch is open + audited. Missing coordinate → not-found (404 / NotFound / None). Last-wins on re-attach.</text>
</svg>