Repository navigation
Description
Design: Parse the window you were sent, or none — one window contract for the provider quota parsers
Design doc: files/designs/opencode-quota-window-contract.md in the goal workspace (keep-the-four-repos-healthy) — problem, evidence, alternatives, risks, and open questions in full. Grounded on main aa2fe24fe3981756d37452191175e0416d3853ae (files read via the GitHub API; the sandbox clone was missing at run start).
Problem
#1718 / PR #1722 (2026-11-05) landed thirteen per-provider quota functions in packages/ai/src/quota/providers/, each parsing a different wire format into one published shape — usage.windows, a Record<string, QuotaWindow> (src/quota/types.ts) — now the fleet's single quota source (developing-today/code#6125). Within a day the unstated window rules produced one defect per provider:
- Derived-label keying erases windows: codex fix(ai): codex quota windows keyed only by derived duration label — a missing or shared limit_window_seconds silently drops one window, and an empty window object mints a phantom all-null window #1762 (closed — windows keyed only by derived duration label; one silently dropped, an empty object minted a phantom all-null window); meta fix(ai): meta quota rolling window keyed only by derived label — a weekly-length rolling window is silently overwritten by the weekly window #1777 (open —
parseSubscriptionQuota's literal andkeyUsageResultboth let a weekly-length rolling window be overwritten byweekly); antigravity fix(ai): antigravity quota buckets keyed only by derived label — an unrecognized bucket overwrites the real 5h window, same-label buckets silently drop, missing remainingFraction counts as fully remaining #1768 (open — buckets keyed by substring-derived label, unrecognized strings defaulting to"5h",windows[parsed.label] = parsed.windowat antigravity.ts:314). - Duplicates last-wins, against the adopted convention: keep-first was established by test(ai): quota suite pins none of claude / meta-billing / opendesign's pure parsers — billing banner rejects numeric strings, claude transform drops legacy windows when limits is non-empty, coding-plan publishes a zero-limit window #1750 (opendesign), re-established by fix(ai): cline-pass quota silently overwrites duplicate limit entries (last wins) — diverges from the #1750 keep-first convention, and the mapping is unpinned #1769 (cline-pass), and claude fix(ai): claude quota transform silently overwrites duplicate limits entries (last wins) — diverges from the #1750 keep-first convention #1781 (open) still overwrites unconditionally (
addWindow, claude.ts:158). - Missing figures invented or windows dropped for a missing field: fix(ai): quota toNumber coerces empty strings to 0 — an empty upstream field reports a pool 100% used (or 0% used) #1741 (closed — shared
toNumbercoerced""to 0); fix(ai): antigravity quota buckets keyed only by derived label — an unrecognized bucket overwrites the real 5h window, same-label buckets silently drop, missing remainingFraction counts as fully remaining #1768 (missingremainingFractionbecomes?? 1); opencode-go test(ai): PR #1722's quota suite pins 4 of its pure parsers — opencode-go's three parsers and openrouter's reset math have none, and opencode-go silently drops windows its siblings would keep #1725 (open — drops a window whenresetAtis null, where meta's parser states the opposite precedent in a comment: an absent reset is an honest null on the window). - A window's fields disagree: mistral fix(ai): mistral quota budget window disagrees with itself — usedPercent clamped, usedAmount derived from the raw percentage #1785 (open —
usedPercentclamped,usedAmountderived from the raw percentage, so thevalueLabelcontradicts the window). - Out-of-range makes windows vanish: command-code fix(ai): Command Code quota monthly window vanishes when remaining credits exceed the hard-coded 10 included — an over-limit payload reads as 'could not be parsed' #1744 (closed — over-included monthly credit dropped the window; fixed by publishing truthfully, per turso fix(quota): publish over-limit Turso windows above 100 percent (code#6227) #1743).
The tell: src/quota/utils.ts shares the scalar layer (toNumber, clampPercent, resolveWindowLabel, toUsageWindow) but there is no shared window layer — every provider inserts by direct assignment, so the landed rules exist only as comment-cited precedents inside one provider each (opendesign's keep-first, codex's -primary/-secondary collision qualifiers at codex.ts:137, meta's honest null).
The contract
Parse the window you were sent, or none.
- Identity is semantic; a derived label never erases a window — the key names the window's role/kind where the payload carries one; collisions disambiguate both keys or keep the semantic key; overwriting a published window is a violation in every spelling.
- Duplicates keep the first — "contradictory duplicates are a payload anomaly, not an update" (the test(ai): quota suite pins none of claude / meta-billing / opendesign's pure parsers — billing banner rejects numeric strings, claude transform drops legacy windows when limits is non-empty, coding-plan publishes a zero-limit window #1750/fix(ai): cline-pass quota silently overwrites duplicate limit entries (last wins) — diverges from the #1750 keep-first convention, and the mapping is unpinned #1769 wording).
- Missing is null, and nulls the field — not the window, and never a number — never 0, never 1-as-fully-remaining, no fabricated default label.
- No figure, no window — an empty/all-null/zero-limit window-shaped object publishes no window; only quota figures are windows (fix(ai): Command Code quota publishes purchasedCredits as a quota window — contradicting the module's own READ RULE that it is never a quota figure #1746's
purchasedCreditsREAD RULE, generalised). - A window's fields agree — derived fields are computed from the published (post-disposition) values; the out-of-range disposition (clamp vs publish-truthfully) is the provider's stated choice, applied to the whole window, never split; out-of-range never means the window vanishes.
- Unknown kinds are skipped, not renamed — no fabricated familiar label; groq's
no-quota-surfaceis the same rule at result level. - Every pure parser is pinned against this contract — the oracle is the contract itself; one shared probe matrix (duplicate, collision, unknown kind, missing figure, empty object, out-of-range) per parser, and a complete 13-provider inventory where a provider with no pure parser says so with its reason.
Scope boundary: #587 owns what consumers do with windows (core routing — this design is its producer-side sibling); #1729 owns whether a provider result is emitted (dispatch). This design owns only what a parser puts in windows, and under what key.
Phases
- The contract, the parser inventory, and the shared probe matrix — filed as a new sub-issue this run (contract stated in
src/quota/, a 13-provider parser inventory, the matrix asserting the already-conforming parsers; known-lossy rows linked, not asserted; no production behaviour changes). - The four open per-site conformances land — adopts existing open fix(ai): antigravity quota buckets keyed only by derived label — an unrecognized bucket overwrites the real 5h window, same-label buckets silently drop, missing remainingFraction counts as fully remaining #1768 + fix(ai): meta quota rolling window keyed only by derived label — a weekly-length rolling window is silently overwritten by the weekly window #1777 + fix(ai): claude quota transform silently overwrites duplicate limits entries (last wins) — diverges from the #1750 keep-first convention #1781 + fix(ai): mistral quota budget window disagrees with itself — usedPercent clamped, usedAmount derived from the raw percentage #1785 as sub-issues; all keep their own queue lifecycles (not re-enqueued); each landing flips its inventory row.
- The unpinned remainder is pinned, drop-vs-null decided — adopts existing open test(ai): PR #1722's quota suite pins 4 of its pure parsers — opencode-go's three parsers and openrouter's reset math have none, and opencode-go silently drops windows its siblings would keep #1725 (opencode-go's three parsers + openrouter's
resolveResetAt) as a sub-issue, lifecycle untouched; the design records the rule-3 recommendation (meta precedent: keep the window, null the field) for its closing note to adopt or rebut. - Not filed — a shared insertion helper in
utils.tswith all providers migrated, a new-provider ratchet, and the downstream key-spelling compat question (code#6125 consumers). Open questions in full in the design doc.
No Drew decision is needed: the contract adopts conventions the repo's own landed fixes already stated (keep-first, collision qualifiers, honest nulls), and phase 1 changes no production behaviour.
🤖 Agent note · filed by repo-design-architect (opencode pass, 2026-11-05). Evidence verified singly via gh against main aa2fe24f this run; the code claims (antigravity.ts:314 last-wins assignment, claude.ts:158 unconditional addWindow, codex.ts:137 collision qualifiers, meta's object-literal + honest-null comment, utils.ts with no window layer) were read verbatim from current main, not from the citing issues alone.
Acceptance criteria
- One window contract for the
packages/aiquota parsers is written down where the parsers live, covering identity/keying, duplicates, missing figures, phantom windows, field self-consistency, and unknown kinds. - Every open defect named here (fix(ai): antigravity quota buckets keyed only by derived label — an unrecognized bucket overwrites the real 5h window, same-label buckets silently drop, missing remainingFraction counts as fully remaining #1768, fix(ai): meta quota rolling window keyed only by derived label — a weekly-length rolling window is silently overwritten by the weekly window #1777, fix(ai): claude quota transform silently overwrites duplicate limits entries (last wins) — diverges from the #1750 keep-first convention #1781, fix(ai): mistral quota budget window disagrees with itself — usedPercent clamped, usedAmount derived from the raw percentage #1785, test(ai): PR #1722's quota suite pins 4 of its pure parsers — opencode-go's three parsers and openrouter's reset math have none, and opencode-go silently drops windows its siblings would keep #1725) is either fixed under the contract or carries its disposition as a linked sub-issue of this design.
- A complete parser inventory (all 13 registered providers) and a shared probe matrix exist in the quota test suite, with known-lossy rows marked and linked — so the next provider or payload shape is judged against the contract, not rediscovered per incident.
- The design doc (
files/designs/opencode-quota-window-contract.md) is updated to match the adopted contract, or superseded by an in-repo doc this issue links.
Activity
dezren39 commentedon Nov 6, 2026
Handoff (task 11096, lane opencode-a): verified OPEN 2026-11-06, no hard-stop labels. Execution lane is
opencode— checkpointedhanded_offand parked for the opencode claimant to bind and work it. Attempts 4/6.15 remaining items
dezren39 commentedon Nov 8, 2026
Partial verification — staying open (2026-11-07 done-task-only audit, tracking: developing-today/code#7016).
Queue task(s) [11176] were marked done, and some work did land:
No merged change closes this issue's full scope, so the done status is not treated as completion. Remainder enqueue deferred: this issue was active in tonight's lane shifts after the audit snapshot.
dezren39 commentedon Nov 8, 2026
Triage update (task 11855, lane opencode-a) — re-verified fresh 2026-11-07 against origin/main 24200c3 and live issue/queue states; supersedes the task-11176 triage where noted:
developing-today commentedon Nov 8, 2026
Triage update (task 12815, lane opencode-a) — re-verified fresh 2026-11-08 against live issue/queue states; supersedes the task-11855 triage where noted:
packages/ai/src/quota/types.ts/packages/ai/test/quota.test.ts.resolveResetAt, rule-3 decision) — fix task 12745, handed off to the opencode claimant this shift.queue/queued.queue/queued; fix(ai): codex quota credits publishes a negative balance — "-5" as the credits_balance label, and a negative-only payload still returns ok:true #1924 (codex) OPENqueue/queued; fix(ai): deepseek quota publishes a negative balance — the no-positive fallback bypasses its own >0 filter, so a negative-only payload returns "$-5.00" with ok:true #1928 (deepseek) OPENqueue/claimed+status/in-progress— all inside the queue system with their own lifecycles.types.tsflips to conforming. design(ai): parse the window you were sent, or none — one window contract for the provider quota parsers #1794 stays open as the tracker; re-queue its triage once the adoptees close. No Drew decision is needed.dezren39 commentedon Nov 8, 2026
🤖 Agent note · inventory flip (phase 3 landed)
12745· lane3fe37e8a-c61b-419c-9e97-769ecd264d5d42e133c49b55a199b5c60b46d68293a1a07aac62.resetAtwas null. Now conforming to rule 3 for a percent-bearing entry (window kept, absent reset →resetAt: null) and to rule 4 for a figure-less entry (no usable percent → no window; matches codex's pinned percent-less case). All three parsers (parseOpenCodeGoUsage,parseConsoleGoUsage,parseConsoleBillingBalance) are pinned, as isfetchOpenCodeGoUsage's all-dropped → thrown path.resolveResetAt's daily/weekly/monthly UTC arithmetic (weekly next-Mondayd + ((8 - dow) % 7 || 7), on-Monday+7d) is now pinned and the UTC/weekday assumption is commented in the code. The zero-limit rule-4 conformance (fix(ai): openrouter quota publishes a zero-limit window — $0.00 / $0.00 with null percent reads as headroom where there is no limit #1799) was already conforming.dezren39 commentedon Nov 8, 2026
🤖 Agent note · inventory flip (phase 2 meta collision landed)
12776· lane3fe37e8a-c61b-419c-9e97-769ecd264d5d2961e602aa011cd3c8dbefb51ec3708ca126c333.parseSubscriptionQuotaand the module-privatekeyUsageResult. A weekly-length rolling window (604800s) no longer overwrites the fixedweeklyentry: it publishes under its semantic role keyrolling(carrying its realwindowSeconds), so both windows survive (contract rule 1). Non-colliding labels (5h,1d) are unchanged. Pinned by the filing probe plus controls and thekeyUsageResultfetch path.dezren39 commentedon Nov 10, 2026
🤖 Agent note · triaged
task/small-fix· priority/normal · effort/m · kind/design (as filed)repo-health:v1:fix:developing-today-forks/opencode:issue:1794→ queuerepo-health-opencode-pilot(enqueue result: see triage log; raw: ": "2026-11-10T02:43:48Z","updated_at": "2026-11-10T02:43:48Z",
"completed_at": null
}
packet valid (repo-task/v1)
)
dezren39 commentedon Nov 10, 2026
🤖 Agent note · completed (all phases landed)
packages/ai/src/quota/types.ts/packages/ai/test/quota.test.tsvia PR test(ai): quota window contract — parser inventory + shared probe matrix (#1795) #1796 (merged).