95f03cf1813b

fix: PCH-01-010 deriveShardCounts rejects dataShards above MAX_DATA_SHARDS_PER_STRIPE — makes the stripe-inversion unique, prevents wedged restore from a crafted total (v3.8.1)

time
2026-06-10 19:50 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
pch-01 fix integration
parent
cdac7526ea77
3 files src/popup/codex/codex.adversarial.test.ts+13 −0src/popup/codex/restore.ts+2 −2src/shared/codex-types.ts+6 −0
src/popup/codex/codex.adversarial.test.ts+13 −0
@@ -234,6 +234,19 @@234234     const buf = new ChunkBuffer();235235     await expect(reconstructAndVerify(buf)).rejects.toThrow(CodexError);236236   });237+238+  // PCH-01-010: computeParityShards saturates at 35, so without an upper bound on239+  // dataShards the inversion accepts absurd totals and wedges a stripe.240+  it("derive_shard_counts_rejects_absurd_total", () => {241+    expect(() => deriveShardCounts(1_000_000_000)).toThrow(CodexError);242+  });243+244+  it("derive_shard_counts_accepts_real_max", () => {245+    // dataShards = 200 (MAX_DATA_SHARDS_PER_STRIPE) with its saturated parity (35).246+    const total = 200 + computeParityShards(200);247+    const counts = deriveShardCounts(total);248+    expect(counts.dataShards).toBe(200);249+  });237250 });238251 239252 // ── verifyChunkIntegrity adversarial ────────────────────────────────────────

view file

src/popup/codex/restore.ts+2 −2
@@ -131,8 +131,8 @@131131     if (!stripeState) {132132       // First envelope from a new stripe fixes that stripe's total + data133133       // shard count. `deriveShardCounts` inverts `computeParityShards` over134-      // the within-stripe total — bounded by MAX_DATA_SHARDS_PER_STRIPE so135-      // the inversion always has a unique solution.134+      // the within-stripe total — bounded by MAX_DATA_SHARDS_PER_STRIPE in135+      // deriveShardCounts so the inversion has a unique, sane solution (PCH-01-010).136136       let dataShards: number;137137       try {138138         dataShards = deriveShardCounts(envelope.t).dataShards;

view file

src/shared/codex-types.ts+6 −0
@@ -250,6 +250,12 @@250250   ) {251251     const dataShards = totalChunks - parityShards;252252     if (dataShards < 1) continue;253+    // PCH-01-010: a saturated parity count (35) round-trips for any254+    // dataShards >= 234, so without this bound computeParityShards's saturation255+    // makes the inversion accept absurd totals (e.g. 1e9), wedging a stripe that256+    // isReady() can never satisfy. Reject anything a real generator could never257+    // have produced — this is what makes the uniqueness claim in restore.ts true.258+    if (dataShards > MAX_DATA_SHARDS_PER_STRIPE) continue;253259     if (computeParityShards(dataShards) === parityShards) {254260       return { dataShards, parityShards };255261     }

view file