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
↓ peach-browser-95f03cf1.tar.gzatlas at this save
files changed3 files
src/popup/codex/codex.adversarial.test.ts+13 −0src/popup/codex/restore.ts+2 −2src/shared/codex-types.ts+6 −0src/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 ────────────────────────────────────────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;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 }