/**
 * Chat source state machine.
 *
 * Replaces the ad-hoc anchor + freshness + 6-trigger ladder that
 * chat-commands.ts:1500-1800 grew over time. The machine has four states and
 * three events; every other signal collapses to one of those three.
 *
 *   States                Meaning
 *   ─────────────────────────────────────────────────────────────────
 *   Booting               No native attempt evaluated yet. PTY is the source
 *                         because we have nothing else; this is not a
 *                         "fallback", just the starting condition.
 *   NativeLocked          Native transcript is the source. The lock holds even
 *                         when PTY receives newer bytes — only NativeRegressed
 *                         or NativeUnavailable can break it.
 *   PtyOnly               Native is genuinely not usable (unavailable /
 *                         regressed / never observed). PTY is the source.
 *   Recovering            We were NativeLocked, native disappeared, but we
 *                         have not yet seen enough PTY-only turns to commit
 *                         to PtyOnly. Behaves like PtyOnly for the current
 *                         read but a single NativeProgressed re-locks.
 *
 *   Events                Meaning
 *   ─────────────────────────────────────────────────────────────────
 *   NativeProgressed      Native transcript has fresh, safely-mapped messages
 *                         whose newest sequence is >= the last observed peak.
 *   NativeRegressed       Native messages dropped, mapping became unsafe, or
 *                         the transcript shrank. Strong unlock signal.
 *   NativeUnavailable     Native transcript could not be fetched at all
 *                         (read error, provider not supported, schema invalid).
 *                         Soft unlock — Recovering tolerates a transient miss.
 *
 * Why this replaces the v1 design:
 *   - The v1 freshness check (`isNativeHistoryFreshEnough`) compared native's
 *     newest receivedAt to the PTY buffer's. PTY arrived every turn, so
 *     native looked stale by default and only the 30-minute anchor TTL kept
 *     things stable. When the anchor expired the source flipped, which is
 *     the "plipping" the user reported. The new machine does not compare
 *     PTY freshness against native at all; the only thing that unlocks a
 *     NativeLocked state is evidence that native itself moved backwards or
 *     vanished.
 *   - The 6 trigger strings (`native_history_partial`, `native_history_stale`,
 *     `native_history_not_safely_mapped`, `native_history_empty`,
 *     `native_history_error:*`, `native_history_unavailable:*`) collapse to
 *     three events. Each transition records the original trigger as `cause`
 *     for observability, so we lose nothing diagnostic.
 *   - There is no anchor store. The "lock" is a state, not a piece of
 *     mutable data living on the adapter. Callers pass the previous state in
 *     and get the next state out.
 */
import type { ChatContractVersion } from '../providers/transcript-v2.js';
import { CHAT_CONTRACT_VERSION_V1, CHAT_CONTRACT_VERSION_V2 } from '../providers/transcript-v2.js';
export type ChatSourceStateName = 'Booting' | 'NativeLocked' | 'PtyOnly' | 'Recovering';
export type ChatSourceSelected = 'native-history' | 'pty-parser';
export type ChatSourceEventKind = 'NativeProgressed' | 'NativeRegressed' | 'NativeUnavailable';
/**
 * Cause codes mirror the legacy 6-trigger vocabulary so existing log
 * pipelines and dashboards can group transitions the same way they do
 * today. They are diagnostic, not control-flow.
 */
export type ChatSourceTransitionCause = 'initial' | 'native_progressed' | 'native_regressed_shrunk' | 'native_regressed_unsafe_mapping' | 'native_regressed_coverage_partial' | 'native_regressed_coverage_unavailable' | 'native_unavailable_read_error' | 'native_unavailable_provider_unsupported' | 'native_unavailable_empty' | 'native_unavailable_not_native_source';
export interface ChatSourceState {
    readonly name: ChatSourceStateName;
    /**
     * Highest sequence number ever observed from native. Used as the watermark
     * NativeProgressed has to reach (or exceed) before re-locking from
     * Recovering. Undefined while we have never seen native.
     */
    readonly nativeSequencePeak: number | undefined;
    /**
     * Highest providerUnitKey set we have committed to. Future progressed events
     * must be a superset (no removals) for the lock to hold; if any prior
     * unit key vanishes we issue NativeRegressed instead. Empty when we have
     * never locked.
     */
    readonly committedUnitKeys: ReadonlySet<string>;
    /**
     * Number of consecutive non-progressed observations while in Recovering.
     * Threshold-based promotion to PtyOnly so transient misses do not commit
     * us to PtyOnly prematurely. Always 0 outside Recovering.
     */
    readonly recoveringMisses: number;
}
export declare const INITIAL_CHAT_SOURCE_STATE: ChatSourceState;
/**
 * Promote from Recovering to PtyOnly after this many consecutive misses.
 * One transient miss should not flip the source for a user who was just
 * reading from native; three in a row signals native is genuinely gone.
 */
