swactor/docs/diagrams/actor_resolution.svg
Developer 59845f4059 feat: Docker realization, node binary, docs reorg, and simulation testing
Docker realization (bridging simulation to real TCP):
- NodeDriver (`crates/distribution/src/driver.rs`): bridges DistributedNode
  tick loop to TcpTransport with piggyback-extended wire messages
- swactor-node binary (`crates/node/`): CLI node with --listen, --seed,
  --dashboard-port, --actors flags
- Dockerfile: multi-stage build (rust:1.93-slim → debian:bookworm-slim)
- Docker integration tests (`tests/docker/`): 5-node cluster with 4
  scenarios (convergence, failure detection, actor resolution, rejoin)
- LAN cluster scripts for cross-machine validation
- TCP transport retry-on-stale-connection logic
- /api/distribution REST endpoint on dashboard (feature-gated)
- Piggyback fields (piggyback + from_addr) on Ping/Ack/PingReq messages

Docs reorganization:
- docs/runtime/ — actor-model, runtime, worker-thread, channels
- docs/distribution/ — distribution, swim, kademlia, transport
- docs/diagrams/ — all SVG files
- docs/connectome/ — connectome analysis
- docs/development_history/ — DOCKER_REALIZATION.md, SIMULATION_TESTING.md
- render_docs.sh outputs to docs/diagrams/
- README links updated to new paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 07:45:44 +00:00

