/**
 * QuotaAwareScheduler — P1.3 of the Subscription & Auth Standard.
 *
 * Picks the optimal subscription account for a session and guarantees a
 * long-lived session never dies on a quota limit: it swaps proactively before
 * the wall, or resumes on another account reactively after hitting it.
 *
 * ── The hard continuity guarantee (Justin's decision 2) ──
 * A long-lived session that hits its account's quota MUST resume on a DIFFERENT
 * account and continue the SAME conversation — never die. This reuses existing
 * machinery: `claude --resume <uuid>` is AGNOSTIC to CLAUDE_CONFIG_DIR, so
 * resuming under a different account just works. The swap path drives
 * SessionRefresh (kill → respawn-with-resume) pointed at the new account's
 * config home; TopicResumeMap preserves the conversation UUID across the swap.
 *
 * ── Selection policy: reset-date-optimal "use-before-reset" draining ──
 * Among eligible accounts, prefer the one with the most unused headroom whose
 * window resets SOONEST — drain each account maximally before its quota resets
 * (unused quota that resets is wasted), rather than spreading evenly.
 *
 * This file is the PURE selection core + the swap-decision logic. Both are
 * hermetically testable (no session lifecycle, no network). The orchestration
 * that actually restarts a session is injected (`refreshFn`) so tests stay pure.
 */
import type { SubscriptionAccount } from './SubscriptionPool.js';
/** The binding window for swap decisions: the 7-day window is the scarce one. */
export interface SelectionOptions {
    /** Soft threshold (binding-window utilization %) above which an account is
     *  considered "at pressure" and excluded from proactive selection. Default 90. */
    softThresholdPct?: number;
    /** ISO 'now' for deterministic urgency scoring (tests pass a fixed clock). */
    nowMs?: number;
}
/**
 * "Use-before-reset" score: more unused headroom AND a sooner reset → higher.
 * unusedHeadroom = 100 - bindingUtilization (0..100).
 * urgency = 1 / hoursUntilReset (sooner reset = more urgent to drain), clamped.
 * An account with no quota data yet (unknown) scores on headroom alone (treated
 * as empty / far reset) so a freshly-enrolled account is still selectable.
 */
export declare function scoreAccount(a: SubscriptionAccount, nowMs: number): number;
/**
 * Select the optimal account for a (new or swapped) session. Returns null when
 * no eligible account exists. `excludeId` lets a reactive swap avoid re-picking
 * the account that just hit its limit.
 */
export declare function selectAccount(accounts: SubscriptionAccount[], opts?: SelectionOptions, excludeId?: string): SubscriptionAccount | null;
/**
 * Pool-headroom summary for the QUOTA THROTTLE (`QuotaTracker` provider). This is
 * DISTINCT from `selectAccount`, which picks WHERE to place a session for
 * use-it-or-lose-it (it favors the soonest-to-reset account, which may be highly
 * used). The throttle asks a different question — "is there capacity for this work
 * ANYWHERE in the pool?" — so it must gate on the MOST-HEADROOM eligible account,
 * not the drain-first winner. (Live proof against the real pool, 2026-06-16: with a
 * fresh 0% reserve alongside an 85% drain-first account, gating on the drain-first
 * account wrongly shed all non-critical work — overriding codex round-2 minor #2's
 * "accepted as marginal" with real evidence that it materially under-delivered.)
 *
 * Crucially it shares the EXACT eligibility predicate `selectAccount` uses
 * (`isLocallyExecutable` + binding utilization below the soft threshold), so
 * `placeable` here ⟺ `selectAccount(...) !== null` — the never-loop invariant
 * holds (the throttle never allows when placement can place nothing). A
 * credential-less meta account (WS5.2 §6.2) is excluded from BOTH by the shared
 * predicate, so the invariant is preserved. Percentages
 * are clamped to [0,100]; `degraded:true` when the best account has no trustworthy
 * live reading, so the throttle applies its bounded degraded cap instead of a
 * phantom "0% fresh".
 */
export declare function poolHeadroom(accounts: SubscriptionAccount[], opts?: SelectionOptions): {
    placeable: boolean;
    weeklyPercent: number | null;
    fiveHourPercent: number | null;
    degraded: boolean;
};
/** Is this account at/over the soft pressure threshold on its binding window? */
export declare function accountAtPressure(a: SubscriptionAccount, softThresholdPct?: number): boolean;
/** Injected session-restart-with-resume (wraps SessionRefresh in prod). */
export type RefreshFn = (opts: {
    sessionName: string;
    reason: string;
    /** The config home of the account to resume under (CLAUDE_CONFIG_DIR). */
    configHome: string;
    /** The account id to record on the resumed session. */
    accountId: string;
}) => Promise<boolean>;
export interface SwapResult {
    swapped: boolean;
    /** The account swapped TO (null when no eligible alternate existed). */
    toAccountId: string | null;
    reason: string;
}
export interface QuotaAwareSchedulerConfig {
    /** Returns the current pool accounts (e.g. () => pool.list()). */
    listAccounts: () => SubscriptionAccount[];
    /** Restarts a session resumed under a given account (wraps SessionRefresh). */
    refreshFn: RefreshFn;
    /** Raised (deduped) when a session is at a wall with NO eligible alternate. */
    onNoAlternate?: (sessionName: string, exhaustedAccountId: string) => void;
    softThresholdPct?: number;
    logger?: {
        log: (m: string) => void;
        warn: (m: string) => void;
    };
}
export declare class QuotaAwareScheduler {
    private readonly cfg;
    constructor(cfg: QuotaAwareSchedulerConfig);
    /** Pick the best account for a NEW session (proactive placement). */
    placeNewSession(nowMs: number): SubscriptionAccount | null;
    /**
     * THE GUARANTEE. A session running under `exhaustedAccountId` has hit (or is
     * about to hit) its quota. Pick another eligible account and resume the
     * session under it — never leave it dead. If no alternate exists, signal
     * onNoAlternate (the caller raises one deduped Attention item) and leave the
     * existing rate-limit back-off as the floor.
     */
    onQuotaPressure(args: {
        sessionName: string;
        exhaustedAccountId: string;
        nowMs: number;
    }): Promise<SwapResult>;
}
//# sourceMappingURL=QuotaAwareScheduler.d.ts.map