import type { WorkflowRun, WorkflowRunStatus } from "../orchestration/workflow.js";
/**
 * design/97 S1b — the **WorkflowRunStore** persistence seam for the workflow-mode primitive
 * (`src/orchestration/workflow.ts`, S1a). It is the durable home for a {@link WorkflowRun} so the
 * `/workflows` observability layer (S1c) can list runs IN PROGRESS *and the history* (completed / failed),
 * across replicas — the "看之前的" core seam (design/97 §A/§D.1).
 *
 * **Distinct from the four load-bearing durable seams** (session / checkpoint / memory / tool-result): those
 * are the engine's suspend/resume命脉, so core ships PG adapters for them. A WorkflowRunStore is an
 * **observation layer — opt-in, non-engine-critical** — so per design/97 §D.8 (2026-06-22 拍) core
 * ships ONLY: this interface + {@link InMemoryWorkflowRunStore} (process-local / default / tests) +
 * `FileWorkflowRunStore` (zero-dependency, TOC-local persistence — `src/stores/file/workflow-run-store.ts`).
 * **The PG backend lives in service (TOB), implementing THIS interface** when it needs cross-replica
 * `/workflows` history. `WorkflowRun` / `WorkflowRunStatus` are imported type-only — this module is purely
 * the store contract + projection, it does NOT re-home the run model (that stays in `workflow.ts`).
 *
 * Mirrors the {@link import("./checkpoint-store.js").CheckpointStore} shape: create-once `put`, `get`, an
 * **atomic CAS `update`** (scope + `rev` optimistic-concurrency — concurrent agents racing to record progress
 * on the same run), a `listByScope` returning lightweight {@link WorkflowRunSummary} projections (history
 * included, NOT pending-only like CheckpointStore.listByScope), and a `reap` retention sweep that only ever
 * deletes TERMINAL runs (never a running one). The {@link summarizeWorkflowRun} projection is SHARED across
 * all backends (anti-drift, exactly like `summarizeCheckpoint`).
 */
/**
 * A lightweight, read-only projection of one {@link WorkflowRun} returned in bulk by
 * {@link WorkflowRunStore.listByScope} so the `/workflows` list view (CLI design/92 / client design/90) can
 * enumerate a scope's runs in ONE call — no N+1 `get`s, no full run payloads. Every field is DERIVED from
 * the persisted run (nothing here is a new source of truth or a gate input). Built by the shared
 * {@link summarizeWorkflowRun} so the projection is IDENTICAL across the in-memory, file, and (service) PG
 * backends.
 */
