Route snapshots through the snapshot_blob lane (inline ≤1MiB, R2 above)#6
Merged
Conversation
Sharing any file whose snapshot ciphertext exceeded the relay's 256 KiB event cap (HARD_MAX_EVENT_BYTES) was broken: publish_snapshot inlined the full SnapshotPlaintext (markdown + anchor index) into the SnapshotCreated event envelope, the relay rejected it with 413, and the unclassified poison envelope retried forever at the head of the outbox — blocking WebRTC signaling behind it, so the reviewer could never join. A ~100 KB markdown file (~1 MB sealed) hit this every time. Snapshot bytes now travel as kind=snapshot_blob envelopes sealed under the room's snapshotKey (relay cap: policy.maxSnapshotBytes, 5 MiB): - ≤ 1 MiB sealed: the blob envelope rides the normal outbox, enqueued BEFORE the SnapshotCreated event so peers receive bytes-then-pointer in serverSeq order (BlobRef storage=mailbox). - > 1 MiB sealed: R2 spillover per relay-spec — presign POST /blobs, PUT nonce||ciphertext||tag (AAD-bound to the wrapper envelope so blob bodies can't be swapped between envelopes), and the outbox envelope carries only the encrypted canonical-JSON BlobRef (storage=r2). The SnapshotCreated event itself stays small (~800 B): inline_snapshot is never on the wire (decision #14); encrypted_blob_ref points at the blob by envelopeId. Receivers persist decrypted blobs in the store (rooms/<room>/blobs/<envelopeId>.bin), resolve R2 refs via the new download-presign endpoint, verify length + content hash against the Ed25519-signed event's BlobRef, and the daemon rehydrates inline_snapshot at the IPC boundary — the Svelte frontend is unchanged. Relay: implement the spec'd-but-missing download presign — a cap-less GET /v2/rooms/:roomId/blobs/:envelopeId now routes to the DO (admission-auth'd, no PoW per spec for GETs) and mints the download cap the cap-bearing GET consumes. crypto-spec amendment: the BlobRef wrapper seals under snapshotKey (not the originally pinned eventKey) — the inbound pipeline keys strictly by envelope kind, and a receiver can't know bytes-vs-BlobRef until after decrypting. Owner side also persists the SnapshotNode (with plaintext + BlobRef), making the WebRTC RequestSnapshot recovery path functional. Verification: cargo test (924 across binaries), relay vitest (296, incl. 3 new presign-endpoint cases + round-trip now driven through the real endpoint), and a new scripts/test-snapshot-blob-e2e.sh that boots a local relay + owner + reviewer daemons and proves both lanes end-to-end (reviewer renders 28 KB and 150 KB docs; no 413 in any log). Release binary 30.25 MiB, under the 32 MiB gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Sharing any file whose snapshot ciphertext exceeded the relay's 256 KiB event cap (
HARD_MAX_EVENT_BYTES) was silently broken.publish_snapshotinlined the fullSnapshotPlaintext(markdown + anchor index) into theSnapshotCreatedevent envelope; the relay rejected it with 413; the unclassified poison envelope retried forever at the head of the outbox — blocking the WebRTC signaling envelopes queued behind it, so the reviewer could never join. A ~100 KB markdown file (~1 MB sealed) hit this every time (production logs:webrtc: signaling drain failed: io: relay 413 Payload Too Large).Fix
Snapshot bytes now travel as
kind=snapshot_blobenvelopes sealed under the room'ssnapshotKey(relay cap:policy.maxSnapshotBytes, 5 MiB), per the already-locked spec (relay-spec.md§R2 spillover,crypto-spec.md§Nonce Discipline, decision #14):SnapshotCreatedevent so peers receive bytes-then-pointer in serverSeq order (BlobRef storage=mailbox).POST /blobs,PUT nonce||ciphertext||tag(AAD-bound to the wrapper envelope so blob bodies can't be swapped between envelopes), and the outbox envelope carries only the encrypted canonical-JSONBlobRef(storage=r2).The
SnapshotCreatedevent stays ~800 B on the wire:inline_snapshotis never serialized;encrypted_blob_refpoints at the blob byenvelopeId.Receivers persist decrypted blobs (
rooms/<room>/blobs/<envelopeId>.bin), resolve R2 refs via the new download-presign endpoint, verify length + content hash against the Ed25519-signed event'sBlobRef, and the daemon rehydratesinline_snapshotat the IPC boundary — the Svelte frontend is unchanged.Relay: implements the spec'd-but-missing download presign — a cap-less
GET /v2/rooms/:roomId/blobs/:envelopeIdroutes to the DO (admission-auth'd; no PoW on GETs per spec) and mints the cap the cap-bearing GET consumes.Spec amendment (
crypto-spec.md): the BlobRef wrapper seals undersnapshotKey, not the originally pinnedeventKey— the inbound pipeline keys strictly by envelope kind, and a receiver can't know bytes-vs-BlobRef until after decrypting.Owner side also persists the
SnapshotNode(plaintext + BlobRef), making the WebRTCRequestSnapshotrecovery path functional (latest_snapshot_for_filepreviously always returnedNonein production).Verification
cargo test— 924 tests across binaries, including new coverage: blob envelope round-trip + cross-key isolation + R2 body wrapper-binding, store blob persistence, inline-vs-R2 routing inpublish_snapshot(wiremock presign/PUT), inbound persistence rules, and rehydration integrity checks.scripts/test-snapshot-blob-e2e.sh— boots a local relay + owner + reviewer daemons, folder-shares a 28 KB doc (mailbox lane) and a 150 KB doc (R2 lane), and asserts the reviewer renders both with full markdown and no 413 in any log. 5/5 passing.task check:size— release binary 30.25 MiB, under the 32 MiB gate.Notes / follow-ups
kind=eventenvelopes; those rooms need to be stopped/deleted and re-shared.🤖 Generated with Claude Code