.nornir/assets/property-policy-flow.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 880 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}
.keep{fill:#065f46;stroke:#10b981}
.block{fill:#7c2d12;stroke:#f97316}
.store{fill:#1e3a5f;stroke:#38bdf8}
.edge{stroke:#64748b;stroke-width:1.5;fill:none}
.elbl{fill:#94a3b8;font-size:11px}
@media (prefers-color-scheme: light){
.box{fill:#f1f5f9;stroke:#cbd5e1}
.lbl{fill:#0f172a}.sub{fill:#475569}.elbl{fill:#475569}
.keep{fill:#d1fae5;stroke:#059669}.block{fill:#ffedd5;stroke:#ea580c}.store{fill:#dbeafe;stroke:#2563eb}
}
</style>
</defs>
<text x="20" y="26" class="lbl" font-size="15" font-weight="700">Property-based policy — the same map_matches predicate gates GC and promotion</text>
<!-- store -->
<rect class="box store" x="330" y="150" width="220" height="70" rx="8"/>
<text x="440" y="178" class="lbl" text-anchor="middle" font-weight="700">PropertyStore</text>
<text x="440" y="198" class="sub" text-anchor="middle">map_matches(map, key, value)</text>
<text x="440" y="212" class="sub" text-anchor="middle">shared predicate (no twin)</text>
<!-- RETENTION side -->
<rect class="box" x="20" y="60" width="240" height="80" rx="8"/>
<text x="140" y="84" class="lbl" text-anchor="middle" font-weight="700">Retention / GC</text>
<text x="140" y="104" class="sub" text-anchor="middle">policy.protect_property = (keep,true)</text>
<text x="140" y="120" class="sub" text-anchor="middle">adapter sets protected_by_property</text>
<text x="140" y="134" class="sub" text-anchor="middle">planner honours the pin</text>
<rect class="box keep" x="20" y="250" width="240" height="66" rx="8"/>
<text x="140" y="276" class="lbl" text-anchor="middle" font-weight="700">tagged blob → KEPT</text>
<text x="140" y="296" class="sub" text-anchor="middle">survives keep_last / age / quota</text>
<text x="140" y="310" class="sub" text-anchor="middle">untagged sibling → reclaimed</text>
<!-- PROMOTION side -->
<rect class="box" x="620" y="60" width="240" height="80" rx="8"/>
<text x="740" y="84" class="lbl" text-anchor="middle" font-weight="700">Promotion gate</text>
<text x="740" y="104" class="sub" text-anchor="middle">require_property = (signed,true)</text>
<text x="740" y="120" class="sub" text-anchor="middle">HOLGER_PROMOTE_REQUIRE_PROPERTY</text>
<text x="740" y="134" class="sub" text-anchor="middle">check SOURCE coord before seal</text>
<rect class="box block" x="620" y="250" width="240" height="66" rx="8"/>
<text x="740" y="276" class="lbl" text-anchor="middle" font-weight="700">unsigned → REFUSED (412)</text>
<text x="740" y="296" class="sub" text-anchor="middle">fail-closed (no store ⇒ refuse)</text>
<text x="740" y="310" class="sub" text-anchor="middle">signed=true → promotes</text>
<line class="edge" x1="220" y1="140" x2="360" y2="168" marker-end="url(#arw)"/>
<line class="edge" x1="660" y1="140" x2="520" y2="168" marker-end="url(#arw)"/>
<line class="edge" x1="400" y1="220" x2="180" y2="248" marker-end="url(#arw)"/>
<line class="edge" x1="480" y1="220" x2="700" y2="248" marker-end="url(#arw)"/>
</svg>