export interface WorkflowRunSummary {
    /** The run id ({@link WorkflowRun.id}). */
    id: string;
    /** The tenant / grouping key this run lives in ({@link WorkflowRun.scope}). */
    scope: string;
    /** The ORIGINATING session id ({@link WorkflowRun.originatingSessionId}),
     *  projected so a serving layer's LIST face can apply its session acceptance/filter arm without an
     *  N+1 `get` of the full run. Absent for a direct `runWorkflow` call / sessionless deployment. */
    originatingSessionId?: string;
    /** design/99 MF-W: the workflow's display name from the script's `export const meta` ({@link WorkflowRun.name}).
     *  Projected here so the `/workflows` list view labels each row WITHOUT an N+1 `get` of the full run.
     *  Absent for a direct `runWorkflow` call that supplied no name. */
    name?: string;
    /** One-line description from the script's meta ({@link WorkflowRun.description}); absent if none. */
    description?: string;
    /** Lifecycle status ({@link WorkflowRun.status}): running / completed / failed. */
    status: WorkflowRunStatus;
    /** The MACHINE code of the failure that ended the run ({@link WorkflowRun.errorCode}) — the same word,
     *  in the same vocabulary, that the full run carries; never re-derived and never a second taxonomy.
     *  The fourth member of the same family as {@link agentFailures} / {@link budgetOvershoot} /
     *  {@link timeoutInterruption}: those three qualify a row that LOOKS clean, and this one says WHY a
     *  `failed` row failed. Nothing else on this projection can answer that — {@link status} is `failed`
     *  for a refused spawn, a blown budget, a stalled run and a body that simply threw alike, so a list
     *  face without this seat must `get` the full run per failed row to say anything at all, which is the
     *  N+1 this projection exists to avoid.
     *
     *  PRESENT EXACTLY WHEN THE RUN CARRIES ONE, absent otherwise — an absent key reads "no code on the
     *  record", never "the run succeeded" and never a `workflow.unknown` stand-in. A `completed` run never
     *  carries one (per {@link WorkflowRun.errorCode}), and a `failed` run whose body threw a codeless
     *  error with no failed child on the record carries none either. Free of any own attribution rule: the
     *  attribution (thrown code → first failed agent → absent) happens once, where the run is written. */
    errorCode?: string;
    /** Count of agent-runs that ended failed ({@link WorkflowRun.agentFailures}) — present only when > 0.
     *  Lets a list view flag a "completed, with failures" run without an N+1 `get` of the full run. */
    agentFailures?: number;
    /** The run's TERMINAL token-budget overshoot ({@link WorkflowRun.budgetOvershoot}) — present ONLY when the
     *  run actually spent MORE than its ceiling, absent on every run that stayed within one and on every run
     *  that set no budget at all. The list-view twin of {@link agentFailures}: it lets a row be flagged
     *  "completed, and over budget" without an N+1 `get` of the full run — the *other* way a `completed` row is
     *  not the unqualified good news it looks like. It cannot be DERIVED from anything else on this projection:
     *  {@link tokens} is a different axis (see its note), so without this seat an over-budget run reads on the
     *  list face exactly like a clean one.
     *
     *  Projected as a de-aliased shallow COPY — every other member here is a primitive, so the projection must
     *  not hand a caller a live reference into a stored run (the same anti-aliasing posture the stores' `get`
     *  keeps with `structuredClone`). Read the members per {@link WorkflowRun.budgetOvershoot}: the overshoot is
     *  `spentTokens + unsettledTokens - budgetTokens`, and a run resting entirely on `unsettledTokens` is a
     *  best-effort disclosure rather than a measurement. */
    budgetOvershoot?: {
        budgetTokens: number;
        spentTokens: number;
        unsettledTokens?: number;
    };
    /** The run's TERMINAL total-timeout interruption ({@link WorkflowRun.timeoutInterruption}) — present ONLY
     *  when the `totalTimeoutMs` deadline had fired by the time the run reached its terminal. The third member
     *  of the same family as {@link agentFailures} and {@link budgetOvershoot}: it lets a list row separate
     *  "this run was CUT SHORT, and here is what it had achieved" from "this run failed", without an N+1 `get`
     *  of the full run. Nothing else on this projection can answer that — {@link status} is `failed` either
     *  way, and {@link agentCount} counts records regardless of how they ended.
     *
     *  Projected as a de-aliased shallow COPY (same anti-aliasing posture as {@link budgetOvershoot}: a list
     *  row must not be a mutable window into the stored run). Read the members per
     *  {@link WorkflowRun.timeoutInterruption} — in particular `agentsFailed` is a PRE-abandonment count and is
     *  NOT {@link agentFailures}, and `approvalWaitedMs` (#485, present only when a leg reported one) is the
     *  LARGEST single leg's human-approval wait, a lower bound on how much of the window went on a person
     *  rather than on work — never a sum, and its ABSENCE is "not supplied", never `0`. */
    timeoutInterruption?: {
        timeoutMs: number;
        agentsCompleted: number;
        agentsFailed: number;
        agentsInFlight: number;
        approvalWaitedMs?: number;
    };
    /** Title of the latest phase recorded ({@link WorkflowRun.phases}`.at(-1).title`) — what the run is on RIGHT
     *  NOW for a `running` row (so the `/workflows` list shows the live phase without subscribing to the event
     *  stream). Absent when no phase has started yet. */
    currentPhase?: string;
    /** Number of phases recorded so far ({@link WorkflowRun.phases}`.length`). */
    phaseCount: number;
    /** Number of agent-runs recorded so far ({@link WorkflowRun.agents}`.length`). */
    agentCount: number;
    /** Total tokens spent = own + nested (`stats.tokens + stats.nested.tokens`) — the figure a triage view
     *  sorts/compares by. own/nested stay SEPARATE on the full run (R-5); the summary folds them for display.
     *  ⚠️ NOT the budget axis's figure: run stats also count REPLAYED work, which the budget deliberately never
     *  charges, so comparing this against a ceiling proves nothing in either direction — whether a run overran
     *  its budget is read off {@link budgetOvershoot}, never inferred from here. */
    tokens: number;
    /** When the run started ({@link WorkflowRun.startedAt}, epoch ms). */
    startedAt: number;
    /** When the run finished ({@link WorkflowRun.endedAt}, epoch ms) — absent while still running. */
    endedAt?: number;
    /** When the run record was created ({@link WorkflowRun.createdAt}, epoch ms) — the `listByScope` sort key. */
    createdAt: number;
}
/**
 * design/97 S1b: project a {@link WorkflowRun} to its lightweight {@link WorkflowRunSummary}. SHARED by every
 * {@link WorkflowRunStore} impl so the projection is IDENTICAL across the in-memory, file, and (service) PG
 * backends (the anti-drift guard — exactly like `summarizeCheckpoint`). Pure; reads no clock/random. Total
 * spend folds own + nested (`stats.tokens + stats.nested.tokens`), the one place the two are summed.
 */
