Conversation
…e 1) tier3MaxTextLength did double duty — the LLM review window AND the only guard on how much the Tier-3 serializer traverses/allocates. Unlike Tier 1/2 (bound by traversal.maxSize = 10MB via estimatedBytes/shouldContinueTraversal), Tier-3 had no resource bound: sumInputStringChars walked the whole payload on every call, the array all-scalar scan was O(array), and tier3SpreadOrder(n) allocated O(n) — a DoS footing worse than the other tiers. Give the Tier-3 traversal its own byte budget = traversal.maxSize (10MB), metered with the shared estimateSize mechanism and decoupled from tier3MaxTextLength (now the review window only): - Meter serialize (per node; an array/object returns its breadth so a huge one trips in O(1)) and sumStringContent/sumInputStringChars; stop past the budget. Reset per traversal so each gets a fresh maxSize (matches Tier 1/2's single-pass bound); sizeLimitHit OR-accumulates. - Cap tier3SpreadOrder output (bounded even-spread sample for huge n; no O(n) scratch); drop the greedy O(n) order/`new Array(n)`/filter (collect+sort, O(emitted)). - Hitting the resource bound flags oversize (budgetExceeded) → onOversize governs it (skip allows+flags, block/scan_anyway fail closed) — same contract as ceiling overflow. No API change. Tests: resource bound flags oversize on large low-string-content bulk (which the string check wouldn't catch); a within-budget payload is not falsely flagged. 439 pass. Phase 2 (move the review cap into the Tier3Provider contract) is out of scope — gated on the Tier-3 extraction/as-a-service decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n the byte meter Adversarial review of PR #98 found: - (HIGH, reproduced) The array/object element loops broke only on the char budget (`used >= outerCap`), not the byte meter. When the meter tripped mid-collection, serialize() bailed per call but the loop still ran O(remaining length) — ~345ms of wasted iteration on a 3M-element array vs ~0.1ms once fixed. Add `meter.hit` to both loop breaks (matching runRecords). - (LOW) tier3SpreadOrder returned >maxOut indices when maxOut<=1 (unreachable in realistic configs); clamp + slice to honor the cap. - (LOW) corrected the serialize meter comment (estimateSize is O(1) for arrays, O(keys) for objects, not O(1) for both). Clean per the review: the collect-then-sort refactor preserves order/content, no false-oversize on benign payloads, meter-trip state coherent, cascade unaffected. Regression test: a mid-array byte-budget trip flags oversize. 440 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Phase 1 — decouple Tier-3's resource bound from the review cap
Problem
tier3.maxTextLength(default 10,000) did double duty: (a) the LLM review window, and (b) the only guard on how much the Tier-3 serializer traverses/allocates. Tier 1/2 already have a decoupled resource bound (traversal.maxSize= 10 MB, viaestimatedBytes/shouldContinueTraversal); Tier-3 had none. Verified onmain, three unbounded input traversals:sumInputStringChars(value)— full payload walk on everytier3_onlycall (dominant, always-on).for (const x of v)) — O(array) to decide the[N numbers]summary.tier3SpreadOrder(n)+Array.from({length:n})+ the final.filter— O(n) allocations for large lists.So Tier-3 was on a strictly worse DoS footing than the other tiers.
Change (no API change)
Give the Tier-3 traversal a byte budget =
traversal.maxSize(10 MB), metered with the sharedestimateSizemechanism, decoupled frommaxTextLength(now the review window only):serialize(per node —estimateSizereturns an array/object's breadth, so a huge one trips in O(1)) andsumStringContent/sumInputStringChars; stop past the budget. Reset before each traversal so each gets a freshmaxSize(matching Tier 1/2's single-pass bound);sizeLimitHitOR-accumulates across passes.tier3SpreadOrderoutput (bounded even-spread sample for hugen, no O(n) scratch); drop the greedy O(n) order array /new Array(n)/.filter(collect + sort, O(emitted)).budgetExceeded) → existingonOversizegoverns it (skipallows + flagscoverageDegraded;block/scan_anywayfail closed) — same contract as ceiling overflow. Not an unconditional block.Decisions (from the design note + measured numbers)
blockHighRisk/onOversizecontract intact.Tests
emitted < inputstring check would NOT catch it), acrossskip/block/scan_anyway.tsc/biomeclean.Out of scope
Phase 2 (evolve the
Tier3Providercontract so the provider owns window truncation; demotemaxTextLengthto a convenience default) — gated on the Tier-3 extraction/as-a-service decision.🤖 Generated with Claude Code
Adversarial review addressed
meter.hittoo.tier3SpreadOrdermaxOut<=1edge; comment accuracy onestimateSize(O(1) arrays / O(keys) objects).Bound accuracy (worth noting)
The byte meter is reset per traversal (greedy serialize, reserve serialize, input-sum), each bounded by
maxSize. A char-oversize payload (common — any list past the ~47.5 KB chunk ceiling) runs the reserve pass too, so worst case a single call does up to ~3 metered traversals (≈3×maxSize= ~30 MB of work), not one. Still a bounded constant; resetting avoids double-counting the same nodes across the greedy/reserve passes (which would false-trip benign payloads).