.nornir/assets/flow_pull.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 600" 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>
<marker id="arrd" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0 0 L7 3 L0 6 Z" fill="#7fb0cc"/></marker>
</defs>
<rect x="0" y="0" width="1180" height="600" fill="url(#board)"/>
<g stroke="#143a52" stroke-width="1"><line x1="0" y1="74" x2="1180" y2="74"/><line x1="0" y1="560" x2="1180" y2="560"/></g>
<g fill="#16415c"><circle cx="40" cy="40" r="2"/><circle cx="1140" cy="40" r="2"/><circle cx="40" cy="580" r="2"/><circle cx="1140" cy="580" r="2"/></g>
<text x="40" y="44" fill="#eaf6ff" font-size="22" font-weight="700">Pull / serve — a client fetches an artifact</text>
<text x="540" y="44" fill="#9fc9e0" font-size="14.5">two front doors, one backend → random-access extract from the znippy archive</text>
<!-- two clients -->
<rect x="40" y="150" width="190" height="84" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="135" y="182" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">native tool</text>
<text x="135" y="202" fill="#9fc9e0" font-size="10.5" text-anchor="middle">cargo / pip / npm / docker</text>
<text x="135" y="220" fill="#86b3cc" font-size="10" text-anchor="middle">speaks its registry HTTP protocol</text>
<rect x="40" y="356" width="190" height="84" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="135" y="388" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">holger client</text>
<text x="135" y="408" fill="#9fc9e0" font-size="10.5" text-anchor="middle">HolgerObject.fetch()</text>
<text x="135" y="426" fill="#86b3cc" font-size="10" text-anchor="middle">gRPC or in-process</text>
<!-- two entrypoints -->
<rect x="296" y="142" width="208" height="100" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="400" y="172" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">handle_http2_request</text>
<text x="400" y="192" fill="#9fc9e0" font-size="10.5" text-anchor="middle">(suburl, body)</text>
<text x="400" y="210" fill="#86b3cc" font-size="10" text-anchor="middle">parse path → protocol verb</text>
<text x="400" y="228" fill="#86b3cc" font-size="10" text-anchor="middle">→ (status, headers, bytes)</text>
<rect x="296" y="348" width="208" height="100" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="400" y="378" fill="#eaf6ff" font-size="14.5" font-weight="700" text-anchor="middle">RepositoryService</text>
<text x="400" y="398" fill="#9fc9e0" font-size="10.5" text-anchor="middle">fetch / stream_artifact</text>
<text x="400" y="416" fill="#86b3cc" font-size="10" text-anchor="middle">tonic → FastRoutes.lookup</text>
<text x="400" y="434" fill="#86b3cc" font-size="10" text-anchor="middle">64 KB chunks on stream</text>
<!-- backend dispatch -->
<rect x="570" y="244" width="206" height="112" rx="10" fill="url(#chip)" stroke="#7fc8ea" stroke-width="2.5"/>
<text x="673" y="276" fill="#eaf6ff" font-size="15" font-weight="700" text-anchor="middle">backend.fetch(id)</text>
<text x="673" y="298" fill="#9fc9e0" font-size="11" text-anchor="middle">RepositoryBackendTrait</text>
<text x="673" y="318" fill="#86b3cc" font-size="10.5" text-anchor="middle">ArtifactId → archive path</text>
<text x="673" y="338" fill="#86b3cc" font-size="10" text-anchor="middle">{name}-{ver}.crate, v2/…, …</text>
<!-- znippy reader -->
<rect x="842" y="244" width="190" height="112" rx="10" fill="url(#chip)" stroke="#5aa6cf" stroke-width="2"/>
<text x="937" y="276" fill="#eaf6ff" font-size="15" font-weight="700" text-anchor="middle">ZnippyReader</text>
<text x="937" y="298" fill="#9fc9e0" font-size="11" text-anchor="middle">extract_file(path)</text>
<text x="937" y="318" fill="#86b3cc" font-size="10.5" text-anchor="middle">random access • no full</text>
<text x="937" y="336" fill="#86b3cc" font-size="10.5" text-anchor="middle">decompress • blake3 verify</text>
<!-- storage rail -->
<rect x="296" y="500" width="736" height="52" rx="10" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="322" y="525" fill="#dff1ff" font-size="15" font-weight="700">archive</text>
<text x="400" y="524" fill="#bcd9ec" font-size="12">immutable, content-addressed znippy file (or {repo}/{path} dev tree)</text>
<text x="400" y="542" fill="#7fa8c4" font-size="10" font-family="'DejaVu Sans Mono', monospace">one archive per repository version · carriable across the airgap</text>
<!-- arrows -->
<g stroke="#6fd3f5" stroke-width="3" fill="none">
<path d="M230 192 H294" marker-end="url(#arr)"/>
<path d="M230 398 H294" marker-end="url(#arr)"/>
<path d="M504 192 H540 V292 H568" marker-end="url(#arr)"/>
<path d="M504 398 H540 V310 H568" marker-end="url(#arr)"/>
<path d="M776 300 H840" marker-end="url(#arr)"/>
<path d="M937 356 V500" marker-end="url(#arr)"/>
</g>
<!-- return bytes (dashed back up) -->
<path d="M842 330 H800 V470 H400 V448" stroke="#7fb0cc" stroke-width="2.5" fill="none" stroke-dasharray="6 4" marker-end="url(#arrd)"/>
<path d="M812 470 V250 H400 V240" stroke="#7fb0cc" stroke-width="2.5" fill="none" stroke-dasharray="6 4" marker-end="url(#arrd)"/>
<text x="600" y="486" fill="#86b3cc" font-size="11">bytes returned → framed in the caller's protocol</text>
</svg>