.nornir/assets/thund-checkpoint-recovery.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 640" 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>
<linearGradient id="slot" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#123448"/>
<stop offset="1" stop-color="#0a2032"/>
</linearGradient>
<linearGradient id="commit" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#12463a"/>
<stop offset="1" stop-color="#0a2c26"/>
</linearGradient>
<marker id="arw" markerWidth="10" markerHeight="10" refX="7" refY="3.2" orient="auto">
<path d="M0,0 L7,3.2 L0,6.4 Z" fill="#6fd3f5"/>
</marker>
<marker id="barw" markerWidth="10" markerHeight="10" refX="7" refY="3.2" orient="auto">
<path d="M0,0 L7,3.2 L0,6.4 Z" fill="#7fb0cc"/>
</marker>
<marker id="grn" markerWidth="10" markerHeight="10" refX="7" refY="3.2" orient="auto">
<path d="M0,0 L7,3.2 L0,6.4 Z" fill="#57d9a3"/>
</marker>
<marker id="cor" markerWidth="10" markerHeight="10" refX="7" refY="3.2" orient="auto">
<path d="M0,0 L7,3.2 L0,6.4 Z" fill="#ff7a7a"/>
</marker>
</defs>
<rect x="0" y="0" width="1180" height="640" fill="url(#board)"/>
<g stroke="#143a52" stroke-width="1">
<line x1="0" y1="72" x2="1180" y2="72"/>
<line x1="0" y1="596" x2="1180" y2="596"/>
</g>
<!-- title -->
<text x="40" y="42" fill="#eaf6ff" font-size="22" font-weight="700">Þund · barrier / epoch checkpoint + crash recovery</text>
<text x="40" y="63" fill="#9fc9e0" font-size="13.5">one epoch = one trigger • commit-by-manifest • restore from the latest <tspan fill="#bfe6ff" font-weight="700">COMMITTED</tspan> epoch and resume from its offset (phases 3-4)</text>
<!-- legend -->
<g font-size="11.5">
<line x1="748" y1="38" x2="782" y2="38" stroke="#7fb0cc" stroke-width="2.5" stroke-dasharray="6 4" marker-end="url(#barw)"/>
<text x="790" y="42" fill="#cfe6f5">barrier → snapshot</text>
<line x1="946" y1="38" x2="980" y2="38" stroke="#57d9a3" stroke-width="2.5" marker-end="url(#grn)"/>
<text x="988" y="42" fill="#cfe6f5">restore & resume</text>
</g>
<!-- ===== stream timeline: 4 triggers ===== -->
<text x="40" y="108" fill="#9fc9e0" font-size="12" font-weight="700">stream → triggers (epochs)</text>
<g stroke="#6fd3f5" stroke-width="3" fill="none">
<path d="M40 150 H1120" marker-end="url(#arw)"/>
</g>
<!-- trigger chips -->
<g>
<!-- t0 -->
<rect x="70" y="122" width="180" height="56" rx="9" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.8"/>
<text x="160" y="146" fill="#eaf6ff" font-size="13" font-weight="700" text-anchor="middle">trigger 0 · epoch 0</text>
<text x="160" y="166" fill="#9fc9e0" font-size="10" text-anchor="middle" font-family="'DejaVu Sans Mono', monospace">observe W · drop late · window</text>
<!-- t1 -->
<rect x="290" y="122" width="180" height="56" rx="9" fill="url(#chip)" stroke="#5aa6cf" stroke-width="1.8"/>
<text x="380" y="146" fill="#eaf6ff" font-size="13" font-weight="700" text-anchor="middle">trigger 1 · epoch 1</text>
<text x="380" y="166" fill="#9fc9e0" font-size="10" text-anchor="middle" font-family="'DejaVu Sans Mono', monospace">offset = 2 committed</text>
<!-- crash gap -->
<rect x="510" y="118" width="150" height="64" rx="9" fill="#2a1420" stroke="#ff7a7a" stroke-width="1.8" stroke-dasharray="5 4"/>
<text x="585" y="146" fill="#ff9a9a" font-size="14" font-weight="700" text-anchor="middle">💥 CRASH</text>
<text x="585" y="166" fill="#c99" font-size="9.5" text-anchor="middle">state lost from heap</text>
<!-- t2 (resumed) -->
<rect x="700" y="122" width="180" height="56" rx="9" fill="url(#chip)" stroke="#57d9a3" stroke-width="1.8"/>
<text x="790" y="146" fill="#eaf6ff" font-size="13" font-weight="700" text-anchor="middle">trigger 2 · epoch 2</text>
<text x="790" y="166" fill="#9fc9e0" font-size="10" text-anchor="middle" font-family="'DejaVu Sans Mono', monospace">resumed at offset 2</text>
<!-- t3 -->
<rect x="920" y="122" width="180" height="56" rx="9" fill="url(#chip)" stroke="#57d9a3" stroke-width="1.8"/>
<text x="1010" y="146" fill="#eaf6ff" font-size="13" font-weight="700" text-anchor="middle">trigger 3 · epoch 3</text>
<text x="1010" y="166" fill="#9fc9e0" font-size="10" text-anchor="middle" font-family="'DejaVu Sans Mono', monospace">windows close once</text>
</g>
<!-- barrier arrows down to store (t0, t1) -->
<g stroke="#7fb0cc" stroke-width="2.4" fill="none" stroke-dasharray="6 4">
<path d="M160 178 V300" marker-end="url(#barw)"/>
<path d="M380 178 V300" marker-end="url(#barw)"/>
</g>
<!-- barrier arrows down (t2, t3) -->
<g stroke="#7fb0cc" stroke-width="2.4" fill="none" stroke-dasharray="6 4">
<path d="M790 178 V300" marker-end="url(#barw)"/>
<path d="M1010 178 V300" marker-end="url(#barw)"/>
</g>
<!-- ===== checkpoint state store row ===== -->
<rect x="40" y="308" width="1080" height="150" rx="12" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2.2"/>
<text x="60" y="334" fill="#eaf6ff" font-size="15" font-weight="700">checkpoint state store</text>
<text x="60" y="353" fill="#9fc9e0" font-size="11">{state_root}/{pipeline}/{flow_target}/ · parquet-rs ArrowWriter over datafusion::object_store (== graphar ChunkStore substrate)</text>
<!-- committed epoch dirs -->
<g font-family="'DejaVu Sans Mono', monospace" font-size="9">
<rect x="70" y="368" width="180" height="72" rx="7" fill="url(#commit)" stroke="#57d9a3" stroke-width="1.6"/>
<text x="160" y="386" fill="#bfeede" font-size="10" text-anchor="middle" font-weight="700">epoch=000000/ ✓</text>
<text x="80" y="404" fill="#cfe6f5">operator-state.parquet</text>
<text x="80" y="418" fill="#cfe6f5">_thund_checkpoint.json</text>
<text x="80" y="432" fill="#9fd8bf">status: COMMITTED</text>
<rect x="290" y="368" width="180" height="72" rx="7" fill="url(#commit)" stroke="#57d9a3" stroke-width="1.6"/>
<text x="380" y="386" fill="#bfeede" font-size="10" text-anchor="middle" font-weight="700">epoch=000001/ ✓</text>
<text x="300" y="404" fill="#cfe6f5">W, input_maxima</text>
<text x="300" y="418" fill="#cfe6f5">offset = 2</text>
<text x="300" y="432" fill="#cfe6f5">emitted_windows[]</text>
<rect x="700" y="368" width="180" height="72" rx="7" fill="url(#slot)" stroke="#6fd3f5" stroke-width="1.3"/>
<text x="790" y="386" fill="#bfe6ff" font-size="10" text-anchor="middle" font-weight="700">epoch=000002/</text>
<text x="710" y="404" fill="#cfe6f5">operator-state.parquet</text>
<text x="710" y="418" fill="#cfe6f5">offset = 3</text>
<rect x="920" y="368" width="180" height="72" rx="7" fill="url(#slot)" stroke="#6fd3f5" stroke-width="1.3"/>
<text x="1010" y="386" fill="#bfe6ff" font-size="10" text-anchor="middle" font-weight="700">epoch=000003/</text>
<text x="930" y="404" fill="#cfe6f5">offset = 4</text>
<text x="930" y="418" fill="#cfe6f5">_thund_latest.json →</text>
</g>
<!-- latest pointer callout -->
<rect x="500" y="386" width="170" height="40" rx="7" fill="url(#slot)" stroke="#7fb0cc" stroke-width="1.3"/>
<text x="585" y="404" fill="#bfe6ff" font-size="9.5" text-anchor="middle" font-family="'DejaVu Sans Mono', monospace">_thund_latest.json</text>
<text x="585" y="418" fill="#86b3cc" font-size="8.5" text-anchor="middle">flip = publish (last write)</text>
<!-- ===== recovery arrow: latest committed epoch -> resumed trigger ===== -->
<g stroke="#57d9a3" stroke-width="2.6" fill="none">
<path d="M470 404 H500" />
<path d="M380 368 V330 H705 V178" marker-end="url(#grn)"/>
</g>
<text x="548" y="256" fill="#9fe6c9" font-size="11" font-weight="700" text-anchor="middle">restore latest COMMITTED (epoch 1)</text>
<text x="548" y="272" fill="#86b3cc" font-size="9.5" text-anchor="middle">open rows + W + maxima + emitted set + offset → resume, no double-emit / no loss</text>
<!-- ===== ground rail ===== -->
<rect x="40" y="500" width="1080" height="78" rx="11" fill="url(#rail)" stroke="#7fc8ea" stroke-width="2"/>
<text x="64" y="524" fill="#dff1ff" font-size="13" font-weight="700">guarantees</text>
<text x="64" y="544" fill="#bcd9ec" font-size="10.5">state exactly-once w.r.t. the checkpoint (restore == state as of the committed epoch) • output at-least-once, exactly-once with a keyed _gar_id MERGE sink</text>
<text x="64" y="562" fill="#7fa8c4" font-size="10" font-family="'DejaVu Sans Mono', monospace">single-node sequential (trivial barrier alignment) · replayable source · commit order: state.parquet → manifest (COMMITTED) → latest · partial epoch ignored</text>
<text x="40" y="618" fill="#7fa8c4" font-size="10">Additive: Recompute stays the default; this is the opt-in Incremental path with a state_root. Restart boundary in tests/benches = NativeBackend::with_max_triggers.</text>
</svg>