/**
 * QuotaPoller — per-account live quota reader (P1.2 of the Subscription & Auth
 * Standard, decision C hybrid read).
 *
 * Produces an AccountQuotaSnapshot per SubscriptionPool account: 5-hour and
 * 7-day utilization + reset dates, per-model breakdown, and extra-usage credit
 * state — exactly what the QuotaAwareScheduler (P1.3) needs to drain each
 * account optimally before its reset and swap before a limit.
 *
 * ── Read mechanism (decision C, grounded by hands-on finding) ──
 * Justin chose C: drive Claude Code's own /usage surface by default, the
 * `GET /api/oauth/usage` endpoint as a bounded fallback. FINDING (2026-06-06):
 * Claude Code does NOT persist usage to disk and exposes no non-interactive
 * usage command, so a truly "read what the client cached" primary does not
 * exist. The only viable Claude mechanism is the OAuth usage endpoint — the
 * same endpoint the client's /usage screen calls internally. So the poller:
 *   - resolves each account's OAuth access token TRANSIENTLY from that account's
 *     own config-home credential store (never persisted, never logged),
 *   - calls the read-only usage endpoint at LOW frequency, and
 *   - stamps the snapshot `source: 'oauth-usage-endpoint-fallback'` for honesty.
 * This is read-only TELEMETRY, not inference — distinct from the inference-
 * spoofing Anthropic enforces against. It stays within decision C's accepted
 * bounds (subscription-only, no API keys, official-client login reused).
 *
 * ── Burn rate, not call count ──
 * The scheduler must decide on MEASURED utilization deltas over time, never raw
 * call volume (lesson: call counts overstate real burn ~100× because the
 * LlmQueue shed layer absorbs most background traffic). The poller exposes a
 * per-account burn rate (utilization %/hour) computed from consecutive reads.
 *
 * Testability: `fetchImpl` and `tokenResolver` are injectable so the whole
 * poller runs hermetically with zero credentials and zero network in tests.
 */
import type { SubscriptionPool, SubscriptionAccount, AccountQuotaSnapshot } from './SubscriptionPool.js';
import { type RefreshResult } from './OAuthRefresher.js';
import type { CredentialLocationGate } from './CredentialLocationGate.js';
/** Injectable token resolver — returns an account's OAuth access token or null. */
export type TokenResolver = (account: SubscriptionAccount) => string | null;
/**
 * Injectable account refresher — exchanges a config home's stored refresh token
 * for a fresh access token (see OAuthRefresher). Defaults to the real keychain/
 * file-backed refresh; tests inject a stub so the poller runs hermetically.
 */
export type AccountRefresher = (account: SubscriptionAccount) => Promise<RefreshResult>;
/** Minimal fetch surface so tests inject a stub (no global fetch dependency). */
export type FetchImpl = (url: string, init: {
    headers: Record<string, string>;
}) => Promise<{
    ok: boolean;
    status: number;
    json: () => Promise<unknown>;
}>;
export interface QuotaPollerConfig {
    pool: SubscriptionPool;
    /** Poll cadence. Default 15 min — low frequency by design (telemetry, not hot path). */
    pollIntervalMs?: number;
    /** Injected for tests; defaults to global fetch. */
    fetchImpl?: FetchImpl;
    /** Injected for tests; defaults to the config-home credential resolver. */
    tokenResolver?: TokenResolver;
    /**
     * Injected for tests; defaults to the real OAuth refresh-token exchange. On a
     * usage-read auth failure the poller calls this BEFORE declaring needs-reauth,
     * so a routine access-token expiry recovers silently instead of crying wolf.
     */
    refresher?: AccountRefresher;
    /** Logger (defaults to console). */
    logger?: {
        log: (m: string) => void;
        warn: (m: string) => void;
    };
    /**
     * Census re-routing gate (§2.2 rows #1–#4). When present AND enabled, the poller resolves
     * each account's LIVE slot via the ledger instead of reading its enrollment `configHome` —
     * so a swap mid-poll can't make the poller read the wrong tenant's token, refresh the wrong
     * slot, cross-contaminate pool emails, or attribute needs-reauth to the wrong account. Absent
     * (or flag-off / ledger-unknown) → byte-for-byte today's enrollment-home behavior.
     */
    locationGate?: CredentialLocationGate;
}
export interface BurnRate {
    /** Utilization points per hour on the binding (7-day) window. */
    sevenDayPctPerHour: number | null;
    /** Utilization points per hour on the 5-hour window. */
    fiveHourPctPerHour: number | null;
    /** Wall-clock ms between the two samples the rate was computed from. */
    spanMs: number;
}
/**
 * Resolve a claude-code account's OAuth access token from its config home,
 * TRANSIENTLY. Never persisted, never logged. Reads via the shared OAuthRefresher
 * locator (macOS keychain `Claude Code-credentials-<sha256(configHome)[0:8]>`,
 * else `<configHome>/.credentials.json`) so the resolver and the refresher always
 * agree on WHERE a config home's credentials live. NOTE: an EXPIRED access token
 * is still returned here (it's a valid string) — expiry is detected by the usage
 * read's 401 and recovered by the refresher, not by this resolver.
 */
