4c15b8de49bb

test: T-A38 root-cause proof — >768 KiB snapshot succeeds because duress rides its own frame

The single test that proves the root-cause fix for the frame-ceiling bug, not just the new wire path. Combines the high-level end-to-end success assertion (T-A33) with the structural proof (T-A34): a payload whose combined raw exceeds the 768 KiB wire ceiling succeeds because the duress slot rides its own frame instead of piggybacking on the entries final chunk. Pre-v4.6.4, duress counted against the entries frame, so a single frame would exceed SNAPSHOT_FRAME_WIRE_CEILING and the receiver would reject the whole snapshot with ADOPT_SNAPSHOT_CHUNK_TOO_LARGE. T-A33 alone proves the high-level success; T-A34 alone proves the structural shape; T-A38 proves the root-cause: a >768 KiB payload succeeds BECAUSE duress is on its own frame (asserted by both frames being under the ceiling and combined raw exceeding it). That's the one test that distinguishes the wire-fix from the root-cause fix. Versions: peach-browser unchanged at 4.6.4 (T-A38 is part of the same duress-on-separate-frame feature; the wire shape and cap math already shipped at v4.6.4). Gate: tsc --noEmit RC=0; vitest run src/shared/vault-adopt.test.ts 26 tests / 0 failed (was 25 + T-A38 = 26); vitest run src/shared/vault-adopt-snapshot.test.ts 13 tests / 0 failed. pnpm run build RC=0 across Chrome Firefox Mobile.

time
2026-07-30 22:28 UTC · 2 months ago
machine
koh2
parent
1077ccaa33eb
1 file src/shared/vault-adopt.test.ts+61 −0
src/shared/vault-adopt.test.ts+61 −0
@@ -792,4 +792,65 @@792792     expect(Object.keys(result.entries).length).toBeLessThan(200);793793     expect(result.tombstones).toEqual({ "dead-0": 1 });794794   });795+796+  // T-A38: the single test that proves the *root-cause fix* for the frame797+  // ceiling bug, not just the new wire path. Combines the high-level798+  // end-to-end success assertion (T-A33) with the structural proof (T-A34):799+  // a payload whose combined raw exceeds the 768 KiB wire ceiling succeeds800+  // because the duress slot rides its own frame instead of piggybacking on801+  // the entries final chunk. Pre-v4.6.4, duress counted against the entries802+  // frame, so a single frame would exceed SNAPSHOT_FRAME_WIRE_CEILING and the803+  // receiver would reject the whole snapshot with ADOPT_SNAPSHOT_CHUNK_TOO_LARGE.804+  it("T-A38: >768 KiB snapshot succeeds because duress no longer counts against the entries frame", async () => {805+    const [source, receiver] = memoryPair();806+    // Single entry that is ~450 KiB on its own; combined with a ~640 KiB807+    // duress JSON, the total payload exceeds the 768 KiB wire ceiling.808+    const entries: Record<string, string> = { "id-0": "x".repeat(450 * 1024) };809+    const tombstones: Record<string, number> = { "dead-0": 1 };810+    const duressJson = JSON.stringify({811+      version: 1,812+      salt: "aa".repeat(16),813+      nonce: "bb".repeat(12),814+      ciphertext: "cc".repeat(320 * 1024),815+      tag: "dd".repeat(16),816+    });817+    const CEILING = 768 * 1024;818+819+    // Probe the wire so we can assert the structural shape alongside the820+    // end-to-end success.821+    const observed: Array<{ size: number; hasDuress: boolean; entriesKeys: number }> = [];822+    const probe: PairingTransport = {823+      async send(msg) {824+        const f = JSON.parse(new TextDecoder().decode(msg)) as Record<string, unknown>;825+        observed.push({826+          size: msg.length,827+          hasDuress: typeof f.duress_slot === "string",828+          entriesKeys: Object.keys((f.entries as Record<string, unknown>) ?? {}).length,829+        });830+        return source.send(msg);831+      },832+      receive() { return source.receive(); },833+    };834+835+    const [result] = await Promise.all([836+      receiveVaultSnapshot(receiver),837+      sendVaultSnapshot(probe, entries, tombstones, duressJson),838+    ]);839+840+    // High-level success: the receiver accepts the >768 KiB combined payload.841+    expect(result.entries).toEqual(entries);842+    expect(result.tombstones).toEqual(tombstones);843+    expect(result.duressSlot).toBe(duressJson);844+845+    // Root-cause proof: duress is on its own frame, not on the entries frame.846+    // Every frame stays under the ceiling; only the combined raw exceeds it.847+    expect(observed.length).toBe(2);848+    expect(observed[0].hasDuress).toBe(false);849+    expect(observed[0].entriesKeys).toBe(1);850+    expect(observed[0].size).toBeLessThan(CEILING);851+    expect(observed[1].hasDuress).toBe(true);852+    expect(observed[1].entriesKeys).toBe(0);853+    expect(observed[1].size).toBeLessThan(CEILING);854+    expect(observed[0].size + observed[1].size).toBeGreaterThan(CEILING);855+  });795856 });

view file