/**
 * EscalationGovernor — §7 (subscription envelope + lease) and §8 (cost
 * guards) of docs/specs/FABLE-MODEL-ESCALATION-SPEC.md.
 *
 * Admission control for model-tier escalation. Every refusal means "the
 * session stays on its default model" — the governor can never block a
 * message, tool call, or session (§3.5).
 *
 * Invariants implemented here:
 *  - **Lease, not bare headroom read** (round-1 Adversarial-H2): concurrent
 *    escalation onto one account goes through a reservation with
 *    `maxConcurrentEscalatedPerAccount`.
 *  - **Crash-safe lease** (round-2 Security-N3 / Adversarial-NEW-1): leases
 *    carry a TTL, are keyed on the session-instance id (the spawn-generated
 *    INSTAR_SESSION_ID — unforgeable from outside, monotonic per spawn), are
 *    released on session-end (SessionManager 'sessionReaped' — wired at
 *    boot), and a lease whose holder is no longer live is reclaimable.
 *    Expiry is evaluated lazily on read — no dedicated poller.
 *  - **Quota fail-closed** (round-1 Adversarial-H3): `requireQuotaHeadroom`
 *    reads the subscription pool's CACHED snapshot via an injected provider
 *    (never a live poll on the hot path); unavailable/errored ⇒ refuse.
 *  - **Once-per-episode counting** (round-3 Adversarial-NEW-5): an
 *    escalation counts against `maxEscalationsPerHour` exactly once per
 *    (instance-id, tier-transition) episode; canary retries and per-turn
 *    re-derivations never multiply the count. **Accounting fails toward
 *    counting** (round-2 Adversarial-NEW-2): `recordInjection` is called at
 *    injection time, regardless of read-back outcome.
 *  - **Free windows** (§8): UTC-date, inclusive through the named day;
 *    informational only — crossing the expiry emits ONE audit note (no
 *    silent cost cliff). The window relaxes nothing structural.
 *  - **Daily ultra-token cap as admission control** (§8): refuses NEW
 *    escalations once today's ultra spend (injected provider) crosses the
 *    cap. Mid-run visibility is UltraSessionCapMonitor's job.
 *
 * State is file-backed under `<stateDir>/state/model-tier-escalation/`
 * (atomic tmp+rename writes; single-writer — the server process). The
 * audit trail is an append-only JSONL with structured fields only — never
 * raw operator text (round-2 Security-F7).
 */
import { type TierEscalationConfig } from './ModelTierEscalation.js';
/** Conservative "has headroom" bound: a cached window utilization at/over
 *  this percentage is treated as capped for escalation purposes. */
export declare const QUOTA_HEADROOM_MAX_UTILIZATION_PCT = 90;
/** Shape-compatible with SubscriptionPool's AccountQuotaSnapshot — narrowed
 *  to what admission needs so the provider is easy to inject/test. */
export interface QuotaSnapshotLike {
    fiveHour?: {
        utilizationPct: number;
        resetsAt?: string;
    };
    sevenDay?: {
        utilizationPct: number;
        resetsAt?: string;
    };
    measuredAt?: string;
}
export interface EscalationLease {
    instanceId: string;
    accountId: string;
    acquiredAt: number;
    ttlMs: number;
}
export type AdmitRefusalReason = 'disabled' | 'no-instance-id' | 'lease-capacity' | 'hourly-budget-exhausted' | 'quota-unavailable' | 'quota-capped' | 'daily-cap-exhausted';
export interface AdmitResult {
    allow: boolean;
    reason?: AdmitRefusalReason;
    /** True when the model is inside a configured free window (informational). */
    freeWindow?: boolean;
}
export interface EscalationGovernorDeps {
    /** The agent state dir (`.instar`). */
    stateDir: string;
    /** Live config getter — re-read per call so operator flips apply. */
    getConfig: () => TierEscalationConfig;
    /**
     * CACHED quota snapshot for the account (never a live poll — §7 /
     * round-1 Scalability-M7). Return null when no snapshot is available;
     * with `requireQuotaHeadroom:true` that REFUSES (fail closed).
     */
    quotaSnapshot?: (accountId: string) => QuotaSnapshotLike | null;
    /**
     * Today's (UTC) ultra-model token total, from the token ledger. Used for
     * the §8 daily-cap admission check. Return null when unavailable — with a
     * cap configured, unavailable ⇒ refuse (fail closed).
     */
    ultraTokensTodayUtc?: () => number | null;
    /** Liveness probe for lease reclaim — false ⇒ the holder crashed/ended. */
    isHolderLive?: (instanceId: string) => boolean;
    /** Injectable clock for tests. */
    now?: () => number;
}
export declare class EscalationGovernor {
    private readonly deps;
    private readonly dir;
    private readonly statePath;
    private readonly auditPath;
    private readonly now;
    constructor(deps: EscalationGovernorDeps);
    /**
     * §7/§8 admission decision for escalating `instanceId` on `accountId`.
     * Pure cost/capacity routing — a refusal means "stay on default".
     * Acquiring is idempotent: an instance already holding a lease is
     * re-admitted without consuming additional capacity.
     */
    admitEscalation(input: {
        instanceId: string;
        accountId?: string;
        modelId: string;
        /** e.g. 'default→escalated' — the episode key half (§8 once-per-episode). */
        transition: string;
        /** dryRun evaluation: run every check but acquire NOTHING — a dry-run
         *  swap must never consume lease capacity (§9 dryRun semantics). */
        dry?: boolean;
    }): AdmitResult;
    /**
     * Count the escalation against the hourly budget — called AT INJECTION
     * time (fails toward counting, round-2 Adversarial-NEW-2), idempotent per
     * (instance, transition) episode (round-3 Adversarial-NEW-5).
     * Returns true when this call newly counted the episode.
     */
    recordInjection(instanceId: string, transition: string): boolean;
    /** Release the instance's lease (wired to SessionManager 'sessionReaped'
     *  at boot — the same close event that retires the session, §7). */
    releaseLease(instanceId: string): void;
    /** Current live leases (post-reclaim) — observability/testing surface. */
    activeLeases(): EscalationLease[];
    private episodeKey;
    /** Drop expired leases and leases whose holder is no longer live. Lazy —
     *  runs on every read; no poller (§7). Returns true when anything changed. */
    private reclaimStale;
    private noteFreeWindowExpiryOnce;
    private loadState;
    private saveState;
    /** Structured-fields-only audit appender (Sec-F7: no raw operator text). */
    private audit;
    private refuse;
}
//# sourceMappingURL=EscalationGovernor.d.ts.map