185 lines
12 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 780" font-family="system-ui, sans-serif">
<defs>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="1" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.10"/>
</filter>
<marker id="arrow-gray" viewBox="0 0 10 7" refX="10" refY="3.5"
markerWidth="10" markerHeight="7" orient="auto-start-reverse" fill="#5f6368">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
<marker id="arrow-green" viewBox="0 0 10 7" refX="10" refY="3.5"
markerWidth="10" markerHeight="7" orient="auto-start-reverse" fill="#34a853">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
<marker id="arrow-red" viewBox="0 0 10 7" refX="10" refY="3.5"
markerWidth="10" markerHeight="7" orient="auto-start-reverse" fill="#ea4335">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
<marker id="arrow-blue" viewBox="0 0 10 7" refX="10" refY="3.5"
markerWidth="10" markerHeight="7" orient="auto-start-reverse" fill="#4285f4">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
</defs>
<!-- Background -->
<rect width="1100" height="780" rx="8" fill="#f8f9fb" stroke="#e0e3e8" stroke-width="1.5"/>
<!-- Title -->
<text x="550" y="38" text-anchor="middle" font-size="20" font-weight="bold" fill="#202124">Actor Registration &amp; Resolution</text>
<text x="550" y="58" text-anchor="middle" font-size="12" fill="#5f6368">node.rs · kademlia/directory.rs · kademlia/lookup.rs · cache.rs</text>
<!-- ============================================ -->
<!-- LEFT LANE: REGISTRATION -->
<!-- ============================================ -->
<rect x="30" y="80" width="480" height="570" rx="6" fill="#e6f4ea" fill-opacity="0.2" stroke="#34a853" stroke-width="1" stroke-opacity="0.4"/>
<text x="50" y="102" font-size="13" font-weight="700" fill="#34a853" letter-spacing="1">REGISTRATION</text>
<!-- Step 1: register_actor() -->
<rect x="80" y="125" width="200" height="50" rx="12" fill="#e6f4ea" stroke="#34a853" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="147" text-anchor="middle" font-size="13" font-weight="600" fill="#137333">register_actor(</text>
<text x="180" y="164" text-anchor="middle" font-size="13" font-weight="600" fill="#137333">addr, generation)</text>
<!-- Arrow down -->
<line x1="180" y1="175" x2="180" y2="210" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Step 2: sign entry -->
<rect x="80" y="218" width="200" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="237" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">keypair.sign_entry()</text>
<text x="180" y="253" text-anchor="middle" font-size="10" fill="#5f6368">ed25519 over payload</text>
<!-- Arrow down -->
<line x1="180" y1="262" x2="180" y2="295" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Step 3: store locally -->
<rect x="80" y="302" width="200" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="321" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">directory.store(entry)</text>
<text x="180" y="337" text-anchor="middle" font-size="10" fill="#5f6368">local shard</text>
<!-- Arrow down -->
<line x1="180" y1="346" x2="180" y2="380" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Step 4: cache locally -->
<rect x="80" y="386" width="200" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="405" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">cache.insert(addr, self)</text>
<text x="180" y="421" text-anchor="middle" font-size="10" fill="#5f6368">LRU location cache</text>
<!-- Arrow down -->
<line x1="180" y1="430" x2="180" y2="465" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Step 5: register for republish -->
<rect x="80" y="472" width="200" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="491" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">republish.register()</text>
<text x="180" y="507" text-anchor="middle" font-size="10" fill="#5f6368">periodic re-STORE</text>
<!-- Arrow down -->
<line x1="180" y1="516" x2="180" y2="550" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Step 6: return signed entry -->
<rect x="80" y="556" width="200" height="44" rx="12" fill="#e6f4ea" stroke="#34a853" stroke-width="1.8" filter="url(#shadow)"/>
<text x="180" y="575" text-anchor="middle" font-size="12" font-weight="600" fill="#137333">return entry</text>
<text x="180" y="591" text-anchor="middle" font-size="10" fill="#5f6368">caller STOREs to r-closest</text>
<!-- Side note: STORE replication -->
<rect x="320" y="300" width="170" height="90" rx="10" fill="#fff" stroke="#e0e3e8" stroke-width="1" filter="url(#shadow)"/>
<text x="340" y="320" font-size="11" font-weight="600" fill="#202124">Replication</text>
<text x="340" y="338" font-size="10" fill="#5f6368">STORE to r-closest</text>
<text x="340" y="353" font-size="10" fill="#5f6368">nodes by XOR distance</text>
<text x="340" y="368" font-size="10" fill="#5f6368">to actor_addr (treated</text>
<text x="340" y="383" font-size="10" fill="#5f6368">as 256-bit Kademlia key)</text>
<line x1="280" y1="340" x2="310" y2="340" stroke="#5f6368" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-gray)"/>
<!-- ============================================ -->
<!-- RIGHT LANE: RESOLUTION -->
<!-- ============================================ -->
<rect x="540" y="80" width="530" height="570" rx="6" fill="#fef7e0" fill-opacity="0.2" stroke="#f9ab00" stroke-width="1" stroke-opacity="0.4"/>
<text x="560" y="102" font-size="13" font-weight="700" fill="#e37400" letter-spacing="1">RESOLUTION</text>
<!-- Step 1: resolve_actor() -->
<rect x="620" y="125" width="200" height="44" rx="12" fill="#fef7e0" stroke="#f9ab00" stroke-width="1.8" filter="url(#shadow)"/>
<text x="720" y="144" text-anchor="middle" font-size="13" font-weight="600" fill="#e37400">resolve_actor(addr)</text>
<text x="720" y="160" text-anchor="middle" font-size="10" fill="#5f6368">3-tier lookup chain</text>
<!-- Arrow down -->
<line x1="720" y1="169" x2="720" y2="205" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Diamond: cache hit? -->
<polygon points="720,210 790,240 720,270 650,240" fill="#fef7e0" stroke="#f9ab00" stroke-width="1.5" filter="url(#shadow)"/>
<text x="720" y="237" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">cache</text>
<text x="720" y="249" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">hit?</text>
<!-- Arrow: cache hit → return (right) -->
<line x1="790" y1="240" x2="890" y2="240" stroke="#34a853" stroke-width="1.8" marker-end="url(#arrow-green)"/>
<rect x="900" y="220" width="140" height="40" rx="10" fill="#e6f4ea" stroke="#34a853" stroke-width="1.5" filter="url(#shadow)"/>
<text x="970" y="237" text-anchor="middle" font-size="12" font-weight="600" fill="#137333">Cached(node)</text>
<text x="970" y="252" text-anchor="middle" font-size="10" fill="#34a853">fast path</text>
<text x="840" y="233" text-anchor="middle" font-size="10" fill="#34a853" font-weight="600">hit</text>
<!-- Arrow: cache miss → local shard (down) -->
<line x1="720" y1="270" x2="720" y2="310" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<text x="740" y="295" font-size="10" fill="#5f6368">miss</text>
<!-- Diamond: local shard? -->
<polygon points="720,315 790,345 720,375 650,345" fill="#fef7e0" stroke="#f9ab00" stroke-width="1.5" filter="url(#shadow)"/>
<text x="720" y="342" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">local</text>
<text x="720" y="354" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">shard?</text>
<!-- Arrow: local hit → return + cache (right) -->
<line x1="790" y1="345" x2="890" y2="345" stroke="#34a853" stroke-width="1.8" marker-end="url(#arrow-green)"/>
<rect x="900" y="325" width="140" height="40" rx="10" fill="#e6f4ea" stroke="#34a853" stroke-width="1.5" filter="url(#shadow)"/>
<text x="970" y="342" text-anchor="middle" font-size="12" font-weight="600" fill="#137333">Cached(node)</text>
<text x="970" y="357" text-anchor="middle" font-size="10" fill="#34a853">+ cache insert</text>
<text x="840" y="338" text-anchor="middle" font-size="10" fill="#34a853" font-weight="600">found</text>
<!-- Arrow: local miss → Kademlia lookup (down) -->
<line x1="720" y1="375" x2="720" y2="415" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<text x="740" y="400" font-size="10" fill="#5f6368">not found</text>
<!-- Step: find closest nodes -->
<rect x="620" y="420" width="200" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="720" y="440" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">routing_table</text>
<text x="720" y="455" text-anchor="middle" font-size="10" fill="#5f6368">.closest(target, 3)</text>
<!-- Arrow down -->
<line x1="720" y1="464" x2="720" y2="498" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- Diamond: have nodes? -->
<polygon points="720,502 780,528 720,554 660,528" fill="#fef7e0" stroke="#f9ab00" stroke-width="1.5" filter="url(#shadow)"/>
<text x="720" y="525" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">nodes</text>
<text x="720" y="537" text-anchor="middle" font-size="10" font-weight="600" fill="#e37400">found?</text>
<!-- Arrow: no nodes → NotFound (right) -->
<line x1="780" y1="528" x2="890" y2="528" stroke="#ea4335" stroke-width="1.8" marker-end="url(#arrow-red)"/>
<rect x="900" y="508" width="140" height="40" rx="10" fill="#fce8e6" stroke="#ea4335" stroke-width="1.5" filter="url(#shadow)"/>
<text x="970" y="526" text-anchor="middle" font-size="12" font-weight="600" fill="#c5221f">NotFound</text>
<text x="970" y="541" text-anchor="middle" font-size="10" fill="#ea4335">no nodes known</text>
<text x="835" y="520" text-anchor="middle" font-size="10" fill="#ea4335" font-weight="600">empty</text>
<!-- Arrow: has nodes → NeedsLookup (down) -->
<line x1="720" y1="554" x2="720" y2="588" stroke="#5f6368" stroke-width="1.8" marker-end="url(#arrow-gray)"/>
<!-- NeedsLookup result -->
<rect x="600" y="594" width="240" height="44" rx="12" fill="#e8f0fe" stroke="#4285f4" stroke-width="1.8" filter="url(#shadow)"/>
<text x="720" y="613" text-anchor="middle" font-size="12" font-weight="600" fill="#1a73e8">NeedsLookup</text>
<text x="720" y="629" text-anchor="middle" font-size="10" fill="#5f6368">caller runs iterative FIND_VALUE</text>
<!-- ============================================ -->
<!-- BOTTOM: NETWORK LOOKUP -->
<!-- ============================================ -->
<rect x="30" y="665" width="1040" height="90" rx="6" fill="#e8f0fe" fill-opacity="0.2" stroke="#4285f4" stroke-width="1" stroke-opacity="0.4"/>
<text x="50" y="686" font-size="13" font-weight="700" fill="#4285f4" letter-spacing="1">NETWORK LOOKUP (caller-driven)</text>
<text x="80" y="710" font-size="11" fill="#5f6368">FIND_VALUE to closest nodes</text>
<line x1="260" y1="707" x2="310" y2="707" stroke="#5f6368" stroke-width="1.5" marker-end="url(#arrow-gray)"/>
<text x="330" y="710" font-size="11" fill="#5f6368">quorum read (f+1)</text>
<line x1="480" y1="707" x2="530" y2="707" stroke="#5f6368" stroke-width="1.5" marker-end="url(#arrow-gray)"/>
<text x="550" y="710" font-size="11" fill="#5f6368">verify signatures</text>
<line x1="680" y1="707" x2="730" y2="707" stroke="#5f6368" stroke-width="1.5" marker-end="url(#arrow-gray)"/>
<text x="750" y="710" font-size="11" fill="#5f6368">highest generation wins</text>
<line x1="910" y1="707" x2="960" y2="707" stroke="#34a853" stroke-width="1.8" marker-end="url(#arrow-green)"/>
<text x="980" y="710" font-size="11" fill="#34a853" font-weight="600">cache</text>
<text x="720" y="740" text-anchor="middle" font-size="10" fill="#9aa0a6">driven by NodeLookup state machine (kademlia/lookup.rs)</text>
<!-- Source ref -->
<text x="550" y="770" text-anchor="middle" font-size="10" fill="#9aa0a6">node.rs:188-229 · directory.rs:27-157 · lookup.rs:48-193 · cache.rs:36-61</text>
</svg>