/**
 * The terminal cause — the ONE record of how a run ended (`TaskResult.terminal`), its closed kind set,
 * the one disposition table over that set (which cause a resume journal may replay) and the admission
 * read a journal entry's cause passes through. Layer 0 vocabulary, beside `gate-outcome.ts` (the record
 * of one gate pass) and `pause-registry.ts` (the pause gate × pending-action pairing a paused cause
 * points at): no runtime import above this layer, so any module may read the words.
 *
 * The plane-word projection a face that still speaks `status` needs lives in
 * `runner/terminal-projection.ts` — the one cause→plane derivation.
 */
import type { AssertAllKeysHandled } from "./ask-origin.js";
/**
 * WHY a run ended — ONE tagged cause, and the ONLY terminal record a {@link TaskResult} carries. The run
 * loop's terminal arbitration used to decide a `status` word plus seven optional plane fields one by one
 * across an if/else-if ladder over competing optional flags, and the legal combinations
 * ("`status:"suspended"` ⇔ a token is present", "a review pause and an approval pause are never both set")
 * lived only in that ladder's order. Here they are the SHAPE: a paused run carries its token; a completed
 * run cannot. Branch on `kind`; the five-word {@link TaskStatus} a cause reads as (a `paused` cause reads
 * its gate kind's registry row) is derived by {@link import("./runner/terminal-projection.js").terminalProjection},
 * the one cause→plane derivation, for the faces that still speak the word.
 *  - `completed` — the run finished (a person's clean halt included — see `TaskResult.haltedByUser`).
 *  - `failed` — a limit, a provider failure, an abort, an invalid output…
 *  - `blocked` — the run ended without doing the work, and `detail` says by whom: ABSENT = the AGENT reported,
 *    through `ReportBlocked`, that it cannot finish, and why; `detail.kind === "prompt_blocked_by_hook"` = a
 *    `userPromptSubmit` hook kept the prompt from being submitted (see {@link TerminalBlockedDetail}). A TERMINAL
 *    cause: the leg is over and it waits for nobody (nothing to resume, no token). The waiting-on-a-person
 *    states are the `paused` cause's two status words — `suspended` (a pre-action approval of a tool
 *    call) and `needs_review` (a review pause); the task-registry face spells both as `parked`.
 *  - `paused` — a durable pause committed a checkpoint and the run is resumable.
 * A `GateOutcome` is the record of ONE gate pass (per call); a `TerminalCause` is the record of ONE run
 * (per leg); a paused run's `gate` and the park row's `gate.kind` are the same word, joined by the token.
 */
