Design doc: v0.3.2 + v0.3.3 changelog, rate limiting, N2/N3 freshness, bootstrap recovery, schema versioning docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-16 18:47:38 -04:00
parent 8fad30cf95
commit ce176a2299

View file

@ -43,7 +43,9 @@
<p>This is the canonical technical reference for ItsGoin. It describes the vision, the architecture, and the current state of every subsystem &mdash; with full implementation detail. This document is versioned; each update records what changed.</p>
<div class="card" style="margin-top: 1rem;">
<strong style="font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;">Changelog</strong>
<p style="margin-top: 0.5rem;"><strong>v0.3.1</strong> (2026-03-13): Share links + QUIC proxy + content search. Share link format: <code>itsgoin.net/p/&lt;postid_hex&gt;/&lt;author_nodeid_hex&gt;</code> &mdash; simple, no host encoding needed. itsgoin.net web handler acts as QUIC proxy: receives browser request, searches the network for the post, fetches it on-demand via PostFetch (0xD4/0xD5), renders HTML, serves to browser. No permanent storage of fetched content. Extended worm search &mdash; <code>WormQuery</code> now carries optional <code>post_id</code> and <code>blob_id</code> fields for unified node/post/blob search. Each peer checks local storage, CDN downstream tree (up to 100 hosts per post), and blob store. <code>WormResponse</code> gains <code>post_holder</code> and <code>blob_holder</code> fields. Nova fan-out pattern &mdash; burst peers include one N2 wide referral; referred peer does its own 101-burst, reaching ~10K nodes with ~202 relay hops. PostFetch (0xD4/0xD5) &mdash; lightweight single-post retrieval after worm finds a holder, much lighter than full PullSync. itsgoin.net node deployed as anchor + web handler (<code>--web 8080</code>). &ldquo;Unavailable&rdquo; page with honest network model explanation + install CTA. Universal Links / App Links planned for native app interception. | Engagement sync &mdash; pull sync now fetches reactions, comments, and policies via BlobHeaderRequest/Response after every sync. Profile push fix &mdash; profile updates now sent to all connected mesh peers (not just audience). Auto-sync on follow &mdash; following a peer triggers immediate post pull + engagement fetch. Popover UI &mdash; notifications settings, network diagnostics, and message threads now open as popovers. Notification settings &mdash; per-key settings table in SQLite, configurable message/post/nearby notifications with JS Notification API. Tiered DM polling &mdash; smart message refresh based on conversation recency. Reaction display &mdash; posts show top 5 most popular emoji + total response count. UI cleanup &mdash; removed Suggested Peers and Find Nearby sections, placeholder text changed to &ldquo;How&rsquo;s it goin?&rdquo;, clickable node IDs in activity log.</p>
<p style="margin-top: 0.5rem;"><strong>v0.3.3</strong> (2026-03-16): Connection rate limiting &mdash; incoming auth failures rate-limited per source IP (3 attempts, exponential backoff to ~256s). Schema versioning &mdash; PRAGMA user_version tracks DB version with migration framework. N2/N3 freshness &mdash; TTL 7d&rarr;5h, full N1/N2 re-broadcast every 4h, startup sweep clears stale entries. Bootstrap isolation recovery &mdash; 24h check verifies bootstrap is in N1/N2/N3, reconnects + sticky N1 advertisement if absent. IPv6 HTTP address fix &mdash; nodes advertise actual public IPv6 (not 0.0.0.0) for share link redirects. Upstream tracking &mdash; post_upstream table records post source for engagement diff routing toward author. Video preload fix &mdash; share links and in-app videos use preload=auto. Following Online/Offline split. DM filter from My Posts. Any-type file attachments with download prompt + trust warning. Image lightbox. Audio player.</p>
<p><strong>v0.3.2</strong> (2026-03-14): Bidirectional engagement propagation &mdash; BlobHeaderDiff flows upstream + downstream through CDN tree. Auto downstream registration on pull sync/push notification. TCP hole punch protocol (TcpPunchRequest/Result 0xD6/0xD7). Tiered web serving (redirect &rarr; TCP punch &rarr; QUIC proxy). Video playback fix (asset protocol + blob URL fallback). On-demand blob fetch for synced posts missing blob data.</p>
<p><strong>v0.3.1</strong> (2026-03-13): Share links + QUIC proxy + content search. Share link format: <code>itsgoin.net/p/&lt;postid_hex&gt;/&lt;author_nodeid_hex&gt;</code> &mdash; simple, no host encoding needed. itsgoin.net web handler acts as QUIC proxy: receives browser request, searches the network for the post, fetches it on-demand via PostFetch (0xD4/0xD5), renders HTML, serves to browser. No permanent storage of fetched content. Extended worm search &mdash; <code>WormQuery</code> now carries optional <code>post_id</code> and <code>blob_id</code> fields for unified node/post/blob search. Each peer checks local storage, CDN downstream tree (up to 100 hosts per post), and blob store. <code>WormResponse</code> gains <code>post_holder</code> and <code>blob_holder</code> fields. Nova fan-out pattern &mdash; burst peers include one N2 wide referral; referred peer does its own 101-burst, reaching ~10K nodes with ~202 relay hops. PostFetch (0xD4/0xD5) &mdash; lightweight single-post retrieval after worm finds a holder, much lighter than full PullSync. itsgoin.net node deployed as anchor + web handler (<code>--web 8080</code>). &ldquo;Unavailable&rdquo; page with honest network model explanation + install CTA. Universal Links / App Links planned for native app interception. | Engagement sync &mdash; pull sync now fetches reactions, comments, and policies via BlobHeaderRequest/Response after every sync. Profile push fix &mdash; profile updates now sent to all connected mesh peers (not just audience). Auto-sync on follow &mdash; following a peer triggers immediate post pull + engagement fetch. Popover UI &mdash; notifications settings, network diagnostics, and message threads now open as popovers. Notification settings &mdash; per-key settings table in SQLite, configurable message/post/nearby notifications with JS Notification API. Tiered DM polling &mdash; smart message refresh based on conversation recency. Reaction display &mdash; posts show top 5 most popular emoji + total response count. UI cleanup &mdash; removed Suggested Peers and Find Nearby sections, placeholder text changed to &ldquo;How&rsquo;s it goin?&rdquo;, clickable node IDs in activity log.</p>
<p><strong>v0.3.0</strong> (2026-03-12): Full rename distsoc &rarr; ItsGoin. ALPN, crypto contexts, data paths, Android package ID all changed. Clean break &mdash; incompatible with prior versions.</p>
<p><strong>v0.2.11</strong> (2026-03-12): Engagement system &mdash; reactions (public + private encrypted via X25519 DH + ChaCha20-Poly1305), inline comments with ed25519 signatures, author-controlled comment/react policies (audience-only, public, none), blocklist enforcement. CDN tree for all posts &mdash; new <code>post_downstream</code> table (keyed by PostId, max 100 peers) gives every post a propagation tree; <code>PostDownstreamRegister</code> (0xD3) sent when any peer stores a post. 4 new wire messages: BlobHeaderDiff (0xD0) for incremental engagement propagation, BlobHeaderRequest/Response (0xD1/0xD2), PostDownstreamRegister (0xD3). 6 new SQLite tables, 9 new IPC commands. Thread splitting &mdash; headers exceeding 16KB auto-split oldest comments into linked thread posts. Frontend: emoji picker, reaction pills, comment threads, policy selects in compose area.</p>
<p><strong>v0.2.10</strong> (2026-03-12): Per-family NAT classification &mdash; IPv4 and IPv6 public reachability now detected independently. Previously, a public IPv6 address incorrectly set <code>has_public_v4=true</code>, causing nodes behind IPv4 NAT to skip hole punching. STUN now always runs (unless <code>--bind</code>) so IPv6-only anchors correctly classify their IPv4 NAT. Anchor advertised address fallback &mdash; anchors without <code>--bind</code> or UPnP now advertise their first public bound address (e.g. IPv6 SLAAC), so peers store them in <code>known_anchors</code> for preferential reconnection. Bootstrap anchor deprioritization &mdash; startup connection sequence now tries discovered (non-bootstrap) anchors first, falling back to hardcoded bootstrap anchors only when no discovered anchor is reachable. Reduces load on bootstrap infrastructure as the network grows.</p>
@ -1027,6 +1029,50 @@ FAILURE: C &rarr; B &rarr; A: AnchorProbeResult { reachable: false }</code></pre
<li><strong>Pull (safety net)</strong>: Every 5 minutes, the pull cycle requests <code>BlobHeaderRequest</code> (0xD1) with the local header timestamp. Peers respond with the full header only if theirs is newer. Additive merge &mdash; <code>store_reaction</code> upserts, <code>store_comment</code> inserts with ON CONFLICT DO NOTHING.</li>
<li><strong>Planned</strong>: Pull engagement from both upstream and downstream peers to catch missed diffs from either direction.</li>
</ul>
<h3>Connection rate limiting</h3>
<p>Incoming QUIC connections that fail authentication are rate-limited per source IP to prevent CPU exhaustion from rogue or stale nodes:</p>
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
<li><strong>First 3 failures</strong>: logged normally, connection attempts proceed</li>
<li><strong>4+ failures</strong>: silently dropped with exponential backoff (2<sup>n-3</sup> seconds, capped at ~256s)</li>
<li><strong>Successful connection</strong>: clears the failure count for that IP</li>
<li><strong>Cleanup</strong>: stale entries removed every 60 seconds (pruned after 5 minutes of inactivity)</li>
</ul>
<h3>N2/N3 freshness</h3>
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
<li><strong>TTL</strong>: N2/N3 entries expire after <strong>5 hours</strong> (pruned during rebalance cycle)</li>
<li><strong>Full state broadcast</strong>: Every <strong>4 hours</strong>, nodes re-broadcast their complete N1/N2 state (not just diffs) to catch any missed incremental updates</li>
<li><strong>Disconnect cleanup</strong>: When a mesh peer disconnects, all N2/N3 entries they reported are immediately removed (<code>clear_peer_n2</code>/<code>clear_peer_n3</code>)</li>
<li><strong>Startup sweep</strong>: On boot, all N2/N3 and mesh_peers entries are cleared &mdash; they're stale from the previous session and will be rebuilt as connections establish</li>
</ul>
<h3>Bootstrap isolation recovery</h3>
<p>Prevents network segments from becoming permanently isolated from the main network:</p>
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
<li><strong>24-hour check</strong>: Starting 24 hours after boot, nodes verify the bootstrap anchor is within their N1/N2/N3 reach</li>
<li><strong>If absent</strong>: reconnect to bootstrap, request referrals, connect to referred peers</li>
<li><strong>Sticky N1</strong>: The bootstrap is added to a sticky N1 set for 24 hours, so mesh peers learn about it via routing diffs and can independently bridge back to the main network</li>
<li><strong>Self-limiting</strong>: Once the bootstrap is in N3, the check passes and no action is taken. Goes silent as the network grows.</li>
</ul>
<h3>Schema versioning</h3>
<p>SQLite databases track their schema version via <code>PRAGMA user_version</code>. On startup:</p>
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
<li>If the database version is older than <code>MIN_MIGRATABLE_VERSION</code>, the database is reset (preserving identity key)</li>
<li>If older than the current version, data migrations run once and the version is bumped</li>
<li>Schema-level changes (new tables, columns) are handled by <code>init_tables()</code> (<code>CREATE TABLE IF NOT EXISTS</code>) and <code>migrate()</code> (column-level <code>ALTER TABLE</code> checks)</li>
</ul>
<h3>Upstream tracking (<code>post_upstream</code>)</h3>
<p>Each post tracks which peer it was received from in the <code>post_upstream</code> table (post_id &rarr; peer_node_id). Set during pull sync and push notification. Used for:</p>
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
<li>Engagement diff propagation toward the post author (hop-by-hop upstream)</li>
<li>Future: N+10 identification in blob headers (upstream source's N+10)</li>
</ul>
<h3>IPv6 HTTP address advertisement</h3>
<p>Nodes with public IPv6 addresses advertise their actual routable address (from <code>endpoint.addr().ip_addrs()</code>) paired with their bound port, rather than the bind address (<code>0.0.0.0</code>). This enables direct browser-to-node HTTP serving for share links. Unroutable addresses (<code>0.0.0.0</code>, <code>127.x</code>) are filtered out in the tiered web serving redirect path.</p>
</section>
<!-- 20. Encryption -->