import type { QuotaAbsence, QuotaSnapshot, QuotaSubject } from "@claudexor/schema"; import { QuotaPollPacer, type QuotaPacerStateStore } from "./quota-poll-pacer.js"; export declare const QUOTA_POLL_INTERVAL_MS = 60000; /** One refresh cycle's fruit: the snapshots a source observed, plus the typed * absences it CLAIMS for subjects it tried and could not observe. Absence is * stated by the source, never inferred from an empty snapshot list. */ export interface QuotaRefreshResult { snapshots: QuotaSnapshot[]; absences?: QuotaAbsence[]; } /** Which kind of cycle is asking: a paced background poll of one vendor lane, * or an explicit foreground refresh (`POST /v2/quota`, the atomic Accounts * snapshot) — the user's "ask the vendor again now", which a source may honor * by re-presenting a credential it would otherwise leave alone. A source that * ignores the argument keeps the pre-existing contract. */ export interface QuotaRefreshCycle { readonly foreground: boolean; } export type QuotaRefresher = (cycle?: QuotaRefreshCycle) => Promise; /** A refresher bound to its vendor pacing lane. Refreshers of one vendor * share ONE QuotaPollPacer, so a permanently unsatisfiable subject of one * vendor backs off alone instead of pinning every vendor's cadence at the * 15-minute ceiling. A bare QuotaRefresher (legacy embedders, tests) rides an * anonymous lane with the pre-existing global-demand semantics. */ export interface QuotaVendorRefresher { readonly vendor: string; readonly refresh: QuotaRefresher; } export interface PacingLane { readonly vendor: string | null; readonly pacer: QuotaPollPacer; } export interface RefresherLanes { /** Declaration-ordered refreshers, each bound to its pacing lane. */ readonly entries: ReadonlyArray<{ lane: PacingLane; refresh: QuotaRefresher; }>; /** Distinct pacing lanes in first-appearance order. */ readonly lanes: readonly PacingLane[]; } export declare function buildRefresherLanes(refreshers: readonly (QuotaRefresher | QuotaVendorRefresher)[], pacerStore?: QuotaPacerStateStore): RefresherLanes; /** One lane's demand at a poll horizon. The three facts answer different * questions: `remains` (some registered subject lacks satisfying primary * evidence by the horizon) drives the retry ladder; `renewalNotBefore` (the * last poll tick before the earliest evidence satisfied at this horizon * expires — or the next tick, when evidence observed since the cycle began * hits a vendor window boundary before it; null when none) caps it; * `renewalDueObservedAt` (the latest * observation instant of satisfying evidence whose renewal is due by the * horizon; null when none) lets evidence installed after the ladder was armed * bypass it. */ export interface LaneDemand { readonly remains: boolean; readonly renewalNotBefore: number | null; readonly renewalDueObservedAt: number | null; } export interface PollSweepDeps { readonly now: () => Date; readonly publishClockTransition: () => void; readonly laneDemand: (vendor: string | null, now: number, dueBefore: number, since: number) => LaneDemand; readonly currentGeneration: () => number; readonly isCurrentGeneration: (generation: number) => boolean; readonly runLaneCycle: (lane: PacingLane) => Promise; } /** One background poll sweep: drive every vendor lane in order, running a * coalesced cycle for each lane that still has refresh demand and is past its * pacer gates, so one vendor's backoff never starves a sibling vendor's * freshness. The retry ladder paces subjects that produced no evidence and * never postpones a renewal: after a cycle it advances on remaining demand * but is capped at the renewal tick of the evidence the cycle DID satisfy, * and evidence installed while it is armed (a foreground refresh, an ingested * harness event) bypasses it when its renewal comes due — one revoked or * never-logged-in profile used to pin every healthy sibling of its vendor to * the 15-minute ceiling. The vendor rate-limit floor is never bypassed. * Resolves true when any lane refreshed. Single-flight of the sweep itself * belongs to the caller. */ export declare function performPollSweep(lanes: readonly PacingLane[], deps: PollSweepDeps): Promise; /** Per-lane refresh demand: a vendor lane sees only its own subjects and * snapshots; an anonymous lane keeps the pre-existing global semantics. * `since` is the start of the cycle being judged: evidence it observed whose * window resets before the next tick is renewal due at that tick. */ export declare function laneDemand(vendor: string | null, snapshots: readonly QuotaSnapshot[], subjects: readonly QuotaSubject[] | undefined, now: number, dueBefore: number, since: number): LaneDemand; export interface LaneCycleSelection { readonly running: ReadonlyArray<{ lane: PacingLane; refresh: QuotaRefresher; }>; readonly skipped: Array<{ vendor: string; not_before: string; }>; } /** Select which refreshers one cycle runs. A full (foreground) cycle honors * each vendor lane's rate-limit floor: a vendor that just said 429 is not * re-fanned-out by an explicit refresh — its skip is returned for additive * disclosure. A poll-scoped cycle was already gated by the sweep. Anonymous * lanes never carry a floor and always run. Every running refresher is bound * to the cycle kind (foreground vs poll), so a source can honor an explicit * refresh while the registry keeps calling `refresh()`. */ export declare function selectCycleEntries(refresherLanes: RefresherLanes, scope: PacingLane | null, nowMs: number): LaneCycleSelection; /** Absence recomputation covers exactly the lanes that RAN: an anonymous * lane's coverage is unknowable, so its cycle keeps the full rebuild. */ export declare function recomputeScopeFor(running: LaneCycleSelection["running"]): ReadonlySet | null; /** Derived `poll_paced` gap rows: while a vendor lane's rate-limit floor is * active, every universe subject of that vendor lacking FRESH snapshot cover * and lacking a stored absence row is stated as paused — a live projection * (never journaled), stable per floor so the projection signature does not * churn markers on every read. Keeps a suppressed vendor's subjects from * falling silent (daemon restarts with a store-loaded floor included) and * downstream exhaustion readers fail-open. */ export declare function derivePollPacedRows(lanes: readonly PacingLane[], subjects: readonly QuotaSubject[], existingRows: readonly QuotaAbsence[], freshCovered: ReadonlySet, now: number): QuotaAbsence[]; /** Subject-identity cover sets over one active-snapshot view. Gap rows are * silenced only by the FRESH set; every other reason by any active snapshot. */ export declare function subjectCoverSets(active: readonly QuotaSnapshot[]): { covered: ReadonlySet; freshCovered: ReadonlySet; }; /** Pure absence fold for one cycle (V11a semantics): preserved out-of-scope * rows first, then first-claim-wins claims, then `no_source` for uncovered * in-scope universe subjects. Failed, skipped, or paced refresh claims are * silenced only by a FRESH * snapshot — a stale one is exactly the state the gap row explains, and * dropping the row there would let a stale spent window read as exhausted * downstream. */ export declare function foldAbsenceClaims(input: { claims: readonly QuotaAbsence[]; prior: readonly QuotaAbsence[]; rebuilt: (harness: string) => boolean; covered: ReadonlySet; freshCovered: ReadonlySet; subjects: readonly QuotaSubject[]; now: number; }): QuotaAbsence[]; //# sourceMappingURL=quota-poll-lanes.d.ts.map