export type TerminalCause = {
    kind: "completed";
} | {
    kind: "failed";
    /**
     * Machine-readable failure code when the failure carried one — e.g. a `SessionError.code` such as
     * `"conflict"` (a cross-instance write lost the optimistic lock), or a Node error code. Lets a
     * caller branch programmatically (e.g. evict a stale cache + retry on `"conflict"`) instead of
     * matching `message` strings. Absent only for the failures that never had one (an abort with no
     * limit axis, no assistant message produced).
     *
     * Terminal codes use a **dotted namespace** so a caller can prefix-match a whole class:
     * `"limits.max_tokens_exceeded"` / `"limits.max_cost_exceeded"` / `"limits.max_turns_exceeded"` /
     * `"limits.max_walltime_exceeded"` / `"config.limit_invalid"` / `"config.limit_unknown_key"` /
     * `"config.attachment_invalid"` (a mode-valued `TaskSpec.attachments` member outside its closed
     * set, refused at the same door as the limits) (e.g. `code.startsWith("limits.")`). Brain codes
     * (`auth`/`network`/`rate_limit`/…) and `"conflict"` remain flat.
     *
     * Two codes name the EXTERNAL stop causes — neither is a `limits.` code, because neither is an
     * allowance the task chose, and a caller that retries on `limits.*` should NOT treat these the same:
     * - `"env.lifetime_expired"` — the execution environment's platform lifetime ran out and the run
     *   could not be suspended durably. Retrying needs a NEW environment, not a smaller budget.
     * - `"usage.window_exhausted"` — a deployment usage-governance window is full (see
     *   `RunnerDeps.usageWindows`). Reported at task ENTRY (nothing ran) or after a suspend was
     *   impossible; the wait hint rides the thrown error's `retryAfterMs` (delivered through
     *   `RunnerDeps.onError`) and the message text. Retrying before the window frees is refused again.
     * Their config-time siblings are `"config.env_lifetime_invalid"` / `"config.usage_window_invalid"`.
     * A governance window with a MONEY ceiling adds two more, both of which say "the ceiling could not
     * be evaluated" rather than "the ceiling was reached" — neither is retryable without a config change:
     * - `"config.usage_window_unpriced"` — a `UsageWindow.maxCostUsd` over a run with no cost figure
     *   (no `RunnerDeps.pricing` entry and no `Model.cost`).
     * - `"usage_window.store_cost_unanswered"` — the wired ledger does not carry the money arm.
     * A NESTED orchestrator that met a durable pause it cannot drive stamps `"unexpected.suspended"` /
     * `"unexpected.needs_review"` and carries the pause on {@link nestedPause}.
     */
    code?: string;
    /** Human-readable failure text. */
    message?: string;
    /**
     * A machine-readable account of the failure beside `code`, for the one shape a consumer offers its own way out
     * of: `provider_rejected` — the provider REFUSED the request as sent (a 4xx answer) and that refusal named this
     * result. `status` is the refusal's HTTP status (`TaskResult.apiFailure.status`, the same value);
     * `code` the provider's own word for it from its error envelope (`AssistantMessage.apiErrorCode`), when it
     * stated one. PRESENCE: only on that terminal — a run whose higher-ranked cause named the result, a 5xx, a
     * connect / stream failure, an in-band error inside a 200 and every non-provider failure leave it absent.
     * A 401 / 403 (credentials) and a 429 (rate limit) are refusals too: branch on `status` (or on `code` above)
     * to tell a request a consumer may change and resend from one it may not.
     */
    detail?: TerminalFailureDetail;
    /**
     * The durable pause this failure STANDS IN FOR — set only by the nested hard boundary
     * (`mapNestedSuspend`): an orchestrator's nested leg paused on a durable gate, the orchestrator cannot
     * drive a resume from the inside, so at ITS boundary the pause is a failure — but the pause's own
     * cause travels here verbatim so the top-level caller keeps the recovery capability
     * (`nestedPause.token` → `runner.resume`). Absent on every failure the engine itself assembles.
     */
    nestedPause?: PausedCause;
} | {
    kind: "blocked";
    /**
     * Why the run ended without doing the work — one of two sources, told apart by {@link detail}. With no
     * `detail`: the agent's own words from its `ReportBlocked` call. With `detail.kind ===
     * "prompt_blocked_by_hook"`: the engine's sentence about the deployment's `userPromptSubmit` hook, framed
     * as a reminder for the model-facing lane (the hook's own words, when it gave some, sit inside the
     * frame). Terminal: the run is over and nobody is being waited on (contrast `paused`, whose token is the
     * resume capability).
     */
    reason: string;
    /**
     * WHO stopped the run, for the one shape a consumer offers its own way out of: `prompt_blocked_by_hook` —
     * the prompt was NOT submitted (the objective never became a message and the loop never started), so
     * changing it and sending again is meaningful; `cause` says which of the hook's four endings it was.
     * PRESENCE: only on that terminal. ABSENT means the agent reported `ReportBlocked` ("I cannot go on"), or an
     * engine from before this key — branch on `detail?.kind`, never on the prose in `reason`.
     */
    detail?: TerminalBlockedDetail;
} | {
    kind: "paused";
    /**
     * WHICH pause — who/what must resume (e.g. `{kind:"human", reason, toolName}`), so the caller knows
     * what decision the checkpoint awaits and which resume outcome it takes (a `policy_ask` for an
     * approval gate; a `dry_run_review` on a `needs_review` gate; a `plan_review` on a `plan_review`
     * gate). Its kind's {@link import("./pause-registry.js").PAUSE_REGISTRY} row is what
     * {@link import("./runner/terminal-projection.js").terminalProjection} reads the status word from
     * (`"suspended"` for an approval / a resource slice / a platform pause, `"needs_review"` for a review).
     */
    gate: import("./checkpoint-store.js").CheckpointGate;
    /**
     * The durable-checkpoint token to resume this task with via `runner.resume(token, outcome)`.
     * Branded `CheckpointToken`; **never log it or put it in a URL** (it is the resume capability, §6).
     */
    token: import("./checkpoint-store.js").CheckpointToken;
    /**
     * The pause's NON-SECRET stable identity ({@link import("./checkpoint-store.js").Checkpoint.checkpointId})
     * — the display/correlation key a consumer may log or render where the token must not travel.
     * Absent on pre-identity checkpoints.
     */
    checkpointId?: string;
    /**
     * HOW the paused task's remote workspace will come back, present whenever the pause captured a
     * remote workspace. The two modes bill and behave very differently:
     * - `"snapshot"` — the workspace VM was `suspendVM`-paused into a snapshot. Provider billing
     *   typically stops; in-memory process state is captured; resume restores it.
     * - `"park_only"` — the env declared itself non-suspendable (an SSH host / an ADB device), so
     *   NOTHING was paused: the machine keeps running (and keeps costing), any in-memory process state
     *   is at the target's mercy, and resume simply reconnects to the still-present workspace.
     *   Deliberate and honest (core does not fabricate a snapshot) — but a scheduler that assumes
     *   "paused ⇒ idle and free" must be able to see it.
     * ABSENT for a process-local pause (a static caller-owned env: no remote workspace was captured).
     * The same discriminant rides {@link import("./checkpoint-store.js").CheckpointSummary} for the
     * inbox/`listByScope` face.
     */
    restoreMode?: "snapshot" | "park_only";
};
/** The machine-readable account a `failed` cause may carry ({@link TerminalCause}). `kind` is closed; one member. */
export interface TerminalFailureDetail {
    kind: "provider_rejected";
    /** The refusal's HTTP status, 400–499. */
    status: number;
    /** The provider's own word for the refusal (its error envelope's `error.code`, else `error.type`), when stated. */
    code?: string;
}
/**
 * The endings of a `userPromptSubmit` hook that keep a prompt from being submitted — the closed set of
 * {@link TerminalBlockedDetail.cause} words:
 * - `"block"` — the hook answered and refused the prompt (its words ride `hookReason`).
 * - `"crashed"` — the hook threw or rejected; the seat is fail-closed, a screen that failed has not cleared the prompt.
 * - `"timeout"` — the hook did not answer within its `timeoutMs` bound; fail-closed the same way.
 * - `"cancelled"` — the task was cancelled while the hook was still screening.
 */
