/** * Availability ladders — spec §5.1-5.3 of docs/history/quota-metering-spec-2026-08-16.md. * * `remaining` and `resetsAt` are resolved per (scope, axis, period) through fixed rungs, and * staleness is handled HERE, at read time, as a pure function of the caller's clock — never by a * repair write. freellmapi runs an UPDATE inside its GET path to restore `remaining` once a reset * passes; the same correctness falls out of simply declining an observation that predates the * current period boundary, because rung 2 recomputes from the ledger and rung 3 is null. A read * path that never mutates cannot invent a replenished number. * * Provenance (repo invariant): every figure carries its basis; unknown stays null, never 0; * no published limit, ceiling or multiplier is invented anywhere in this module. The one * arithmetic step — rung 2's limit − localUsed — is labelled with WHERE its inputs came from * ("derived:"), so a reader can always tell a stated figure from a computed one. * * Pure: no IO, no clock of its own (`now` is an argument), no imports from server.ts. It reads no * store either — callers hand it observations, limits, usage and facts they already read. Its * runtime imports are pure vocabularies: configured-limit axes/mapping and `target-facts.ts`'s * closed kind set, so neither can be restated (and drift) in consumers. Gap 12 calls * `resolveRemaining` from the routing path under `routingEligible`; nothing here reorders or * refuses anything today. */ import { type QuotaAxis, type QuotaObservation, type QuotaPeriod } from "./quota-observation.js"; import type { UsedInWindowReading } from "./accounting-store.js"; import { type ConfiguredLimitAxis } from "./configured-limits.js"; /** Where a rung-2 limit may come from, in precedence order (see resolveRemaining). */ export type LimitProvenance = "provider-stated" | "configured" | "learned" | "published"; /** How the relay obtained one axis-projected local-usage figure. */ export type LocalUsedProvenance = "reported" | "estimated" | "mixed" | "relay-counted"; export interface LocalUsedReading { /** Tokens or requests consumed in the current period by THIS credential/deployment. */ readonly value: number | null; /** How the ledger obtained it; null when there is no reading at all. */ readonly basis: LocalUsedProvenance | null; } /** * Project one raw accounting window onto the requested quota axis. The store's `basis` describes * its TOKEN figure only; completed requests are counted by the relay and must never borrow that * token provenance. Keeping this projection in one place makes the three ledger consumers agree. */ export declare function projectLocalUsed(reading: UsedInWindowReading | null, axis: QuotaAxis): LocalUsedReading; /** * The ONE definition of "which buckets may read the local ledger at all", wrapping * `projectLocalUsed` so its three consumers cannot disagree. * * ⚠ An `"unknown"` period has no window to count, so rung 2 must not run for it and the ledger is * never asked. `AccountingStore.usedInWindow` also declines the period by type; this is the * runtime half of the same rule, in one place rather than hand-copied into * `quota-demotion.ts`, `availability-snapshot.ts` and `candidates.ts` — three hand-copies of the * quota bucket key is exactly how the last drift of this shape happened. */ export declare function localUsedForPeriod(read: (period: Exclude) => UsedInWindowReading | null, period: QuotaPeriod, axis: QuotaAxis): LocalUsedReading; /** The limits a caller has already resolved, each optional — absent until its gap lands. */ export interface LimitInputs { configured?: number | null; learned?: number | null; /** * A catalog-published rate limit (Gap 13). Absent/undefined until then; accepted now so this * ladder does not need to change shape when the rung lands. */ published?: number | null; } /** * ⚠ `quota-observation.ts` keeps a PRIVATE interface of the same name for its own header * parsing; this exported shape is the one consumers see. Exporting the other would collide. */ /** * ⚠ `period` INCLUDES `"unknown"`, and that is load-bearing rather than lax. A provider whose * header name carries no period word (groq's `x-ratelimit-limit-requests`, the whole * `anthropic-ratelimit-*` family) still states limit, remaining AND reset on the row. Such a * bucket is admitted only when the observation supplies its own reset — see `collectQuotaBuckets` * — so nothing here is ever derived from a period that does not exist: `periodStart`/`periodEnd` * both return null for `"unknown"`, and `usedInWindow` declines the period by type, so the * rung-2 arithmetic and the rung-3 boundary fail closed on their own. */ export interface QuotaBucket { readonly axis: QuotaAxis; readonly period: QuotaPeriod; readonly observations: QuotaObservation[]; readonly limits: LimitInputs; } export interface CollectQuotaBucketsInput { readonly observations: readonly QuotaObservation[]; readonly learned: readonly { readonly axis: QuotaAxis; readonly period: Exclude; readonly limit: number; }[]; readonly configured: Partial> | null; } /** * Gather every (axis, period) carrying admissible quota evidence into insertion order. * Callers still choose whether learned figures participate, how buckets are ordered after * collection, and which local ledger reading feeds resolution. */ export declare function collectQuotaBuckets(input: CollectQuotaBucketsInput): Map; export interface ResolveRemainingInput { /** Provider-stated observations for exactly this (scope, axis, period); newest wins per tuple. */ observations: readonly QuotaObservation[]; axis: QuotaAxis; period: QuotaPeriod; limits?: LimitInputs; localUsed: LocalUsedReading; now: number; } /** The §5.1 output: a remaining figure plus everything needed to say where it came from. */ export interface RemainingResolution { remaining: number | null; /** * `derived:provider-stated` covers rung 2 against a header-observed limit whose REMAINING half * went stale: the limit was stated, the arithmetic was ours, and neither label alone is true. */ basis: "provider-stated" | "derived:provider-stated" | "derived:configured" | "derived:learned" | "derived:published" | null; limit: number | null; limitBasis: "provider-stated" | "configured" | "learned" | "published" | null; localUsed: number | null; localUsedBasis: LocalUsedReading["basis"]; /** The observation rung 1 used, when it did. Staleness and eligibility live here, not in data. */ eligibleObservation: QuotaObservation | null; staleObservations: number; /** * May this figure gate routing (spec §5.4 / decision M2)? Only what the provider itself stated, * or what the operator asserted in config, qualifies. A derived:learned remaining comes from a * regex over vendor prose — displayed from day one, never allowed to throttle on its own. */ routingEligible: boolean; } export declare function periodStart(now: number, period: QuotaPeriod): number | null; export declare function periodEnd(now: number, period: QuotaPeriod): number | null; /** * Rung 2's LIMIT, when several provenances offer one. * * provider-stated > configured > learned > published, justified: * - A limit read off THIS deployment's response header is first-party evidence about the exact * quota window being resolved. Even an old observation's limit stays usable — a header limit is * a statement about entitlement, not point-in-time state, and nothing fresher contradicts it. * - An operator-declared figure outranks a parsed one because the operator can see their plan; * `configured` is also the only rung whose wording was validated against a closed axis list. * - `learned` came from a regex over refusal prose — real evidence, but parse-risky, so it ranks * below both statements. * - `published` (Gap 13) ranks last: catalogue figures are generic and frequently stale for free * rosters (the context-window measurement recorded 0 of 29 pool/high members publishing any). */ export declare function resolveLimit(input: Pick): { limit: number | null; basis: RemainingResolution["limitBasis"]; }; /** The spec §5.1 ladder, one call per (scope, axis, period). */ export declare function resolveRemaining(input: ResolveRemainingInput): RemainingResolution; export type ResetsAtResolution = { resetsAt: number | null; basis: "provider-stated" | "reviewed-rule" | "derived-boundary" | null; }; /** * The §5.2 ladder. Rung 1 (provider-stated) carries the same read-time eligibility rule as §5.1: * a stated reset must still be in the future, else it is declined and the next rung answers. * * `providerStated` / `reviewedRule` arrive pre-resolved: the request path already applies * ResetRules beside header parses (`server.ts` resolveReset), and re-parsing vendor prose here * would risk two readers disagreeing about one response. This module only decides WHICH rung wins * and computes the fallback boundary. */ export declare function resolveResetsAt(input: { providerStated: number | null; reviewedRule: number | null; period: QuotaPeriod; now: number; }): ResetsAtResolution; import { type FactKind, type FactResetBasis } from "./target-facts.js"; /** * One covering fact, in exactly the shape `factsFor()` returns (wider objects satisfy it). * Type-only coupling to the store plus its closed kind set — this module still reads nothing. */ export interface FactResetCandidate { readonly kind: FactKind; readonly until: number; readonly untilBasis?: FactResetBasis; } /** * Turn the facts covering ONE credential×deployment cell into this bucket's rung-1/rung-2 inputs. * * The ONE place that policy lives, because both the dashboard producer (`availability-snapshot.ts`) * and `llm-relay candidates` (`candidates.ts`) resolve the same cell: two implementations is how * the two surfaces come to disagree about one row (the shape CLAUDE.md's "two paths, one policy * empty" gotcha names). Neither caller re-parses vendor prose — a fact already carries the reset * the request path resolved AND the rung it came from (`untilBasis`, minted by `server.ts` * `resolveReset`). * * A fact may answer a bucket only when EVERY one of these holds; each clause is a provenance * guard, not a nicety: * - the fact's kind is one the QUOTA vocabulary can speak about (`QUOTA_RESET_FACT_KINDS`): an * evicting condition ("this deployment is removed from selection") is not a statement about when * an allowance refills, and rendering it as one is a category error dressed as a measurement; * - the fact carries an explicit `untilBasis`: a legacy row or a kind's default TTL is the relay's * own fallback, never something anybody stated; * - its `until` is still in the future (rung 2 has no eligibility test of its own, so it gets one * here rather than rendering a reset that has already passed); * - the bucket is MEASURED-SPENT (`remaining !== null && remaining <= 0`). Unknown remaining has no * effect whatsoever — the same rule quota demotion follows — and a bucket with headroom must * never be handed a credential-wide reset: that would invent availability for a bucket nobody * made a claim about, which is precisely the provenance invariant; * - the ladder would otherwise fall through to the derived boundary (`observationReset === null`). * A reset this response stated about this bucket always outranks a stored one. * * Within those gates the MOST-SPECIFIC scope wins per basis class, not the soonest expiry: callers * pass `facts` in `factsFor()` order (attempt → group → deployment → credential → provider → * model), and the first hit of each class is taken. Racing the two classes on recency would let an * unrelated short fact pre-empt rung 2 entirely, so both inputs are returned independently and * `resolveResetsAt` picks the rung. * * ⚠ Residual, stated rather than papered over: a fact carries no axis/period attribution, so a * fact admitted here answers whichever spent bucket of the cell asked. The conjunction above IS * the containment (same cell, measured-spent, and the provider said nothing about this bucket * itself); an axis/period mapping inferred from the fact's kind would be exactly the invention * `target-facts.ts` refuses ("scope comes from evidence, never from counting"). */ export declare function factResetInputs(input: { /** Covering facts in `factsFor()` order — most-specific scope first. */ facts: readonly FactResetCandidate[]; /** Rung 1's own input: the reset the eligible observation stated, if any. */ observationReset: number | null; remaining: number | null; now: number; }): { providerStated: number | null; reviewedRule: number | null; }; import type { LimitBasis, LocalUsedBasis, RemainingBasis, ResetsAtBasis } from "./dashboard-contract.js"; /** * Map internal bases onto the dashboard wire vocabulary in ONE place, so the producer and the * contract cannot drift. `derived:published` maps onto `derived_configured`'s sibling spelling * `derived_published`, added to the contract additively (see the design-doc note dated 2026-08-22). */ export declare function mapRemainingBasis(basis: RemainingResolution["basis"]): RemainingBasis | null; export declare function mapLimitBasis(basis: RemainingResolution["limitBasis"]): LimitBasis | null; /** The request-count spelling is additive on the dashboard wire; token spellings are unchanged. */ export declare function mapLocalUsedBasis(basis: LocalUsedReading["basis"]): LocalUsedBasis | null; export declare function mapResetsAtBasis(basis: ResetsAtResolution["basis"]): ResetsAtBasis | null; export type { LimitBasis, LocalUsedBasis, RemainingBasis };