fix(billing): keep billing a request whose period start moved forward before its first charge - #8480
Conversation
… before its first charge A Stripe anchor reset between admission and a request's first cost callback stamps row 0 with the reset period. The ledger binding only accepted a later period starting at or after the admitted period's end, so every later callback was refused with a billing-context mismatch and its spend went unbilled. The binding now accepts the same forward-only rule the roll uses: a start later than the admitted one. An earlier period is still refused. Also bound the upgrade-card subscription read in update-cost under the same 1 s standing deadline as the verdict read.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
…slow The shared deadline discarded an exceeded verdict when the card lookup ran past the budget. The verdict read keeps the deadline; the card lookup now falls back to the plan-upgrade card past the same deadline.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
Problem. If a Stripe billing-anchor reset moved a subscription's period start forward between a request's admission and its first cost callback, every cost callback after the first one for that request was refused with a billing-context mismatch. That spend was never billed.
Root cause. The first charge stamps the ledger row with the period that is current at that moment, which is the reset period. Later callbacks still carry the period frozen at admission.
assertCumulativeUsageLedgerBindingaccepted a later stamped period only when it started at or after the admitted period's end. That covers a request that outlives its period, but not an anchor reset, where the new start falls inside the admitted period.Fix. The binding now uses the same forward-only rule as the roll into a new period row: it accepts any stamped period whose start is later than the admitted start. A stamped period that starts earlier than the admitted one is still refused.
The upgrade-card subscription read in
update-costnow runs under the same 1 s standing deadline as the verdict read. Before, a slow read there could hold the callback past its budget.Behaviour changes
billing periodas the mismatched field.update-coststill answersusageExceeded: falsewhen the verdict read outlasts the standing budget. When the verdict is exceeded but the upgrade-card read runs past the same budget, it keepsusageExceeded: trueand falls back to theupgrade_plancard. Before, the upgrade-card read was unbounded.Test plan
usage-log.integration.tsagainst disposable Postgres (bun run test:integration): new casekeeps billing a request whose period start moved forward before its first chargefails on the old>= endrule and passes with the fix;refuses a request admitted after the period its first charge was stamped withpins the backward direction. Suite: 19/19 passed.update-cost/route.test.ts: new casekeeps the exceeded verdict with the plan-upgrade card when the card read outlasts the callback budgetfails before the deadline covers the upgrade read and passes after. Suite: 52 passed, 1 skipped.bun run type-check(apps/sim)bun run check:audits