export declare const PROMPT_BLOCK_CAUSES: readonly ["block", "crashed", "timeout", "cancelled"];
export type PromptBlockCause = (typeof PROMPT_BLOCK_CAUSES)[number];
/** Membership test for {@link PromptBlockCause}. */
export declare function isPromptBlockCause(v: unknown): v is PromptBlockCause;
/** The machine-readable account a `blocked` cause may carry ({@link TerminalCause}). `kind` is closed; one member. */
export interface TerminalBlockedDetail {
    kind: "prompt_blocked_by_hook";
    /** Which of the hook seat's endings kept the prompt back ({@link PROMPT_BLOCK_CAUSES}). */
    cause: PromptBlockCause;
    /**
     * The hook's own words for refusing the prompt, folded to one line, bounded, with the engine's envelope tags
     * defused — still the hook's text, so a consumer renders it as untrusted. PRESENT only on cause `"block"` and
     * only when the hook gave words; never carries the engine's reminder frame or its session mark (the framed
     * sentence is `reason`).
     */
    hookReason?: string;
}
/** The paused arm alone — the shape the run's committed-pause holder carries. */
/**
 * The one disposition table over `TerminalCause["kind"]`: which causes a resume journal may REPLAY
 * instead of re-running. Only a completed cause is a finished leg; every other cause means the leg has
 * work left (or failed) and a resume runs it live. The table is also the admission list for a journal
 * entry's cause at load: a kind absent here (a journal written before `TaskResult.terminal`, a corrupted
 * or version-skewed entry) is refused before anything is dispatched — it is never read as "not
 * completed, so run it again", which would replay the side effects of finished work.
 */
export declare const TERMINAL_CAUSE_IS_REPLAYABLE: {
    readonly completed: true;
    readonly failed: false;
    readonly blocked: false;
    readonly paused: false;
};
/** Compile-time fence: every `TerminalCause` kind has a row in the replay table (and, via `satisfies`, only those). */
export type ReplayTableCoversEveryTerminalCause = AssertAllKeysHandled<Exclude<TerminalCause["kind"], keyof typeof TERMINAL_CAUSE_IS_REPLAYABLE>>;
/** The admission read for a journaled cause: a closed-set member, by table row. */
export declare function isTerminalCauseKind(kind: unknown): kind is TerminalCause["kind"];
export type PausedCause = Extract<TerminalCause, {
    kind: "paused";
}>;