export declare function summarizeWorkflowRun(run: WorkflowRun): WorkflowRunSummary;
/** Whether a run is in a TERMINAL state — the ONLY states {@link WorkflowRunStore.reap} may delete (a
 *  `running` run is never reaped, so an in-flight workflow's record can't vanish out from under it). */
export declare function isTerminalWorkflowStatus(status: WorkflowRunStatus): boolean;
/** A typed workflow-run-store error so callers branch on `code` (mirrors `CheckpointError`). */
export declare class WorkflowRunStoreError extends Error {
    readonly code: "workflow_run.already_exists";
    constructor(code: "workflow_run.already_exists", message: string);
}
/** REF-D18: `listByScope`'s query options — the SINGLE shape the interface method and both the
 *  In-Memory and File implementations reference. */
export interface WorkflowRunListByScopeOptions {
    status?: WorkflowRunStatus;
    limit?: number;
    session?: string;
}
/**
 * The pluggable persistence seam for workflow runs (design/97 S1b). Symmetric with `CheckpointStore`:
 * create-once `put`, `get`, an atomic CAS `update`, a `listByScope` query (history included), and a `reap`
 * retention sweep. A durable backend (service's PG) makes `/workflows` history cross-replica; the default
 * {@link InMemoryWorkflowRunStore} is process-only (single instance / tests).
 */
