v0.3.6: Active CDN replication, device roles, budgets, tombstones, engagement fix, DOS hardening

Active CDN replication:
- All devices proactively replicate recent posts (<72h, <2 replicas) to peers
- Target priority: desktops (300) > anchors (200) > phones (100) + cache_pressure
- ReplicationRequest/Response (0xE1/0xE2) wire messages
- 10-min cycle, 2-min initial delay, cap 20 posts per request
- Graceful with small networks (1 peer = 1 replica, 0 peers = silent skip)

Device roles & budgets:
- Intermittent (phone), Available (desktop), Persistent (anchor)
- Advertised in InitialExchange, stored per-peer
- Replication budget: phones 100MB/hr, desktops/anchors 200MB/hr
- Delivery budget: phones 1GB/hr, desktops 2GB/hr, anchors 1GB/hr
- Hourly auto-reset, enforcement on blob serving

Cache management:
- 1GB default cache limit, configurable in settings UI
- Eviction cycle activated (was implemented but never started)
- Share-link priority boost (+100 for 3+ downstream)
- Cache pressure score (0-255) for replication targeting

Engagement distribution fix:
- BlobHeader JSON rebuilt after BlobHeaderDiff ops
- Previously reactions/comments stored in tables but header stayed stale

Tombstone system:
- deleted_at column on reactions and comments
- Tombstones propagate through pull sync (additive merge respects timestamps)
- UI queries filter WHERE deleted_at IS NULL

Persistent notifications:
- seen_engagement and seen_messages tables replace in-memory Sets
- Only notify on genuinely unseen content, survives restarts

DOS hardening:
- BlobHeaderDiff fan-out: single batched task, max 10 concurrent via JoinSet
- Blob prefetch: cap 20 per cycle, newest first
- PostDownstreamRegister: cap 50 per sync
- Delivery budget enforcement on BlobRequest handler
- Pull preference: non-anchors first to preserve anchor delivery budget

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-20 21:00:28 -04:00
parent b7f2d369fa
commit a7e632de88
16 changed files with 1254 additions and 158 deletions

View file

