import type { DurableJournal } from "@claudexor/journal"; import { type QuotaSnapshot, type QuotaSubject } from "@claudexor/schema"; import { type QuotaRefresher, type QuotaVendorRefresher } from "./quota-poll-lanes.js"; import type { QuotaPacerStateStore } from "./quota-poll-pacer.js"; /** The registered subject UNIVERSE: every subject the daemon expects to hear * about, so a subject with neither snapshot nor a source claim still surfaces * a "no_source" absence instead of vanishing. */ export type QuotaSubjectUniverse = () => QuotaSubject[]; /** Global-journal authority for vendor-owned quota snapshots. */ export declare class QuotaRegistry { private readonly journal; private readonly now; private readonly subjects?; private readonly snapshots; /** Ephemeral typed-absence state, recomputed each refresh/poll cycle — NOT * journaled: an absence is a live derivation of "who reported nothing this * cycle", never a durable fact to replay. */ private absences; /** Signature carried by the last durable projection marker. Raw quota * evidence may span several records; this is the commit/recovery boundary * consumed by snapshot-then-SSE clients. */ private lastPublishedProjectionSignature; private readonly refreshCoordinator; private readonly refresherLanes; private pollSweepInFlight; private recoveryMarkerPending; constructor(journal: DurableJournal, refreshers?: readonly (QuotaRefresher | QuotaVendorRefresher)[], now?: () => Date, subjects?: QuotaSubjectUniverse | undefined, pacerStore?: QuotaPacerStateStore); /** Publish the recovered projection boundary only after bootstrap activation. */ recoverAfterStartup(): void; read(): { snapshots: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; constraints: { id: string; label: string; applies_to_models?: string[] | null | undefined; applies_to_unspecified_model?: boolean | undefined; used_ratio: number | null; window_seconds: number | null; resets_at: string | null; cooldown_until: string | null; }[]; source: "agy_command_usage" | "claude_api_retry" | "claude_oauth_usage" | "claude_statusline" | "codex_app_server" | "codex_rollout" | "cursor_rate_limit"; observed_at: string; freshness: "fresh" | "stale" | "unknown"; availability?: { state: "available" | "cooldown" | "exhausted"; blocking_constraints: string[]; resets_at: string | null; model_scoped_exhaustions: { constraint_id: string; applies_to_models: string[]; resets_at: string | null; }[]; } | undefined; }[]; absences: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; reason: "auth_revoked" | "credential_profile_ambiguous" | "no_source" | "not_logged_in" | "platform_unsupported" | "poll_paced" | "probe_skipped_rate_limited" | "rate_limited" | "refresh_failed" | "transport_unavailable"; detail: string | null; observed_at: string; retry_after_ms?: number | undefined; }[]; refreshed_at: string | null; refresh_skipped?: { vendor: string; not_before: string; }[] | undefined; }; /** Freshness-annotated snapshots with expired (>24h) observations pruned. * An old observation whose constraint still EXTENDS into the future (a * weekly cooldown/reset seen once) is kept and stale-marked: pruning it * would hide a live cap from both the footer and the router's ledger. */ private activeSnapshots; refresh(): Promise<{ snapshots: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; constraints: { id: string; label: string; applies_to_models?: string[] | null | undefined; applies_to_unspecified_model?: boolean | undefined; used_ratio: number | null; window_seconds: number | null; resets_at: string | null; cooldown_until: string | null; }[]; source: "agy_command_usage" | "claude_api_retry" | "claude_oauth_usage" | "claude_statusline" | "codex_app_server" | "codex_rollout" | "cursor_rate_limit"; observed_at: string; freshness: "fresh" | "stale" | "unknown"; availability?: { state: "available" | "cooldown" | "exhausted"; blocking_constraints: string[]; resets_at: string | null; model_scoped_exhaustions: { constraint_id: string; applies_to_models: string[]; resets_at: string | null; }[]; } | undefined; }[]; absences: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; reason: "auth_revoked" | "credential_profile_ambiguous" | "no_source" | "not_logged_in" | "platform_unsupported" | "poll_paced" | "probe_skipped_rate_limited" | "rate_limited" | "refresh_failed" | "transport_unavailable"; detail: string | null; observed_at: string; retry_after_ms?: number | undefined; }[]; refreshed_at: string | null; refresh_skipped?: { vendor: string; not_before: string; }[] | undefined; }>; /** Fresh quota plus the exact global-journal fence for snapshot-then-SSE. * The cursor is captured inside refreshCycle, synchronously with `response`, * so a later append can never be skipped by a client resuming from it. */ refreshWithCursor(): Promise<{ response: { snapshots: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; constraints: { id: string; label: string; applies_to_models?: string[] | null | undefined; applies_to_unspecified_model?: boolean | undefined; used_ratio: number | null; window_seconds: number | null; resets_at: string | null; cooldown_until: string | null; }[]; source: "agy_command_usage" | "claude_api_retry" | "claude_oauth_usage" | "claude_statusline" | "codex_app_server" | "codex_rollout" | "cursor_rate_limit"; observed_at: string; freshness: "fresh" | "stale" | "unknown"; availability?: { state: "available" | "cooldown" | "exhausted"; blocking_constraints: string[]; resets_at: string | null; model_scoped_exhaustions: { constraint_id: string; applies_to_models: string[]; resets_at: string | null; }[]; } | undefined; }[]; absences: { subject: { harness: string; credential_route: "local" | "managed_api_key" | "vendor_native"; plan_label: string | null; subject_id: string | null; }; reason: "auth_revoked" | "credential_profile_ambiguous" | "no_source" | "not_logged_in" | "platform_unsupported" | "poll_paced" | "probe_skipped_rate_limited" | "rate_limited" | "refresh_failed" | "transport_unavailable"; detail: string | null; observed_at: string; retry_after_ms?: number | undefined; }[]; refreshed_at: string | null; refresh_skipped?: { vendor: string; not_before: string; }[] | undefined; }; quotaEventCursor: string; }>; /** One coalesced atomic refresh cycle; a poll passes its lane so only that * vendor's refreshers run. Join semantics are asymmetric on purpose: a poll * joining a foreground FULL cycle keeps its (superset) result, but a FULL * caller that joined a lane-SCOPED poll cycle re-runs a full cycle once it * completes — an explicit refresh must not silently return with sibling * vendors unre-fetched and undisclosed. Bounded retry; on exhaustion the * last (complete-projection) result serves. */ private refreshCycle; private performRefreshCycle; /** Fold claims against (harness, subject_id): fresh snapshots silence * refresh gaps; stale snapshots retain their explanations. Other claims * keep their existing precedence and retirement rules; no evidence yields * "no_source". Route/source never split a subject. * * `scope` (a vendor-lane cycle) rebuilds only that vendor's rows plus every * REFRESHERLESS harness's rows (those can only ever be `no_source`, and * skipping them would leave e.g. a cursor subject silently unstated until * the next full cycle); other vendors' claimed rows are preserved so a * claude-only poll cannot degrade codex's typed reasons to no_source. * `null` scope (a full cycle, or an anonymous-lane cycle whose coverage is * unknowable) keeps the pre-existing full rebuild. */ private recomputeAbsences; /** Refresh gaps coexist with stale snapshots and are silenced by fresh ones. * Other absences require no active snapshot. Floor-suppressed subjects gain * derived `poll_paced` rows (see derivePollPacedRows). */ private activeAbsences; /** Credential or routability state changed (login/profile/native/settings): * drop the credential-demand backoff so the next poll observes the new * subject universe instead of waiting out up to 15 minutes of old-state * pacing. Each lane's vendor rate-limit floor deliberately survives — a * login does not un-rate-limit the vendor endpoint. */ noteCredentialChange(): void; /** Background official-source refresh for per-subject primary demand. One * single-flight sweep drives every vendor lane in order; each eligible lane * runs its own coalesced cycle, so one vendor's backoff never starves a * sibling vendor's freshness. Resolves true when any lane refreshed. */ pollStale(): Promise; ingest(harnessId: string, value: unknown): void; upsert(value: QuotaSnapshot): void; private recordUpsert; /** `subjectId: null` retires a harness's legacy default/native subject — * the unified-accounts migration's quota step (no replay alias: the new row * refreshes fresh, legacy null evidence is removed here or ages out). */ removeSubject(harness: string, subjectId: string | null): number; private appendProjectionMarker; private publishClockTransitionIfNeeded; private projectionSignature; validateProjection(): void; private upsertCooldown; private apply; private remove; } //# sourceMappingURL=quota-registry.d.ts.map