export interface WorkflowRunStore {
    /** Create-once. Throws {@link WorkflowRunStoreError} `already_exists` on a token collision (never a silent
     *  overwrite — a reused id would clobber a live run's record). */
    put(id: string, run: WorkflowRun): Promise<void>;
    get(id: string): Promise<WorkflowRun | null>;
    /**
     * Atomic CAS update of an EXISTING run (`UPDATE … SET … WHERE id=? AND scope=?` [`AND rev=?`]). Returns
     * `true` for the winner, `false` when the row is missing, the `scope` does not match (multi-tenant
     * isolation — a wrong-scope update must not win), or `expect.rev` does not match the live row's `rev`
     * (an intervening update bumped it). On success the store bumps the row's `rev` (monotonic) and persists
     * the new `run`. The workflow owner is the sole writer (best-effort progress recording — design/97 §F),
     * so `expect.rev` lets it detect a lost write rather than silently clobber.
     */
    update(id: string, scope: string, run: WorkflowRun, expect?: {
        rev: number;
    }): Promise<boolean>;
    /**
     * List lightweight {@link WorkflowRunSummary} projections for runs in `scope` — **history included** (the
     * completed / failed runs, NOT pending-only like `CheckpointStore.listByScope`), so this is the "看之前的"
     * core query. Newest first (`createdAt` DESC). `opts.status` filters to one lifecycle state; `opts.limit`
     * caps the result count (applied AFTER the sort, so it keeps the newest N). An empty scope returns `[]`.
     */
    /** `opts.session`: STORE-side push-down filter on
     *  `originatingSessionId` — strict equality, rows WITHOUT the field excluded (filter semantics,
     *  deliberately unlike the per-id acceptance arm's absent-pass). Applied BEFORE limit, so the
     *  serving layer's "this session's workflows" is FULL-history, not page-window (cli note). */
    listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
    /**
     * Retention sweep: delete OLD **terminal** runs in `scope` (a `running` run is NEVER deleted — design/97
     * §D.1/§F). Returns the count deleted (for metrics). Retention is by either policy (apply BOTH when both
     * given — a run is reaped if it fails either bound):
     *  - `opts.maxAgeMs`: delete a terminal run whose `endedAt` (else `createdAt`) is older than `now -
     *    maxAgeMs`.
     *  - `opts.keep`: keep only the newest `keep` terminal runs (by `createdAt` DESC); delete the rest.
     * With NEITHER option set, nothing is deleted (a no-op `0`) — retention is always an explicit policy.
     */
    reap(scope: string, now: number, opts?: {
        maxAgeMs?: number;
        keep?: number;
    }): Promise<number>;
}
/**
 * REF-A5 — THE optimistic-concurrency guard for {@link WorkflowRunStore.update}, shared by every backend:
 * scope in the WHERE (multi-tenant isolation — a wrong-scope update never wins), row must exist, and an
 * `expect.rev` that no longer matches the LIVE row loses (an intervening update bumped it). Returns the
 * row to persist, or `null` when the CAS lost; PURE — it neither reads nor writes any store state, so
 * each backend keeps its own persistence mechanism (in-memory `Map.set` vs ledger commit) around it.
 * RB-59 (a cross-instance CAS both instances won) landed on this predicate.
 */
export declare function nextWorkflowRunOnUpdate(cur: WorkflowRun | undefined, id: string, scope: string, run: WorkflowRun, expect?: {
    rev: number;
}): WorkflowRun | null;
/**
 * REF-A5 — THE `listByScope` query projection, shared by every backend: scope/status/session filter →
 * newest-first by `createdAt` → truncate → {@link summarizeWorkflowRun}. Pure over any row iterable, so
 * the in-memory backend passes its `Map` values and the file backend passes the replayed ledger's.
 */
export declare function queryWorkflowRuns(runs: Iterable<WorkflowRun>, scope: string, opts?: WorkflowRunListByScopeOptions): WorkflowRunSummary[];
/**
 * Default in-process {@link WorkflowRunStore}. Single-instance / tests only — it does NOT survive a restart
 * or span replicas. Single-threaded JS already serializes `update`, so the CAS is trivially atomic here;
 * `structuredClone` on the boundaries prevents aliasing (a later mutation of the caller's object — the live
 * `WorkflowRun` the workflow keeps mutating — must not corrupt the stored row, and a returned run must not
 * be a live reference the caller can mutate back into the store).
 */
export declare class InMemoryWorkflowRunStore implements WorkflowRunStore {
    private runs;
    put(id: string, run: WorkflowRun): Promise<void>;
    get(id: string): Promise<WorkflowRun | null>;
    update(id: string, scope: string, run: WorkflowRun, expect?: {
        rev: number;
    }): Promise<boolean>;
    listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
    reap(scope: string, now: number, opts?: {
        maxAgeMs?: number;
        keep?: number;
    }): Promise<number>;
    /** Test/inspection helper: number of stored runs. */
    get size(): number;
}
