A run reserves its share of the account's daily ceiling when it starts, so runs started together cannot overshoot it. #52

Merged
grokbot merged 1 commit from dev/ceiling-reserve into main 2026-10-17 21:40:54 +00:00
Owner

Problem. Two runs that start together on one account can each spend the full per-run ceiling, so the account goes past its daily ceiling. With daily $1.50 and per-run $1.00, two runs at the same time were each handed a $1.00 budget ($2.00 in total).

Root cause. checkCeiling read only the spend already in usage. A run that has started but has not spent yet counted as zero, so the next run saw the whole day as free.

Fix.

  • New column run.ceiling_reserved_usd (migration 20261017195831-ceiling-reserved).
  • reserveCeiling replaces checkCeiling. In one transaction it computes what is left of the day: the daily ceiling, less today's spend, less what the account's other queued/running runs reserved and did not spend yet. It writes the run's cap (smaller of per-run, left, and the run's own wanted budget) to the run. This is the same pattern as the subscription budget (budget_reserved_usd).
  • The reservation stops counting when the run leaves queued/running. Its real spend stays in usage.
  • runner.ts computes the wanted budget before the call and passes the run id. The live meter (dailyLeft) is not changed.

Test. test/account-ceiling-reserve.test.ts: crew concurrency 2, daily $1.50, per-run $1.00, two runs held running together. The sum of the two handed budgets must be ≤ $1.50. On main: fails (received $2.00). With the fix: passes (first run $1.00, second gets only what is left). test/account-budget.test.ts uses reserveCeiling.

Validate (fast). Result: flaky, no new failure. contain.test.ts and web-editor.test.ts are baseline (red on main). following.test.ts failed one time and passes alone; it is not red on main. tsc passes.

**Problem.** Two runs that start together on one account can each spend the full per-run ceiling, so the account goes past its daily ceiling. With daily $1.50 and per-run $1.00, two runs at the same time were each handed a $1.00 budget ($2.00 in total). **Root cause.** `checkCeiling` read only the spend already in `usage`. A run that has started but has not spent yet counted as zero, so the next run saw the whole day as free. **Fix.** - New column `run.ceiling_reserved_usd` (migration `20261017195831-ceiling-reserved`). - `reserveCeiling` replaces `checkCeiling`. In one transaction it computes what is left of the day: the daily ceiling, less today's spend, less what the account's other `queued`/`running` runs reserved and did not spend yet. It writes the run's cap (smaller of per-run, left, and the run's own wanted budget) to the run. This is the same pattern as the subscription budget (`budget_reserved_usd`). - The reservation stops counting when the run leaves `queued`/`running`. Its real spend stays in `usage`. - `runner.ts` computes the wanted budget before the call and passes the run id. The live meter (`dailyLeft`) is not changed. **Test.** `test/account-ceiling-reserve.test.ts`: crew concurrency 2, daily $1.50, per-run $1.00, two runs held running together. The sum of the two handed budgets must be ≤ $1.50. On main: fails (received $2.00). With the fix: passes (first run $1.00, second gets only what is left). `test/account-budget.test.ts` uses `reserveCeiling`. **Validate (fast).** Result: flaky, no new failure. `contain.test.ts` and `web-editor.test.ts` are baseline (red on main). `following.test.ts` failed one time and passes alone; it is not red on main. tsc passes.
A run reserves its share of the account's daily ceiling when it starts, so runs started together cannot overshoot it.
All checks were successful
check / gate (2) (pull_request) Successful in 4m55s
check / gate (1) (pull_request) Successful in 10m43s
check / gate (3) (pull_request) Successful in 15m34s
check / check (pull_request) Successful in 0s
preview / preview (pull_request) Successful in 9s
45575a2f7b
grokbot deleted branch dev/ceiling-reserve 2026-10-17 21:40:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
trevato-apps/forge!52
No description provided.