.nornir/assets/release-doctor-loop.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 1000" font-family="ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif">
<defs>
<marker id="arw" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L8,3 L0,6 Z" fill="#4b5563"/>
</marker>
<marker id="arwLoop" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L8,3 L0,6 Z" fill="#2563eb"/>
</marker>
</defs>
<style>
.lbl{font-size:14px;fill:#111827}.sm{font-size:11px;fill:#6b7280}
.box{stroke-width:1.5}.edge{stroke:#4b5563;stroke-width:1.6;fill:none}
.loop{stroke:#2563eb;stroke-width:1.6;fill:none;stroke-dasharray:5 4}
.elbl{font-size:12px;font-weight:600}
</style>
<text x="380" y="34" text-anchor="middle" font-size="20" font-weight="700" fill="#111827">release doctor — invertive solve loop</text>
<text x="380" y="54" text-anchor="middle" class="sm">assess → apply auto-remedies → re-assess → ask decisions → rehearse → fixpoint</text>
<!-- entry -->
<rect x="300" y="76" width="160" height="40" rx="20" fill="#eef2ff" stroke="#6366f1" class="box"/>
<text x="380" y="101" text-anchor="middle" class="lbl">nornir release doctor</text>
<!-- ASSESS -->
<rect x="280" y="150" width="200" height="52" rx="10" fill="#dbeafe" stroke="#2563eb" class="box"/>
<text x="380" y="172" text-anchor="middle" class="lbl" font-weight="600">ASSESS</text>
<text x="380" y="190" text-anchor="middle" class="sm">dirty · version-gaps · skew · cycles · block</text>
<!-- D1 auto? -->
<polygon points="380,232 500,282 380,332 260,282" fill="#dcfce7" stroke="#16a34a" class="box"/>
<text x="380" y="278" text-anchor="middle" class="lbl">AUTO-remediable</text>
<text x="380" y="296" text-anchor="middle" class="sm">finding present?</text>
<!-- APPLY -->
<rect x="548" y="256" width="185" height="52" rx="10" fill="#bbf7d0" stroke="#16a34a" class="box"/>
<text x="640" y="278" text-anchor="middle" class="lbl" font-weight="600">APPLY</text>
<text x="640" y="296" text-anchor="middle" class="sm">--fix · --tidy · bump-skew</text>
<!-- D2 decision? -->
<polygon points="380,360 500,410 380,460 260,410" fill="#fef3c7" stroke="#d97706" class="box"/>
<text x="380" y="406" text-anchor="middle" class="lbl">DECISION</text>
<text x="380" y="424" text-anchor="middle" class="sm">needed?</text>
<!-- ASK -->
<rect x="548" y="384" width="185" height="52" rx="10" fill="#fde68a" stroke="#d97706" class="box"/>
<text x="640" y="406" text-anchor="middle" class="lbl" font-weight="600">ASK</text>
<text x="640" y="424" text-anchor="middle" class="sm">one crisp question → human</text>
<!-- D3 unsolvable? -->
<polygon points="380,488 500,538 380,588 260,538" fill="#fee2e2" stroke="#dc2626" class="box"/>
<text x="380" y="534" text-anchor="middle" class="lbl">UNSOLVABLE</text>
<text x="380" y="552" text-anchor="middle" class="sm">by the engine?</text>
<!-- EXPLAIN -->
<rect x="548" y="512" width="185" height="52" rx="10" fill="#fecaca" stroke="#dc2626" class="box"/>
<text x="640" y="530" text-anchor="middle" class="lbl" font-weight="600">EXPLAIN & HALT</text>
<text x="640" y="548" text-anchor="middle" class="sm">why + what a human must do</text>
<!-- REHEARSE -->
<rect x="280" y="616" width="200" height="52" rx="10" fill="#ede9fe" stroke="#7c3aed" class="box"/>
<text x="380" y="638" text-anchor="middle" class="lbl" font-weight="600">REHEARSE → /sparring</text>
<text x="380" y="656" text-anchor="middle" class="sm">publish topo order · build from registry</text>
<!-- D4 green? -->
<polygon points="380,696 490,741 380,786 270,741" fill="#e0e7ff" stroke="#4f46e5" class="box"/>
<text x="380" y="746" text-anchor="middle" class="lbl">green?</text>
<!-- parse error -->
<rect x="520" y="715" width="215" height="52" rx="10" fill="#e5e7eb" stroke="#6b7280" class="box"/>
<text x="627" y="737" text-anchor="middle" class="lbl" font-weight="600">parse cargo error</text>
<text x="627" y="755" text-anchor="middle" class="sm">→ new symptom</text>
<!-- DONE -->
<rect x="288" y="836" width="184" height="48" rx="24" fill="#16a34a" class="box" stroke="#166534"/>
<text x="380" y="865" text-anchor="middle" font-size="16" font-weight="700" fill="#fff">✅ RELEASE-READY</text>
<!-- edges -->
<path class="edge" d="M380,116 L380,150" marker-end="url(#arw)"/>
<path class="edge" d="M380,202 L380,232" marker-end="url(#arw)"/>
<path class="edge" d="M500,282 L548,282" marker-end="url(#arw)"/>
<text x="524" y="274" text-anchor="middle" class="elbl" fill="#16a34a">yes</text>
<path class="edge" d="M380,332 L380,360" marker-end="url(#arw)"/>
<text x="392" y="350" class="elbl" fill="#6b7280">no</text>
<path class="edge" d="M500,410 L548,410" marker-end="url(#arw)"/>
<text x="524" y="402" text-anchor="middle" class="elbl" fill="#d97706">yes</text>
<path class="edge" d="M380,460 L380,488" marker-end="url(#arw)"/>
<text x="392" y="480" class="elbl" fill="#6b7280">no</text>
<path class="edge" d="M500,538 L548,538" marker-end="url(#arw)"/>
<text x="524" y="530" text-anchor="middle" class="elbl" fill="#dc2626">yes</text>
<path class="edge" d="M380,588 L380,616" marker-end="url(#arw)"/>
<text x="392" y="608" class="elbl" fill="#6b7280">no</text>
<path class="edge" d="M380,668 L380,696" marker-end="url(#arw)"/>
<path class="edge" d="M490,741 L520,741" marker-end="url(#arw)"/>
<text x="505" y="733" text-anchor="middle" class="elbl" fill="#6b7280">no</text>
<path class="edge" d="M380,786 L380,836" marker-end="url(#arw)"/>
<text x="392" y="816" class="elbl" fill="#16a34a">yes</text>
<!-- loop-backs (dashed blue) -->
<path class="loop" d="M640,256 C640,150 560,130 480,176" marker-end="url(#arwLoop)"/>
<text x="655" y="200" class="elbl" fill="#2563eb">re-assess</text>
<path class="loop" d="M640,384 C640,150 545,120 480,164" marker-end="url(#arwLoop)"/>
<path class="loop" d="M627,715 C627,150 545,110 480,158" marker-end="url(#arwLoop)"/>
<text x="150" y="410" class="sm" transform="rotate(-90 150 410)">every AUTO remedy shrinks the open-symptom set → the loop converges</text>
</svg>