stratumiops/assets/orchestrator/arch-stratum-orchestrator.svg
Jesús Pérez 9095ea6d8e
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
feat: add stratum-orchestrator with graph, state, NATS, and Nickel action nodes
New crates: stratum-orchestrator (Cedar authz, Vault secrets, Nu/agent executors,
  saga runner), stratum-graph (petgraph DAG + SurrealDB repo), stratum-state
  (SurrealDB tracker), platform-nats (NKey auth client), ncl-import-resolver.

  Updates: stratum-embeddings (SurrealDB store + persistent cache), stratum-llm
  circuit breaker. Adds Nickel action-nodes, schemas, config, Nushell scripts,
  docker-compose dev stack, and ADR-003.
2026-02-22 21:33:26 +00:00

478 lines
41 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1170 830">
<defs>
<style>
.flow { animation: flow-a 3s linear infinite; }
.flow-fast { animation: flow-a 1.8s linear infinite; }
.flow-slow { animation: flow-a 5s linear infinite; }
.flow-vslow { animation: flow-a 7s linear infinite; }
@keyframes flow-a { from { stroke-dashoffset: 24; } to { stroke-dashoffset: 0; } }
.orbit { animation: orbit-a 8s linear infinite; }
@keyframes orbit-a { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -68; } }
.glow-pulse { animation: glow-a 3s ease-in-out infinite; }
@keyframes glow-a { 0%,100% { opacity:.08; } 50% { opacity:.28; } }
.hb { animation: hb-a 3s ease-in-out infinite; }
.hb-d1 { animation-delay: .4s; }
.hb-d2 { animation-delay: .8s; }
.hb-d3 { animation-delay:1.2s; }
.hb-d4 { animation-delay:1.6s; }
.hb-d5 { animation-delay:2.0s; }
@keyframes hb-a { 0%,100% { opacity:.85; } 50% { opacity:1; } }
.particle-spin1 { animation: spin1 6s linear infinite; }
.particle-spin2 { animation: spin2 9s linear infinite; }
.particle-spin3 { animation: spin3 14s linear infinite; }
@keyframes spin1 { from { transform: rotate(0deg) translateX(120px); } to { transform: rotate(360deg) translateX(120px); } }
@keyframes spin2 { from { transform: rotate(120deg) translateX(120px); } to { transform: rotate(480deg) translateX(120px); } }
@keyframes spin3 { from { transform: rotate(240deg) translateX(120px); } to { transform: rotate(600deg) translateX(120px); } }
</style>
<radialGradient id="bg-grad" cx="50%" cy="42%" r="58%">
<stop offset="0%" stop-color="#131929"/>
<stop offset="100%" stop-color="#0F172A"/>
</radialGradient>
<linearGradient id="title-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#1e1b4b"/>
<stop offset="50%" stop-color="#0f172a"/>
<stop offset="100%" stop-color="#1e1b4b"/>
</linearGradient>
<linearGradient id="orch-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1e1b4b"/>
<stop offset="100%" stop-color="#0d0b22"/>
</linearGradient>
<linearGradient id="auth-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1c1700"/>
<stop offset="100%" stop-color="#0f0e00"/>
</linearGradient>
<linearGradient id="surreal-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1a0f2e"/>
<stop offset="100%" stop-color="#100820"/>
</linearGradient>
<linearGradient id="vault-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1c1400"/>
<stop offset="100%" stop-color="#110d00"/>
</linearGradient>
<linearGradient id="oci-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#021f20"/>
<stop offset="100%" stop-color="#011314"/>
</linearGradient>
<linearGradient id="forgejo-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1e0f02"/>
<stop offset="100%" stop-color="#120900"/>
</linearGradient>
<linearGradient id="exec-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#021a0b"/>
<stop offset="100%" stop-color="#011007"/>
</linearGradient>
<linearGradient id="agent-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#12082a"/>
<stop offset="100%" stop-color="#0a0518"/>
</linearGradient>
<linearGradient id="ncl-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#6366F1" stop-opacity=".25"/>
<stop offset="50%" stop-color="#22D3EE" stop-opacity=".15"/>
<stop offset="100%" stop-color="#6366F1" stop-opacity=".25"/>
</linearGradient>
<linearGradient id="event-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1e0a00"/>
<stop offset="100%" stop-color="#110600"/>
</linearGradient>
<linearGradient id="mod-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1a1850"/>
<stop offset="100%" stop-color="#10102e"/>
</linearGradient>
<linearGradient id="mod-ag-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1E1060"/>
<stop offset="100%" stop-color="#140C48"/>
</linearGradient>
<linearGradient id="mod-pc-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#082030"/>
<stop offset="100%" stop-color="#041520"/>
</linearGradient>
<linearGradient id="mod-sr-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0A2010"/>
<stop offset="100%" stop-color="#061508"/>
</linearGradient>
<linearGradient id="mod-re-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#201800"/>
<stop offset="100%" stop-color="#140F00"/>
</linearGradient>
<linearGradient id="orch-stripe" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#6366F1"/>
<stop offset="100%" stop-color="#22D3EE"/>
</linearGradient>
<filter id="shadow" x="-15%" y="-15%" width="130%" height="130%">
<feDropShadow dx="0" dy="3" stdDeviation="8" flood-color="#000" flood-opacity=".5"/>
</filter>
<filter id="glow-c" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<marker id="arr-cyan" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#22D3EE"/></marker>
<marker id="arr-gold" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#F59E0B"/></marker>
<marker id="arr-green" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#10B981"/></marker>
<marker id="arr-orange" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#F97316"/></marker>
<marker id="arr-purple" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#A78BFA"/></marker>
<marker id="arr-indigo" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#818CF8"/></marker>
<marker id="arr-silver" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#64748B"/></marker>
<marker id="arr-dcyan" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#06B6D4"/></marker>
<!-- ─── stratumiops-h logo defs ─── -->
<linearGradient id="hpLayerGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#6366F1"/><stop offset="100%" style="stop-color:#4F46E5"/>
</linearGradient>
<linearGradient id="hpProcessorGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#22D3EE"/><stop offset="100%" style="stop-color:#06B6D4"/>
</linearGradient>
<linearGradient id="hpFlowGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#6366F1"/><stop offset="50%" style="stop-color:#22D3EE"/><stop offset="100%" style="stop-color:#6366F1"/>
<animate attributeName="x1" values="0%;100%;0%" dur="2.5s" repeatCount="indefinite"/>
<animate attributeName="x2" values="100%;200%;100%" dur="2.5s" repeatCount="indefinite"/>
</linearGradient>
<linearGradient id="hpUnderlineGrad" x1="280" y1="195" x2="750" y2="195" gradientUnits="userSpaceOnUse">
<stop offset="0%" style="stop-color:#6366F1"/><stop offset="50%" style="stop-color:#22D3EE"/><stop offset="100%" style="stop-color:#6366F1"/>
</linearGradient>
<linearGradient id="hpShimmer" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#6366F1"/><stop offset="40%" style="stop-color:#6366F1"/>
<stop offset="50%" style="stop-color:#22D3EE"/><stop offset="60%" style="stop-color:#6366F1"/><stop offset="100%" style="stop-color:#6366F1"/>
<animate attributeName="x1" values="-100%;100%" dur="3s" repeatCount="indefinite" begin="2s"/>
<animate attributeName="x2" values="0%;200%" dur="3s" repeatCount="indefinite" begin="2s"/>
</linearGradient>
<filter id="hpProcessorGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="5" result="coloredBlur"><animate attributeName="stdDeviation" values="4;8;4" dur="1.5s" repeatCount="indefinite"/></feGaussianBlur>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="hpNodeGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<clipPath id="hpTextReveal">
<rect x="280" y="80" width="0" height="150">
<animate attributeName="width" values="0;800" dur="1s" fill="freeze" begin="1s" calcMode="spline" keySplines="0.25 0.1 0.25 1"/>
</rect>
</clipPath>
<path id="hpPathIn1" d="M25 40 Q25 65 55 80 Q85 95 95 100" fill="none"/>
<path id="hpPathIn2" d="M215 40 Q215 65 185 80 Q155 95 145 100" fill="none"/>
<path id="hpPathOut1" d="M95 100 Q85 105 55 120 Q25 135 25 160" fill="none"/>
<path id="hpPathOut2" d="M145 100 Q155 105 185 120 Q215 135 215 160" fill="none"/>
</defs>
<!-- ─── BACKGROUND ─── -->
<rect width="1170" height="830" fill="url(#bg-grad)"/>
<g opacity=".025">
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M40 0L0 0 0 40" fill="none" stroke="#fff" stroke-width=".5"/>
</pattern>
<rect width="1170" height="830" fill="url(#grid)"/>
</g>
<g opacity=".3">
<circle cx="90" cy="200" r=".8" fill="#fff"/>
<circle cx="420" cy="88" r=".6" fill="#fff"/>
<circle cx="980" cy="130" r="1" fill="#fff"/>
<circle cx="55" cy="600" r=".5" fill="#fff"/>
<circle cx="700" cy="800" r=".6" fill="#fff"/>
</g>
<!-- ─── TITLE BAR ─── -->
<rect x="0" y="0" width="1170" height="60" fill="url(#title-grad)"/>
<line x1="0" y1="60" x2="1170" y2="60" stroke="#6366F1" stroke-width=".8" opacity=".5"/>
<text x="600" y="30" text-anchor="middle" fill="#22D3EE" font-family="'IBM Plex Mono',monospace" font-size="19" font-weight="700" letter-spacing="3" dominant-baseline="middle">STRATUM ORCHESTRATOR</text>
<text x="600" y="50" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="9" letter-spacing="1.5">Event-Driven · Graph-Guided · Atomic Execution · Stateless · OCI-Native</text>
<!-- ─── EVENT SOURCES ROW ─── -->
<text x="400" y="82" text-anchor="middle" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" letter-spacing="2" opacity=".8">EVENT SOURCES</text>
<rect x="55" y="90" width="128" height="60" rx="6" fill="url(#event-grad)" filter="url(#shadow)" class="hb"/>
<rect x="55" y="90" width="3" height="60" rx="1" fill="#F97316"/>
<rect x="55" y="90" width="128" height="60" rx="6" fill="none" stroke="#F97316" stroke-width=".8" opacity=".4"/>
<text x="70" y="111" fill="#fff" font-family="Inter,sans-serif" font-size="11" font-weight="600">provisioning</text>
<text x="70" y="127" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".8">emits dev.crate.&gt;</text>
<text x="70" y="141" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">crate-modified · deploy</text>
<rect x="200" y="90" width="118" height="60" rx="6" fill="url(#event-grad)" filter="url(#shadow)" class="hb hb-d1"/>
<rect x="200" y="90" width="3" height="60" rx="1" fill="#F97316"/>
<rect x="200" y="90" width="118" height="60" rx="6" fill="none" stroke="#F97316" stroke-width=".8" opacity=".4"/>
<text x="215" y="111" fill="#fff" font-family="Inter,sans-serif" font-size="11" font-weight="600">kogral</text>
<text x="215" y="127" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".8">emits dev.knowledge.&gt;</text>
<text x="215" y="141" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">node-updated · indexed</text>
<rect x="335" y="90" width="120" height="60" rx="6" fill="url(#event-grad)" filter="url(#shadow)" class="hb hb-d2"/>
<rect x="335" y="90" width="3" height="60" rx="1" fill="#F97316"/>
<rect x="335" y="90" width="120" height="60" rx="6" fill="none" stroke="#F97316" stroke-width=".8" opacity=".4"/>
<text x="350" y="111" fill="#fff" font-family="Inter,sans-serif" font-size="11" font-weight="600">syntaxis</text>
<text x="350" y="127" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".8">emits dev.project.&gt;</text>
<text x="350" y="141" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">phase · task-completed</text>
<rect x="472" y="90" width="135" height="60" rx="6" fill="url(#event-grad)" filter="url(#shadow)" class="hb hb-d3"/>
<rect x="472" y="90" width="3" height="60" rx="1" fill="#F97316"/>
<rect x="472" y="90" width="135" height="60" rx="6" fill="none" stroke="#F97316" stroke-width=".8" opacity=".4"/>
<text x="487" y="111" fill="#fff" font-family="Inter,sans-serif" font-size="11" font-weight="600">stratumiops</text>
<text x="487" y="127" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".8">emits dev.model.&gt;</text>
<text x="487" y="141" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">llm-call · embed-request</text>
<rect x="624" y="90" width="118" height="60" rx="6" fill="url(#event-grad)" filter="url(#shadow)" class="hb hb-d4"/>
<rect x="624" y="90" width="3" height="60" rx="1" fill="#F97316"/>
<rect x="624" y="90" width="118" height="60" rx="6" fill="none" stroke="#F97316" stroke-width=".8" opacity=".4"/>
<text x="639" y="111" fill="#fff" font-family="Inter,sans-serif" font-size="11" font-weight="600">typedialog</text>
<text x="639" y="127" fill="#F97316" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".8">emits dev.form.&gt;</text>
<text x="639" y="141" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">submitted · validated</text>
<rect x="755" y="90" width="120" height="60" rx="6" fill="none" stroke="#64748B" stroke-width=".8" stroke-dasharray="4 3" opacity=".5"/>
<text x="815" y="127" text-anchor="middle" fill="#64748B" font-family="Inter,sans-serif" font-size="19">+ more ...</text>
<!-- ─── CONNECTIONS: PROJECTS → NATS (NATS cx=355 cy=385 r=129 → anillo exterior que pulsa) ─── -->
<line x1="119" y1="150" x2="317" y2="262" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" opacity=".7"/>
<line x1="259" y1="150" x2="337" y2="257" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" opacity=".7"/>
<line x1="395" y1="150" x2="355" y2="256" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" opacity=".7"/>
<line x1="539" y1="150" x2="374" y2="258" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" opacity=".7"/>
<line x1="686" y1="150" x2="396" y2="263" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" opacity=".7"/>
<!-- AUTH LAYER removed: NKeys → badge on NATS→Orch arrow; Cedar → inside RuleEngine -->
<!-- ─── NATS ORBITAL RING (cx=355, cy=385, r×0.9: 129/123/120/66) ─── -->
<circle cx="355" cy="385" r="129" fill="none" stroke="#22D3EE" stroke-width="14" opacity=".04" class="glow-pulse"/>
<circle cx="355" cy="385" r="123" fill="none" stroke="#22D3EE" stroke-width="10" opacity=".06" class="glow-pulse" filter="url(#glow-c)"/>
<circle cx="355" cy="385" r="120" fill="none" stroke="#64748B" stroke-width="1.5" stroke-dasharray="10 7" class="orbit"/>
<circle cx="355" cy="385" r="66" fill="none" stroke="#6366F1" stroke-width=".5" opacity=".2"/>
<radialGradient id="nats-inner" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#22D3EE" stop-opacity=".12"/>
<stop offset="100%" stop-color="#6366F1" stop-opacity=".04"/>
</radialGradient>
<circle cx="355" cy="385" r="66" fill="url(#nats-inner)"/>
<text x="355" y="379" text-anchor="middle" fill="#22D3EE" font-family="'IBM Plex Mono',monospace" font-size="20" font-weight="700" filter="url(#glow-c)">NATS</text>
<text x="355" y="398" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="9">JetStream</text>
<text x="344" y="235" text-anchor="middle" fill="#22D3EE" font-family="'IBM Plex Mono',monospace" font-size="9" opacity=".7">dev.&gt;</text>
<g transform="translate(355,385)">
<circle r="4" fill="#22D3EE" opacity=".9" class="particle-spin1"/>
<circle r="3.5" fill="#6366F1" opacity=".7" class="particle-spin2"/>
<circle r="3" fill="#F97316" opacity=".5" class="particle-spin3"/>
</g>
<!-- ─── NATS → ORCHESTRATOR (orbit right x=475, orch left x=498) — NKey checkpoint badge ─── -->
<path d="M 475,385 L 495,385" stroke="#22D3EE" stroke-width="3" stroke-dasharray="8 4" class="flow-fast" marker-end="url(#arr-cyan)" filter="url(#glow-c)"/>
<circle r="4" fill="#22D3EE" opacity=".85">
<animateMotion dur="0.25s" repeatCount="indefinite" path="M 475,385 L 495,385"/>
</circle>
<circle r="3" fill="#6366F1" opacity=".7">
<animateMotion dur="0.25s" repeatCount="indefinite" begin="0.08s" path="M 475,385 L 495,385"/>
</circle>
<!-- NKey verify badge — checkpoint at connection boundary -->
<rect x="379" y="419" width="78" height="22" rx="3" fill="#1c1700" stroke="#F59E0B" stroke-width=".7" opacity=".9"/>
<text x="418" y="429" text-anchor="middle" fill="#F59E0B" font-family="'IBM Plex Mono',monospace" font-size="7">NKey verify</text>
<text x="418" y="439" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="6.5">ed25519 JWT</text>
<!-- ─── ORCHESTRATOR BOX (x=498, y=250, w=375, h=300, center x=685) ─── -->
<rect x="498" y="250" width="375" height="300" rx="10" fill="#000" opacity=".2" transform="translate(3,5)"/>
<rect x="498" y="250" width="375" height="300" rx="10" fill="url(#orch-grad)" filter="url(#shadow)" class="hb"/>
<rect x="498" y="250" width="375" height="300" rx="10" fill="none" stroke="#6366F1" stroke-width=".8" opacity=".5" class="glow-pulse"/>
<rect x="498" y="250" width="4" height="300" rx="2" fill="url(#orch-stripe)"/>
<text x="685" y="273" text-anchor="middle" fill="#fff" font-family="Inter,sans-serif" font-size="14" font-weight="700">STRATUM ORCHESTRATOR</text>
<text x="685" y="290" text-anchor="middle" fill="#64748B" font-family="Inter,sans-serif" font-size="9">Agnostic · Stateless · Graph-Guided</text>
<!-- Internal modules 2×2 (cols of 170px each, gap 8px) -->
<!-- ActionGraph -->
<rect x="510" y="305" width="170" height="72" rx="6" fill="url(#mod-ag-grad)" stroke="#4F46E5" stroke-width=".7" opacity=".9"/>
<text x="522" y="325" fill="#A5B4FC" font-family="Inter,sans-serif" font-size="11" font-weight="600">◈ ActionGraph</text>
<text x="522" y="341" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">in-memory · Nickel nodes</text>
<text x="522" y="355" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">topo-sort · cycle-detect</text>
<!-- PipelineContext -->
<rect x="690" y="305" width="171" height="72" rx="6" fill="url(#mod-pc-grad)" stroke="#06B6D4" stroke-width=".7" opacity=".9"/>
<text x="702" y="325" fill="#A5B4FC" font-family="Inter,sans-serif" font-size="11" font-weight="600">⬡ PipelineCtx</text>
<text x="702" y="341" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">DB-first · typed caps</text>
<text x="702" y="355" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">schema-validated</text>
<!-- StageRunner -->
<rect x="510" y="389" width="170" height="130" rx="6" fill="url(#mod-sr-grad)" stroke="#10B981" stroke-width=".7" opacity=".9"/>
<text x="522" y="407" fill="#A5B4FC" font-family="Inter,sans-serif" font-size="11" font-weight="600">▶ StageRunner</text>
<text x="522" y="423" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">JoinSet · parallel stages</text>
<text x="522" y="437" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">CancellationToken</text>
<text x="522" y="451" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">retry + backoff on failure</text>
<text x="522" y="465" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">saga compensate.nu</text>
<!-- RuleEngine (Cedar + NKey as named sub-sections) -->
<rect x="690" y="389" width="171" height="130" rx="6" fill="url(#mod-re-grad)" stroke="#F59E0B" stroke-width=".7" opacity=".9"/>
<text x="702" y="407" fill="#A5B4FC" font-family="Inter,sans-serif" font-size="11" font-weight="600">⚙ RuleEngine</text>
<rect x="810" y="393" width="44" height="13" rx="3" fill="#1c1700" stroke="#F59E0B" stroke-width=".6"/>
<text x="832" y="402" text-anchor="middle" fill="#F59E0B" font-family="'IBM Plex Mono',monospace" font-size="7">Cedar</text>
<line x1="702" y1="415" x2="854" y2="415" stroke="#4F46E5" stroke-width=".4" opacity=".5"/>
<text x="702" y="428" fill="#F59E0B" font-family="'IBM Plex Mono',monospace" font-size="8" font-weight="600">◈ Cedar</text>
<text x="702" y="441" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">permit · forbid · conditions</text>
<text x="702" y="454" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">per-node authz policies</text>
<line x1="702" y1="463" x2="854" y2="463" stroke="#4F46E5" stroke-width=".4" opacity=".5"/>
<text x="702" y="476" fill="#F59E0B" font-family="'IBM Plex Mono',monospace" font-size="8" font-weight="600">◈ NKey</text>
<text x="702" y="489" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">ed25519 asymmetric keys</text>
<text x="702" y="502" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">JWT per-process · verify</text>
<text x="685" y="538" text-anchor="middle" fill="#F87171" font-family="'IBM Plex Mono',monospace" font-size="8" opacity=".75">↺ Saga rollback on failure · compensate.nu in reverse</text>
<!-- ─── RIGHT PANEL: DATA STORES (x=926, w=232, ends x=1158) ─── -->
<!-- Orch right x=873 → stores: flechas a sus centros verticales (cajas 25px arriba) -->
<path d="M 873,256 L 926,216" stroke="#A78BFA" stroke-width="1.5" stroke-dasharray="4 3" class="flow" marker-end="url(#arr-purple)" fill="none" opacity=".8"/>
<path d="M 873,350 L 926,310" stroke="#F59E0B" stroke-width="1.5" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-gold)" fill="none" opacity=".8"/>
<path d="M 873,444 L 926,404" stroke="#06B6D4" stroke-width="1" stroke-dasharray="3 4" class="flow-slow" marker-end="url(#arr-dcyan)" fill="none" opacity=".7"/>
<!-- SurrealDB -->
<rect x="926" y="175" width="197" height="82" rx="8" fill="url(#surreal-grad)" filter="url(#shadow)" class="hb hb-d2"/>
<rect x="926" y="175" width="3" height="82" rx="1" fill="#A78BFA"/>
<rect x="926" y="175" width="197" height="82" rx="8" fill="none" stroke="#A78BFA" stroke-width=".8" opacity=".5"/>
<text x="941" y="198" fill="#A78BFA" font-family="Inter,sans-serif" font-size="12" font-weight="700">SurrealDB</text>
<text x="941" y="215" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">Pipeline state · Step results</text>
<text x="941" y="231" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">orchestrator_state ns</text>
<text x="941" y="247" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">crash recovery</text>
<!-- SecretumVault -->
<rect x="926" y="269" width="197" height="82" rx="8" fill="url(#vault-grad)" filter="url(#shadow)" class="hb hb-d3"/>
<rect x="926" y="269" width="3" height="82" rx="1" fill="#F59E0B"/>
<rect x="926" y="269" width="197" height="82" rx="8" fill="none" stroke="#F59E0B" stroke-width=".8" opacity=".5"/>
<text x="941" y="292" fill="#F59E0B" font-family="Inter,sans-serif" font-size="12" font-weight="700">SecretumVault</text>
<text x="941" y="309" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">Credentials · TTL leases</text>
<text x="941" y="325" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">vault:/secret/...</text>
<text x="941" y="341" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">never in NATS payload</text>
<!-- Zot OCI -->
<rect x="926" y="363" width="197" height="82" rx="8" fill="url(#oci-grad)" filter="url(#shadow)" class="hb hb-d4"/>
<rect x="926" y="363" width="3" height="82" rx="1" fill="#06B6D4"/>
<rect x="926" y="363" width="197" height="82" rx="8" fill="none" stroke="#06B6D4" stroke-width=".8" opacity=".5"/>
<text x="941" y="386" fill="#06B6D4" font-family="Inter,sans-serif" font-size="12" font-weight="700">Zot OCI Registry</text>
<text x="941" y="403" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">Node defs · Nickel libs</text>
<text x="941" y="419" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">oci://registry/nodes/</text>
<text x="941" y="435" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">content-addressed · signed</text>
<!-- ─── RIGHT PANEL: OPTIONAL SERVICES ─── -->
<text x="1042" y="533" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8" letter-spacing="2">OPTIONAL</text>
<!-- Flechas opcionales: escalonadas, cajas 40px más abajo -->
<path d="M 873,495 C 903,495 903,559 926,559" stroke="#64748B" stroke-width="1" stroke-dasharray="3 5" class="flow-vslow" marker-end="url(#arr-silver)" fill="none" opacity=".5"/>
<path d="M 873,510 C 905,510 905,653 926,653" stroke="#64748B" stroke-width="1" stroke-dasharray="3 5" class="flow-vslow" marker-end="url(#arr-silver)" fill="none" opacity=".5"/>
<path d="M 873,525 C 907,525 907,744 926,744" stroke="#64748B" stroke-width="1" stroke-dasharray="3 5" class="flow-vslow" marker-end="url(#arr-silver)" fill="none" opacity=".4"/>
<!-- Kogral -->
<rect x="926" y="518" width="197" height="82" rx="8" fill="#0F172A" stroke="#64748B" stroke-width=".8" stroke-dasharray="5 4" opacity=".7" class="hb hb-d2"/>
<text x="941" y="541" fill="#94A3B8" font-family="Inter,sans-serif" font-size="12" font-weight="600">Kogral</text>
<text x="941" y="558" fill="#64748B" font-family="Inter,sans-serif" font-size="9">Knowledge graph</text>
<text x="941" y="574" fill="#4B5563" font-family="'IBM Plex Mono',monospace" font-size="8">node-updated triggers</text>
<rect x="1071" y="522" width="46" height="16" rx="3" fill="#1E293B"/>
<text x="1094" y="533" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">optional</text>
<!-- Syntaxis -->
<rect x="926" y="612" width="197" height="82" rx="8" fill="#0F172A" stroke="#64748B" stroke-width=".8" stroke-dasharray="5 4" opacity=".7" class="hb hb-d3"/>
<text x="941" y="635" fill="#94A3B8" font-family="Inter,sans-serif" font-size="12" font-weight="600">Syntaxis</text>
<text x="941" y="652" fill="#64748B" font-family="Inter,sans-serif" font-size="9">Project orchestration</text>
<text x="941" y="668" fill="#4B5563" font-family="'IBM Plex Mono',monospace" font-size="8">phase-transition events</text>
<rect x="1071" y="616" width="46" height="16" rx="3" fill="#1E293B"/>
<text x="1094" y="627" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="7">optional</text>
<!-- TypeDialog -->
<rect x="926" y="706" width="197" height="75" rx="8" fill="#0F172A" stroke="#64748B" stroke-width=".8" stroke-dasharray="5 4" opacity=".6" class="hb hb-d4"/>
<text x="941" y="729" fill="#94A3B8" font-family="Inter,sans-serif" font-size="12" font-weight="600">TypeDialog</text>
<text x="941" y="746" fill="#64748B" font-family="Inter,sans-serif" font-size="9">Service config UI</text>
<text x="941" y="764" fill="#4B5563" font-family="'IBM Plex Mono',monospace" font-size="8">startup config NCL only</text>
<!-- ─── LEFT: Git repo (x=10, y=295 — arriba izq junto a NATS) ─── -->
<!-- Orch → Git repo: bezier más curvada para alejarse de NATS -->
<path d="M 498,545 C 120,650 50,350 180,337" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" fill="none" opacity=".5"/>
<!-- Git repo → NATS: línea recta acortada 5px -->
<path d="M 180,337 L 230,353" stroke="#F97316" stroke-width="1" stroke-dasharray="4 3" class="flow-slow" marker-end="url(#arr-orange)" fill="none" opacity=".6"/>
<rect x="30" y="295" width="150" height="85" rx="8" fill="url(#forgejo-grad)" filter="url(#shadow)" class="hb hb-d5"/>
<rect x="30" y="295" width="3" height="85" rx="1" fill="#F97316"/>
<rect x="30" y="295" width="150" height="85" rx="8" fill="none" stroke="#F97316" stroke-width=".8" opacity=".5"/>
<text x="45" y="318" fill="#F97316" font-family="Inter,sans-serif" font-size="12" font-weight="700">Git repo</text>
<text x="45" y="335" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">Git events → NATS</text>
<text x="45" y="351" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">webhook → dev.crate.&gt;</text>
<text x="45" y="367" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">push · tag · pr</text>
<!-- ─── EXECUTION LAYER mismo nivel (Nu x=380 y=623, AI x=615 y=623, sep=35px) ─── -->
<!-- Orch → Nu Executor: sale borde inferior x=510 (20px dcha), U-curve por debajo, llega al fondo (480,733) apuntando arriba, acortada 30px -->
<path d="M 518,550 C 500,560 485,610 483,614" stroke="#10B981" stroke-width="1.5" stroke-dasharray="5 3" class="flow" marker-end="url(#arr-green)" fill="none" opacity=".8"/>
<!-- Orch → AI Agent: copia de Nu pero adaptada a AI, color indigo -->
<path d="M 773,550 C 755,560 717,610 714,616" stroke="#818CF8" stroke-width="1.5" stroke-dasharray="5 3" class="flow" marker-end="url(#arr-indigo)" fill="none" opacity=".8"/>
<rect x="380" y="623" width="200" height="80" rx="8" fill="url(#exec-grad)" filter="url(#shadow)" class="hb"/>
<rect x="380" y="623" width="3" height="80" rx="1" fill="#10B981"/>
<rect x="380" y="623" width="200" height="80" rx="8" fill="none" stroke="#10B981" stroke-width=".8" opacity=".5"/>
<text x="395" y="644" fill="#10B981" font-family="Inter,sans-serif" font-size="12" font-weight="700">Nu Executor</text>
<text x="395" y="660" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">Atomic steps · Pure functions</text>
<text x="395" y="675" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">scripts/nu/*.nu</text>
<text x="395" y="689" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">stdout=output · exit-code=status</text>
<rect x="615" y="623" width="200" height="80" rx="8" fill="url(#agent-grad)" filter="url(#shadow)" class="hb hb-d1"/>
<rect x="615" y="623" width="3" height="80" rx="1" fill="#818CF8"/>
<rect x="615" y="623" width="200" height="80" rx="8" fill="none" stroke="#818CF8" stroke-width=".8" opacity=".5"/>
<text x="630" y="644" fill="#818CF8" font-family="Inter,sans-serif" font-size="12" font-weight="700">AI Agent</text>
<text x="630" y="660" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">stratum-llm · NATS protocol</text>
<text x="630" y="675" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">dev.agent.*.requested/responded</text>
<text x="630" y="689" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8">oneshot correlation · timeout</text>
<!-- ─── NICKEL BASE LIBRARY (x=390, y=738) ─── -->
<rect x="390" y="738" width="415" height="65" rx="8" fill="#0F172A" stroke="none"/>
<rect x="390" y="738" width="415" height="65" rx="8" fill="url(#ncl-grad)"/>
<rect x="390" y="738" width="415" height="65" rx="8" fill="none" stroke="#6366F1" stroke-width=".8" stroke-dasharray="6 4" opacity=".6"/>
<text x="597" y="761" text-anchor="middle" fill="#A5B4FC" font-family="Inter,sans-serif" font-size="12" font-weight="600">Nickel Base Library</text>
<text x="597" y="777" text-anchor="middle" fill="#64748B" font-family="Inter,sans-serif" font-size="9">OCI-published · content-addressed · build-verified · typecheck-gated</text>
<text x="597" y="791" text-anchor="middle" fill="#4B5563" font-family="'IBM Plex Mono',monospace" font-size="8">orchestrator-types.ncl · capability-schemas.ncl · defaults.ncl</text>
<!-- Zot OCI → Nickel: arco hacia adentro, sale desde Zot (cajas 40px abajo) -->
<path d="M 926,414 C 900,390 900,750 805,770" stroke="#06B6D4" stroke-width="1" stroke-dasharray="3 3" class="flow-slow" marker-end="url(#arr-dcyan)" fill="none" opacity=".6"/>
<!-- ─── LOGO above LEGEND ─── -->
<g transform="translate(8,5) scale(0.2)">
<g transform="translate(20,50)">
<rect x="-220" y="18" width="220" height="24" rx="5" fill="url(#hpLayerGrad)" opacity="0.7">
<animate attributeName="x" values="-220;0" dur="0.5s" fill="freeze" calcMode="spline" keySplines="0.25 0.1 0.25 1"/>
<animate attributeName="opacity" values="0.5;0.8;0.5" dur="3s" repeatCount="indefinite" begin="1.5s"/>
</rect>
<rect x="-220" y="88" width="220" height="24" rx="5" fill="url(#hpLayerGrad)">
<animate attributeName="x" values="-220;0" dur="0.5s" fill="freeze" begin="0.15s" calcMode="spline" keySplines="0.25 0.1 0.25 1"/>
</rect>
<rect x="-220" y="158" width="220" height="24" rx="5" fill="url(#hpLayerGrad)" opacity="0.7">
<animate attributeName="x" values="-220;0" dur="0.5s" fill="freeze" begin="0.3s" calcMode="spline" keySplines="0.25 0.1 0.25 1"/>
<animate attributeName="opacity" values="0.8;0.5;0.8" dur="3s" repeatCount="indefinite" begin="1.5s"/>
</rect>
<path d="M35 42 Q35 65 70 85 Q95 100 95 100" stroke="url(#hpFlowGrad)" stroke-width="3" fill="none" opacity="0" stroke-linecap="round"><animate attributeName="opacity" values="0;0.6" dur="0.3s" fill="freeze" begin="0.5s"/></path>
<path d="M185 42 Q185 65 150 85 Q125 100 125 100" stroke="url(#hpFlowGrad)" stroke-width="3" fill="none" opacity="0" stroke-linecap="round"><animate attributeName="opacity" values="0;0.6" dur="0.3s" fill="freeze" begin="0.6s"/></path>
<path d="M95 100 Q95 100 70 115 Q35 135 35 158" stroke="url(#hpFlowGrad)" stroke-width="3" fill="none" opacity="0" stroke-linecap="round"><animate attributeName="opacity" values="0;0.6" dur="0.3s" fill="freeze" begin="0.7s"/></path>
<path d="M125 100 Q125 100 150 115 Q185 135 185 158" stroke="url(#hpFlowGrad)" stroke-width="3" fill="none" opacity="0" stroke-linecap="round"><animate attributeName="opacity" values="0;0.6" dur="0.3s" fill="freeze" begin="0.8s"/></path>
<circle r="4" fill="#22D3EE" filter="url(#hpNodeGlow)"><animateMotion dur="1.2s" repeatCount="indefinite" begin="1.5s"><mpath href="#hpPathIn1"/></animateMotion><animate attributeName="opacity" values="1;0.5;0.2" dur="1.2s" repeatCount="indefinite" begin="1.5s"/></circle>
<circle r="4" fill="#22D3EE" filter="url(#hpNodeGlow)"><animateMotion dur="1.2s" repeatCount="indefinite" begin="1.8s"><mpath href="#hpPathIn2"/></animateMotion><animate attributeName="opacity" values="1;0.5;0.2" dur="1.2s" repeatCount="indefinite" begin="1.8s"/></circle>
<circle r="3" fill="#6366F1"><animateMotion dur="1.2s" repeatCount="indefinite" begin="2.1s"><mpath href="#hpPathOut1"/></animateMotion><animate attributeName="opacity" values="0.2;0.5;1" dur="1.2s" repeatCount="indefinite" begin="2.1s"/></circle>
<circle r="3" fill="#6366F1"><animateMotion dur="1.2s" repeatCount="indefinite" begin="2.4s"><mpath href="#hpPathOut2"/></animateMotion><animate attributeName="opacity" values="0.2;0.5;1" dur="1.2s" repeatCount="indefinite" begin="2.4s"/></circle>
<circle cx="35" cy="30" r="0" fill="#22D3EE" filter="url(#hpNodeGlow)"><animate attributeName="r" values="0;6" dur="0.2s" fill="freeze" begin="0.25s"/><animate attributeName="r" values="5;7;5" dur="2s" repeatCount="indefinite" begin="1.5s"/></circle>
<circle cx="185" cy="30" r="0" fill="#22D3EE" filter="url(#hpNodeGlow)"><animate attributeName="r" values="0;6" dur="0.2s" fill="freeze" begin="0.3s"/><animate attributeName="r" values="6;5;6" dur="2s" repeatCount="indefinite" begin="1.5s"/></circle>
<circle cx="35" cy="170" r="0" fill="#6366F1"><animate attributeName="r" values="0;6" dur="0.2s" fill="freeze" begin="0.4s"/><animate attributeName="r" values="5;7;5" dur="2s" repeatCount="indefinite" begin="1.8s"/></circle>
<circle cx="185" cy="170" r="0" fill="#6366F1"><animate attributeName="r" values="0;6" dur="0.2s" fill="freeze" begin="0.45s"/><animate attributeName="r" values="6;5;6" dur="2s" repeatCount="indefinite" begin="1.8s"/></circle>
<rect x="85" y="75" width="50" height="50" rx="9" fill="url(#hpProcessorGrad)" filter="url(#hpProcessorGlow)" opacity="0"><animate attributeName="opacity" values="0;1" dur="0.3s" fill="freeze" begin="0.45s"/></rect>
<rect x="97" y="87" width="26" height="26" rx="5" fill="#ffffff" opacity="0"><animate attributeName="opacity" values="0;0.95" dur="0.25s" fill="freeze" begin="0.55s"/></rect>
<rect x="101" y="96" width="4" height="8" rx="1" fill="#22D3EE" opacity="0"><animate attributeName="opacity" values="0;1" dur="0.15s" fill="freeze" begin="0.7s"/><animate attributeName="height" values="8;14;6;10;8" dur="0.6s" repeatCount="indefinite" begin="1.2s"/><animate attributeName="y" values="96;93;97;95;96" dur="0.6s" repeatCount="indefinite" begin="1.2s"/></rect>
<rect x="108" y="93" width="4" height="14" rx="1" fill="#06B6D4" opacity="0"><animate attributeName="opacity" values="0;1" dur="0.15s" fill="freeze" begin="0.75s"/><animate attributeName="height" values="14;8;12;16;14" dur="0.55s" repeatCount="indefinite" begin="1.25s"/><animate attributeName="y" values="93;96;94;92;93" dur="0.55s" repeatCount="indefinite" begin="1.25s"/></rect>
<rect x="115" y="95" width="4" height="10" rx="1" fill="#22D3EE" opacity="0"><animate attributeName="opacity" values="0;1" dur="0.15s" fill="freeze" begin="0.8s"/><animate attributeName="height" values="10;16;8;12;10" dur="0.5s" repeatCount="indefinite" begin="1.3s"/><animate attributeName="y" values="95;92;96;94;95" dur="0.5s" repeatCount="indefinite" begin="1.3s"/></rect>
<rect x="85" y="75" width="50" height="50" rx="9" fill="none" stroke="#22D3EE" stroke-width="1.5" opacity="0"><animate attributeName="x" values="85;70" dur="2s" repeatCount="indefinite" begin="1.5s"/><animate attributeName="y" values="75;60" dur="2s" repeatCount="indefinite" begin="1.5s"/><animate attributeName="width" values="50;80" dur="2s" repeatCount="indefinite" begin="1.5s"/><animate attributeName="height" values="50;80" dur="2s" repeatCount="indefinite" begin="1.5s"/><animate attributeName="rx" values="9;14" dur="2s" repeatCount="indefinite" begin="1.5s"/><animate attributeName="opacity" values="0.5;0" dur="2s" repeatCount="indefinite" begin="1.5s"/></rect>
</g>
<g clip-path="url(#hpTextReveal)">
<text x="280" y="175" font-family="'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-weight="700" font-size="72" fill="url(#hpShimmer)">Stratum<tspan fill="url(#hpProcessorGrad)">I</tspan>Ops</text>
</g>
<rect x="280" y="115" width="4" height="70" rx="2" fill="#22D3EE" opacity="0"><animate attributeName="opacity" values="0;1;1;0;0" dur="0.8s" fill="freeze" begin="1s" keyTimes="0;0.1;0.5;0.51;1"/><animate attributeName="x" values="280;980" dur="1s" fill="freeze" begin="1s" calcMode="spline" keySplines="0.25 0.1 0.25 1"/></rect>
<line x1="280" y1="195" x2="280" y2="195" stroke="url(#hpUnderlineGrad)" stroke-width="3" stroke-linecap="round" opacity="0"><animate attributeName="opacity" values="0;0.7" dur="0.1s" fill="freeze" begin="1.4s"/><animate attributeName="x2" values="280;750" dur="0.8s" fill="freeze" begin="1.4s" calcMode="spline" keySplines="0.25 0.1 0.25 1"/></line>
</g>
<!-- ─── LEGEND (y=718, x=45, w=270) ─── -->
<rect x="45" y="718" width="270" height="88" rx="6" fill="#0c1018" stroke="#1E293B" stroke-width=".8"/>
<text x="180" y="736" text-anchor="middle" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="8" letter-spacing="2">LEGEND</text>
<line x1="55" y1="750" x2="85" y2="750" stroke="#22D3EE" stroke-width="2" stroke-dasharray="6 3"/>
<text x="91" y="754" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">event flow (NATS)</text>
<line x1="55" y1="768" x2="85" y2="768" stroke="#F59E0B" stroke-width="2" stroke-dasharray="6 3"/>
<text x="91" y="772" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">auth / credentials</text>
<line x1="55" y1="786" x2="85" y2="786" stroke="#10B981" stroke-width="2" stroke-dasharray="6 3"/>
<text x="91" y="790" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">execution</text>
<line x1="213" y1="750" x2="243" y2="750" stroke="#A78BFA" stroke-width="2" stroke-dasharray="6 3"/>
<text x="249" y="754" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">state (DB)</text>
<line x1="213" y1="768" x2="243" y2="768" stroke="#06B6D4" stroke-width="2" stroke-dasharray="6 3"/>
<text x="249" y="772" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">OCI / registry</text>
<line x1="213" y1="786" x2="243" y2="786" stroke="#64748B" stroke-width="1.5" stroke-dasharray="3 5"/>
<text x="249" y="790" fill="#94A3B8" font-family="Inter,sans-serif" font-size="9">optional</text>
<!-- ─── BRANDING ─── -->
<text x="1140" y="817" text-anchor="end" fill="#64748B" font-family="'IBM Plex Mono',monospace" font-size="9">stratumiops · v0.1</text>
</svg>