crates/jera/.nornir/assets/multimachine-dispatch-flow.svg
<svg xmlns="http://www.w3.org/2000/svg" width="880" height="470" viewBox="0 0 880 470" font-family="ui-sans-serif,system-ui,sans-serif">
<defs>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0 L10 5 L0 10 z" fill="#6fd3f5"/>
</marker>
<marker id="arrN" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0 L10 5 L0 10 z" fill="#c9a24b"/>
</marker>
</defs>
<style>
.box{fill:#0f1720;stroke:#6fd3f5;stroke-width:1.5;rx:8}
.grp{fill:#0c1a12;stroke:#5fbf7f;stroke-width:1.4;rx:8}
.pay{fill:#141020;stroke:#a98fe0;stroke-width:1.5;rx:8}
.led{fill:#1a1409;stroke:#c9a24b;stroke-width:1.4;rx:8}
.t{fill:#dfe9f0;font-size:13px}
.h{fill:#6fd3f5;font-size:13px;font-weight:700}
.hg{fill:#7fe0a0;font-size:13px;font-weight:700}
.hp{fill:#c3aef0;font-size:13px;font-weight:700}
.hl{fill:#d9b968;font-size:13px;font-weight:700}
.s{fill:#9fb3c0;font-size:11px}
.e{stroke:#6fd3f5;stroke-width:1.6;fill:none}
.en{stroke:#c9a24b;stroke-width:1.6;fill:none;stroke-dasharray:4 3}
.wire{stroke:#a98fe0;stroke-width:1.8;fill:none;stroke-dasharray:6 4}
</style>
<text x="20" y="26" class="h" font-size="15">jera::agent — multi-machine dispatch (stages 1–3)</text>
<text x="20" y="44" class="s">a WorkUnit carries WHAT to run (payload) → scheduled to a live roster node → the ledger records WHICH node ran it</text>
<!-- WorkUnit + payload (stage 1) -->
<rect class="box" x="20" y="76" width="210" height="120"/>
<text x="32" y="100" class="h">WorkUnit + WorkPayload</text>
<text x="32" y="120" class="t">id · group · required_caps</text>
<rect class="pay" x="32" y="130" width="186" height="54"/>
<text x="42" y="149" class="hp">payload (the keystone)</text>
<text x="42" y="167" class="s">Process · Container · Vm</text>
<text x="42" y="180" class="s">policy() → JobPolicy · serde-ready</text>
<!-- Scheduler -->
<rect class="box" x="280" y="84" width="200" height="104"/>
<text x="292" y="108" class="h">Scheduler (control-plane)</text>
<text x="292" y="130" class="t">match: idle ∧ group== ∧ caps⊇</text>
<text x="292" y="149" class="t">dispatch → emits testmatrix</text>
<text x="292" y="168" class="s">row for payload units (obs.)</text>
<path class="e" d="M230 136 H280" marker-end="url(#arr)"/>
<!-- GrpcAgent -->
<rect class="box" x="280" y="220" width="200" height="72"/>
<text x="292" y="244" class="h">GrpcAgent (remote)</text>
<text x="292" y="264" class="t">payload_json over the wire</text>
<text x="292" y="282" class="s">bidirectional kill · unreachable→Offline</text>
<path class="e" d="M380 188 V220" marker-end="url(#arr)"/>
<!-- remote worker -->
<rect class="grp" x="540" y="210" width="310" height="92"/>
<text x="554" y="233" class="hg">worker box · <host> agent --group …</text>
<text x="554" y="255" class="t">LocalAgent runs the payload</text>
<text x="554" y="274" class="t">Process→Exe · Container→OCI · Vm→KVM</text>
<text x="554" y="291" class="s">maps payload → jera boot/container/vm (draupnir)</text>
<path class="wire" d="M480 256 H540" marker-end="url(#arr)"/>
<text x="486" y="248" class="hp" font-size="11">gRPC</text>
<!-- Roster (stage 2) -->
<rect class="grp" x="540" y="76" width="310" height="110"/>
<text x="554" y="99" class="hg">Roster — durable fleet (stage 2)</text>
<text x="554" y="120" class="t">Loki 12c · Odin 32c · NUC 6c · Njord</text>
<text x="554" y="139" class="t">NodeEntry: spec · endpoint · last_heartbeat</text>
<text x="554" y="158" class="t">is_live(now) · heartbeat · evict_dead</text>
<text x="554" y="176" class="s">load/save JSON · stale past window → dead</text>
<path class="e" d="M540 122 C505 128 505 130 480 132" marker-end="url(#arr)"/>
<text x="486" y="118" class="s">seeds live agents</text>
<!-- Ledger (stage 3) -->
<rect class="led" x="280" y="330" width="570" height="96"/>
<text x="292" y="354" class="hl">JobRecord ledger (redb) — stage 3</text>
<text x="292" y="376" class="t">node: Option<String> ← which machine ran it</text>
<text x="292" y="395" class="t">JobHandle::on_node("odin") · #[serde(default)] non-breaking</text>
<text x="292" y="413" class="s">a distributed job now shows its node; a local job stays node: None</text>
<path class="en" d="M690 302 V330" marker-end="url(#arrN)"/>
<text x="700" y="320" class="s">stamps node</text>
</svg>