Skip to content

LAB-375: fix Set-branch aggregate byte-budget and count-cap fail-fast in interop encodeCanonical#72

Open
27Bslash6 wants to merge 1 commit into
mainfrom
lab-375-set-budget-fail-fast
Open

LAB-375: fix Set-branch aggregate byte-budget and count-cap fail-fast in interop encodeCanonical#72
27Bslash6 wants to merge 1 commit into
mainfrom
lab-375-set-budget-fail-fast

Conversation

@27Bslash6

Copy link
Copy Markdown
Contributor

Closes LAB-375 — the residual finding from the #71 review cycle (#71 merged before this could land on its branch, so it ships as a fast-follow).

Problem

The Set branch of encodeCanonical seeded every element sub-sink from the frozen parent total and only advanced the parent budget in the final push loop. A Set whose elements are each under the 1 MiB budget but collectively over it fully encoded and buffered all N elements before ValueTooLargeError fired — peak transient memory ≈ Σ(element bytes), contradicting ChunkSink's own "rejected during traversal" contract. The element-count cap (10,000) had the same timing: it fired in encodeArrayHeader after the loop, so a huge-count Set retained every per-element buffer first.

Fix

Thread a running byte total across element sub-encodes and dedupe on insert (keyed by the element's encoded bytes via the already-imported bytesToHex):

  • Byte budget fails mid-iteration, counting exactly what the output retains — duplicates advance nothing, so a duplicate-heavy Set whose deduped encoding fits still encodes byte-identically instead of being falsely rejected at its pre-dedupe sum (a Set of content-equal distinct objects is everyday JS; the expert panel confirmed this rejection empirically against a pre-dedupe draft of this fix).
  • Count cap moves in-loop as checkCollectionSize(seen.size)seen.size is by construction the post-dedupe count, so acceptance is identical to the post-loop header check, just earlier. Retained buffers are now bounded (~1 MiB payload / 10,000 elements) for every input.
  • The post-sort adjacent-dedupe filter is deleted — the seen-set makes it provably dead.

One documented conservative edge remains, inherent to fail-fast: a duplicate element arriving with less than its own encoded size left in the byte budget throws mid-encode (duplicateness is unknowable until encoded). That only fires when the deduped total is already within one element of the cap.

Byte-identity (load-bearing)

The running total and seen-set feed only the failure checks, never the emitted chunks. All 110 protocol tests pass byte-for-byte, including the Set-ordering vectors (mixed-sign {-5,"a",1.5}["a",1.5,-5]) and the {2,2.0} dedupe vector. Full suite 559 passed, lint/prettier/type-check clean.

Tests

Three regression tests (getter-spy elements count how many Set members actually encoded, pinning the timing, which a plain toThrow cannot — the old code also threw, just after buffering everything):

  • aggregate byte overflow throws during iteration (element 16 of 32);
  • duplicate-heavy Set (~4 MiB pre-dedupe, ~200 KiB deduped) encodes byte-identically to its singleton;
  • count-cap overflow throws on the 10,001st retained element, not after all 10,002.

Review gate

Mandatory crypto/protocol expert-panel review (high stakes) ran before this PR: bug-hunter, security, craftsman, and pragmatism panelists. All surviving findings are applied in this diff (dedupe-on-insert acceptance fix, in-loop count cap, comment/doc corrections, test placement). One out-of-scope finding — encodeMapEntries materialises and sorts all map keys before its count cap fires (pre-existing, same class) — is raised as a separate ticket rather than expanding this diff.

…encode

The Set branch of encodeCanonical seeded every element sub-sink from the
frozen parent total, so a Set whose elements are individually small but
collectively over budget encoded and buffered all N elements before
ValueTooLargeError fired — peak transient memory ~sum(element bytes)
instead of ~1 MiB, contradicting ChunkSink's rejected-during-traversal
contract (the residual case of cd80805).

Thread a running byte total across element sub-encodes and dedupe on
insert (keyed by encoded bytes) so both the byte budget and the
collection-size cap fire mid-iteration while counting exactly what the
output retains: duplicate-heavy Sets whose deduped encoding fits still
encode byte-identically instead of being rejected at their pre-dedupe
sum, and the count cap bounds retained buffers at 10,000 elements.
Canonical byte output is unchanged for every accepted input — all 110
protocol vectors pass byte-for-byte.

LAB-375

Co-authored-by: multica-agent <github@multica.ai>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 992c11ef-9006-4185-a076-288a682b5938

📥 Commits

Reviewing files that changed from the base of the PR and between ad0fe0c and 88c3bcb.

📒 Files selected for processing (2)
  • packages/cachekit/src/serialization/interop.test.ts
  • packages/cachekit/src/serialization/interop.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lab-375-set-budget-fail-fast

Comment @coderabbitai help to get the list of available commands.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 41 minutes.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Mandatory crypto/protocol expert panel (critical stakes) is clean — unanimous SHIP, zero findings. Byte-identical wire output was proven (old sort-then-adjacent-dedup ≡ new dedup-on-insert+sort; the new accepted-set is a strict subset of the old, so no cross-SDK decode risk — it only rejects more, always ValueTooLargeError, never a byte change). The fix closes the real CWE-770/400 resource-exhaustion bug (old code buffered every element before the budget fired). Craftsman mutation-tested the fail-fast assertions; 34/34 interop tests + 5 golden Set vectors pass. CI green, CodeRabbit clean. @27Bslash6 ready for your signoff / merge. (Tracked: LAB-375.)

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@27Bslash6
27Bslash6 enabled auto-merge (squash) July 21, 2026 00:04
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant