import type { Instant, MeterReading, ScopeStatus, TenantId, TenantStatus } from '@substrat-run/contracts'; /** * §5's meters, folded once (#38). * * Every adapter has the same three directory tables and could each write the same * `GROUP BY`. They must not: the billable rule is a *commercial* definition, and two * copies of it in two dialects is how the Cloudflare fleet and the SQLite fleet end up * quoting different numbers for the same month. So the adapters fetch rows — a job SQL * is genuinely better at — and the arithmetic that decides what counts lives here, in * one pure function, testable without a database. * * The counterpart to `migration-progress.ts`: the same shape of directory-derived * answer, computed above the storage seam rather than inside each adapter. */ /** One tenant row the fold needs — the directory's `tenants`, narrowed. */ export interface MeterTenantInput { tenantId: TenantId; slug: string; status: TenantStatus; } /** One scope row the fold needs. Its tenant and status, and for storage what it was archived from. */ export interface MeterScopeInput { tenantId: TenantId; status: ScopeStatus; /** #1524: `provisioning` = archived before it held data, so it has no storage to count. */ archivedFromStatus?: ScopeStatus | null; } /** One entitlement grant the fold needs; `quota` is expression only, so it is absent. */ export interface MeterEntitlementInput { tenantId: TenantId; entitlementKey: string; plan: string | null; expiresAt: string | null; } export interface MeterInput { /** * The instant the directory was read — supplied by the caller, never taken from a * clock in here, so the reading's stamp and every expiry comparison are the SAME * instant and a test can pin both. */ readAt: Instant; tenants: MeterTenantInput[]; scopes: MeterScopeInput[]; entitlements: MeterEntitlementInput[]; /** * The stored storage gauge (#1524): the latest sample of each non-reaped scope. Absent when * the host keeps no gauge, and then the reading carries no `storage` at all, rather than a * zero that would read as "this tenant stores nothing". */ storage?: MeterStorageInput[]; /** Each non-reaped scope's latest storage-read attempt, so a failing scope is named, not missing. */ storageAttempts?: { tenantId: TenantId; attemptedAt: string; error: string | null; }[]; /** Latest scope-store samples (#2212). Absent when the host has not started recording them. */ storeStorage?: { tenantId: TenantId; bytes: number; }[]; } /** One scope's latest storage sample, as the fold needs it. */ export interface MeterStorageInput { tenantId: TenantId; bytes: number; readAt: string; } /** * Fold directory rows into one reading. Pure: same rows in, same numbers out, no clock * of its own — `readAt` is supplied so the reading's instant and the expiry comparisons * are the same instant, and so a test can pin it. * * Rows belonging to a tenant that is not in `tenants` are IGNORED rather than counted * against nothing. That is what makes a `{ tenantId }`-narrowed reading come out of the * same code path as the fleet one: the caller narrows the tenant set, and everything * else follows from it. */ export declare function foldMeterReading(input: MeterInput): MeterReading; //# sourceMappingURL=meters.d.ts.map