.nornir/assets/flow_families.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 720" font-family="'DejaVu Sans', 'Segoe UI', sans-serif">
<defs>
<linearGradient id="board" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#0b1c2e"/><stop offset="1" stop-color="#06111d"/>
</linearGradient>
<linearGradient id="chip" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#14384f"/><stop offset="1" stop-color="#0e2a3e"/>
</linearGradient>
<linearGradient id="rail" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#173d57"/><stop offset="1" stop-color="#0c2438"/>
</linearGradient>
<marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 Z" fill="#6fd3f5"/></marker>
</defs>
<rect x="0" y="0" width="1180" height="720" fill="url(#board)"/>
<g stroke="#143a52" stroke-width="1"><line x1="0" y1="74" x2="1180" y2="74"/><line x1="0" y1="680" x2="1180" y2="680"/></g>
<g fill="#16415c"><circle cx="40" cy="40" r="2"/><circle cx="1140" cy="40" r="2"/><circle cx="40" cy="700" r="2"/><circle cx="1140" cy="700" r="2"/></g>
<text x="40" y="44" fill="#eaf6ff" font-size="22" font-weight="700">Per-family — where the backends differ</text>
<text x="470" y="44" fill="#9fc9e0" font-size="14.5">same trait, three serving styles • each maps ArtifactId → a different archive path layout</text>
<!-- ===== Class 1: real registry protocol ===== -->
<rect x="40" y="100" width="356" height="252" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="60" y="128" fill="#dff1ff" font-size="15" font-weight="700">A · real registry protocol</text>
<text x="60" y="148" fill="#7fa8c4" font-size="10.5">handle_http2_request synthesizes the index from the archive</text>
<g font-size="11">
<rect x="58" y="160" width="320" height="34" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="70" y="178" fill="#eaf6ff" font-weight="700">python</text><text x="158" y="178" fill="#9fc9e0" font-size="10">PEP 503 /simple/ HTML index</text>
<text x="70" y="190" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">packages/{norm}/{file}</text>
<rect x="58" y="200" width="320" height="34" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="70" y="218" fill="#eaf6ff" font-weight="700">npm</text><text x="158" y="218" fill="#9fc9e0" font-size="10">packument JSON + tarball</text>
<text x="70" y="230" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{pkg}/-/{pkg}-{ver}.tgz</text>
<rect x="58" y="240" width="320" height="34" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="70" y="258" fill="#eaf6ff" font-weight="700">nuget</text><text x="158" y="258" fill="#9fc9e0" font-size="10">V3 service + flat-container</text>
<text x="70" y="270" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{id}/{ver}/{id}.{ver}.nupkg</text>
<rect x="58" y="280" width="320" height="34" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="70" y="298" fill="#eaf6ff" font-weight="700">go</text><text x="158" y="298" fill="#9fc9e0" font-size="10">GOPROXY @v/list,.info,.mod,.zip</text>
<text x="70" y="310" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{module}/@v/{ver}.zip</text>
<rect x="58" y="320" width="320" height="26" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="70" y="337" fill="#eaf6ff" font-weight="700">docker</text><text x="158" y="337" fill="#9fc9e0" font-size="10">OCI Registry V2 manifests+blobs</text>
</g>
<!-- ===== Class 1b: index-generating ===== -->
<rect x="412" y="100" width="356" height="252" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="432" y="128" fill="#dff1ff" font-size="15" font-weight="700">A′ · index built on demand</text>
<text x="432" y="148" fill="#7fa8c4" font-size="10.5">scans .tgz / .nupkg, emits the manifest at request time</text>
<g font-size="11">
<rect x="430" y="160" width="320" height="50" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="442" y="180" fill="#eaf6ff" font-weight="700">helm</text><text x="530" y="180" fill="#9fc9e0" font-size="10">index.yaml (pre-baked or generated)</text>
<text x="442" y="196" fill="#86b3cc" font-size="9.5">parses Chart.yaml, SHA-256 digest per chart</text>
<text x="442" y="206" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{chart}-{ver}.tgz</text>
<rect x="430" y="216" width="320" height="40" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="442" y="236" fill="#eaf6ff" font-weight="700">composer</text><text x="560" y="236" fill="#9fc9e0" font-size="10">v2 p2/{vendor}/{pkg}.json</text>
<text x="442" y="250" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">dist/{vendor}/{pkg}/{ref}.zip</text>
<rect x="430" y="262" width="320" height="40" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="442" y="282" fill="#eaf6ff" font-weight="700">rust</text><text x="530" y="282" fill="#9fc9e0" font-size="10">cargo sparse index + config.json</text>
<text x="442" y="296" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{crate}-{ver}.crate</text>
<rect x="430" y="308" width="320" height="38" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="442" y="327" fill="#eaf6ff" font-weight="700">gem</text><text x="530" y="327" fill="#9fc9e0" font-size="10">compact-index (skeleton)</text>
<text x="442" y="340" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">gems/{name}-{ver}.gem</text>
</g>
<!-- ===== Class 2: path passthrough ===== -->
<rect x="784" y="100" width="356" height="252" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="804" y="128" fill="#dff1ff" font-size="15" font-weight="700">B · path passthrough</text>
<text x="804" y="148" fill="#7fa8c4" font-size="10.5">client knows the layout; holger serves the bytes verbatim</text>
<g font-size="11">
<rect x="802" y="160" width="320" height="44" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="814" y="180" fill="#eaf6ff" font-weight="700">maven</text><text x="908" y="180" fill="#9fc9e0" font-size="10">groupId→path, content-type sniff</text>
<text x="814" y="196" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{group}/{art}/{ver}/{art}-{ver}.jar</text>
<rect x="802" y="210" width="320" height="40" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="814" y="230" fill="#eaf6ff" font-weight="700">deb</text><text x="908" y="230" fill="#9fc9e0" font-size="10">APT dists/… + pool/…</text>
<text x="814" y="244" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">Release, Packages.gz, *.deb</text>
<rect x="802" y="256" width="320" height="40" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="814" y="276" fill="#eaf6ff" font-weight="700">rpm</text><text x="908" y="276" fill="#9fc9e0" font-size="10">yum repodata/…</text>
<text x="814" y="290" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">repomd.xml, primary.xml.gz, *.rpm</text>
<rect x="802" y="302" width="320" height="44" rx="7" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.5"/>
<text x="814" y="322" fill="#eaf6ff" font-weight="700">conda</text><text x="908" y="322" fill="#9fc9e0" font-size="10">channel subdir + repodata.json</text>
<text x="814" y="336" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{subdir}/{pkg}.conda | .tar.bz2</text>
</g>
<!-- native class -->
<rect x="40" y="380" width="356" height="84" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="60" y="408" fill="#dff1ff" font-size="15" font-weight="700">C · native (znippy-package)</text>
<text x="60" y="430" fill="#9fc9e0" font-size="11">serves znippy's own format — no translation layer</text>
<text x="60" y="450" fill="#86b3cc" font-size="9.5" font-family="'DejaVu Sans Mono', monospace">{name}-{ver}.znippy · GET / lists packages</text>
<!-- common floor -->
<rect x="412" y="380" width="728" height="84" rx="12" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="432" y="408" fill="#eaf6ff" font-size="15" font-weight="700">all three classes share the same trait + storage</text>
<text x="432" y="430" fill="#9fc9e0" font-size="11">fetch(id) and handle_http2_request() both bottom out in ZnippyReader.extract_file(path)</text>
<text x="432" y="450" fill="#86b3cc" font-size="10.5">the only per-family code is the ArtifactId→path mapping and the protocol framing — everything else is reused</text>
<!-- converge -->
<g stroke="#6fd3f5" stroke-width="3" fill="none">
<path d="M218 352 V500 H560" marker-end="url(#arr)"/>
<path d="M590 352 V500" marker-end="url(#arr)"/>
<path d="M962 352 V500 H620" marker-end="url(#arr)"/>
<path d="M218 464 V500" marker-end="url(#arr)"/>
</g>
<!-- archive rail -->
<rect x="290" y="540" width="600" height="100" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2.5"/>
<text x="316" y="572" fill="#dff1ff" font-size="16" font-weight="700">ZnippyReader → one immutable archive</text>
<text x="316" y="596" fill="#bcd9ec" font-size="12">random-access extract by path · blake3 content-addressed · airgap-carriable</text>
<text x="316" y="620" fill="#7fa8c4" font-size="11" font-family="'DejaVu Sans Mono', monospace">znippy_type_id() pins each format to a DenseUnion discriminant — znippy is the source of truth</text>
</svg>