export declare const RECOVERING_MISS_PROMOTION_THRESHOLD = 3;
/**
 * Per-message identity slice the machine needs. Producers normalise their
 * native transcript output into this shape (v2 producers already do so via
 * transcript-v2.ts; v1 producers go through a thin adapter — see
 * source-resolver.ts).
 */
export interface ResolverNativeMessageIdentity {
    providerUnitKey: string;
    sequence: number;
}
/**
 * Observation the resolver hands to the machine for a single readChat call.
 * Three mutually exclusive shapes — represented as a discriminated union so
 * the type system enforces honesty about what happened.
 */
export type ChatSourceObservation = {
    kind: 'native_present';
    contractVersion: ChatContractVersion;
    messages: ReadonlyArray<ResolverNativeMessageIdentity>;
    /** Producer-declared coverage. Drives Progressed vs Regressed when the
     *  transcript is non-empty but only partial. */
    coverage: 'full' | 'tail' | 'current-turn' | 'partial';
    /** True when the daemon can prove these native messages belong to the
     *  intended workspace+session. False is treated as regression. */
    safeMapping: boolean;
} | {
    kind: 'native_unavailable';
    reason: 'provider_not_supported' | 'read_error' | 'empty' | 'not_native_source' | 'coverage_unavailable';
};
export interface ChatSourceTransition {
    readonly fromState: ChatSourceStateName;
    readonly toState: ChatSourceStateName;
    readonly event: ChatSourceEventKind | 'NoOp';
    readonly cause: ChatSourceTransitionCause;
    /** Wall-clock ms at the moment of resolution. Producer time, not ordering. */
    readonly at: number;
}
export interface ChatSourceLockState {
    readonly locked: boolean;
    /**
     * ms since epoch when the current lock began. Undefined when not locked or
     * not yet observed. Producers may use this for "anchored Xs ago" UI but
     * the machine itself never expires a lock based on it — only events do.
     */
    readonly lockedSince?: number;
}
export interface ChatSourceDecision {
    readonly selected: ChatSourceSelected;
    readonly nextState: ChatSourceState;
    readonly transition: ChatSourceTransition;
    readonly lockState: ChatSourceLockState;
}
/**
 * Pure transition function. Given a previous state and a single observation,
 * returns the next state, the resulting transition record, and the selected
 * source. No side effects. No mutation of `prev`.
 *
 * Lock semantics:
 *   - Booting + native_present(safeMapping, not partial-only) → NativeLocked
 *   - NativeLocked + native_present(superset of committed keys, safe) → stays NativeLocked
 *   - NativeLocked + native_present(missing committed keys OR unsafe OR partial-shrunk) → PtyOnly
 *   - NativeLocked + native_unavailable → Recovering (lock holds for the source decision but watermark survives)
 *   - Recovering + native_present(progressed past peak) → NativeLocked
 *   - Recovering + native_unavailable (RECOVERING_MISS_PROMOTION_THRESHOLD times) → PtyOnly
 *   - PtyOnly is sticky: only an explicit progressed observation that meets
 *     the original superset rule moves us back to NativeLocked. This is the
 *     intended behaviour — once we have concluded native is dead, we do not
 *     re-lock on a single transient revival.
 */
export declare function transitionChatSourceState(prev: ChatSourceState, observation: ChatSourceObservation, at: number, lockedSince: number | undefined): {
    next: ChatSourceState;
    transition: ChatSourceTransition;
    selected: ChatSourceSelected;
    lockState: ChatSourceLockState;
};
export { CHAT_CONTRACT_VERSION_V1, CHAT_CONTRACT_VERSION_V2 };
