.nornir/assets/search-flow.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 860 380" 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}
.engine{fill:#1e293b;stroke:#38bdf8;stroke-width:2}
.hit{fill:#065f46;stroke:#10b981}
.surface{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}
.engine{fill:#e0f2fe;stroke:#0284c7}
.hit{fill:#d1fae5;stroke:#059669}
.surface{fill:#e0e7ff;stroke:#4f46e5}
}
</style>
</defs>
<text x="20" y="26" class="lbl" font-size="15" font-weight="700">Cross-repo SEARCH — one pure engine, three surfaces</text>
<!-- Three surfaces feed the same query -->
<rect class="surface" x="20" y="56" width="180" height="42" rx="8"/>
<text x="110" y="82" class="lbl" text-anchor="middle">gRPC SearchService.Search</text>
<rect class="surface" x="20" y="112" width="180" height="42" rx="8"/>
<text x="110" y="138" class="lbl" text-anchor="middle">HTTP GET /-/search</text>
<rect class="surface" x="20" y="168" width="180" height="42" rx="8"/>
<text x="110" y="194" class="lbl" text-anchor="middle">CLI holger-server search</text>
<!-- The query -->
<rect class="box" x="250" y="96" width="170" height="78" rx="8"/>
<text x="335" y="120" class="lbl" text-anchor="middle">SearchQuery</text>
<text x="335" y="139" class="sub" text-anchor="middle">name · namespace · version</text>
<text x="335" y="155" class="sub" text-anchor="middle">checksum · path · repos</text>
<path class="edge" d="M200,77 C225,90 225,110 250,118" marker-end="url(#arw)"/>
<path class="edge" d="M200,133 L250,133" marker-end="url(#arw)"/>
<path class="edge" d="M200,189 C225,176 225,156 250,150" marker-end="url(#arw)"/>
<!-- The engine -->
<rect class="engine" x="470" y="86" width="180" height="98" rx="8"/>
<text x="560" y="112" class="lbl" text-anchor="middle" font-weight="700">search_repos()</text>
<text x="560" y="132" class="sub" text-anchor="middle">for each repo (AND axes):</text>
<text x="560" y="149" class="sub" text-anchor="middle">list() → name/ns/ver match</text>
<text x="560" y="165" class="sub" text-anchor="middle">fetch()+sha256 for checksum</text>
<text x="560" y="181" class="sub" text-anchor="middle">archive_files() for path</text>
<path class="edge" d="M420,135 L470,135" marker-end="url(#arw)"/>
<!-- Backends -->
<rect class="box" x="470" y="220" width="180" height="60" rx="8"/>
<text x="560" y="245" class="lbl" text-anchor="middle">every configured backend</text>
<text x="560" y="263" class="sub" text-anchor="middle">RepositoryBackendTrait</text>
<path class="edge" d="M560,184 L560,220" marker-end="url(#arw)"/>
<path class="edge" d="M560,220 L560,186" marker-end="url(#arw)"/>
<!-- Results -->
<rect class="hit" x="700" y="86" width="140" height="98" rx="8"/>
<text x="770" y="112" class="lbl" text-anchor="middle" font-weight="700">SearchResults</text>
<text x="770" y="133" class="sub" text-anchor="middle">artifacts[]</text>
<text x="770" y="150" class="sub" text-anchor="middle">paths[]</text>
<text x="770" y="167" class="sub" text-anchor="middle">truncated</text>
<path class="edge" d="M650,135 L700,135" marker-end="url(#arw)"/>
<text x="20" y="330" class="sub">Read-only aggregate — no auth on the read path, audited as a List. One bad archive is skipped, never sinks the search.</text>
<text x="20" y="352" class="sub">checksum is the one expensive axis (fetch + hash); it fires only when supplied.</text>
</svg>