export declare function defaultTokenResolver(account: SubscriptionAccount): string | null;
/**
 * Read the account email (`oauthAccount.emailAddress`) Claude Code records for a
 * config home. This is a PUBLIC account identifier (not a secret) — it lets the
 * pool show WHICH account a slot actually authenticated as, so a login into the
 * wrong account surfaces instead of hiding. Tries `<configHome>/.claude.json`,
 * then (for the default home) the home-root `~/.claude.json`. Null if unreadable.
 */
export declare function readAccountEmail(configHome: string): string | null;
/**
 * Map the REAL /api/oauth/usage response (verified live 2026-06-06) into an
 * AccountQuotaSnapshot. The live shape is `five_hour: {utilization, resets_at}`,
 * `seven_day: {utilization, resets_at}`, `seven_day_sonnet`, `seven_day_opus`,
 * `extra_usage: {is_enabled, used_credits, monthly_limit}`.
 */
export declare function mapUsageResponse(body: Record<string, unknown>, source: AccountQuotaSnapshot['source'], nowIso: string): AccountQuotaSnapshot;
export declare class QuotaPoller {
    private readonly pool;
    private readonly pollIntervalMs;
    private readonly fetchImpl;
    private readonly tokenResolver;
    private readonly refresher;
    private readonly logger;
    private readonly locationGate?;
    private interval;
    /** Most-recent snapshot per account id. */
    private readonly lastByAccount;
    /** The snapshot BEFORE the most recent, per account — the burn-rate baseline. */
    private readonly prevByAccount;
    constructor(config: QuotaPollerConfig);
    /**
     * Census re-routing (§2.2 rows #1–#4): resolve the account's LIVE slot home through the ledger
     * gate when enabled, else its enrollment `configHome` (today's behavior). The result is the
     * config home every per-account credential read in this poller targets — so a swap mid-poll
     * reads/refreshes/attributes against the slot the credential ACTUALLY lives in now. Sync,
     * fail-open-loud (the gate never throws), back-compat when the ledger is unknown/never-seeded.
     *
     * Returns the account UNCHANGED when no re-route is needed, so the byte-identical flag-off path
     * does not allocate a clone (and the default token resolver / refresher see the exact same
     * object they do today).
     */
    private accountForReads;
    start(): void;
    stop(): void;
    /**
     * One usage read. Returns the parsed body (or null body on a non-auth non-ok),
     * an auth-failed marker (401/403), or null on a network failure. NEVER logs the
     * token.
     */
    private readUsage;
    private markNeedsReauth;
    /**
     * Poll one account: resolve token transiently, read usage, map to snapshot.
     * On a usage-read auth failure (401/403) the access token may simply have
     * EXPIRED while the refresh token is still valid — so the poller attempts a
     * refresh-token exchange and ONE retry BEFORE declaring needs-reauth. Only a
     * genuinely dead login (no refresh token / refresh rejected / still 401 after
     * a fresh token) yields needs-reauth. Returns null when the token is
     * unresolvable or the read fails. NEVER logs or returns the token.
     */
    pollAccount(account: SubscriptionAccount): Promise<AccountQuotaSnapshot | null>;
    /**
     * Poll every claude-code/anthropic account in the pool and persist each
     * account's latest snapshot (and a recovered status when a prior needs-reauth
     * account now reads cleanly).
     */
    pollAll(): Promise<{
        polled: number;
        failed: number;
    }>;
    /**
     * Burn rate for an account, computed from the two most recent reads. Returns
     * null until at least two distinct reads exist. Uses MEASURED utilization
     * deltas — never call counts. The caller (P1.3 scheduler) decides on these.
     */
    burnRate(accountId: string): BurnRate | null;
    /** Expose the last in-memory snapshot for an account (test/diagnostic). */
    lastSnapshot(accountId: string): AccountQuotaSnapshot | null;
}
//# sourceMappingURL=QuotaPoller.d.ts.map