Skip to content

fix(quota): apply the equal-split fallback in the pool usage snapshot - #13159

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.51from
datrixlab:fix/pool-usage-equal-split
Sep 17, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.51from
datrixlab:fix/pool-usage-equal-split

Conversation

@datrixlab

Copy link
Copy Markdown
Contributor

Summary

A quota pool whose allocations all have weight 0 is reported as fully over-subscribed
on the dashboard, while enforcement is happily letting the same keys through.

enforce.ts:238-246 treats such a pool as an equal split, with a comment saying why:

// Equal-split fallback: when ALL allocations in the pool have weight 0 (e.g. newly
// added keys whose weight was never set), treat each as an equal share so the pool
// is usable without requiring a re-save. Original non-zero weights are kept as-is.
const effectiveWeight =
  poolTotalWeight > 0 ? poolAllocation.weight : allocCount > 0 ? 100 / allocCount : 0;

upsertAllocations (src/lib/db/quotaPools.ts:646) normalises the same case on write,
for the same stated reason. But poolUsageWithDimensions — in both stores — did not:

const effectiveWeight = totalWeight > 0 ? alloc.weight : 0;
const fairShare = (effectiveWeight / 100) * planDim.limit;
const deficit = consumed - fairShare;
const borrowing = consumed > fairShare;

so GET /api/quota/pools/[id]/usage returns fairShare: 0, deficit: <everything consumed>, borrowing: true for every key in the pool.

The line could not do anything

Weights are non-negative, so totalWeight === 0 implies every alloc.weight is already
0 — both arms of that ternary return the same value. It is a no-op sitting exactly
where the fallback belongs, which is what convinced me this was an omission rather than
a deliberate difference.

Effect

Pool with two allocations, both weight: 0, one tokens dimension with limit: 1000,
one key having consumed 100:

fairShare deficit borrowing
enforceQuotaShare 500 — allows
/usage before 0 100 true
/usage after 500 −400 false

sqliteQuotaStore.ts:201 and redisQuotaStore.ts:274 carried the identical line; both
now mirror enforce.ts.

Pools with real weights are untouched. So is a weight: 0 key sitting next to a
weighted one — there totalWeight > 0, the fallback stays out, and that deliberate zero
share is preserved. There is a test for exactly that case.

Related Issues

Validation

  • Change type: DB (quota stores) — no UI, provider, routing or i18n surface
  • Focused tests: quota-pool-usage-equal-split, plus quota-equal-split,
    quota-pool-usage-summed-budget, quota-pool-usage-shape, quota-fair-share,
    quota-enforce for regressions
  • npm run lint — not run; I do not have the repo's node_modules installed on
    this machine, so eslint.config.mjs cannot resolve @eslint/compat. The changed
    files are prettier --write clean against prettier.config.mjs, and the new
    expression is structurally identical to the one already at enforce.ts:246
    (nested ternary, no eslint-disable), so I expect CI to be green — but I would
    rather say I skipped it than tick a box I did not exercise.
  • Reconciled with the current active release base (release/v3.8.51)
  • Production-code changes include a new automated test in this PR

Tests Added Or Updated

  • tests/unit/quota-pool-usage-equal-split.test.ts (new)

Structured like quota-pool-usage-summed-budget.test.ts:

  • Level A (structural) — both stores compute effectiveWeight with the equal-split
    fallback and the empty-list guard, the old no-op form is gone, and enforce.ts still
    has the shape they mirror.
  • Level B (logic) — replicates the store's per-key math to show the fallback fixes
    fairShare/deficit/borrowing, that the snapshot share now equals what enforce.ts
    computes, and that weighted pools (including [100, 0]) are unchanged.

Reverting the two store files alone makes exactly the four Level A tests fail (9 pass /
0 fail → 5 pass / 4 fail); Level B passes either way by design, since it carries both
rules.

Coverage Notes

The change is two lines in src/lib/quota/, both covered by the Level A structural
assertions and by the Level B math that mirrors the surrounding function.

Reviewer Notes

  • No migration, no feature flag, no schema change.
  • Reachability: since upsertAllocations normalises all-zero weights on write, a pool in
    that state today comes from rows written before that normalisation landed — or from any
    future writer that bypasses it. enforce.ts keeps its own fallback for the same reason,
    so I have matched the reporting path to the enforcement path rather than assume the
    state is now impossible. If you would rather delete the fallback everywhere and rely
    solely on write-time normalisation, that is a coherent alternative — it just needs to be
    one decision applied to all three places.
  • I could not run npm run lint locally; see Validation above.

enforce.ts treats every allocation as an equal 100/N share when a pool's
weights are all 0, so an unconfigured pool stays usable without a re-save.
upsertAllocations normalises the same case on write. Neither quota store
did it in poolUsageWithDimensions, so GET /api/quota/pools/[id]/usage
reported fairShare = 0 for every key in such a pool: each one rendered as
borrowing its whole consumption while enforcement was granting it half,
or a quarter, of the budget.

The tell is that the ternary could not do anything. Weights are
non-negative, so totalWeight === 0 implies every alloc.weight is already
0, and

    totalWeight > 0 ? alloc.weight : 0

returned alloc.weight either way. The branch sat exactly where the
fallback belonged.

Both stores carried the identical line; both now mirror enforce.ts.
Pools with real weights are unaffected, including a 0-weight key sitting
next to a weighted one -- there totalWeight > 0, so the fallback stays
out and the deliberate zero share is preserved.

Same divergence class as cheaper-inference#10253, which corrected the accountCount side of
the enforce/usage split; the new test follows the structure of the one
that shipped with it.
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for this — clean, minimal fix that mirrors the exact logic enforce.ts already
uses, and the equal-split-vs-weighted-pool distinction is well tested (9/9 passing locally,
including the case that proves weighted pools stay untouched). This looks merge-ready as-is.
Only optional nit: a changelog.d/fixes/ fragment would be nice to have alongside it.

@diegosouzapw
diegosouzapw merged commit 3bcb181 into cheaper-inference:release/v3.8.51 Sep 17, 2026
3 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…cheaper-inference#13159)

The equal-split fallback was applied when computing allocations but not when building the pool usage snapshot, so `sqliteQuotaStore.ts:201` still read `totalWeight > 0 ? alloc.weight : 0` — a zero-weight pool reported every member at 0 instead of its equal share. Probe on your head: 9/9 pass in `tests/unit/quota-pool-usage-equal-split.test.ts`, with the bug confirmed unfixed on the tip.

Thanks, @datrixlab — fixing the snapshot path and not just the allocation path is the part that makes the dashboard numbers agree with the enforcement.

**Batch validation** — boarded with the other 10 PRs of your batch into one worktree cut from `release/v3.8.51`; every PR verified as an ancestor of the combined HEAD before validating.

- Focused tests across all 11 PRs: **104/104 pass** on the combined tree.
- Gates on the combined tree: `check-changelog-integrity` PASS, `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check:open-sse-typecheck` PASS.
- `check-file-size` is red, but reproduces with byte-identical line counts on the pure `release/v3.8.51` tip (`open-sse/handlers/imageGeneration.ts` 3304, `open-sse/services/combo/roundRobinCombo.ts` 1221, `open-sse/utils/stream.ts` 3115). Inherited base-red, nothing added by this batch — it is also why this PR's "Fast Quality Gates" check was red.
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.

2 participants