No central server, user-owned data, reverse-chronological feed. Rust core + Tauri desktop + Android app + plain HTML/CSS/JS frontend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1116 lines
49 KiB
HTML
1116 lines
49 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>distsoc — Discovery Protocol v2 Design Review</title>
|
||
<style>
|
||
:root {
|
||
--bg: #1a1a2e;
|
||
--surface: #16213e;
|
||
--surface2: #0f3460;
|
||
--text: #e0e0e0;
|
||
--text-muted: #8892a0;
|
||
--accent: #53a8b6;
|
||
--warn: #e2a03f;
|
||
--idea: #4ecca3;
|
||
--danger: #e74c3c;
|
||
--feedback-bg: #1e2a45;
|
||
--feedback-border: #3a506b;
|
||
--border: #2a2a4a;
|
||
--layer1: #5b8def;
|
||
--layer2: #e8a838;
|
||
--layer3: #e05893;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||
background: var(--bg); color: var(--text);
|
||
line-height: 1.7; padding: 2rem; max-width: 960px; margin: 0 auto;
|
||
}
|
||
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; color: var(--accent); }
|
||
h2 { font-size: 1.4rem; margin: 1.5rem 0 0.5rem; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
|
||
h3 { font-size: 1.1rem; margin: 1rem 0 0.3rem; color: #c0c0d0; }
|
||
p { margin: 0.5rem 0; }
|
||
code { background: #0d1b2a; padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.9em; color: #7ec8e3; }
|
||
pre { background: #0d1b2a; padding: 1rem; border-radius: 6px; overflow-x: auto; margin: 0.5rem 0; font-size: 0.85em; line-height: 1.5; color: #c0d0e0; }
|
||
ul, ol { margin: 0.3rem 0 0.5rem 1.5rem; }
|
||
li { margin: 0.2rem 0; }
|
||
a { color: var(--accent); }
|
||
details { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; margin: 0.8rem 0; }
|
||
details > summary { padding: 0.8rem 1rem; cursor: pointer; font-weight: 600; user-select: none; list-style: none; display: flex; align-items: center; gap: 0.5rem; }
|
||
details > summary::before { content: '\25B6'; font-size: 0.7em; transition: transform 0.2s; flex-shrink: 0; }
|
||
details[open] > summary::before { transform: rotate(90deg); }
|
||
details > summary::-webkit-details-marker { display: none; }
|
||
details > .content { padding: 0 1rem 1rem 1rem; }
|
||
details details { background: rgba(255,255,255,0.02); border-color: rgba(255,255,255,0.08); }
|
||
.callout { border-left: 4px solid; border-radius: 0 6px 6px 0; padding: 0.7rem 1rem; margin: 0.8rem 0; font-size: 0.92em; }
|
||
.callout-current { border-color: var(--accent); background: rgba(83,168,182,0.08); }
|
||
.callout-concern { border-color: var(--warn); background: rgba(226,160,63,0.08); }
|
||
.callout-idea { border-color: var(--idea); background: rgba(78,204,163,0.08); }
|
||
.callout-danger { border-color: var(--danger); background: rgba(231,76,60,0.08); }
|
||
.callout .label { font-weight: 700; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.3rem; }
|
||
.callout-current .label { color: var(--accent); }
|
||
.callout-concern .label { color: var(--warn); }
|
||
.callout-idea .label { color: var(--idea); }
|
||
.callout-danger .label { color: var(--danger); }
|
||
.feedback { background: var(--feedback-bg); border: 2px dashed var(--feedback-border); border-radius: 6px; padding: 0.8rem 1rem; margin: 0.8rem 0; min-height: 3rem; position: relative; }
|
||
.feedback::before { content: attr(data-label); position: absolute; top: -0.7rem; left: 0.8rem; background: var(--feedback-bg); padding: 0 0.4rem; font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--feedback-border); }
|
||
.feedback[contenteditable] { outline: none; cursor: text; }
|
||
.feedback[contenteditable]:focus { border-color: var(--accent); }
|
||
.feedback[contenteditable]:empty::after { content: 'Click to type your feedback...'; color: #4a5568; font-style: italic; }
|
||
.sequence { background: #0a0f1a; border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin: 0.5rem 0; font-family: 'Fira Code','Consolas',monospace; font-size: 0.8em; line-height: 1.4; overflow-x: auto; white-space: pre; color: #88a0b8; }
|
||
table { width: 100%; border-collapse: collapse; margin: 0.8rem 0; font-size: 0.9em; }
|
||
th, td { border: 1px solid var(--border); padding: 0.5rem 0.8rem; text-align: left; }
|
||
th { background: var(--surface2); color: var(--accent); font-weight: 600; }
|
||
td { background: rgba(0,0,0,0.15); }
|
||
.toolbar { display: flex; gap: 0.8rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
|
||
.toolbar button { background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem 1rem; cursor: pointer; font-size: 0.85em; transition: background 0.2s; }
|
||
.toolbar button:hover { background: var(--accent); color: #000; }
|
||
.status-bar { position: fixed; bottom: 0; left: 0; right: 0; background: var(--surface); border-top: 1px solid var(--border); padding: 0.4rem 1rem; font-size: 0.75em; color: var(--text-muted); display: flex; justify-content: space-between; z-index: 100; }
|
||
.badge { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 3px; font-size: 0.75em; font-weight: 600; font-family: monospace; }
|
||
.badge-l1 { background: rgba(91,141,239,0.2); color: var(--layer1); }
|
||
.badge-l2 { background: rgba(232,168,56,0.2); color: var(--layer2); }
|
||
.badge-l3 { background: rgba(224,88,147,0.2); color: var(--layer3); }
|
||
.layer-tag { display: inline-block; padding: 0.05rem 0.4rem; border-radius: 3px; font-size: 0.7em; font-weight: 700; margin-right: 0.3rem; vertical-align: middle; }
|
||
.layer-tag-1 { background: var(--layer1); color: #000; }
|
||
.layer-tag-2 { background: var(--layer2); color: #000; }
|
||
.layer-tag-3 { background: var(--layer3); color: #000; }
|
||
body { padding-bottom: 3rem; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<h1>distsoc Discovery Protocol v2 — Design Review</h1>
|
||
<p style="color:var(--text-muted); margin-bottom:0.3rem;">
|
||
Three-layer architecture: <span class="layer-tag layer-tag-1">L1</span> Peer Discovery
|
||
<span class="layer-tag layer-tag-2">L2</span> File Storage + Content Routing
|
||
<span class="layer-tag layer-tag-3">L3</span> Social Routing.
|
||
<strong>Click any section to expand.</strong>
|
||
</p>
|
||
<p style="color:var(--text-muted); font-size:0.85em; margin-bottom:1rem;">
|
||
101 persistent QUIC connections (81 social + 20 wide). Single ALPN. 2-min diffs. ~350K 3-hop map.
|
||
Your inline feedback is auto-saved to localStorage.
|
||
</p>
|
||
|
||
<div class="toolbar">
|
||
<button onclick="toggleAll(true)">Expand All</button>
|
||
<button onclick="toggleAll(false)">Collapse All</button>
|
||
<button onclick="exportDoc()">Export Annotated HTML</button>
|
||
<button onclick="clearFeedback()">Clear All Feedback</button>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 1: OVERVIEW -->
|
||
<!-- ============================================================ -->
|
||
<details open>
|
||
<summary>1. Architecture Overview</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>1.1 The Three Layers</summary>
|
||
<div class="content">
|
||
<table>
|
||
<tr><th>Layer</th><th>Purpose</th><th>Map contents</th><th>Update mechanism</th></tr>
|
||
<tr>
|
||
<td><span class="layer-tag layer-tag-1">L1</span> Peer Discovery</td>
|
||
<td>Find any node's address</td>
|
||
<td>NodeIds + hop distance + addresses (1-hop only). ~350K entries.</td>
|
||
<td>2-min diffs (1-hop forwarding), worm lookup</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="layer-tag layer-tag-2">L2</span> File Storage</td>
|
||
<td>Content-addressed storage + author update propagation</td>
|
||
<td><code>node:postid</code> + media + <code>author_recent_posts</code> (256KB max)</td>
|
||
<td>File replication, piggybacked updates, staleness pulls</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="layer-tag layer-tag-3">L3</span> Social Routing</td>
|
||
<td>Direct routes to follows / audience</td>
|
||
<td>Cached routes to socially-connected nodes</td>
|
||
<td>Push (audience), pull (follows), route validation</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Design Principle</div>
|
||
<p><strong>Layer 1</strong> answers "where is this node?" Layer 2 answers "where is this content?"
|
||
Layer 3 answers "how do I reach the people I care about?" Each layer operates independently
|
||
but they reinforce each other — a file layer hit can bypass a Layer 1 worm entirely.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-layers" data-label="Your thoughts on the 3-layer architecture"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>1.2 Follow vs Audience</summary>
|
||
<div class="content">
|
||
<table>
|
||
<tr><th></th><th>Follow</th><th>Audience</th></tr>
|
||
<tr><td>Initiation</td><td>Unilateral — no request needed</td><td>Requires request + author approval</td></tr>
|
||
<tr><td>Delivery</td><td><strong>Pull only</strong> — follower pulls updates</td><td><strong>Push</strong> — author pushes via push worm</td></tr>
|
||
<tr><td>Author awareness</td><td>Author does not know</td><td>Author knows (approved the request)</td></tr>
|
||
<tr><td>Latency</td><td>Minutes (pull cycle or file-chain propagation)</td><td>Seconds (direct push)</td></tr>
|
||
<tr><td>Resource cost</td><td>Follower bears cost</td><td>Author bears cost</td></tr>
|
||
<tr><td>Scale</td><td>Unlimited followers (pull is distributed)</td><td>Author pushes to approved list</td></tr>
|
||
</table>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Key Distinction</div>
|
||
<p>Follows are <strong>private and passive</strong> — the author never learns who follows them. Content
|
||
reaches followers via the file layer (author_recent_posts propagates through stored files)
|
||
or via periodic pull. Audience is <strong>consented and active</strong> — the author pushes in real-time.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-follow-audience" data-label="Your thoughts on follow vs audience"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>1.3 Connection Model — 101 Persistent QUIC</summary>
|
||
<div class="content">
|
||
<pre>
|
||
┌──────────────────────────────────┐
|
||
│ This Node (101 conns) │
|
||
├──────────────────────────────────┤
|
||
│ 81 Social Peers │
|
||
│ ├─ Mutual follows │
|
||
│ ├─ Audience (granted) │
|
||
│ ├─ Users we follow (online) │
|
||
│ ├─ Recent sync partners │
|
||
│ └─ (evicted by priority) │
|
||
│ │
|
||
│ 20 Wide Peers │
|
||
│ ├─ Diversity-maximizing │
|
||
│ ├─ Re-evaluated every 10 min │
|
||
│ └─ At least 2 must be anchors │
|
||
└──────────────────────────────────┘
|
||
|
||
Mobile: 10 social + 5 wide = 15 connections.
|
||
</pre>
|
||
|
||
<p>All connections use a <strong>single ALPN</strong> (<code>distsoc/2</code>) with multiplexed message types.
|
||
One TLS handshake per peer. QUIC keep-alive every 20 seconds.</p>
|
||
|
||
<table>
|
||
<tr><th>Resource</th><th>Desktop (101)</th><th>Mobile (15)</th></tr>
|
||
<tr><td>Memory (connection state)</td><td>~1.5 MB</td><td>~250 KB</td></tr>
|
||
<tr><td>Keep-alive bandwidth</td><td>~22 MB/day</td><td>~3.2 MB/day</td></tr>
|
||
<tr><td>CPU</td><td>Negligible</td><td>Negligible</td></tr>
|
||
</table>
|
||
<div class="feedback" contenteditable="true" data-id="v2-connections" data-label="Your thoughts on connection model"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>1.4 Unified Protocol — Single ALPN</summary>
|
||
<div class="content">
|
||
<p>All communication over one ALPN <code>distsoc/2</code>. Message types via 1-byte header per QUIC stream:</p>
|
||
<pre>
|
||
Layer 1: Peer Discovery
|
||
0x01 RoutingDiff 2-min gossip diff
|
||
0x02 InitialMapSync Full map exchange on new connection
|
||
0x10 WormRequest Forwarded search
|
||
0x11 WormQuery Fan-out to peers (local check)
|
||
0x12 WormResponse Results to originator
|
||
0x20 AddressRequest Resolve NodeId → address
|
||
0x21 AddressResponse
|
||
|
||
Layer 2: File / Content
|
||
0x30 FileRequest Request a post by PostId
|
||
0x31 FileResponse
|
||
0x32 AuthorUpdateRequest Request fresh author_recent_posts
|
||
0x33 AuthorUpdateResponse
|
||
0x34 AuthorUpdatePush Push updated author_recent_posts
|
||
0x35 PostNotification Real-time new post notification
|
||
|
||
Layer 3: Social
|
||
0x40 PullSyncRequest Follower requests posts since seq N
|
||
0x41 PullSyncResponse
|
||
0x42 PushPost Audience push delivery
|
||
0x43 AudienceRequest Request to join audience
|
||
0x44 AudienceResponse
|
||
|
||
General
|
||
0x50 ProfileUpdate
|
||
0x51 DeleteRecord
|
||
0x52 VisibilityUpdate
|
||
</pre>
|
||
|
||
<div class="callout callout-idea">
|
||
<div class="label">Why Single ALPN</div>
|
||
<p>Previous design used 4 ALPNs (sync/6, addr/1, gossip/1, worm/1) requiring separate connections.
|
||
Single ALPN means one TLS handshake per peer, connection reuse for all message types,
|
||
simpler accept loop, easy to add new message types.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-protocol" data-label="Your thoughts on protocol structure"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 2: LAYER 1 -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary><span class="layer-tag layer-tag-1">L1</span> 2. Peer Discovery — 3-Hop Map + Worm</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>2.1 The 3-Hop Discovery Map</summary>
|
||
<div class="content">
|
||
<pre>
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Hop 1: 101 direct peers │
|
||
│ Stored: NodeId + SocketAddr + is_anchor + is_wide │
|
||
│ Source: Direct QUIC connection observation │
|
||
│ │
|
||
│ Hop 2: ~5,500 unique nodes │
|
||
│ Stored: NodeId + reporter_peer_id + is_anchor │
|
||
│ Source: Peers' 1-hop diffs (their direct connections) │
|
||
│ │
|
||
│ Hop 3: ~350,000 unique nodes │
|
||
│ Stored: NodeId only │
|
||
│ Source: Peers' 2-hop diffs (their derived knowledge) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
|
||
Storage: ~11.3 MB (101×96B + 5,500×66B + 350K×32B)
|
||
</pre>
|
||
|
||
<h3>Why ~350K with 20 Wide Peers</h3>
|
||
<table>
|
||
<tr><th>Source (2-hop)</th><th>Raw</th><th>After dedup</th></tr>
|
||
<tr><td>Social intra-cluster (81 × ~50)</td><td>4,050</td><td>~118 (rest of our cluster)</td></tr>
|
||
<tr><td>Social inter-cluster (81 × ~51)</td><td>4,131</td><td>~3,500</td></tr>
|
||
<tr><td>Wide intra-cluster (20 × ~50)</td><td>1,000</td><td>~1,000</td></tr>
|
||
<tr><td>Wide inter-cluster (20 × ~51)</td><td>1,020</td><td>~1,000</td></tr>
|
||
<tr><th colspan="2">Total 2-hop</th><th>~5,500</th></tr>
|
||
</table>
|
||
|
||
<p>3-hop: Each of ~5,500 2-hop nodes has ~100 connections not yet counted. Wide-wide-wide paths contribute
|
||
~80K+ unique nodes from completely different parts of the graph. Total after dedup: <strong>~350,000</strong>.</p>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Wide Peer Multiplier</div>
|
||
<p>Without dedicated wide peers, 101 random social connections in a clustered graph reach ~150-200K.
|
||
With 20 wide peers: ~350K. The wide peers cascade diversity — their wide peers escape <em>their</em>
|
||
neighborhoods, and so on through 3 levels.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-3hop-map" data-label="Your thoughts on 3-hop map sizing"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>2.2 Diff-Based Gossip (2-min cycles)</summary>
|
||
<div class="content">
|
||
<pre>
|
||
Every 2 minutes, each node sends a diff to each of its 100 other peers:
|
||
|
||
RoutingDiff {
|
||
hop1_changes: [Added/Removed/AddressChanged], // our direct observations
|
||
hop2_changes: [Added/Removed], // derived from received diffs
|
||
seq: u64,
|
||
}
|
||
</pre>
|
||
|
||
<h3>How Diffs Propagate (1-hop forwarding only)</h3>
|
||
<div class="sequence">T=0 Node X goes offline.
|
||
X's 101 direct peers detect connection drop.
|
||
|
||
T=0-2m X's peers include "X removed" in their next 1-hop diff.
|
||
→ X's peers' neighbors learn "X gone from 2-hop"
|
||
|
||
T=2-4m Those neighbors include "X removed" in their 2-hop diff.
|
||
→ Nodes 3 hops from X learn "X gone from 3-hop"
|
||
|
||
T=4-6m Further propagation for deeper views.
|
||
|
||
3-hop propagation: ~6 min worst case, ~3 min average.</div>
|
||
|
||
<p><strong>No amplification:</strong> You don't re-forward received diffs. You compute your own view's
|
||
changes and report those. Each change is re-derived at every hop.</p>
|
||
|
||
<h3>Bandwidth</h3>
|
||
<table>
|
||
<tr><th>Churn rate</th><th>Diff size/peer</th><th>Per day (Layer 1)</th></tr>
|
||
<tr><td>1% hourly (low)</td><td>~200 bytes</td><td><strong>~50 MB</strong></td></tr>
|
||
<tr><td>5% hourly (mobile-heavy)</td><td>~700 bytes</td><td><strong>~122 MB</strong></td></tr>
|
||
</table>
|
||
<p>Previous design: ~318 MB/day. This is <strong>2.5-6x better</strong>.</p>
|
||
|
||
<div class="feedback" contenteditable="true" data-id="v2-gossip" data-label="Your thoughts on diff-based gossip"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>2.3 Worm Lookup with Fan-Out</summary>
|
||
<div class="content">
|
||
<p>Used when target NodeId is not in local 3-hop map.</p>
|
||
|
||
<div class="sequence">Worm arrives at node A looking for targets [T1, T2, T3]:
|
||
|
||
Step 1: LOCAL CHECK
|
||
A checks own 3-hop map (~350K entries). O(1) per target.
|
||
Found T2 → send WormResponse directly to originator.
|
||
|
||
Step 2: FAN-OUT CHECK (parallel, 500ms timeout)
|
||
A sends WormQuery to all 100 peers.
|
||
Each peer checks their ~350K map. O(1) per target.
|
||
Peer P7 finds T1 → resolves address → WormResponse to originator.
|
||
|
||
Step 3: FORWARD remaining targets
|
||
Select best forwarding peer (wide, not visited, not queried).
|
||
Forward WormRequest { ttl: ttl-1 }.
|
||
That peer repeats Steps 1-3.</div>
|
||
|
||
<h3>Coverage Per Hop</h3>
|
||
<table>
|
||
<tr><th>Component</th><th>Entries checked</th></tr>
|
||
<tr><td>Local 3-hop map</td><td>~350,000</td></tr>
|
||
<tr><td>100 peers' maps (fan-out)</td><td>100 × ~350,000 = ~35,000,000</td></tr>
|
||
<tr><td><strong>After overlap dedup</strong></td><td><strong>~25,000,000 (1.25% of 2B)</strong></td></tr>
|
||
</table>
|
||
|
||
<p>With iterative routing (each hop guided toward target):</p>
|
||
<ul>
|
||
<li><strong>TTL=3:</strong> ~75M entries — finds most socially-proximate targets</li>
|
||
<li><strong>TTL=5:</strong> ~125M entries — finds virtually any reachable target</li>
|
||
<li><strong>Expected resolution: 3-5 hops, 1.5-2.5 seconds</strong></li>
|
||
</ul>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">vs Previous Design</div>
|
||
<p>Previous worm checked only local map (~2M per hop with 2-hop tables).
|
||
Fan-out to 100 peers gives <strong>12x more coverage per hop</strong>.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-worm" data-label="Your thoughts on worm fan-out"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>2.4 Address Resolution Chain</summary>
|
||
<div class="content">
|
||
<pre>
|
||
1. DIRECT — T in 1-hop → have address (instant)
|
||
2. 2-HOP REF — T in 2-hop → ask reporter for address (1 RTT)
|
||
3. 3-HOP REF — T in 3-hop → ask peers who's closer → chain (2 RTT)
|
||
4. WORM — T not in map → worm search (1.5-5 sec)
|
||
5. ANCHOR — Worm fails → profile anchor or bootstrap (1-5 sec)
|
||
</pre>
|
||
|
||
<table>
|
||
<tr><th>Tier</th><th>Nodes covered</th><th>% of 2B</th></tr>
|
||
<tr><td>1-hop</td><td>101</td><td>0.000005%</td></tr>
|
||
<tr><td>2-hop</td><td>~5,500</td><td>0.00028%</td></tr>
|
||
<tr><td>3-hop</td><td>~350,000</td><td>0.018%</td></tr>
|
||
<tr><td>Worm (1 hop)</td><td>~25,000,000</td><td>1.25%</td></tr>
|
||
<tr><td>Worm (5 hops)</td><td>~125,000,000</td><td>6.25%</td></tr>
|
||
</table>
|
||
<div class="feedback" contenteditable="true" data-id="v2-resolution" data-label="Your thoughts on address resolution"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 3: LAYER 2 -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary><span class="layer-tag layer-tag-2">L2</span> 3. File Storage + Content Routing</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>3.1 Core Concept — Files Carry Their Own Routing</summary>
|
||
<div class="content">
|
||
<p>Every stored file (post + media) carries a small metadata blob: <strong><code>author_recent_posts</code></strong>
|
||
(max 256 KB, author-signed). This blob lists the author's recent post IDs.</p>
|
||
|
||
<div class="callout callout-idea">
|
||
<div class="label">Key Insight</div>
|
||
<p>If you have <em>any</em> file by author X, you passively know X's recent posts. You can then
|
||
request specific posts from <em>any peer who has them</em> — you don't need to find author X.</p>
|
||
<p>This creates a <strong>natural CDN</strong>: popular authors' post updates propagate through the file
|
||
storage network as each copy of their files carries the latest post list.</p>
|
||
</div>
|
||
|
||
<pre>
|
||
StoredFile {
|
||
post: Post, // content-addressed, immutable
|
||
post_id: PostId, // blake3(content)
|
||
media_blobs: Vec<MediaBlob>,
|
||
|
||
author_recent_posts: AuthorRecentPosts {
|
||
author_id: NodeId,
|
||
posts: Vec<RecentPostEntry>, // newest first
|
||
updated_at: u64, // ms timestamp
|
||
signature: Signature, // author signs this blob
|
||
// Max 256 KB total → ~8,000 recent post entries
|
||
}
|
||
}
|
||
</pre>
|
||
<div class="feedback" contenteditable="true" data-id="v2-file-concept" data-label="Your thoughts on files as routing"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>3.2 Update Propagation — Three Paths</summary>
|
||
<div class="content">
|
||
<div class="sequence">Author A publishes a new post.
|
||
|
||
Path 1: DIRECT PUSH (audience, seconds)
|
||
A pushes new post to audience members via push worm.
|
||
Recipients update their stored copies of A's files
|
||
with the new author_recent_posts blob.
|
||
|
||
Path 2: FILE-CHAIN PROPAGATION (followers, <12 min typical)
|
||
A's 101 persistent peers receive the update.
|
||
When ANY peer accesses a file by A, they see the fresh
|
||
author_recent_posts and can request the new post.
|
||
Propagates naturally as files are accessed/synced.
|
||
|
||
Path 3: STALENESS PULL (>1 hour fallback)
|
||
If author_recent_posts.updated_at is older than 1 hour,
|
||
the holder triggers an update pull:
|
||
- Check Layer 3 social route to author
|
||
- Check other peers who hold author's files
|
||
- Worm request for latest author_recent_posts</div>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Result</div>
|
||
<p>Popular authors' updates reach most file holders within minutes.
|
||
Unpopular authors' updates reach followers within 1 hour (staleness pull).</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-update-propagation" data-label="Your thoughts on update propagation"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>3.3 Popular Author Scale (1M Audience)</summary>
|
||
<div class="content">
|
||
<div class="sequence">Author A has 1,000,000 audience members. Posts a new photo.
|
||
|
||
Layer 1: A has 101 persistent connections. PostNotification sent to all.
|
||
→ 101 audience members get it instantly.
|
||
→ 101 copies of updated author_recent_posts now exist.
|
||
|
||
Layer 2: Those 101 peers have files by A. Each has 101 peers.
|
||
→ T+2m: 101 × 100 = ~10,000 peers see fresh author_recent_posts
|
||
→ T+4m: 10,000 × 100 = ~1,000,000 peers reached
|
||
→ Natural file-chain propagation covers the full audience
|
||
|
||
No destination declared. The file layer IS the CDN.
|
||
Author does 101 pushes. O(log N) hops to reach everyone.</div>
|
||
|
||
<div class="callout callout-idea">
|
||
<div class="label">vs Previous Design</div>
|
||
<p>Previous design: author splits audience into chunks of 10, tries to push to each chunk leader.
|
||
1M audience = 100K chunks = author's machine saturated for hours.</p>
|
||
<p>New design: author does <strong>101 pushes total</strong>. File layer handles the rest via natural
|
||
propagation. O(1) work for the author, O(log N) time to reach everyone.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-popular-author" data-label="Your thoughts on popular author scaling"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>3.4 Requesting Posts via File Layer</summary>
|
||
<div class="content">
|
||
<pre>
|
||
You see author A has new post P in author_recent_posts.
|
||
You don't have post P stored locally.
|
||
|
||
1. Check if any persistent peer has P:
|
||
→ Fan-out WormQuery to 100 peers (they check local storage)
|
||
→ Any peer with the file can serve it
|
||
|
||
2. Request posts newest-to-oldest:
|
||
→ Prioritize catching up on recent content
|
||
→ Older posts can wait or be skipped
|
||
|
||
3. No need to contact author A at all.
|
||
</pre>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">File Authority Chain</div>
|
||
<p>Each node caches a route back to the author for each file they hold. When <code>author_recent_posts</code>
|
||
is stale (>1 hour), follow the authority chain hop-by-hop toward the author. Each hop may have a
|
||
fresher copy — you don't need to reach the author, just a fresher copy.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-file-requests" data-label="Your thoughts on file-layer content fetching"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>3.5 File Keep Priority</summary>
|
||
<div class="content">
|
||
<h3>Formula</h3>
|
||
<pre>priority = pin_bonus + (relationship × heart_recency × post_age / (peer_copies + 1))</pre>
|
||
|
||
<h3>Scoring Tables</h3>
|
||
<table>
|
||
<tr><th colspan="2">Relationship (to file's author)</th></tr>
|
||
<tr><td>Self (our own content)</td><td>∞ (never evicted)</td></tr>
|
||
<tr><td>We are audience of author</td><td>10</td></tr>
|
||
<tr><td>We follow author</td><td>8</td></tr>
|
||
<tr><td>Author has >10 hearts from network</td><td>5</td></tr>
|
||
<tr><td>Author has >3 hearts</td><td>3</td></tr>
|
||
<tr><td>Author has >2 hearts</td><td>2</td></tr>
|
||
<tr><td>No relationship</td><td>1</td></tr>
|
||
</table>
|
||
|
||
<table>
|
||
<tr><th>Time Window</th><th>Heart Recency Score</th><th>Post Age Score</th></tr>
|
||
<tr><td>< 72 hours</td><td>100</td><td>100</td></tr>
|
||
<tr><td>3-14 days</td><td>50</td><td>50</td></tr>
|
||
<tr><td>14-45 days</td><td>25</td><td>25</td></tr>
|
||
<tr><td>45-90 days</td><td>12</td><td>12</td></tr>
|
||
<tr><td>90-365 days</td><td>6</td><td>6</td></tr>
|
||
<tr><td>1-3 years</td><td>3</td><td>3</td></tr>
|
||
<tr><td>4-10 years</td><td>1</td><td>1</td></tr>
|
||
</table>
|
||
|
||
<p><strong>Peer Copies:</strong> Divides priority. More copies nearby = lower urgency to keep ours.
|
||
0 copies → full priority. 10 copies → 1/11 priority.</p>
|
||
|
||
<p><strong>Pin:</strong> 99,999 bonus. Even pins compete when storage is full.</p>
|
||
|
||
<h3>Examples</h3>
|
||
<pre>
|
||
YOUR OWN post:
|
||
∞ → never evicted
|
||
|
||
Audience author, yesterday, hearted today, 0 copies:
|
||
0 + (10 × 100 × 100 / 1) = 100,000 → very high
|
||
|
||
Followed author, last week, hearted 2 days ago, 2 copies:
|
||
0 + (8 × 100 × 50 / 3) = 13,333 → high
|
||
|
||
Popular stranger (>10 hearts), yesterday, 20 copies:
|
||
0 + (5 × 100 × 100 / 21) = 2,381 → moderate
|
||
|
||
Random, 6 months old, 3 hearts, 8 copies:
|
||
0 + (3 × 6 × 6 / 9) = 12 → very low
|
||
|
||
Unknown, no hearts, old, many copies:
|
||
0 + (1 × 1 × 1 / 11) = 0.09 → first evicted</pre>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Storage Budget</div>
|
||
<p>Default: 10 GB. At 256 KB avg: ~40K files. At 1 MB avg (with media): ~10K files.
|
||
The formula ensures: own posts always kept, audience/follow prioritized, rare content preserved,
|
||
old/common/unrelated content evicted first.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-keep-priority" data-label="Your thoughts on file keep priority"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 4: LAYER 3 -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary><span class="layer-tag layer-tag-3">L3</span> 4. Social Routing</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>4.1 Purpose — Cached Routes to People You Care About</summary>
|
||
<div class="content">
|
||
<p>Layer 3 is a <strong>personal routing cache</strong> for follows and audience. It stores recently-working
|
||
routes so you can push/pull content without going through the Layer 1 worm every time.</p>
|
||
|
||
<pre>
|
||
SocialRoute {
|
||
target: NodeId,
|
||
relationship: Follow | Audience | Mutual,
|
||
last_route: Vec<NodeId>, // path that worked
|
||
last_success: u64,
|
||
address_hint: Option<SocketAddr>, // if direct worked
|
||
}
|
||
</pre>
|
||
<div class="feedback" contenteditable="true" data-id="v2-social-purpose" data-label="Your thoughts on social routing layer"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>4.2 Follow Pull Path</summary>
|
||
<div class="content">
|
||
<div class="sequence">Follower F wants updates from author A:
|
||
|
||
1. Is A a persistent peer? (Layer 1, 1-hop)
|
||
→ Yes: content flows in real-time. Done.
|
||
|
||
2. Check social route cache (Layer 3)
|
||
→ Have recent route? Follow it to A.
|
||
→ Pull author_recent_posts + new posts.
|
||
→ Update route cache.
|
||
|
||
3. Check file layer (Layer 2)
|
||
→ Have any of A's files? Check author_recent_posts freshness.
|
||
→ If <1 hour old: up to date. Request missing posts via worm.
|
||
→ If >1 hour old: follow file authority chain for fresher data.
|
||
|
||
4. Fall back to Layer 1 worm.
|
||
|
||
Typical: step 1 or 2 (fast, no worm needed).</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-follow-pull" data-label="Your thoughts on follow pull path"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>4.3 Audience Push Path</summary>
|
||
<div class="content">
|
||
<div class="sequence">Author A creates a post. Has approved audience members.
|
||
|
||
1. Audience members who are persistent peers (1-hop):
|
||
→ Push PostNotification on persistent connection. Instant.
|
||
|
||
2. Audience members with social routes (Layer 3):
|
||
→ Follow cached route. Push post via push worm.
|
||
→ Update route cache on success.
|
||
|
||
3. Audience members with no cached route:
|
||
→ Layer 1 worm to find address.
|
||
→ Push post. Cache route for next time.
|
||
|
||
For audience >101: post also pushed to file layer.
|
||
File storage network handles further propagation.
|
||
No destination declared — the CDN effect takes over.</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-audience-push" data-label="Your thoughts on audience push path"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>4.4 Route Maintenance</summary>
|
||
<div class="content">
|
||
<ul>
|
||
<li>On successful push/pull: update route + address hint</li>
|
||
<li>On failure: clear route, fall back to Layer 1</li>
|
||
<li>Every 30 min: validate routes for top-priority follows/audience</li>
|
||
<li>Routes older than 2 hours without verification → stale</li>
|
||
</ul>
|
||
<div class="feedback" contenteditable="true" data-id="v2-route-maintenance" data-label="Your thoughts on route maintenance"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 5: HOW IT ALL FITS TOGETHER -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary>5. How It All Fits Together — Lifecycles</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>5.1 Public Post — From Creation to Feed</summary>
|
||
<div class="content">
|
||
<div class="sequence">T=0 Author A creates post P. PostId = blake3(content).
|
||
Stores in local DB. Updates own author_recent_posts.
|
||
|
||
Persistent peers (Layer 1):
|
||
→ PostNotification on all 101 connections.
|
||
→ All persistent peers have P + updated author_recent_posts.
|
||
|
||
Audience push (Layer 3):
|
||
→ Persistent audience members: already done.
|
||
→ Social route audience: push via cached route.
|
||
→ No route: worm to find, then push.
|
||
|
||
T<2m Peers' peers see updated author_recent_posts (Layer 2):
|
||
→ File-chain propagation begins.
|
||
|
||
T<12m File layer reaches most file holders (Layer 2):
|
||
→ Anyone accessing any file by A sees new post listed.
|
||
→ Can request P from any peer who has it.
|
||
|
||
T=60m Pull cycle for distant followers (Layer 3 fallback):
|
||
→ Follower checks author_recent_posts → sees P → requests it.</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-lifecycle-public" data-label="Your thoughts on public post lifecycle"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>5.2 Encrypted Post (DM / Circle)</summary>
|
||
<div class="content">
|
||
<div class="sequence">T=0 Author A creates post with VisibilityIntent::Direct([R]).
|
||
|
||
1. Generate random CEK
|
||
2. Encrypt content with ChaCha20-Poly1305
|
||
3. Wrap CEK per-recipient via X25519 DH
|
||
4. PostId = blake3(encrypted_content)
|
||
|
||
Push to recipient R:
|
||
→ R is persistent peer? Push directly.
|
||
→ Social route? Push via route.
|
||
→ Worm to find R.
|
||
|
||
author_recent_posts updated (includes PostId + VisibilityHint::Encrypted).
|
||
Peers see there's a new post but can't read it without the wrapped key.
|
||
|
||
On receipt, R:
|
||
→ DH to derive shared secret
|
||
→ Unwrap CEK
|
||
→ Decrypt content</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-lifecycle-encrypted" data-label="Your thoughts on encrypted post lifecycle"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>5.3 Discovering a New User to Follow</summary>
|
||
<div class="content">
|
||
<div class="sequence">User has author X's NodeId (from out-of-band sharing).
|
||
|
||
1. Layer 1 map: X in 3-hop? (~350K entries)
|
||
→ If yes: resolve address via referral chain. Connect. Done.
|
||
|
||
2. Worm search (Layer 1): fan-out to 100 peers.
|
||
~25M entries checked per hop, 3-5 hops.
|
||
→ Found? Connect to X. Pull profile + recent posts. Done.
|
||
|
||
3. File layer (Layer 2): anyone we know have X's files?
|
||
→ Check if any peer has author_recent_posts for X.
|
||
→ If yes: get X's recent posts without finding X directly.
|
||
|
||
4. Anchor fallback: contact bootstrap anchors.
|
||
|
||
5. Once connected to X (or X's file holders):
|
||
→ Cache social route (Layer 3) for future pulls.
|
||
→ Store X's files → future updates via file layer.</div>
|
||
|
||
<div class="callout callout-current">
|
||
<div class="label">Three layers cooperate</div>
|
||
<p>Layer 1 finds the person. Layer 2 lets you get their content even without finding them.
|
||
Layer 3 remembers the route for next time. Each layer is a fallback for the others.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-lifecycle-discover" data-label="Your thoughts on user discovery flow"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>5.4 Popular Author with 1M Audience</summary>
|
||
<div class="content">
|
||
<div class="sequence">Author A: 1,000,000 audience members. Posts a photo.
|
||
|
||
T=0s A pushes to 101 persistent peers.
|
||
Author's work: 101 sends. Done.
|
||
|
||
T=0-2s 101 audience members have post + fresh author_recent_posts.
|
||
|
||
T=2m 101 × 100 = ~10,000 peers see updated author_recent_posts
|
||
via file-chain propagation (Layer 2).
|
||
|
||
T=4m 10,000 × 100 = ~1,000,000 peers reached.
|
||
Full audience covered.
|
||
|
||
Total author effort: 101 pushes.
|
||
Total time to full coverage: ~4 minutes.
|
||
Total bandwidth (author): 101 × post_size.
|
||
No audience member list transmitted. No destination declared.</div>
|
||
|
||
<div class="callout callout-idea">
|
||
<div class="label">The File Layer IS the CDN</div>
|
||
<p>Popular content replicates because many peers have the author's files.
|
||
The <code>author_recent_posts</code> blob travels with every file copy. The author doesn't
|
||
need to know or manage the delivery — the storage network handles it.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-lifecycle-1m" data-label="Your thoughts on 1M audience scaling"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 6: BANDWIDTH & NUMBERS -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary>6. Bandwidth & Resource Budget</summary>
|
||
<div class="content">
|
||
|
||
<details>
|
||
<summary>6.1 Per-Node Summary</summary>
|
||
<div class="content">
|
||
<table>
|
||
<tr><th>Metric</th><th>Desktop (101 conns)</th><th>Mobile (15 conns)</th></tr>
|
||
<tr><td>Layer 1 map</td><td>~350K entries, ~11 MB</td><td>~15K entries, ~500 KB</td></tr>
|
||
<tr><td>Layer 2 files</td><td>10K-40K files, ~10 GB</td><td>1K-5K files, ~1 GB</td></tr>
|
||
<tr><td>Layer 3 routes</td><td>~200-500 entries, ~50 KB</td><td>same</td></tr>
|
||
<tr><td>Layer 1 bandwidth</td><td>~50 MB/day</td><td>~8 MB/day</td></tr>
|
||
<tr><td>Layer 2 bandwidth</td><td>~50-100 MB/day (varies)</td><td>~10-30 MB/day</td></tr>
|
||
<tr><td>Layer 3 bandwidth</td><td>~10-20 MB/day</td><td>~5-10 MB/day</td></tr>
|
||
<tr><td><strong>Total bandwidth</strong></td><td><strong>~110-170 MB/day</strong></td><td><strong>~23-48 MB/day</strong></td></tr>
|
||
<tr><td>Worm coverage/hop</td><td>~25M (1.25%)</td><td>~2M (0.1%)</td></tr>
|
||
<tr><td>Worm hops to find any target</td><td>3-5</td><td>5-8 (or anchor)</td></tr>
|
||
</table>
|
||
<div class="feedback" contenteditable="true" data-id="v2-bandwidth" data-label="Your thoughts on bandwidth budget"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>6.2 Comparison to Previous Design</summary>
|
||
<div class="content">
|
||
<table>
|
||
<tr><th>Aspect</th><th>Phase F (current code)</th><th>Protocol v2 (this spec)</th></tr>
|
||
<tr><td>Connections</td><td>Ephemeral (connect/sync/disconnect)</td><td>101 persistent</td></tr>
|
||
<tr><td>ALPNs</td><td>4</td><td>1</td></tr>
|
||
<tr><td>Gossip</td><td>Full peer list each time</td><td>2-min diffs, 1-hop forward</td></tr>
|
||
<tr><td>Map depth</td><td>2-hop (~5K)</td><td>3-hop (~350K)</td></tr>
|
||
<tr><td>Content delivery</td><td>Pull-only (60 min)</td><td>3 layers: push + pull + file propagation</td></tr>
|
||
<tr><td>File storage</td><td>Not managed</td><td>Priority-based with keep formula</td></tr>
|
||
<tr><td>Worm coverage/hop</td><td>~2M</td><td>~25M</td></tr>
|
||
<tr><td>Daily bandwidth</td><td>~318 MB</td><td>~110-170 MB</td></tr>
|
||
<tr><td>Popular author scale</td><td>Author pushes to all (O(N) work)</td><td>File layer propagates (O(log N) time)</td></tr>
|
||
<tr><td>First-contact latency</td><td>10-30 seconds</td><td>1-5 seconds</td></tr>
|
||
</table>
|
||
<div class="feedback" contenteditable="true" data-id="v2-comparison" data-label="Your thoughts on the improvements"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>6.3 Anchor Node Costs</summary>
|
||
<div class="content">
|
||
<p>A well-connected anchor (listed by 10,000 users as profile anchor):</p>
|
||
<table>
|
||
<tr><th>Activity</th><th>Per day</th></tr>
|
||
<tr><td>Persistent connections (~200)</td><td>~30 MB RAM</td></tr>
|
||
<tr><td>Gossip diffs (200 peers)</td><td>~10 MB</td></tr>
|
||
<tr><td>Map storage (larger map)</td><td>~50 MB disk</td></tr>
|
||
<tr><td>Worm forwarding (~100/hr)</td><td>~5 MB</td></tr>
|
||
<tr><td>Address lookups (~500/hr)</td><td>~2 MB</td></tr>
|
||
<tr><td>Content relay</td><td>~100 MB</td></tr>
|
||
<tr><td><strong>Total</strong></td><td><strong>~170 MB/day, ~80 MB RAM</strong></td></tr>
|
||
</table>
|
||
<p>A $5/month VPS handles this comfortably.</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-anchor-costs" data-label="Your thoughts on anchor economics"></div>
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 7: BOOTSTRAP -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary>7. Bootstrap — Entering the Network</summary>
|
||
<div class="content">
|
||
<div class="sequence">New node, first launch:
|
||
|
||
1. Read bootstrap anchors from anchors.json (shipped with app)
|
||
2. Connect to 1-2 bootstrap anchors
|
||
3. Exchange Layer 1 maps (InitialMapSync) — learn ~350K NodeIds
|
||
4. Begin wide peer selection from learned nodes
|
||
5. Connect to 20 wide peers
|
||
6. Fill social peer slots based on follow list
|
||
7. Worm search for followed users not yet found
|
||
8. Within ~10 minutes: fully operational with 101 connections</div>
|
||
|
||
<div class="callout callout-idea">
|
||
<div class="label">Lightweight Bootstrap (Future)</div>
|
||
<p>New nodes don't need full map exchange. "I'm new, give me 200 diverse peers" (~15 KB response).
|
||
Connect to received peers, build maps via normal gossip. Reduces anchor load from ~11 MB to ~15 KB per new node.</p>
|
||
</div>
|
||
<div class="feedback" contenteditable="true" data-id="v2-bootstrap" data-label="Your thoughts on bootstrap"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 8: IMPLEMENTATION ORDER -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary>8. Implementation Order</summary>
|
||
<div class="content">
|
||
|
||
<h3>Phase 1: Foundation</h3>
|
||
<ol>
|
||
<li>Single ALPN (<code>distsoc/2</code>) with message type multiplexing</li>
|
||
<li>Persistent connection manager (81 social + 20 wide slots)</li>
|
||
<li><code>discovery_map</code> table (Layer 1)</li>
|
||
<li>1-hop map population from persistent connections</li>
|
||
</ol>
|
||
|
||
<h3>Phase 2: Layer 1 Gossip + Worm</h3>
|
||
<ol start="5">
|
||
<li><code>RoutingDiff</code> and 2-min gossip cycle</li>
|
||
<li>2-hop + 3-hop derivation from diffs</li>
|
||
<li>Wide peer diversity scoring</li>
|
||
<li>Worm v2 with fan-out</li>
|
||
<li>Address resolution chain</li>
|
||
</ol>
|
||
|
||
<h3>Phase 3: Layer 2 File Storage</h3>
|
||
<ol start="10">
|
||
<li><code>stored_files</code> + <code>author_recent_posts</code> tables</li>
|
||
<li>File keep priority calculation + eviction</li>
|
||
<li><code>author_recent_posts</code> update propagation</li>
|
||
<li>File authority chain routing</li>
|
||
<li>Post request via file layer (fetch from any holder)</li>
|
||
</ol>
|
||
|
||
<h3>Phase 4: Layer 3 Social Routing</h3>
|
||
<ol start="15">
|
||
<li><code>social_routes</code> table</li>
|
||
<li>Follow pull via cached routes</li>
|
||
<li>Audience push via cached routes + worm fallback</li>
|
||
<li>Route maintenance</li>
|
||
</ol>
|
||
|
||
<h3>Phase 5: Integration + Optimization</h3>
|
||
<ol start="19">
|
||
<li>Popular author file-chain propagation</li>
|
||
<li>Lazy 3-hop streaming on connection</li>
|
||
<li>Mobile mode (15 conns, smaller maps)</li>
|
||
<li>Delta sync for content (sequence numbers)</li>
|
||
<li>Bloom filter caching (optional)</li>
|
||
</ol>
|
||
|
||
<div class="feedback" contenteditable="true" data-id="v2-impl-order" data-label="Your thoughts on implementation order"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 9: OPEN QUESTIONS -->
|
||
<!-- ============================================================ -->
|
||
<details open>
|
||
<summary>9. Open Questions & Decisions Needed</summary>
|
||
<div class="content">
|
||
|
||
<h3>Q1: Peer Eviction Policy</h3>
|
||
<p>When all 81 social slots are full and a higher-priority peer comes online, which peer gets dropped?
|
||
Need to prevent thrashing (repeatedly connecting/disconnecting borderline peers).</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q1-eviction" data-label="Your decision on eviction policy"></div>
|
||
|
||
<h3>Q2: <code>author_recent_posts</code> Authenticity</h3>
|
||
<p>The blob is author-signed, but a malicious peer could serve a stale (valid but old) blob.
|
||
Include sequence numbers? If you see seq 50 from one peer and seq 45 from another, seq 45 is stale.</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q2-authenticity" data-label="Your decision on freshness verification"></div>
|
||
|
||
<h3>Q3: Peer Copy Counting for Keep Priority</h3>
|
||
<p>How do we learn <code>peer_copies</code>? Passively from worm responses and file requests.
|
||
Exact counting isn't needed — order-of-magnitude is sufficient.</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q3-peer-copies" data-label="Your decision on copy counting"></div>
|
||
|
||
<h3>Q4: File Layer Bandwidth</h3>
|
||
<p>If every file carries 256 KB of <code>author_recent_posts</code>, that's substantial overhead on small posts.
|
||
Compact format (just PostIds at 32 bytes each = ~8000 entries per 256 KB) or fetch separately on demand?</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q4-file-bandwidth" data-label="Your decision on file metadata format"></div>
|
||
|
||
<h3>Q5: Storage Quotas / 3x Hosting Rule</h3>
|
||
<p>Design spec mentions 3x hosting quota. How does this interact with the keep priority formula?
|
||
Quota sets overall budget, priority decides what fills it?</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q5-quotas" data-label="Your decision on storage quotas"></div>
|
||
|
||
<h3>Q6: Global Lookup for Isolated Nodes</h3>
|
||
<p>Worms + anchors handle most cases. For truly isolated nodes (~0.01% of lookups that fail),
|
||
do we need a structured DHT layer? Or is anchor fallback sufficient at scale?</p>
|
||
<div class="feedback" contenteditable="true" data-id="v2-q6-global-lookup" data-label="Your decision on global lookup"></div>
|
||
|
||
<h3>Overall Notes</h3>
|
||
<div class="feedback" contenteditable="true" data-id="v2-overall" data-label="Overall thoughts, priorities, or direction"></div>
|
||
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- SECTION 10: GLOSSARY -->
|
||
<!-- ============================================================ -->
|
||
<details>
|
||
<summary>10. Glossary</summary>
|
||
<div class="content">
|
||
<table>
|
||
<tr><th>Term</th><th>Definition</th></tr>
|
||
<tr><td>NodeId</td><td>ed25519 public key (32 bytes, 64 hex chars). Permanent identity.</td></tr>
|
||
<tr><td>Connect string</td><td><code>NodeId@host:port</code>. Enough to establish first contact.</td></tr>
|
||
<tr><td>Anchor</td><td>Node with stable public address. Network entry point + relay.</td></tr>
|
||
<tr><td>Wide peer</td><td>One of 20 peers selected for maximum graph diversity.</td></tr>
|
||
<tr><td>Social peer</td><td>One of 81 peers selected by social relationship priority.</td></tr>
|
||
<tr><td>3-hop map</td><td><span class="badge badge-l1">L1</span> ~350K NodeIds reachable within 3 hops of this node.</td></tr>
|
||
<tr><td>Worm</td><td><span class="badge badge-l1">L1</span> Bounded-depth search with fan-out. ~25M entries checked per hop.</td></tr>
|
||
<tr><td>author_recent_posts</td><td><span class="badge badge-l2">L2</span> 256KB signed blob listing author's recent posts. Travels with every stored file.</td></tr>
|
||
<tr><td>File authority chain</td><td><span class="badge badge-l2">L2</span> Cached route back to a file's author for freshness updates.</td></tr>
|
||
<tr><td>Keep priority</td><td><span class="badge badge-l2">L2</span> Score determining which files to keep vs evict when storage is limited.</td></tr>
|
||
<tr><td>Social route</td><td><span class="badge badge-l3">L3</span> Cached working path to a followed user or audience member.</td></tr>
|
||
<tr><td>Follow</td><td>Unilateral pull-only. Author doesn't know. Follower bears cost.</td></tr>
|
||
<tr><td>Audience</td><td>Consented push. Author knows + approves. Author pushes in real-time.</td></tr>
|
||
<tr><td>CEK</td><td>Content Encryption Key. Random per-post, ChaCha20-Poly1305.</td></tr>
|
||
<tr><td>RoutingDiff</td><td><span class="badge badge-l1">L1</span> 2-min gossip message. 1-hop + 2-hop changes only.</td></tr>
|
||
<tr><td>Push worm</td><td>Worm that delivers content (not just searches). Used for audience push.</td></tr>
|
||
<tr><td>Heart</td><td>User endorsement of a post. Affects file keep priority.</td></tr>
|
||
<tr><td>Peer copies</td><td>Number of copies of a file within 3-hop range. More copies = lower keep priority.</td></tr>
|
||
</table>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- JAVASCRIPT -->
|
||
<!-- ============================================================ -->
|
||
<div class="status-bar">
|
||
<span id="save-status">Feedback auto-saved</span>
|
||
<span id="feedback-count">0 feedback entries</span>
|
||
</div>
|
||
|
||
<script>
|
||
const STORAGE_KEY = 'distsoc-design-review-v2-feedback';
|
||
|
||
function loadFeedback() {
|
||
const saved = localStorage.getItem(STORAGE_KEY);
|
||
if (!saved) return;
|
||
try {
|
||
const data = JSON.parse(saved);
|
||
document.querySelectorAll('.feedback[data-id]').forEach(el => {
|
||
const id = el.getAttribute('data-id');
|
||
if (data[id]) el.innerHTML = data[id];
|
||
});
|
||
updateCount();
|
||
} catch (e) { console.warn('Failed to load feedback:', e); }
|
||
}
|
||
|
||
function saveFeedback() {
|
||
const data = {};
|
||
document.querySelectorAll('.feedback[data-id]').forEach(el => {
|
||
const id = el.getAttribute('data-id');
|
||
const content = el.innerHTML.trim();
|
||
if (content) data[id] = content;
|
||
});
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
document.getElementById('save-status').textContent = 'Saved ' + new Date().toLocaleTimeString();
|
||
updateCount();
|
||
}
|
||
|
||
function updateCount() {
|
||
const saved = localStorage.getItem(STORAGE_KEY);
|
||
const count = saved ? Object.keys(JSON.parse(saved)).length : 0;
|
||
document.getElementById('feedback-count').textContent = count + ' feedback entr' + (count === 1 ? 'y' : 'ies');
|
||
}
|
||
|
||
document.addEventListener('input', e => { if (e.target.classList.contains('feedback')) saveFeedback(); });
|
||
document.addEventListener('DOMContentLoaded', loadFeedback);
|
||
|
||
function toggleAll(open) { document.querySelectorAll('details').forEach(d => d.open = open); }
|
||
|
||
function exportDoc() {
|
||
const clone = document.documentElement.cloneNode(true);
|
||
const blob = new Blob(['<!DOCTYPE html>\n' + clone.outerHTML], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'distsoc-v2-design-review-annotated.html';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function clearFeedback() {
|
||
if (!confirm('Clear all feedback? This cannot be undone.')) return;
|
||
localStorage.removeItem(STORAGE_KEY);
|
||
document.querySelectorAll('.feedback[data-id]').forEach(el => el.innerHTML = '');
|
||
updateCount();
|
||
document.getElementById('save-status').textContent = 'Feedback cleared';
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|