Skip to content

fix(ai): shared quota toUsageWindow derives remainingPercent above 100% — the percent cap floors at 0 but never caps at 100 #1904

Closed

Description

@dezren39

Problem

The shared quota window builder toUsageWindow (packages/ai/src/quota/utils.ts:113-135) derives remainingPercent from the published usedPercent at utils.ts:125:

remainingPercent: hasFiniteUsedPercent ? Math.max(0, 100 - usedPercent) : null,

The derivation guards only one side. It floors the result at 0, so an over-100 usedPercent correctly derives 0% remaining — but it never caps at 100, so a negative usedPercent derives a remainingPercent above 100%, a percent that cannot exist. That violates the window contract in packages/ai/src/quota/types.ts (rule 5: "A window's fields agree — derived fields are computed from the published (post-disposition) values"): the one shared place every parser's derived field is computed can publish a derived field outside the percent range.

Evidence (verified this run on main 2d11e0f649436d6c4d20a64ce13fd7e0501bdd96)

Probe probe-usage-window.ts, run with bun in a laptop worktree of origin/main (~/git/fleet/probe-2026-11-06-creator-1935), calling the real toUsageWindow:

usedPercent=-20 -> remainingPercent=120
usedPercent=-5  -> remainingPercent=105
usedPercent=0   -> remainingPercent=100
usedPercent=50  -> remainingPercent=50
usedPercent=100 -> remainingPercent=0
usedPercent=150 -> remainingPercent=0

Caller audit (same main): every current parser happens to clamp its own percent before calling toUsageWindow (antigravity, claude, cline-pass, codex, command-code, groq, meta, mistral, opencode-go, opendesign all clamp; openrouter caps above; turso filters negative ratios), so no live parser publishes this today. That is exactly how the sibling shared-converter defect shipped and was fixed in #1845 (toTimestamp published 1969/1970 resets until the guard moved into the shared converter): the per-parser clamping saga (#1785, #1821, #1879, #1897, #1882) shows parsers repeatedly arrive without a clamp, and a new parser that computes a raw percent from a remaining-above-limit payload — the groq/opendesign shape — would publish remainingPercent: 120 straight to consumers of the quota shape (aggregator gatherer / dashboard, code#6125) with nothing downstream re-checking the range. toUsageWindow itself has no direct pin in packages/ai/test/quota.test.ts today (grep: 0 references).

What NOT to do

Do not clamp usedPercent itself in toUsageWindow. Turso deliberately publishes over-100 usage truthfully (code#6227, turso.ts: "Over-100 is a real state, not bad data"), and its remainingPercent: 0 for those windows is correct. The defect is only in the derived field's missing upper cap.

Fix shape

Cap the derived field on both sides, e.g. Math.min(100, Math.max(0, 100 - usedPercent)). This is a no-op for every conforming input (any usedPercent >= 0 derives exactly what it derives today, including turso's over-100 → 0) and changes only the negative-input derivation (120 → 100).

Acceptance criteria

  • toUsageWindow({ usedPercent: -20 }) derives remainingPercent: 100, and -5 derives 100; usedPercent itself is published unchanged (provider disposition preserved, turso over-100 case included: 150 still derives 0).
  • New pins in packages/ai/test/quota.test.ts for the out-of-range derivations (negative, zero, 100, over-100) — the shared builder's first direct pins.
  • Existing quota suite passes unchanged for all in-range inputs; packages/ai typecheck clean.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    app/aiPackage: ai (pkgs/ or apps/ in this repo)
    work/self-assignedAn agent grabbed this item directly, outside the queue
    effort/xsTrivial: expected under 30 minutes of work
    on Nov 7, 2026
  2. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-opencode)
    • Session / ID: cron new-issue-creator-opencode, run 2026-11-07T01:44:00Z
    • Found via: filed this run
    • Task: Shared quota toUsageWindow derives remainingPercent above 100% for a negative usedPercent — verified by probe on main 2d11e0f (probe-usage-window.ts, laptop worktree probe-2026-11-06-creator-1935: -20 -> 120, -5 -> 105, 150 -> 0).
    • Expectation: effort xs; done = derived field capped at 100 with pins in quota.test.ts, turso's publish-truthfully usedPercent untouched.
    • Links: body of this issue carries the full evidence and acceptance criteria.
  3. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    🤖 Agent note · picked up

    • Agent: new-issue-creator job (new-issue-creator-opencode)
    • Session / ID: cron new-issue-creator-opencode, run 2026-11-07T01:44:00Z
    • Found via: self-assigned during new-issue-creator-opencode run (guarded self-solve, lifecycle protocol §4: filed this run, genuinely trivial one-line shared-converter cap + pins, no safety labels)
    • Task: Cap the derived remainingPercent at 100 in toUsageWindow (utils.ts:125) and pin the out-of-range derivations in quota.test.ts. No queue packet (§4 self-solve path).
    • Expectation: effort xs; done = PR merged with quota suite green and a negative control (new pins fail on the unpatched converter).
    • Links: branch creator/1904-tousagewindow-remaining-cap
  4. added a commit that references this issue on Nov 7, 2026
  5. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    🤖 Agent note · done

    • Agent: new-issue-creator job (new-issue-creator-opencode)
    • Session / ID: cron new-issue-creator-opencode, run 2026-11-07T01:44:00Z
    • Found via: filed this run
    • Task: Done — PR fix(ai): cap toUsageWindow remainingPercent at 100 (#1904) #1905 MERGED as 69016ea; this issue auto-closed by the merge.
    • Expectation: met (effort xs). Fix: remainingPercent derivation in toUsageWindow capped at 100 (Math.min(100, Math.max(0, …))); usedPercent published unchanged, so turso's publish-truthfully over-100 windows (code#6227) derive 0 exactly as before.
    • Links: PR fix(ai): cap toUsageWindow remainingPercent at 100 (#1904) #1905 (head 213d3e8). Verification: probe pre-fix -20 -> remaining 120 / post-fix 100; quota suite 115 pass on the branch and again on post-merge main 69016ea; negative control 1 fail with the converter change stashed; packages/ai typecheck clean.
  6. removed
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    work/self-assignedAn agent grabbed this item directly, outside the queue
    on Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/aiPackage: ai (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator job

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions