/**
 * ModelTierEscalation — §5.1 of docs/specs/FABLE-MODEL-ESCALATION-SPEC.md.
 *
 * The per-framework (framework, tier) → concrete-model-id resolver plus the
 * closed model-id enumerations and per-adapter swap-capability declarations.
 *
 * Core contracts (all spec-cited, all fail-closed):
 *  - Reads ONLY trusted config (`models.tierEscalation`) — never the
 *    mode-state file. The mode-state can request a TIER; it can never
 *    supply a model ID (spec Sec-F3).
 *  - `escalated: null` (or an absent framework entry) resolves to the
 *    default ⇒ no swap ever — the backwards-compat contract for
 *    codex/gemini/pi until they ship an ultra model (spec §5.1).
 *  - Every id is validated by regex AND membership in the framework's
 *    closed `knownModelIds` enumeration before it can reach a launch arg
 *    or tmux send-keys (spec Sec-F1/F2 — keystroke-injection guard).
 *  - Worst-case failure of every path = `null` = the session stays on its
 *    default model (spec §3.5: a routing decision, never a block).
 *
 * Pure logic, no I/O. The governor (§7/§8) and swap service (§5.3) live in
 * EscalationGovernor.ts / ModelSwapService.ts.
 */
/** The frameworks the escalation policy knows. Closed enum — anything else
 *  resolves to null (spec §5.1 line 1). Kept as a literal union to avoid an
 *  import cycle with core/types.ts (matches the inline pattern used there). */
export type EscalationFramework = 'claude-code' | 'codex-cli' | 'gemini-cli' | 'pi-cli';
export declare const ESCALATION_FRAMEWORKS: readonly EscalationFramework[];
export type EscalationTier = 'default' | 'escalated';
/** §5.6 — machine-checkable per-adapter swap capability, not prose. */
export type SwapCapability = 'mid-session' | 'launch-time-only';
export interface TierEscalationFrameworkEntry {
    /** `null` ⇒ use the account default (today's behavior). */
    default: string | null;
    /** `null` ⇒ no ultra model ⇒ escalation is a strict no-op (back-compat). */
    escalated: string | null;
}
export interface TierEscalationTriggers {
    /** Skill names whose entry signals an escalation work-mode (§4). */
    skills: string[];
    /** Trigger #1 also fires on project-initiative design state (§4). */
    projectDesign: boolean;
    /** Optional LLM intent check for ambiguous phrasing (§5.4). Default OFF. */
    llmIntentCheck: boolean;
}
export interface TierEscalationCostGuards {
    /** Map of model id → last free-window UTC date (inclusive). §8. */
    respectFreeWindows: Record<string, string>;
    /** Refuse escalation without a cached quota snapshot showing headroom (§7).
     *  Quota unavailable/errored ⇒ fail CLOSED to default. */
    requireQuotaHeadroom: boolean;
    /** §7 lease cap — admission control for concurrent escalated sessions. */
    maxConcurrentEscalatedPerAccount: number;
    /** §8 — load-bearing for Trigger #1 (short in-conversation swaps). */
    maxEscalationsPerHour: number;
    /** §8 — load-bearing for Trigger #2 (long spawned runs). null = disabled. */
    dailyUltraTokenCap: number | null;
    /** §5.5 — mode-state self-expiry. Expiry INVALIDATES (fresh trigger needed). */
    maxEscalationTtlMs: number;
    /** §5.5 asymmetric hysteresis — never swap twice within this window. */
    minTierDwellMs: number;
    /** §5.5 — de-escalate only after the condition is clear this many turns. */
    minTierDwellTurns: number;
}
export interface TierEscalationConfig {
    /** Fleet default OFF; dev agents (Echo/Codey) ship ENABLED (§10). */
    enabled: boolean;
    /** Log intended swaps without performing them. `enabled:false` wins. */
    dryRun: boolean;
    /**
     * WS5.3 — "escalation rides the topic". When a topic running on the
     * escalated tier is moved via `POST /pool/transfer`, carry its active
     * escalation TRIGGER as an ephemeral hint so the destination RE-ADMITS the
     * resumed session through ITS OWN `EscalationGovernor.admit()` cost guards
     * (a trigger carry, NEVER a tier grant — the destination always re-decides).
     * Rides UNDER `enabled`: with `enabled:false` the whole carry+re-admit path
     * is a strict no-op. Default false ⇒ ships dark independently of the rest
     * of the escalation feature. */
    ridesTopic: boolean;
    triggers: TierEscalationTriggers;
    frameworks: Partial<Record<EscalationFramework, TierEscalationFrameworkEntry>>;
    costGuards: TierEscalationCostGuards;
}
/** §9 schema defaults — exactly the converged spec's config block. */
export declare const DEFAULT_TIER_ESCALATION_CONFIG: TierEscalationConfig;
export declare const KNOWN_CLAUDE_MODEL_IDS: readonly ["claude-fable-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "claude-haiku-4-5-20251001", "opus", "sonnet", "haiku"];
export declare const KNOWN_CODEX_MODEL_IDS: readonly ["gpt-5.2", "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5"];
export declare const KNOWN_MODEL_IDS: Record<EscalationFramework, readonly string[]>;
/**
 * §5.6 — per-adapter swap capability. `claude-code` declares 'mid-session'
 * but the §5.3 contract still verifies EVERY swap with the independent-oracle
 * canary at runtime; the pre-enable proof is the gated live E2E
 * (tests/e2e/model-swap-live-canary.test.ts). The other three are
 * launch-time-only today (and strict no-ops until an escalated model is
 * populated), so "documented, not silently dropped" is enforced by code.
 */
export declare const SWAP_CAPABILITY: Record<EscalationFramework, SwapCapability>;
/** §5.1 injection-safe id shape. Anything else is audit+reject. */
export declare const MODEL_ID_RE: RegExp;
/** Reason codes for fail-closed resolutions — structured, audit-friendly. */
export type ResolveRejectReason = 'unknown-framework' | 'no-framework-entry' | 'null-id' | 'invalid-id-shape' | 'id-not-in-closed-enum';
export interface ResolveAuditEvent {
    framework: string;
    tier: EscalationTier;
    reason: ResolveRejectReason;
    /** Escaped + truncated — never the raw value verbatim past 80 chars (Sec-F7). */
    rejectedId?: string;
}
/** Escape + truncate an untrusted id for audit records (spec Sec-F7). */
export declare function escapeIdForAudit(id: string): string;
/**
 * §5.1 resolver — maps (framework, tier) → concrete model id | null,
 * reading ONLY the trusted config. Every rejection fails closed to null
 * (session stays on default) and reports a structured audit event.
 */
export declare function resolveTierModel(framework: string, tier: EscalationTier, config: TierEscalationConfig | undefined, audit?: (event: ResolveAuditEvent) => void): string | null;
/**
 * True when escalation could ever change behavior for this framework:
 * an escalated id is configured AND resolves through the closed enum.
 * Used by callers to keep the no-escalated-model case a true no-op.
 */
export declare function hasEscalatedModel(framework: string, config: TierEscalationConfig | undefined): boolean;
/** The set of configured escalated ids across all frameworks — used by the
 *  §11 router-exclusion guard and the §8 ultra-cap monitor. */
export declare function escalatedModelIds(config: TierEscalationConfig | undefined): Set<string>;
/**
 * §8 free-window semantics: compared as a UTC date, inclusive through the
 * named day. Returns true while the window is open. After expiry the
 * quota/budget guards apply unchanged — the window relaxes nothing
 * structural (informational + drives the dev-agent dogfood window).
 */
export declare function isWithinFreeWindow(modelId: string, guards: TierEscalationCostGuards, nowMs: number): boolean;
/**
 * Merge an operator's (possibly partial / absent) config block over the §9
 * defaults. Add-missing-only semantics live in PostUpdateMigrator; this is
 * the READ-side normalizer so every consumer sees a complete shape.
 */
export declare function normalizeTierEscalationConfig(raw: unknown): TierEscalationConfig;
//# sourceMappingURL=ModelTierEscalation.d.ts.map