Skip to content

Commit ed269c6

Browse files
committed
fix(billing): bound the cycle-close per-user sum and state the helper's scope precisely
1 parent 6f36c45 commit ed269c6

3 files changed

Lines changed: 28 additions & 13 deletions

File tree

‎apps/sim/lib/billing/core/ledger-read.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { USAGE_LEDGER_STATEMENT_TIMEOUT_MS } from '@/lib/billing/constants'
33
import type { DbClient, DbTransaction } from '@/lib/db/types'
44

55
/**
6-
* Runs one aggregate over the usage ledger in a transaction of its own, bounded by
6+
* Runs one aggregate over a payer's usage ledger in a transaction of its own, bounded by
77
* {@link USAGE_LEDGER_STATEMENT_TIMEOUT_MS}. `SET LOCAL` scopes the bound to that transaction,
8-
* so it ends with the read and never reaches the pool. Every ledger aggregate reads through
9-
* here: a payer whose period has grown past what one statement can sum within the bound fails
10-
* at the database instead of holding a connection without limit, and a caller that admits on
11-
* the answer can size its own deadline from the bound.
8+
* so it ends with the read and never reaches the pool. Every sum over a payer's billing period
9+
* reads through here, whether it admits a run, closes a cycle or previews a bill: a payer whose
10+
* period has grown past what one statement can sum within the bound fails at the database
11+
* instead of holding a connection without limit, and a caller that admits on the answer can
12+
* size its own deadline from the bound. Reads keyed to one execution or one stamped period
13+
* boundary, and the platform-wide admin analytics, are not period sums and read directly.
1214
*/
1315
export function readLedgerBounded<T>(
1416
executor: DbClient,

‎apps/sim/lib/billing/core/usage-log.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
getBillingPeriodUsageCostByUser,
4242
getBillingPeriodUsageCostWithSourceSubset,
4343
getBillingPeriodWorkflowRunCount,
44+
getStampedPeriodRangeUsageCostByUser,
4445
getUserUsageLogs,
4546
getWorkspaceUsageLogs,
4647
recordCumulativeUsage,
@@ -598,6 +599,16 @@ describe('ledger aggregates', () => {
598599
rows: [{ userId: 'user-1', cost: '3' }],
599600
expected: new Map([['user-1', 3]]),
600601
},
602+
{
603+
name: 'getStampedPeriodRangeUsageCostByUser',
604+
read: () =>
605+
getStampedPeriodRangeUsageCostByUser(billingEntity, {
606+
from: billingPeriod.start,
607+
to: billingPeriod.end,
608+
}),
609+
rows: [{ userId: 'user-2', cost: '4' }],
610+
expected: new Map([['user-2', 4]]),
611+
},
601612
]
602613

603614
beforeEach(() => {

‎apps/sim/lib/billing/core/usage-log.ts‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -417,14 +417,16 @@ export async function getStampedPeriodRangeUsageCostByUser(
417417
)
418418
}
419419

420-
const rows = await executor
421-
.select({
422-
userId: usageLog.userId,
423-
cost: sql<string>`COALESCE(SUM(${usageLog.cost}), 0)`,
424-
})
425-
.from(usageLog)
426-
.where(and(...conditions))
427-
.groupBy(usageLog.userId)
420+
const rows = await readLedgerBounded(executor, (tx) =>
421+
tx
422+
.select({
423+
userId: usageLog.userId,
424+
cost: sql<string>`COALESCE(SUM(${usageLog.cost}), 0)`,
425+
})
426+
.from(usageLog)
427+
.where(and(...conditions))
428+
.groupBy(usageLog.userId)
429+
)
428430

429431
return new Map(rows.map((row) => [row.userId, Number.parseFloat(row.cost ?? '0')]))
430432
}

0 commit comments

Comments
 (0)