@ -25,16 +25,16 @@
<section>
<h1 style="font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.25rem;">Download ItsGoin</h1>
<p>Available for Android and Linux. Free and open source.</p>
<p style="color: var(--text-muted); font-size: 0.85rem;">Version 0.3.5 &mdash; March 15, 2026</p>
<p style="color: var(--text-muted); font-size: 0.85rem;">Version 0.3.6 &mdash; March 15, 2026</p>
<div class="downloads">
<a href="itsgoin-0.3.5.apk" class="download-btn btn-android">
<a href="itsgoin-0.3.6.apk" class="download-btn btn-android">
Android APK
<span class="sub">v0.3.5</span>
<span class="sub">v0.3.6</span>
</a>
<a href="itsgoin_0.3.5_amd64.AppImage" class="download-btn btn-linux">
<a href="itsgoin_0.3.6_amd64.AppImage" class="download-btn btn-linux">
Linux AppImage
<span class="sub">v0.3.5</span>
<span class="sub">v0.3.6</span>
</a>
</div>
</section>
@ -46,7 +46,7 @@
<h3 style="color: var(--accent);">Android</h3>
<ol class="steps">
<li><strong>Download the APK</strong> &mdash; Tap the button above. Your browser may warn that this type of file can be harmful &mdash; tap <strong>Download anyway</strong>.</li>
<li><strong>Open the file</strong> &mdash; When the download finishes, tap the notification or find <code>itsgoin-0.3.5.apk</code> in your Downloads folder and tap it.</li>
<li><strong>Open the file</strong> &mdash; When the download finishes, tap the notification or find <code>itsgoin-0.3.6.apk</code> in your Downloads folder and tap it.</li>
<li><strong>Allow installation</strong> &mdash; Android will ask you to allow installs from this source. Tap <strong>Settings</strong>, toggle <strong>"Allow from this source"</strong>, then go back and tap <strong>Install</strong>.</li>
<li><strong>Launch the app</strong> &mdash; Once installed, tap <strong>Open</strong> or find ItsGoin in your app drawer.</li>
</ol>
@ -59,8 +59,8 @@
<h3 style="color: var(--green);">Linux (AppImage)</h3>
<ol class="steps">
<li><strong>Download the AppImage</strong> &mdash; Click the button above to download.</li>
<li><strong>Make it executable</strong> &mdash; Open a terminal and run:<br><code>chmod +x itsgoin_0.3.5_amd64.AppImage</code></li>
<li><strong>Run it</strong> &mdash; Double-click the file, or from the terminal:<br><code>./itsgoin_0.3.5_amd64.AppImage</code></li>
<li><strong>Make it executable</strong> &mdash; Open a terminal and run:<br><code>chmod +x itsgoin_0.3.6_amd64.AppImage</code></li>
<li><strong>Run it</strong> &mdash; Double-click the file, or from the terminal:<br><code>./itsgoin_0.3.6_amd64.AppImage</code></li>
</ol>
<div class="note">
<strong>Note:</strong> If it doesn't launch, you may need to install FUSE:<br><code>sudo apt install libfuse2</code> (Debian/Ubuntu) or <code>sudo dnf install fuse</code> (Fedora).
@ -71,16 +71,26 @@
<section>
<h2>Changelog</h2>
<div class="changelog">
<div class="changelog-date">v0.3.6 &mdash; March 20, 2026</div>
<ul>
<li><strong>Active CDN replication</strong> &mdash; All devices proactively request replication of their recent posts (&lt;72h) to connected peers. Targets prioritized: desktops &gt; anchors &gt; phones. Graceful with small networks (1 peer = 1 replica). ReplicationRequest/Response (0xE1/0xE2) wire messages.</li>
<li><strong>Device roles</strong> &mdash; Nodes classified as Intermittent (phones), Available (desktops), or Persistent (anchors). Advertised in InitialExchange. Influences replication target selection and budget defaults.</li>
<li><strong>Bandwidth budgets</strong> &mdash; Hourly replication budget (content pulled to cache) and delivery budget (content served). Phones: 100MB/1GB, Desktops: 200MB/2GB, Anchors: 200MB/1GB. Auto-reset hourly. Blob serving declines when delivery budget exhausted.</li>
<li><strong>Cache management</strong> &mdash; 1GB default cache limit (configurable 256MB&ndash;unlimited). Eviction cycle now active (was implemented but never started). Priority scoring with share-link boost (+100 for 3+ downstream). Cache pressure score (0&ndash;255) for future budget advertisement.</li>
<li><strong>Engagement distribution fix</strong> &mdash; BlobHeader JSON now rebuilt after processing BlobHeaderDiff ops. Previously reactions/comments stored in tables but header JSON stayed stale, breaking pull-based sync for downstream peers.</li>
<li><strong>Tombstone system</strong> &mdash; Deleted reactions/comments are tombstoned (<code>deleted_at</code> timestamp) instead of hard-deleted. Tombstones propagate through pull sync, ensuring deletes reach peers that missed the real-time diff.</li>
<li><strong>Persistent notifications</strong> &mdash; Notification tracking backed by <code>seen_engagement</code> and <code>seen_messages</code> tables. Only notifies on genuinely unseen content. Survives app restarts.</li>
<li><strong>DOS hardening</strong> &mdash; BlobHeaderDiff fan-out capped at 10 concurrent sends. Blob prefetch capped at 20 per cycle. PostDownstreamRegister capped at 50 per sync. Delivery budget enforcement on blob serving.</li>
<li><strong>Pull preference</strong> &mdash; Blob fetches prefer non-anchor sources (phones &gt; desktops &gt; replicas &gt; anchors) to preserve anchor delivery budget for web requests.</li>
</ul>
<div class="changelog-date">v0.3.5 &mdash; March 20, 2026</div>
<ul>
<li><strong>Private blob encryption</strong> &mdash; Attachments on encrypted posts (Friends, Circle, Direct) are now encrypted with the same CEK as the post text. Public blobs remain plaintext. CID computed on ciphertext preserves content addressing.</li>
<li><strong>Blob prefetch on sync</strong> &mdash; When posts are pulled from peers, their attachments are eagerly fetched for offline availability. Previously blobs were only fetched on view.</li>
<li><strong>Crypto refactoring</strong> &mdash; Extracted reusable primitives: <code>encrypt_bytes_with_cek</code>, <code>decrypt_bytes_with_cek</code>, <code>unwrap_cek_for_recipient</code>, <code>unwrap_group_cek</code>. Foundation for encrypted blob storage and future chunk-level encryption.</li>
<li><strong>Intent-based post filtering</strong> &mdash; Feed, My Posts, and Messages now filter on the author's original visibility intent (<code>intentKind</code>) rather than encryption state. Direct messages are identified by intent, not by being &ldquo;encrypted-for-me.&rdquo; Backward-compatible with pre-intent posts.</li>
<li><strong>Blob decryption on retrieval</strong> &mdash; New <code>get_blob_for_post</code> API decrypts private blobs in context of their post&rsquo;s visibility. Public blobs pass through unchanged.</li>
<li><strong>Encrypted receipt slots</strong> &mdash; Private messages get encrypted receipt and comment slots in their BlobHeader. Pre-filled with random noise so slot writes are indistinguishable from creation. Receipt states: delivered, seen, reacted. Only participants with the CEK can read slots; relay nodes propagate opaque bytes.</li>
<li><strong>Message receipts &amp; reactions</strong> &mdash; DM conversations show delivery indicators (checkmark &rarr; double checkmark &rarr; emoji). Opening a conversation marks messages as seen. React to messages with emoji.</li>
<li><strong>Private comment slots</strong> &mdash; Encrypted comment capacity in private post headers (ceil(participants/3) slots, expandable). Participants can write short comments that propagate via CDN without revealing content to relays.</li>
<li><strong>Intent-based post filtering</strong> &mdash; Feed, My Posts, and Messages now filter on the author's original visibility intent (<code>intentKind</code>) rather than encryption state.</li>
<li><strong>Encrypted receipt slots</strong> &mdash; Private messages get encrypted receipt and comment slots in BlobHeader. Delivery indicators, read receipts, and message reactions.</li>
<li><strong>Download filename sanitization</strong> &mdash; Prevents path traversal in downloaded file names.</li>
</ul>