import type { WorkflowEvent } from './schema.js';
import type { ErrorClass, ErrorPayload, OutputRef } from './payloads.js';
export type RunStatus = 'pending' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'cancelled';
export type NodeStatus = 'idle' | 'triggered' | 'running' | 'waiting' | 'retrying' | 'succeeded' | 'failed' | 'skipped' | 'cancelled';
export type ActivityStatus = 'pending' | 'acquired' | 'running' | 'waiting' | 'effectAttempting' | 'succeeded' | 'failed' | 'timedOut' | 'cancelled';
export type AttemptState = {
    attemptId: string;
    attemptNumber: number;
    inputRef: OutputRef;
    status: ActivityStatus;
    leaseId?: string;
    timeoutMs?: number;
    maxOutputBytes?: number;
    effectAttempted?: {
        idempotencyKey: string;
        inputHash: string;
        idempotencyTtlMs: number;
        provider: string;
        attemptedAtEventId: string;
        /** Wall-clock timestamp the effectAttempted envelope landed (ms epoch).
         *  Resume uses this to evaluate the TTL boundary against `now()` —
         *  cheaper and more deterministic than re-deriving the time from
         *  `eventId` parsing. */
        attemptedAtMs: number;
    };
    /**
     * Latest reconcileResult matched to this attempt by idempotencyKey.
     * Resume consults this BEFORE re-running the decision tree: if a
     * previous resume crashed between writing reconcileResult and the
     * terminal event, replay surfaces the prior decision so the next
     * resume can finish what the first started (codex Step 7 round 1
     * finding 1).  reconcileResult.payload doesn't carry attemptId — we
     * match by idempotencyKey, which uniquely identifies the attempt.
     */
    latestReconcileResult?: {
        decision: 'replayed' | 'completedByIdempotentSubmit' | 'manual' | 'freshRetry';
        capability: 'readOnlyLookup' | 'idempotentSubmit' | 'none';
        evidence: Record<string, unknown>;
        eventId: string;
    };
    /**
     * Cancel-in-flight marker (Step 9).  Populated when a `cancelRequested`
     * targets this activity (directly via `kind=activity`, or — Step 10
     * scheduler concern — fan-out from node/run cancel).  Resume reads
     * this to detect dangling cancels (cancelRequested written, terminal
     * missing) and complete them with `activityCanceled`.
     *
     * Cleared / superseded once `activityCanceled` writes a terminal —
     * but we keep `cancelOriginEventId` recoverable from the request
     * event itself, so resume doesn't need an in-flight pointer post-
     * terminal.
     */
    cancelRequest?: {
        cancelOriginEventId: string;
        requestedBy: string;
        reason: string;
        delivered: boolean;
    };
    /**
     * Wait state for human-gate / time / condition activities (Step 8).
     * Populated by `waitCreated` and updated by `waitResolved` /
     * `waitDeadlineExceeded`.  Resume reads this to recover dangling
     * wait resolutions (resolved/exceeded but no terminal written).
     */
    wait?: {
        waitKind: 'human-gate' | 'time' | 'condition';
        deadlineAt?: number;
        /** Inline prompt — only set for small prompts (≤1 KiB producer policy)
         *  AND for historical pre-v0.1.3 events.  When the prompt was spilled
         *  to a blob, `prompt` is undefined and consumers must use `promptRef`
         *  (full text on demand) or `promptPreview` (cheap, card-safe). */
        prompt?: string;
        /** Blob spill ref (v0.1.3+).  Replay never reads the blob; cards
         *  must render `promptPreview` only and dashboard reads on demand. */
        promptRef?: OutputRef;
        /** Short preview carried inline on waitCreated when promptRef is set;
         *  ≤500 chars by schema, byte-budgeted by the producer. */
        promptPreview?: string;
        /** Default `fail` at the consumer.  Recorded only when waitCreated
         *  carries the field; absent means caller never specified. */
        onTimeout?: 'fail' | 'success';
        /** Open when neither resolution nor deadline event has landed. */
        resolution?: {
            kind: 'resolved';
            resolution: 'approved' | 'rejected' | 'external';
            by: string;
            comment?: string;
            eventId: string;
        } | {
            kind: 'deadlineExceeded';
            deadlineAt: number;
            exceededAtMs: number;
            eventId: string;
        };
    };
    output?: OutputRef;
    externalRefs?: Record<string, unknown>;
    error?: ErrorPayload;
    runningMs?: number;
    cancelOriginEventId?: string;
};
export type ActivityState = {
    activityId: string;
    attempts: AttemptState[];
    status: ActivityStatus;
    currentAttemptId?: string;
    /**
     * Node that owns this Activity (recorded from `attemptCreated.nodeId`).
     * Lets us project node.status when activity-level events arrive
     * (e.g. activityRunning → node.status = 'running').
     */
    ownerNodeId?: string;
};
export type NodeState = {
    nodeId: string;
    status: NodeStatus;
    activityId?: string;
    retryCount: number;
    nextAttemptAt?: number;
    errorClass?: ErrorClass;
    conditionEventId?: string;
    cancelOriginEventId?: string;
};
export type RunState = {
    runId: string;
    status: RunStatus;
    workflowId?: string;
    revisionId?: string;
    initiator?: string;
    input?: OutputRef;
    output?: OutputRef;
    failedNodeId?: string;
    rootCauseEventId?: string;
    cancelOriginEventId?: string;
    /**
     * Immutable bot identity snapshot captured at runCreated time
     * (UI doc §3.4).  Read by the runtime when spawning workers so that
     * subsequent bot-registry rename / re-wire doesn't drift execution
     * away from what was authored.  Absent on legacy runs created before
     * v0.1.3 introduced the field.
     */
    botSnapshots?: Record<string, {
        larkAppId?: string;
        cliId?: string;
        displayName?: string;
        workingDir?: string;
        cliPathOverride?: string;
    }>;
};
export type LoopIterationState = {
    iteration: number;
    status: 'running' | 'approved' | 'rejected' | 'failed' | 'cancelled';
    bodyActivityIds: string[];
    decisionActivityId?: string;
    waitResolvedEventId?: string;
    decisionBy?: string;
    decisionComment?: string;
    timedOut?: boolean;
};
export type LoopState = {
    loopId: string;
    status: 'running' | 'succeeded' | 'failed' | 'cancelled';
    iteration: number;
    maxIterations: number;
    iterations: LoopIterationState[];
    output?: OutputRef;
    errorCode?: string;
    errorClass?: ErrorClass;
};
export type Snapshot = {
    run: RunState;
    nodes: Map<string, NodeState>;
    activities: Map<string, ActivityState>;
    loops: Map<string, LoopState>;
    /** Convenience: terminal outputs by activityId (succeeded events only). */
    outputs: Map<string, OutputRef>;
    /** Last seq seen.  0 if the log is empty. */
    lastSeq: number;
    /**
     * activityIds with attemptCreated but whose latest attempt has no terminal
     * event (succeeded/failed/timedOut/cancelled).  Consumed by resume in
     * Step 7 to drive reconcile decisions.
     */
    danglingActivities: string[];
    /**
     * activityIds whose latest attempt wrote effectAttempted but never reached
     * a terminal event for that attempt.  Subset of danglingActivities.
     */
    danglingEffectAttempted: string[];
    /**
     * activityIds with waitCreated but no waitResolved / waitDeadlineExceeded.
     */
    danglingWaits: string[];
    /**
     * activityIds whose wait was resolved (either by waitResolved or by
     * waitDeadlineExceeded) but whose attempt never reached a terminal
     * event.  Step 8 resume recovery materializes the terminal from the
     * recorded resolution.  Disjoint from `danglingWaits`.
     */
    danglingWaitResolutions: string[];
    /**
     * activityIds with a `cancelRequested` targeting them (directly or
     * via fan-out — Step 10 scheduler concern), but no terminal yet.
     *
     * NOT disjoint from `danglingEffectAttempted` (Step 9 round 1
     * finding 1):  cancel + effectAttempted is the central case that
     * `recoverCancelWithReconcile` handles — reconcile fires FIRST to
     * capture provider evidence, then writes the cancel-flavored
     * terminal (`activityCanceled` for completedByIdempotentSubmit /
     * freshRetry; `activityFailed{manual}` when reconcile is
     * inconclusive).  The orchestrator routes the intersection through
     * the cancel-with-reconcile path; remaining cancels (no effect)
     * go through plain `recoverCancel`.
     *
     * Disjoint from `danglingWaitResolutions` is still upheld — cancel
     * + wait combinations are skipped by wait recovery; cancel wins.
     */
    danglingCancels: string[];
    /**
     * Run-level cancel intent (Step 9 codex round 1 finding 3).  Set when
     * a `cancelRequested{kind:run}` lands and not yet `runCanceled`.  Replay
     * surfaces it so schedulers / dashboards can see "this run is being
     * cancelled" without re-scanning the event list.  Cleared when
     * `runCanceled` lands (run.status === 'cancelled').
     */
    cancelledRunIntent?: {
        cancelOriginEventId: string;
        requestedBy: string;
        reason: string;
    };
    /**
     * Per-node cancel intents.  Same shape and role as `cancelledRunIntent`
     * but scoped to a node — set when `cancelRequested{kind:node}` lands
     * and the node hasn't yet reached `nodeCanceled`.  First request wins
     * on overlap (consistent with the activity-level fan-out semantics).
     */
    cancelledNodeIntents: Map<string, {
        cancelOriginEventId: string;
        requestedBy: string;
        reason: string;
    }>;
};
/**
 * Fold an event log into a state snapshot.  Read-only — never executes
 * activity logic, never calls providers, never writes to the log.  Events
 * doc §5.2.
 *
 * Throws on:
 *   - empty event list (caller must supply at least the runCreated event
 *     to derive runId)
 *   - first event is not runCreated (state machine forbids — events doc §2.1)
 *   - event.runId mismatch (cross-contamination)
 *
 * Does NOT validate state-machine transitions semantically — the log is
 * authoritative.  If transitions look wrong (e.g. activitySucceeded without
 * attemptCreated), the resulting snapshot will simply have weird state;
 * verification is the producer's job.
 */
export declare function replay(events: WorkflowEvent[]): Snapshot;
//# sourceMappingURL=replay.d.ts.map