UsageStore
@forge/monorepo / backend/src / UsageStore
Interface: UsageStore
Defined in: backend/src/persistence/index.ts:712
Append-only usage ledger (docs/12). Events are never edited or deleted; corrections are new
compensating events. Rollups are derived from events, never a second source of truth. Appends are
idempotent on (runId, stepId) (or id) so a recovered run never double-counts.
Methods
append()
append(
input):Promise<void>
Defined in: backend/src/persistence/index.ts:713
Parameters
input
TenantScope & object
Returns
Promise<void>
breakdown()
breakdown(
input):Promise<readonlyobject[]>
Defined in: backend/src/persistence/index.ts:727
Consumption grouped by model or by conversation, over a bounded range (#140).
From the ledger, not the rollups, and that is a deliberate trade rather than an oversight. Rollups are
keyed on (tenant, period, bucket); adding a model and a conversation dimension would multiply the row
count by the product of both cardinalities to serve a panel nobody opens per second. A breakdown over one
period is a bounded scan served by the (tenant_id, occurred_at) index — where the headline total, which
is read on every page load and every quota check, comes from a rollup.
limit because a tenant can have thousands of conversations and a breakdown of all of them is not a
breakdown. Returned largest-first so the truncation drops what matters least.
Parameters
input
TenantScope & object
Returns
Promise<readonly object[]>
listByRun()
listByRun(
input):Promise<Page<UsageEvent>>
Defined in: backend/src/persistence/index.ts:714
Parameters
input
TenantScope & PageRequest & object
Returns
Promise<Page<UsageEvent>>
totals()
totals(
input):Promise<UsageTotals>
Defined in: backend/src/persistence/index.ts:773
Running totals, optionally scoped to a run or conversation — used by reserve() and rollups.
Parameters
input
TenantScope & object
Returns
Promise<UsageTotals>
totalsBetween()
totalsBetween(
input):Promise<{earliestAt:string|null;totals:UsageTotals; }>
Defined in: backend/src/persistence/index.ts:763
Totals over an exact half-open interval [from, to), and the earliest record inside it — #181.
The rollups cannot answer this. They are keyed on (tenant, period, bucket), and a rolling window has no
bucket: a five-hour allowance that began at 09:37 spans parts of six hourly buckets, and summing those
over-counts at both edges by whatever fell outside the window. An approximate spend limit is worse than
none, because the number is used to refuse people.
So this reads the ledger, over the same (tenant_id, occurred_at) index breakdown uses, and the scan
is bounded by the window rather than by history. breakdown was not reused because its limit truncates —
fine for a panel, wrong for a total that decides admission.
earliestAt is null when nothing was spent in the window, and otherwise the occurredAt of the oldest
record in it. A sliding window never "resets", so this is what makes a true statement possible about when
headroom returns: at earliestAt + length that record leaves the window. Deriving it here rather than in
the caller keeps it consistent with the totals it came from — computed from two queries, they could disagree
about a record that arrived between them.
modelId scopes it to one model (#182). principalId scopes it to one person; absent means the whole
tenant, not "unknown principal" — a rolling tenant limit has to see everyone's spend.
Parameters
input
TenantScope & object
Returns
Promise<{ earliestAt: string | null; totals: UsageTotals; }>