test: property-check the circuit breaker's availability contract - #592
Conversation
Five fast-check properties over the real CircuitBreaker under fake timers (small windows so sequences cross every boundary): - isAvailable/getTimeUntilAvailable agree with the very next canExecute outcome after ANY event sequence - the polling view callers route on is a faithful prediction, never stale - an open circuit rejects strictly before resetTimeoutMs and admits the probe exactly at it, from any prior history (fresh forced open) - opens exactly at failureThreshold inside one window, never below - failures spaced beyond failureWindowMs never accumulate - the half-open slot admits exactly one probe; success closes with a clean failure count, failure reopens for a full timeout Companion to the property suites in #574/#575/#579; same conventions. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
More reviews will be available in 25 minutes and 34 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. 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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…racle (#593) * test: property-check the auth rate limiter against a sliding-window oracle Three fast-check properties over the real module under fake timers (maxAttempts 3 / 1s window so sequences cross the expiry boundary): - model-based: for any record/reset/advance interleaving across three accounts with decorated id spellings (case/whitespace variants), getAttemptsRemaining and canAttemptAuth match a trivial timestamps-in-window oracle keyed by canonical id - pinning both the sliding window and the trim+lowercase bucket mapping at once - checkAuthRateLimit throws AuthRateLimitError exactly when blocked, carrying the canonical accountId, zero attemptsRemaining, and a resetAfterMs that agrees with the live getTimeUntilReset - over-recording past maxAttempts can never wedge a bucket: any burst unblocks fully after one quiet window, with getTimeUntilReset bounded by windowMs throughout Config and registry are module state, so each property restores the documented defaults and clears buckets, and ids are namespaced per iteration. Companion to #574/#575/#579/#592; same conventions. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: exhaustive event narrowing and constant-derived gaps length Greptile flagged the bare else branches (a future event kind would silently model as a reset) and the literal 11 coupled to MAX_ATTEMPTS * 4 - 1. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
…contracts (#594) * test: property-check SessionAffinityStore TTL, eviction, and reindex contracts Five fast-check properties over the real store using its injectable now parameters (1s TTL floor so sequences cross expiry often): - model-based TTL/upsert equivalence: for any remember/update/forget/ advance interleaving through whitespace-decorated key spellings, getPreferredAccountIndex and getLastResponseId match a trivial TTL map (remember preserves the continuation id, response-id writes refresh expiry and never create entries) - capacity: size() never exceeds maxEntries, and LRU eviction can never evict the entry just written - write-version conflicts: a stale version loses to a live entry on both the index and response-id channels, but may rebind once the entry expires - forgetAccount + reindexAfterRemoval mirror an account-array splice, with both return counts pinned against the model - prune removes exactly the expired entries; lazily-reaped sessions (touched while expired) correctly do not count as prunable The prune model initially missed that updateLastResponseId deletes an expired entry outright; fast-check found the 8-event counterexample and the model now mirrors the lazy reap. Companion to #574/#575/#579/#592/#593; same conventions. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: fix remember model after lazy reap; cover clearAll; decorate prune keys Greptile P1: the model carried a responseId across expiry, but the assertion block's reads lazily reap expired entries from the store, so a remember after expiry finds no existing entry and the id is gone - the model now inherits the id only from a live entry (verified at FAST_CHECK_NUM_RUNS=1000, where the original 4-event counterexample sequence reproduces without the fix). P2s: a sixth property pins clearAll (#474 invalidation) - size drops to zero, every read goes null, and the store stays usable - and the prune property now routes remember/forget/updateLastResponseId through decorated key spellings like the model property does. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
…595) * test: property-check table formatter alignment under unicode content Four fast-check properties over buildTable/buildTableRow with cell content mixing ASCII, CJK, and emoji so widths are exercised in display columns rather than UTF-16 units (ui-02): - every line of any table (header, separator, all rows) has exactly the layout's display width, for any column set, alignment mix, missing cells, or extra cells beyond the column count - content that fits is preserved verbatim with padding on the declared side - overflowing content truncates to a prefix of the original plus an ellipsis, never exceeding the column width even when a wide glyph cannot fill the final column - zero-width columns render empty and never leak an ellipsis that would desync the row from the header layout Companion to the property suites in #574/#575/#592-#594. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: cover right-aligned overflow truncation too https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: validate overflow prefix on the left channel; generate default align Greptile P1: stripping leading spaces on a right-aligned row conflated alignment padding with spaces that belong to the truncated content (latent spurious failure under a different fast-check seed - e.g. ' a<CJK>' at width 3 right-aligned). Prefix fidelity now checks the left-aligned rendering of the same value, where the cell starts at column 0, while the right-aligned row keeps its width and trailing- ellipsis assertions. Verified at FAST_CHECK_NUM_RUNS=1000. Greptile P2: arbColumn and the fit property now generate undefined align too, exercising formatCell's default-left branch. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
) * test: property-check plugin health classification and report shape Three fast-check properties over getAccountHealth/formatHealthReport: - counts and status derive exactly from the per-account classification for any pool (timestamps straddling now, undefined fallbacks, health 0..100): healthy iff not rate-limited, not cooling, health >= 50, circuit closed; status partitions empty->healthy, none->unhealthy, some->degraded, all->healthy - an open circuit disqualifies an otherwise perfect account: tripping the account:<index> fallback breaker for any subset is reflected in circuitState, subtracted from healthyAccountCount, and drives the status partition - the formatted report names every account with its health percentage and exactly the flags its classification implies, and the summary lines appear iff their counts are nonzero Companion to the property suites in #574/#575/#592-#595. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: exercise half-open disqualification and email-aliased circuits Greptile flagged two coverage gaps: the half-open state was unreachable without advancing the clock, and accounts sharing an email silently shared a breaker without an explicit contract. Property 2 now probes open -> half-open under fake timers (both states disqualify, and the report renders the precise circuit flag), pool emails are unique by construction, and a dedicated property pins shared-email accounts to one breaker while distinct emails stay isolated - derived through the same getAccountIdentityKey the SUT uses. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
* test: property-check model-map resolution invariants Six fast-check properties over the model resolution pipeline (resolveNormalizedModel / getModelProfile / getNormalizedModel), generating known aliases, synthesized GPT-5 spellings (minors 0-9, mini/nano/pro/codex variants, -/./space separators, suffixes), provider prefixes, random casing, and raw garbage: - closed world: every resolution lands on a MODEL_PROFILES key, so getModelProfile's DEFAULT_MODEL fallback is pure defence - idempotence: normalized outputs are fixpoints - provider prefixes and casing never change the resolution - codex dominance: unmapped ids mentioning codex resolve to CURRENT_CODEX_MODEL, never a general model - the inverse: unmapped general GPT-5 spellings stay codex-free and in the gpt-5 family (no silent codex routing for general requests) - every explicit MODEL_MAP alias resolves to its mapped target under any prefix/casing spelling Companion to the property suites in #574/#575/#592-#596. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: combine prefix and casing mutation in the invariance property https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
…598) * test: property-check display-width clustering and parseKey totality Six fast-check properties over the ui-02 measurement layer and the stdin key parser: - displayWidth is total, integer, non-negative, and bounded by two columns per code point over an adversarial alphabet (ZWJ, VS-16, keycap, combining marks, skin tones, regional indicators, CJK, emoji) - plain alphabets (no joiners/modifiers) sum per-character widths exactly and concatenate additively - the table formatter's standing assumption - truncateToWidth returns a self-consistent prefix (its reported width IS displayWidth of the kept text), stays in budget, stops only when the remaining gap is 0 or 1 columns (clusters max out at 2), and is idempotent - truncation prefixes grow monotonically with the width budget - parseKey is total over arbitrary byte buffers and only returns known KeyAction values - the full recognized-sequence table is pinned, and everything else maps to null Companion to the property suites in #574/#575/#592-#597. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: make the 2-column cluster granularity self-enforcing One extra budget column can admit at most two more columns of content, which is externally equivalent to 'no cluster wider than 2' - the assumption the truncation maximality bound relies on. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
) * test: property-check forecast recommendation and summary contracts Seven fast-check properties over recommendForecastAccount, summarizeForecast, and buildForecastExplanation with generated result pools (0-8 accounts, full availability/risk/wait/flag space): - a recommendation always points at a recommendable result (not disabled, hard-failed, exhausted, or unavailable); null only when no such candidate exists - a ready candidate always wins with the minimal risk score among ready candidates, and the reason says so - with no ready candidate, the shortest delayed wait wins - an empty candidate pool names the actual blocker class: 'blocked or exhausted' guidance iff some account is blocked/exhausted rather than disabled/hard-failed (the #exhausted-flag regression guard) - the recommendation is invariant under input order - the summary partitions availability exactly (ready + delayed + unavailable === total) and counts high-risk rows - the explanation mirrors inputs in order and marks selected on exactly the recommended index Companion to the property suites in #574/#575/#592-#598. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: keep forecast generator in the reachable domain; shuffle permutations Greptile flagged that availability and exhausted were drawn independently (evaluateForecastAccount only emits exhausted accounts as delayed) and that order-invariance only probed reversal. The generator now derives the pairing, and the invariance property checks an arbitrary generated permutation plus the reversal. Validated at FAST_CHECK_NUM_RUNS=1000. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
#600) * test: property-check context-overflow classification and synthetic SSE Four fast-check properties over the prompt-too-long recovery path: - the seven documented overflow phrases classify at any casing and position, but only behind the 400 status gate (phrase list pinned verbatim so dropping one fails here) - noise-only and empty bodies never classify at any status - the synthetic response round-trips: 200 with the synthetic headers, parseable Responses-API SSE (created first, completed last, model echoed, /compact notice in the terminal output payload), and is never re-classified as overflow - the recovery path cannot recurse on its own output (recovery-01 dialect guard) - handleContextOverflow intercepts exactly the classifier-positive 400s, and a declined response's body stays readable (clone-read) Companion to the property suites in #574/#575/#592-#599. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: await the async round-trip property so its assertions gate the test Greptile P1: the it callback was synchronous, so the asyncProperty's promise was dropped and vitest greened the test before any assertion ran. Same pattern as the fourth test now. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
…#601) * test: property-check env boolean parsing and policy key/tag contracts Four fast-check properties over the shared env parser and the account policy identity helpers: - parseBooleanEnv: the six documented literals parse under any casing and whitespace padding; every other string (and undefined) returns undefined, never a boolean - the contract that unified three divergent local copies - getAccountPolicyKey: always a sha256: handle that never leaks the raw identity into the policy file key; accountId wins over email; email matches case-insensitively; no identity degrades to the shared unknown bucket - normalizeAccountPolicyTag: outputs are idempotent fixpoints in the [a-z0-9._-]{1,64} language, and null occurs exactly for inputs that trim to nothing (disallowed runs are dashed, not dropped) Validated at FAST_CHECK_NUM_RUNS=1000. Companion to the property suites in #574/#575/#592-#600. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: pin both directions of the whitespace-only-iff-null tag contract https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB * test: generate the whitespace-only accountId fall-through A defined but whitespace-only accountId defers to the email identity (the accountId?.trim() || chain); the existing branch assertions cover it once generated. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --------- Co-authored-by: Claude <[email protected]>
Summary
lib/circuit-breaker.ts— the per-account failure-isolation state machine that rotation uses to decide routability. The existing example suite (28 tests) covers specific paths; this companion pins the contracts that have to hold under any interleaving of failures, successes, attempts, and clock advances.What Changed
New
test/property/circuit-breaker.property.test.ts(5 properties, realCircuitBreakerundervi.useFakeTimers()with small windows so generated sequences cross every timing boundary):isAvailable(now)andgetTimeUntilAvailable(now)agree with what the very nextcanExecute()actually does (allows vs throwsCircuitOpenError), andwait === 0 ⇔ available. This is the load-bearing one: rotation polls the availability view to route accounts, so a stale or disagreeing answer would mis-route.resetTimeoutMs(withgetTimeUntilAvailablereturning the exact remainder) and admits the half-open probe at exactlyresetTimeoutMs.failureThreshold, never below it.failureWindowMsnever accumulate; the circuit stays closed with a count of 1 regardless of how many arrive.One harness bug was caught by fast-check itself during development (property 2 originally assumed the breaker wasn't already open after the arbitrary prefix — counterexample found on run 14); the fix forces a fresh timestamped open, which also strengthens the property to "reset + threshold failures always yields a clean open from any history". No SUT bugs found — the breaker's timing contract held everywhere.
Validation
npm test -- test/property/circuit-breaker.property.test.ts test/circuit-breaker.test.ts— 33/33 (new 5 + existing 28 untouched)npm run typecheck(also via pre-commit hook)npx eslint test/property/circuit-breaker.property.test.ts --max-warnings=0try/finally vi.useRealTimers()plus a guardafterEach, matching the repo's wall-clock test conventionsDocs and Governance Checklist
Risk and Rollback
test/property/suites (test: property-check the unsupported-model fallback invariants #574, test: property-check write-queue serialization and clamp invariants #575, fix: deduplicateAccounts fixpoint loop and identity property tests #579 companions: explicit vitest imports, plainfc.assert).https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Generated by Claude Code
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
adds a fast-check property suite (
test/property/circuit-breaker.property.test.ts) companion to the existing 28-case example suite forlib/circuit-breaker.ts. five properties cover the availability-prediction contract, open-state timing (includinggetTimeUntilReset(), which was previously flagged as missing coverage), threshold exactness, failure-window expiry, and half-open probe discipline — all undervi.useFakeTimers()with small windows so generated event sequences cross every timing boundary.isAvailable(now)andgetTimeUntilAvailable(now)faithfully predict the very nextcanExecute()result, which is the load-bearing contract for account routing.getTimeUntilReset()agrees withgetTimeUntilAvailable(now)while the circuit is open and returns 0 in half-open, closing the gap noted in the prior review thread.try/finallytimer teardown,afterEachguard) match the existingtest/property/suites.Confidence Score: 5/5
additive test-only change; no production code touched, all 5 properties exercise the real SUT under fake timers with correct teardown
no production code is changed; the property tests are logically sound, the availability agreement invariant holds under all circuit states, and fake-timer teardown matches repo conventions
no files require special attention; the single new test file is self-contained
Important Files Changed
Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "Merge branch 'main' into claude/audit-73..." | Re-trigger Greptile