UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

3,083 lines • 242 kB
import type { GateOutcome } from "./gate-outcome.js";
import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
import type { CheckpointPause } from "./pause-registry.js";
import { type SystemInjectionPriority } from "./task-notification.js";
import { type QuestionAnswer } from "./ask-question.js";
import type { ReadEntry } from "../tools/fs/safety.js";
import type { RepairBundle } from "../agents/repair-loop.js";
import type { WorkspaceHandle } from "./remote-env.js";
import type { SessionPermissionRules } from "./session-policy-store.js";
import type { NestedUsageAccum, PermissionMode, ShellGateDoctrine, TaskResult } from "./types.js";
import type { ActiveWorktreeSession } from "../internal/harness-types.js";
/**
 * design/45 — the **durable-checkpoint** primitive: cross-process / resumable `suspend`/`resume`.
 *
 * A {@link CheckpointStore} is the third pluggable durable seam (alongside `ToolResultStore` and
 * `MemoryStore`): a token-addressable store of "enough state to resume a suspended task", with an
 * **atomic CAS `resolve`** so a token is acted on **exactly once** even across replicas / double
 * approvals. Two profiles share this one seam (design/43 原语 4, proven by two service instances):
 *
 *  - **F4** (`gate.kind === "human"`) — a running task hit a tool-policy `ask` that no in-process
 *    approver can answer (headless / long / cross-replica), so it persists a checkpoint and returns
 *    `status:"suspended"`. Later `runner.resume(token, {gate:"policy_ask", decision})` continues it.
 *    This is the cross-process version of the synchronous `onAsk` gate (design/37).
 *  - **1C** (`gate.kind === "task_done"`) — design/38 Path A persists a background sub-task handle here
 *    so a caller can `get` its outcome across replicas. v1 stores the handle only; the parent does NOT
 *    mid-task suspend (Path A unchanged) — `runner.resume()` does NOT serve this gate in v1.
 *
 * **opt-in, default-off**: with no `CheckpointStore` wired (and no durable approval mode requested), a
 * policy `ask` still resolves the 1.63 way (synchronous `onAsk` / headless auto-deny) — behavior
 * unchanged. Default {@link InMemoryCheckpointStore} is process-only (single instance / tests); a
 * durable backend (service's `tidb-approval-store` / `tidb-run-store`) makes it cross-replica.
 *
 * See `design/45-durable-checkpoint原语-suspend-resume.md`.
 */
/**
 * A high-entropy, single-use checkpoint token. Branded so it can't be confused with a `sessionId` /
 * `taskId` (council Question #2): the token doubles as the resume capability (token-as-auth, §6), so a
 * mix-up would be a security bug, not just a type slip. Mint with {@link mintCheckpointToken}.
 */
export type CheckpointToken = string & {
    readonly __brand: "CheckpointToken";
};
/** Mint a CSPRNG 128-bit checkpoint token (token-as-auth, §6/Q5): unguessable, never logged/in-URL. */
export declare function mintCheckpointToken(): CheckpointToken;
/**
 * Mint the NON-SECRET stable checkpoint identity — identification and capability are separate
 * axes (settledBy/approvalId precedent): `checkpointId` is safe to log, render, and correlate across
 * frames (tool_approval cards, replay, resume, snapshots); {@link Checkpoint.token} stays the secret
 * resume capability and never rides a display surface. Minted independently (NOT a token digest —
 * a digest would make the id derivable only where the token is held, and the projection surfaces
 * that need it most are exactly the token-less ones). Legacy rows have no id (absent, additive).
 */
export declare function mintCheckpointId(): string;
/**
 * Who resumes a checkpoint, and how the resume `outcome` is interpreted. The `kind` is the discriminant
 * that {@link ResumeOutcome} must match at the resume entry (council #3: prevents a `task_done` gate
 * being resumed with a `policy_ask` outcome → type confusion / corruption).
 */
/**
 * Why a `resource_limit` suspend was minted. design/164 split the single `budget` member: a token
 * window and a $ window are different allowances with different owners, and a scheduler reading a
 * checkpoint inbox to decide "grant more of what?" could not tell them apart. The reasons match the
 * terminal codes the non-suspending tier reports (`limits.max_*_exceeded`), so one vocabulary covers
 * both tiers.
 *
 * **Consumers pinning the old closed set**: `"budget"` is gone — a switch on it now falls through.
 *
 * design/164 件四/件五 added two more members, and they are a DIFFERENT CLASS from the task axes above:
 * `"env_lifetime"` (the execution environment's platform lifetime is about to expire) and
 * `"usage_window"` (a deployment-level usage-governance window is exhausted) are EXTERNAL causes — the
 * task did not choose them and cannot avoid them by asking for less. They therefore suspend on the
 * INFRASTRUCTURE eligibility alone (checkpoint store + non-volatile offload + remote/no owned env),
 * without the `TaskSpec.resourceSuspend` opt-in the task axes require. When several causes land on one
 * turn boundary the attribution order is
 * `preempt > env_lifetime > usage_window > budget_tokens > budget_cost > turns > walltime`
 * (external causes above self-chosen allowances; preempt keeps the top seat it always had).
 * `"preempt"` is a scheduler's yield (`spec.preemptSignal`), not a limit, riding the same suspend: its resume continues the work.
 *
 * **Consumers pinning the closed set again**: a `switch` that was exhaustive over the five 5.x members
 * now falls through on these two.
 */
export type ResourceLimitReason = "budget_tokens" | "budget_cost" | "walltime" | "turns" | "preempt" | "env_lifetime" | "usage_window";
/**
 * design/164 件四/件五 — the EXTERNAL subset of {@link ResourceLimitReason}: causes that come from the
 * platform or the operator rather than from the task's own declared allowances. Written as an `Extract`
 * of the parent union so the two can never drift into disagreeing about what "external" means, and so a
 * seam that may only be reached by an external cause (the infrastructure-only suspend lane) cannot be
 * handed a task-axis reason by mistake.
 */
export type PlatformLimitReason = Extract<ResourceLimitReason, "env_lifetime" | "usage_window">;
/**
 * design/80 D-2: which SAFETY axis of THIS call caused an `ask` to mint an {@link CheckpointGate}
 * `irreversible_ask`. Derived by the gate (`safetyAxisOf`) from the tool's static marks plus the engine's own
 * per-call facts — never from a decision member a policy composes (forgeable). Persisted on the gate so a
 * network budget/escalation resolver reads WHY this is a safety ask FROM THE DURABLE RECORD. Both can be true.
 */
export interface SafetyAxis {
    /** design/70: the tool is egress-marked (`ToolSpec.egress` — an external write: push, open PR, send). */
    egress?: boolean;
    /** design/77 §4: an irreversibility MANDATE **of the TOOL's own** (its `always`/`maybe` tier); a SHELL SEAT raise is NOT this axis, see {@link shell}. */
    irreversible?: boolean;
    /** The engine's SHELL SEAT raised this ask. Its question is about TEXT — not about undoing an act — so
     *  the two axes are mutually exclusive. */
    shell?: boolean;
}
/**
 * design/80 §D-E: a DETERMINISTIC, REDACTED risk summary attached AT MINT to an escalation
 * {@link CheckpointGate} (`human` / `irreversible_ask` — the tool-call approval escalations) so a supervisor
 * INBOX can sort/triage by TRUE severity without re-deriving risk. **Display/triage metadata ONLY** — core
 * NEVER reads it to gate / budget / suppress anything (structural ask strictly wins via `combinePolicies`;
 * no mint suppression reads it). The profile/inbox READS it; core only ATTACHES it. Pure function of the
 * call (no clock/random) — same call ⇒ identical descriptor.
 */
export interface RiskDescriptor {
    /** ToolEmu-style severity tier 1..5 (5 = most severe). The inbox sorts DESC by this. Deterministic — the
     *  pure {@link riskSeverity} of {@link axes}. The ORDERING is what matters (the inbox's triage key). */
    severity: 1 | 2 | 3 | 4 | 5;
    /** Which safety axes tripped — for `irreversible_ask`, derived from the D-2 {@link SafetyAxis} (+ the shell
     *  seat). `shell` marks a tighten by the engine's shell seat (read-boundary judge and/or `shellGate` doctrine,
     *  NO explicit per-tool egress/irreversible mark). Empty `{}` for a plain budgetable `human` ask. */
    axes: {
        egress?: boolean;
        irreversible?: boolean;
        shell?: boolean;
    };
    /** The tool whose call is gated (mirrors the gate's `toolName`). */
    toolName: string;
    /** The operator's shell floor (`"always"`), present ONLY when this ask was minted through the engine's shell
     *  seat AND a floor was in force. Absent for a shape-tier ask with no floor, and for per-tool safety-axis /
     *  plain policy asks. Answers "was an operator floor live when this gate fired" from the persisted row alone. */
    shellGateDoctrine?: ShellGateDoctrine;
    /** A REDACTED, length-capped ONE-LINE summary of the call (the command for a shell gate; a brief key-arg
     *  digest otherwise) for the inbox preview. NEUTRALIZED via
     *  {@link import("./permission-rule-model.js").renderUntrustedCommandText} + length-capped (it is
     *  persisted + shown to a human inbox, so a crafted tool arg carrying a `</system-reminder>` variant /
     *  newline / huge string must NOT inject into the render or bloat storage — and, since #438, a
     *  directional/zero-width `\p{Cf}` marker cannot ride into the render either: the display baseline
     *  STRIPS the format class, so what the line shows is what the args carry, in the order they carry it).
     *  GENERATIONAL, like every mint-time seat: the summary is minted ONCE at park mint and persisted, so
     *  rows minted before the #438 render change keep their old bytes (which may carry `\p{Cf}`) forever —
     *  a consumer must not assume one byte-form across row generations, and the display obligation
     *  (render through the baseline, or screen) is the same for both. One benign visible consequence on
     *  new rows: an emoji ZWJ sequence in a command displays decomposed here (raw bytes stay in
     *  `pendingAction.args`/`toolInput` — this seat is DISPLAY, not identity).
     *  NEVER raw secrets / full args / env. Deterministic. Optional (omitted when nothing safe to summarize). */
    summary?: string;
    /** Best-effort file paths the action touches (fs-tool `path` args; shell parsing is deliberately NOT
     *  attempted — over-reaching a shell parse risks a wrong/forgeable path). Each path rendered through the
     *  same {@link import("./permission-rule-model.js").renderUntrustedCommandText} baseline as
     *  {@link summary} (a path name can carry the same reordering hazard as a command) + capped.
     *  Omitted when none derivable. */
    touchedPaths?: string[];
    /** The allow-layer members that MATCHED this call and could not clear the ask — one record per member (the member and
     *  the entries it matched), threaded to the mint so the durable-park route carries the same disclosure the synchronous
     *  card does: the inbox renders "their rule is alive, just outranked" instead of the person concluding it silently broke.
     *  Each entry is rendered through the same display baseline as {@link summary} and capped (the bytes may be a person's
     *  stored text, a skill's or a deployment's list — model-authored or a custom provider's — and can carry `\p{Cf}`; this
     *  durable seat strips what the synchronous card keeps, the safe direction, and carries no cross-face same-value
     *  contract, so a byte-pinning consumer reads per face). */
    allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
    /** WHY the reversibility probe did not clear this call, in the probe's own
     *  words — the cause the tighten acted on, which until now existed only inside the probe and left the
     *  card showing WHAT is gated with no account of WHY. Present only for a `"maybe"`-tier tighten whose
     *  probe supplied a cause (an `"always"`-tier tighten runs no probe; a timeout/throw yields no verdict).
     *  Neutralized + capped at {@link PROBE_REASON_MAX} — it is deployment-authored text on a human trust
     *  boundary, so it is DISPLAY/TRIAGE metadata only, never adjudication input.
     *
     *  The UNSTRUCTURED face: a deployment's own probe writes prose here and this engine cannot say what it
     *  means. An engine-minted cause uses {@link probeCause} instead — see that field for why.
     *
     *  RESIDUAL, disclosed (#438): unlike the descriptor-owned display family above, this seat does
     *  NOT strip the `\p{Cf}` display-reordering class — a directional/zero-width marker in the
     *  probe's prose survives here byte-for-byte, because the seat carries a documented SAME-VALUE
     *  contract with its synchronous `AskRequest.probeReason` twin (one string, two faces) and a
     *  one-face re-render would break it. A consumer rendering this seat owes its own screen (or the
     *  descriptor family's display baseline). */
    probeReason?: string;
    /** The STRUCTURED cause, for a probe whose verdict this engine understands —
     *  the built-in shell probe is the first. Carries a machine-readable {@link ProbeCause.code} and the
     *  operand families as ARRAYS with honest totals, so a consumer renders its own sentence in its own
     *  language instead of receiving one this engine had to write in advance.
     *
     *  The prose sibling above is what this replaces for engine-minted causes, and the reason is not
     *  aesthetic: a sentence asserting what a command DOES is false for the demotion's documented
     *  over-fire, a sentence asserting what its OPERANDS are is false when the check synthesizes one, and
     *  serializing a variable-length multi-part disclosure into one length-capped string silently deletes
     *  whichever part sorts last. A code cannot be false and an array cannot be truncated into a lie. */
    probeCause?: ProbeCause;
}
/** One operand family inside a {@link ProbeCause} — the entries a card may NAME, plus how
 *  many the family really has. `shown.length` ≤ {@link total}, and a consumer that renders only `shown`
 *  is still obliged to tell the person when `total` exceeds it: the count is the part that cannot be
 *  cut, and "there is a second unreviewed operand" is exactly what a reader cannot recover from the
 *  command text. */
export interface ProbeCauseOperands {
    /** The named entries. Each `inlineUntrusted`-neutralized and capped at {@link PROBE_CAUSE_PATH_MAX}
     *  INDIVIDUALLY, so one pathological entry costs only itself. Bounded in count by the minting probe.
     *  RESIDUAL, disclosed (#438): `inlineUntrusted` does NOT strip the `\p{Cf}` display-reordering
     *  class the descriptor's own display family strips — a directional/zero-width marker in a path
     *  survives on this seat (cross-face byte parity with the synchronous `AskRequest.probeCause`
     *  twin; see that same-value contract for why a one-face re-render is off the table). A consumer
     *  rendering these entries owes its own screen (or the descriptor family's display baseline). */
    readonly shown: readonly string[];
    /** How many entries the family has in total — `shown.length` when nothing was elided. A non-negative
     *  integer; never a lie by omission. */
    readonly total: number;
}
/**
 * An ENGINE-MINTED, structured account of why a reversibility probe did not clear a call.
 *
 * DISPLAY/TRIAGE metadata, exactly like the rest of {@link RiskDescriptor} — nothing reads it to gate,
 * budget or suppress anything, and it deliberately never reaches the ask's `message` (that string is
 * handed to the auto-mode classifier, which may answer an ask with `allow`; a probe must not own an
 * input to the decider that clears it).
 *
 * STRUCTURE IS THE CONTRACT. The entries are model-influenced text, so a consumer must render them as
 * DATA — never re-derive structure by splitting or joining them. Structural characters are
 * percent-encoded at the mint precisely so that a path cannot spell whatever grammar it is rendered
 * into, but the array is the boundary that makes that guarantee cheap to keep.
 */
export interface ProbeCause {
    /** Machine-readable cause id, `<domain>.<snake_case>` like this codebase's other notice codes. The
     *  consumer maps it to its own prose. A code is the one part of a cause that cannot become false as
     *  the check's edge cases are discovered. */
    readonly code: string;
    /** The paths the check TOOK as traversal roots — deduplicated and resolved, which is why they are
     *  described by what the check did with them rather than by what the caller wrote: a lexical operand
     *  walk has no option-value table (`du -d 2 sub` takes `2`), and it synthesizes the working directory
     *  when no operand is written at all (`du --help`). */
    readonly roots: ProbeCauseOperands;
    /** Operands the SAME verdict left unreviewed for a different reason (an unexpanded glob). Present only
     *  when non-empty. Named separately so a card can say what else this one approval clears — a partial
     *  account of an approval's reach is worse than none. */
    readonly further?: ProbeCauseOperands;
}
/** The bloat ceiling on {@link RiskDescriptor.probeReason} — re-exported from the leaf neutralizer module
 *  for the same reason its `PROBE_CAUSE_PATH_MAX` sibling is homed there (a value import from tools/fs
 *  back into this module closes an ESM cycle), so mint, gate and persist read ONE cap. */
export { PROBE_REASON_MAX } from "./untrusted-text.js";
/**
 * The ONE validator for a {@link ProbeCause} arriving from a probe.
 *
 * The value crosses a trust boundary twice — once at the gate (where a deployment-supplied probe hands
 * it over) and once at the persist boundary (where {@link buildRiskDescriptor} is an exported function a
 * caller may reach directly) — so both call it rather than each writing its own idea of "valid". A
 * malformed cause yields `undefined`: the ASK still stands (the verdict was never this member's to
 * decide), the card simply carries no account. Fail-closed on the disclosure, never on the gate.
 *
 * Every field is re-derived rather than trusted: strings are neutralized and capped individually, the
 * shown list is clamped, and `total` is coerced to a non-negative integer that cannot understate what is
 * shown (a `total` below `shown.length` would turn an honest count into a false reassurance).
 */
export declare function normalizeProbeCause(value: unknown): ProbeCause | undefined;
/**
 * design/80 §D-E: the DETERMINISTIC severity tier (1..5) for an escalation checkpoint, a PURE function of the
 * tripped {@link RiskDescriptor.axes} (NO LLM, NO clock/random) so the inbox's sort order is stable and
 * unit-testable in isolation. The ORDERING is the contract; the absolute numbers map to ToolEmu's 5 tiers:
 *  - irreversible AND egress → **5** (the most severe — an external, irreversible write)
 *  - irreversible only       → **4**
 *  - egress only             → **3**
 *  - shell-gated tighten     → **3** (a coarse shellGate ask with no explicit egress/irreversible mark — the
 *                                     command MAY be benign, so it is not auto-graded as high as a marked tool)
 *  - plain `human` ask       → **2** (no safety axis tripped — a budgetable approval)
 *  - (1 reserved — least-severe / informational; not expected from these gates.)
 * `egress` dominates the shell coarse-grade (an explicitly egress-marked shell tighten is still ≥3). A future
 * arm may refine this — but only with a stated reason, and the ORDERING must stay monotone in risk.
 */
export declare function riskSeverity(axes: {
    egress?: boolean;
    irreversible?: boolean;
    shell?: boolean;
}): 1 | 2 | 3 | 4 | 5;
/** design S1e: the char cap for the {@link CheckpointSummary.toolInput} BOUNDED raw preview of a
 *  `tool_approval` pendingAction's `args` (`JSON.stringify`-ed). Caps the `listByScope` payload size; over-cap
 *  is truncated with a trailing `…`. Bounded raw (NOT neutralized) — redaction is the consumer's job (echo-only). */
export declare const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
/**
 * HRD-APV-4: the char cap {@link validatePendingSteer} enforces on a parked operator steer — the last
 * persisted, model-facing string on a checkpoint row that had none.
 *
 * Sized as an INSTRUCTION budget, not a preview budget: the siblings above cap echo-only projections at
 * 200/512 chars, while a steer is prose a human wrote for the model to act on and is re-delivered on
 * every resume of the checkpoint. 16k chars (~4k tokens) holds any realistic operator instruction and
 * still bounds the durable row and the per-resume context cost. Exported so a BFF can validate before it
 * calls {@link CheckpointStore.setPendingSteer} rather than discovering the reject at persist time.
 */
export declare const MAX_PENDING_STEER_CHARS = 16000;
/**
 * design/171 §6.3 — the BYTE budget the whole parked-steer queue must fit inside, measured on its JSON
 * serialization (the form every backend actually persists).
 *
 * 48,000 bytes sits under a MySQL `TEXT` column's 65,535-BYTE ceiling with room for UTF-8 expansion: the
 * per-entry cap above counts CHARACTERS, and 16,000 CJK characters serialize to ~48,000 bytes, so a
 * char-only cap does not bound the column at all. Both limits are enforced and either one can bind.
 */
export declare const PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48000;
/**
 * design/171 §6.3 — the queue's entry-count cap, DERIVED from the byte budget and the per-entry char cap
 * (how many maximum-size entries the budget can hold). It bounds the count independently of the byte
 * measure so a flood of tiny steers cannot grow the row unboundedly through per-entry overhead alone.
 * Exceeding either limit is a fail-loud `steering.queue_full` — never a silent drop of the oldest entry
 * (a dropped operator instruction is exactly the failure this queue exists to remove).
 */
export declare const MAX_PENDING_STEER_ENTRIES: number;
/**
 * design/171 §6.3 — the FROZEN field set of a persisted {@link PendingSteerEntry}. `validatePendingSteer`
 * copies by this set and REFUSES an unknown key, so a producer one release ahead of this worker gets a
 * loud rejection instead of having its new field silently dropped on the parked leg. Consumers (other
 * repos' backends) read it to assert they round-trip every field rather than hand-listing their own copy.
 */
export declare const PENDING_STEER_FROZEN_FIELDS: readonly ["text", "trusted", "actor", "seq", "inputId", "priority"];
/**
 * design/171 §6.1 — WHO a piece of human input came from, as an attribution label. ONE definition:
 * the shape lives at its layering-legal home (`engine/llm/types.ts`, where `UserMessage.actor`
 * carries it and the engine cannot import core) and this module re-exports it — the live message
 * seat and the parked entry can never drift apart. Normative clauses (never authority; ingress-only
 * `hostAsserted`) are on the definition.
 */
import type { ActorAssertion } from "../internal/llm.js";
export type { ActorAssertion } from "../internal/llm.js";
/**
 * design/171 §5.3/§6.3 — one entry of the parked-steer QUEUE. Replaces the former single seat, whose
 * last-writer-wins overwrite silently destroyed an undelivered operator instruction whenever two people
 * steered the same suspended run.
 */
export interface PendingSteerEntry {
    /** The operator's instruction text (validated exactly like the pre-queue single seat). */
    text: string;
    /** Frozen at append time from the SERVICE's verified-principal check; never recomputed on resume. */
    trusted: boolean;
    /** Attribution only (see {@link ActorAssertion}) — absent = the caller supplied none (anonymous). */
    actor?: ActorAssertion;
    /** Queue-monotonic ordering key, minted by the store on append (first entry = 1). Drain order. */
    seq: number;
    /** Caller-supplied correlation/idempotency id (a service passes the message id it already minted);
     *  a uuidv7 is minted when absent. Re-appending the SAME `inputId` is a no-op, which is what keeps
     *  `setPendingSteer` retry-safe now that it appends instead of overwriting. Because the id lives on
     *  the PERSISTED entry and every replayed drain of this row carries it verbatim, `(notice code,
     *  sessionId, detail.inputId)` is the parked family's cross-replay OCCURRENCE key — consumers dedup
     *  on it directly; no per-delivery key exists or is needed. (Write-path guarantee: a row written by
     *  a non-conforming store may lack it, and read points do not re-validate.) */
    inputId: string;
    /** Carried VERBATIM for the serving layer; it does NOT reorder the drain (drain is `seq` order). */
    priority?: SystemInjectionPriority;
}
/** design/171 §6.3 — the caller-supplied half of a {@link PendingSteerEntry}: everything except `seq`,
 *  which only the store may mint. `inputId` is optional on the way in (minted when absent). */
export interface PendingSteerInput {
    text: string;
    trusted: boolean;
    actor?: ActorAssertion;
    inputId?: string;
    priority?: SystemInjectionPriority;
}
/** True ONLY for a PLAIN data record (not null / array / class-instance / proxy-with-odd-prototype) — the only
 *  shape we safely digest. Adversarial args (a proxy, a getter that throws) must never crash the mint, so this +
 *  {@link safeDataValue}/{@link safeKeys} make {@link buildRiskDescriptor} fully TOTAL. */
export declare function isPlainRecord(x: unknown): x is Record<string, unknown>;
/** Read `obj[key]` as a DATA property only — NEVER invokes a getter/accessor (so a throwing getter can't escape
 *  AND a non-deterministic getter can't vary the digest); a trapped descriptor read ⇒ undefined. */
export declare function safeDataValue(obj: Record<string, unknown>, key: string): unknown;
/**
 * design/80 §D-E: build the DETERMINISTIC, REDACTED {@link RiskDescriptor} for an escalation checkpoint at
 * MINT. Pure — a function ONLY of (`toolName`, `args`, the D-2 `safety` axis, the `shellGated` flag); it reads
 * NO clock/random, so the SAME call ⇒ an IDENTICAL descriptor (pinned by a test).
 *
 * **Determinism contract = plain-DATA args** (the real flow: model-JSON / hook-rewritten plain objects). A
 * Proxy whose `ownKeys` trap returns a DIFFERENT key set per call is OUT OF CONTRACT — JS cannot detect a
 * Proxy, so its (display-only) digest may vary. This NEVER affects a security/budget/mint
 * decision: `riskDescriptor` is INERT (no core path reads it to gate), so an out-of-contract input can at worst
 * degrade an inbox preview, never a permission outcome.
 *
 * **Redaction is load-bearing** (the `summary`/`touchedPaths` are PERSISTED + surfaced to a human inbox):
 * every model-controlled value in the DESCRIPTOR-OWNED display family (`summary` both arms,
 * `touchedPaths`, `allowRuleShadowed`) goes through
 * {@link import("./permission-rule-model.js").renderUntrustedCommandText} — which STRIPS the `\p{Cf}`
 * format class (directional controls / zero-width formatters, the display-reordering hazard the
 * `hasBidiControls` bit warns about; #438) and then applies the shared inline form (folds CR/LF/Unicode
 * separators to one space, defuses `</system-reminder>` variants + `<<<`/`>>>` fence sentinels) — AND a
 * length cap, so a crafted arg carrying a break-out tag / newline / huge string / RLO can neither inject
 * into the inbox render nor display as something other than what runs nor bloat the durable row.
 * TWO SEATS ARE DELIBERATELY NOT IN THE FAMILY: `probeCause` and `probeReason` carry a documented
 * SAME-VALUE contract with their synchronous `AskRequest` twins (their docs say so verbatim), so
 * changing their bytes here alone would break that cross-face contract — they keep the shared
 * `inlineUntrusted`/`normalizeProbeCause` treatment (a residual their docs disclose). NEVER dumps full
 * args / env / secrets — only a bash command string or a short `name=value` digest of the SHOWN args.
 *
 * `summary` for a shell gate = the `command` string (capped 200 cp); otherwise a `name=value` digest of the
 * call's top-level string/number/boolean args (each value capped), omitted when nothing safe to summarize.
 * `touchedPaths` reads ONLY the obvious fs `path` arg (read_file/edit_file/write_file) — shell parsing is
 * deliberately NOT attempted (over-reaching a shell parse risks a wrong/forgeable path, so OMIT for bash).
 */
export declare function buildRiskDescriptor(input: {
    toolName: string;
    args: unknown;
    /** The D-2 {@link SafetyAxis} threaded to the mint (egress/irreversible), or `undefined` for a plain ask. */
    safety?: SafetyAxis;
    /** True ONLY when this is a shell call whose tier is the engine's shell seat's (read-boundary judge and/or
     *  `shellGate` doctrine, no explicit per-tool egress/irreversible mark) — drives the `shell` axis + severity 3. */
    shellGated?: boolean;
    /** The resolved doctrine to persist when `shellGated` (see {@link RiskDescriptor.shellGateDoctrine}). */
    shellGateDoctrine?: ShellGateDoctrine;
    /** The matched-but-not-cleared allow-layer members to persist (see {@link RiskDescriptor.allowRuleShadowed}). */
    allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
    /** The reversibility probe's cause for not clearing this call (see
     *  {@link RiskDescriptor.probeReason}). Re-neutralized here like its shadowed-rule sibling: this
     *  function is exported, so a caller reaching it directly must get the same redaction the gate applies. */
    probeReason?: string;
    /** The STRUCTURED cause (see {@link RiskDescriptor.probeCause}). Re-validated here for
     *  the same reason the prose sibling is re-neutralized — this function is exported, so a caller
     *  reaching it directly must not be able to persist a shape the gate would have rejected. */
    probeCause?: unknown;
}): RiskDescriptor;
/**
 * The durable record of an ask's `requiresRealApproval` bit, which used
 * to DIE at the park: the mint keyed the gate kind on the static tool marks only, so an org-governed
 * ask on an unmarked tool minted a plain `{kind:"human"}` — the one kind a network budget resolver may
 * auto-approve — and the org's "only judgment clears this" demand was silently budgetable after the
 * durability boundary. Present ⇒ the mint escalates to `irreversible_ask` (non-budgetable) and stamps
 * {@link REAL_APPROVAL_CHECKPOINT_VERSION}. `origin` additionally records WHY, for the resume belts:
 *  · `"org_rule"` — an org ASK rule fired (governance was READABLE at mint). Resume keeps the strict
 *    posture: org unavailable at resume still refuses (newly-blind ⇒ fail-closed).
 *  · `"org_unavailable"` — governance could NOT be read at mint and the gate's own message promised
 *    "every allow tightens to a real approval until it can". A person approving THIS park IS that real
 *    approval — so a resume that finds org still unavailable executes instead of refusing and burning
 *    the approval (the park-only livelock #131 closed). The durable row itself — this origin plus the
 *    recorded human outcome — is the audit record of that passage; the resume emits no extra notice.
 *  · `"policy"` — a policy/hook minted the bit (e.g. the always-on classifier-parity rule); no org
 *    semantics, the resume belts treat it as a plain non-budgetable approval.
 * Both org origins double as the #120 governed mark: a resuming worker with NO org adjudication wiring
 * refuses to redeem such a row (the governed boundary must not vanish with a deployment's wiring).
 */
export interface RealApprovalGateBit {
    /** The parked ask's ORIGIN word — a projection of the same closed set the card, the observer and the
     *  `tool_end` frame carry ({@link import("./ask-origin.js").AskOrigin}), never a second vocabulary. It
     *  is the ask's ROUTING word: a marked call reads `ancestor_marked` even when the org governs it, because
     *  the ladder routes a marked call to the park before anything else. Rows minted before the projection
     *  carried a three-word subset of this set (`org_rule` / `org_unavailable` / `policy`), so every stored
     *  value is still a member. */
    origin: import("./ask-origin.js").AskOrigin;
    /** The ORG FACT the ask was minted under (`"rule"` = an org rule asked, `"unavailable"` = org adjudication
     *  unreachable), kept beside the routing word because routing can outrank the word (a marked call reads
     *  `ancestor_marked`) while the governance stays. The #120 governed mark and the resume belts read THIS
     *  member through {@link realApprovalOrgFact} (absent on rows minted before it: the org words imply it). */
    org?: "rule" | "unavailable";
}
/** The ONE read of a bit's governed fact (the member; the two org origin words for rows minted before it). */
export declare function realApprovalOrgFact(bit: RealApprovalGateBit | undefined): "rule" | "unavailable" | undefined;
export type CheckpointGate = 
/** F4: a human (or any external authority) must allow/deny a pending tool call. design/80 §D-E:
 *  carries an OPTIONAL display-only {@link RiskDescriptor} (severity/axes/summary) for the supervisor
 *  inbox to triage by — INERT (core never reads it to gate/budget). */
{
    kind: "human";
    reason: string;
    toolName: string;
    riskDescriptor?: RiskDescriptor;
}
/** design/77 §4 (Gate 4) + design/80 D-2: a PRE-ACTION human approval before a SAFETY-tightened tool runs —
 *  an IRREVERSIBLE tool (send money/email, file a return) OR an EGRESS tool (push, open PR, send). Same
 *  approval family as `human` (a pending tool call to allow/deny → suspendRef → `status:"suspended"`, resumed
 *  with a `policy_ask` outcome). **design/80 D-2 (load-bearing):** minted whenever an `ask` is for a tool
 *  carrying the egress / irreversibility (`always`/`maybe`) SAFETY MARKS — regardless of how the ask arose
 *  (a gate tighten OR a policy/hook that already asked; round-1 council fix: keying on the per-call
 *  `decisionReason` missed the policy-ask case AND was forgeable) — **EVEN WHEN `durableApproval` is wired**
 *  (`durableApproval` supplies scope/ttl, NOT the kind). The DISJOINT-from-`human` kind is what a
 *  network budget resolver keys on to NEVER auto-approve a safety ask (a budget may only down-budget a plain
 *  `human` ask). {@link SafetyAxis} records which axis(es) tightened. NOT a dry-run review. */
 | {
    kind: "irreversible_ask";
    reason: string;
    toolName: string;
    safetyAxis?: SafetyAxis;
    realApproval?: RealApprovalGateBit;
    riskDescriptor?: RiskDescriptor;
}
/** design/74: a resource slice limit (budget/walltime/turns) was reached — suspend (resumable) instead of
 *  fail. There is NO pending tool to adjudicate; resume just continues the run with the next slice's
 *  allowance (sized from {@link Checkpoint.resourceLedger}). The matching {@link ResumeOutcome} arm is
 *  `{ gate: "resource_limit"; decision: "continue" }`.
 *
 *  design/164 件五: `resumeAfterMs` is the RESUME-TIMING hint that pairs with {@link Checkpoint.deadline}
 *  — how long the driver should wait before resuming, because resuming sooner would only re-suspend.
 *  **In-scope condition**: present ONLY for `reason: "usage_window"` (the one cause that knows when it
 *  clears); every other reason omits it, so a consumer must not read its presence as "this is
 *  resumable". Additive on the existing checkpoint version — a reader that ignores it is unchanged. */
 | {
    kind: "resource_limit";
    reason: ResourceLimitReason;
    resumeAfterMs?: number;
}
/** design/76 §2.5 (dry-run / shadow): a POST-PREDICTION REVIEW pause. A profile's dry-run interception ran a
 *  predicted action and produced a buffered state-diff a human (or judge) must REVIEW before it is applied —
 *  so the task suspends to the durable `needs_review` TERMINAL (`TaskStatus:"needs_review"`) instead of
 *  finishing. **DISJOINT from the approval family** (`human`/`irreversible_ask`): those are PRE-ACTION
 *  *approvals* (suspendRef → `status:"suspended"`, resumed with a `policy_ask` outcome); this is a
 *  POST-prediction *review* (reviewRef → `status:"needs_review"`, resumed with a `dry_run_review` outcome).
 *  The split is load-bearing — reusing the approval family would make assemble-result report `"suspended"`
 *  and the `needs_review`/`review.pending` branch dead code (v4 MAJOR-A). It is ALSO disjoint from the
 *  `RepairTerminal.needs_human_oracle` (design/78) — a different type space; never cross-use the two.
 *  The state-diff itself is a PROFILE concern (a REF, not stored here — design/76 §9 / §2.5); core only
 *  owns the gate/status/discriminant plumbing. NOT a pre-action approval. */
 | {
    kind: "needs_review";
    reason: string;
}
/** design/80 D-B (plan-gate): a PRE-ACTION human PLAN REVIEW. Before an agent acts on a high-blast-radius
 *  plan (a profile's plan-gate fires on shell⇒gate + an irreversible/egress/finance mark present + a
 *  blast-radius trigger — NOT every task), the run pauses so a human can **approve / edit / reject the
 *  proposed PLAN** before any step runs. It is its OWN gate kind (r2 ruling — do NOT reuse `needs_review`,
 *  whose gateMatch arm forces a `dry_run_review` outcome; reusing it would make a plan resume a
 *  type-confusion mismatch), but it shares the **review-pause** machinery: it routes to `reviewRef` +
 *  `status:"needs_review"` (a human-review pause), resumed with a `plan_review` outcome.
 *
 *  **THREE-WAY SPLIT (load-bearing):**
 *   - the APPROVAL family (`human`/`irreversible_ask`) → PRE-ACTION *approvals* of a pending TOOL CALL
 *     (suspendRef → `status:"suspended"`, resumed with a `policy_ask` outcome bound to a tool call);
 *   - `needs_review` → a POST-prediction *review* of a buffered state-diff (reviewRef →
 *     `status:"needs_review"`, resumed with a `dry_run_review` outcome);
 *   - `plan_review` (this) → a PRE-ACTION *review* of a PLAN (reviewRef → `status:"needs_review"`, resumed
 *     with a `plan_review` outcome). It binds NO tool call — the human reviews the PLAN, not a pending arg —
 *     so it carries no `tool_approval` pendingAction (its pendingAction is `{kind:"plan_review"}`, with no
 *     tool fields) and the `policy_ask` decision-action binding machinery never touches it. The plan/diff
 *     artifact is a PROFILE concern (a REF, not stored here — design/76 §9); core owns the
 *     gate/status/discriminant plumbing only. NOT a pre-action approval, NOT a dry-run review. */
 | {
    kind: "plan_review";
    reason: string;
}
/** 1C: the checkpoint tracks a background sub-task's completion (design/38 Path A). v1 = handle only. */
 | {
    kind: "task_done";
};
/**
 * The outcome a caller supplies to `runner.resume(token, outcome)` — a **discriminated union** keyed by
 * `gate`, validated against `checkpoint.gate.kind` at the entry (council #3). **v1 implements only the
 * `policy_ask` arm**: `task_done` is a dead branch under 1C Path A (the caller orchestrates, never
 * mid-task `resume()`), typed here for completeness and the v2 join-suspend option.
 */
export type ResumeOutcome = {
    gate: "policy_ask";
    /**
     * design/80 D-1 (decision-action binding): the `toolCallId` the human actually saw/approved. The
     * resume is REJECTED (`checkpoint.invalid_outcome`, fail-closed, pre-CAS) unless it matches the
     * checkpoint's pending tool call (`pendingAction.toolCallId`) — this closes the TOCTOU re-suspend
     * wrong-apply: a stale `yes` minted against pending call X must not resolve a DIFFERENT pending call
     * Y (approve vendor-A $5k applied to vendor-B). The decision must name the action it bound to; the
     * runner never trusts that the caller matched the correct pending call (design/77 doctrine —
     * deterministic structural backstop, not a model self-report).
     *
     * The correct value is the resumed checkpoint's `pendingAction.toolCallId`; the caller has it via the
     * `suspendRef` / pending record it is answering.
     */
    boundCallId: string;
    /**
     * design/80 D-1 §2 (slice 1a.2): the **server-minted opaque** hash of the pending tool call's input
     * that the human actually saw/approved (`PendingAction.tool_approval.boundInputHash`, computed by the
     * engine at suspend-mint via {@link import("./canonical-json.js").boundInputHashOf} over the post-hook
     * `args`). The caller echoes it VERBATIM — it is opaque; the SDK/service NEVER re-canonicalize args
     * (a second runtime's serialization could diverge → false mismatch → fail-closed a
     * legitimate approval). The resume verifies it by **string equality** against the checkpoint's
     * persisted value (`checkpoint.invalid_outcome`, fail-closed, pre-CAS) — closing the TOCTOU "same call
     * id, different input" variant that `boundCallId` alone misses (a re-mint that swapped the input under
     * the same tool-call id). It binds the SHOWN input, NOT `updatedInput`: an `allow` edit is the same
     * operator's authorized rewrite, applied AFTER this binding check (design/37,末位应用), so the echoed
     * hash is always the pending record's value regardless of any edit. (Legacy pre-1a.2 checkpoints have
     * no persisted hash → the resume skips this check, binding on `boundCallId` alone; new mints enforce.)
     */
    boundInputHash: string;
    /** `allow` → execute the pending tool call; `deny` → inject a denial result and continue. */
    decision: "allow" | "deny";
    /** A re-written arg payload (design/37 policy `allow` rewrite); re-validated on execute. */
    updatedInput?: unknown;
    /** Model-readable reason attached to a `deny` (else a default is used). */
    reason?: string;
    /**
     * The ANSWER to a CONTENT-ASK (the reserved AskUserQuestion tool), carried by the decide itself
     * (ruled 2026-08-04). A content-ask is a question TO the user: `allow` alone says "let the tool
     * run" but supplies nothing to run it AGAINST, and the only pre-existing answer seat was the
     * resumed leg's `onQuestion` config closure — a seat an OFFLINE background child has no operator
     * on, because its resume config is rebuilt from the parked row rather than handed in. With the
     * answer on the decision payload, the same `/decide`-shaped call serves both arms: the resume
     * binds it as the resumed leg's answering face, so the pending call executes against the SAME
     * tool, the SAME `selected ⊆ options` fence and the SAME untrusted free-text framing (design/80
     * D-F) as a live answer — one support surface, not two.
     *
     * TRUST: untrusted wire data. It is NEVER read as instructions — the tool splits it into the
     * trusted selections (which must be the model's own option labels) and a fenced free-text
     * remainder. Off-list `selected` values and `note` are exactly the "Other" free-text path.
     *
     * ARM-SCOPED, like `updatedInput`/`reason` on this same arm: only a `policy_ask` `allow` whose
     * pending call IS the reserved tool may carry one. Supplying it anywhere else is rejected
     * pre-CAS (`checkpoint.invalid_outcome`, `detail.field: "answer"`) rather than silently dropped,
     * and OMITTING it on a content-ask allow with no live answering face is rejected the same way —
     * consuming the approval to hand the model a fabricated "no human available" default is the
     * silent-continue this seat exists to end.
     */
    answer?: QuestionAnswer;
    /**
     * The host's DECISION FACTS — who ended this wait: a person (`"person"`, the decide an operator made
     * through the deployment's approval channel, with the attribution that channel reports), the
     * host's own SLA sweep at the row's deadline (`"sla_timeout"` — FACET A of the deadline contract; only
     * a sweep may say it), or the deployment's OWN POLICY (`"policy"` — a machine decided, either way: an
     * unattended seat answering by class, no interactive approval surface for this row, a standing posture;
     * the durable spelling of the synchronous leg's `AskOutcome.settledBy: "policy"`, so ONE decision reads
     * the same on both faces). A person and a policy may each `allow` or `deny`; `"sla_timeout"` names a wait
     * that ended with nobody answering and cannot `allow`.
     * REQUIRED: a decide that names nobody is not a record of anything that happened, and core does not guess.
     *
     * The host supplies FACTS, never the settlement WORD: core mints the record
     * ({@link import("./gate-outcome.js").GateOutcome} — `human_allowed` / `human_refused` / `policy_allowed`
     * / `policy_refused` / `park_sla_expired`, joined with the row's persisted `origin`) at the resume entry, writes it onto the resolved row
     * (`resolvedOutcome.gateOutcome`) and projects it onto the resolved call's `tool_end` frame. A caller
     * that supplies `gateOutcome` itself is refused pre-CAS.
     *
     * `approver` — the attribution the approval channel reports for the party that ended the wait. Core
     * AUTHENTICATES NOTHING: the identity work is the deployment's (its card, its signature check, its
     * console). What core owns is that the claim is well-formed: a plain string, bounded, free of control
     * characters, refused pre-CAS (`checkpoint.invalid_outcome`) otherwise — refused rather than dropped,
     * because a settlement that silently lost its attributed party reads as unattributed.
     *
     * SCOPE — the facts ride the settlement, not the persisted winner: `winnerFromOutcome` records what an
     * `env_failed` replay must reproduce exactly, and who reported a settlement is not part of that
     * authorization; a replay states its own facts.
     */
    hostDecision: {
        decidedBy: "person" | "sla_timeout" | "policy";
        approver?: string;
    };
    /**
     * CORE-MINTED, on the persisted twin only: the settlement record this decide produced. Present on
     * the row's `resolvedOutcome` after the resolve, and on the `tool_end` frame of the resolved call.
     * Never accepted from a caller (refused pre-CAS) — the host does not mint words.
     */
    gateOutcome?: import("./gate-outcome.js").GateOutcome;
}
/** design/74: continue a resource-suspended run with the next slice's allowance. NO decision payload and
 *  NO budget figure — the allowance is computed from {@link Checkpoint.resourceLedger}, so money never
 *  reaches the caller or the model. Matches `CheckpointGate.kind === "resource_limit"`. */
 | {
    gate: "resource_limit";
    decision: "continue";
}
/** design/76 §2.5 (dry-run / shadow): resolve a `needs_review` suspend after a human/judge reviewed the
 *  buffered predicted state-diff. `decision:"approve"` → the profile applies the buffered diff (atomically,
 *  invalidating `readFileState` on touched paths — design/76 §2.5; the apply itself is PROFILE, not core)
 *  then continues; `decision:"reject"` → the prediction is discarded and the run continues without it. Kept
 *  DISJOINT from `policy_ask` (the approval family) so the resume-side discriminant never confuses a
 *  post-prediction review with a pre-action approval (the third `needs_review`/`dry_run_review` gateMatch arm).
 *  No money/diff payload reaches the caller here — the diff lives in the profile's REF store (design/76 §9). */
 | {
    gate: "dry_run_review";
    decision: "approve" | "reject";
    reason?: string;
}
/** design/80 D-B (plan-gate): resolve a `plan_review` PRE-ACTION plan pause after a human reviewed the
 *  proposed PLAN. `decision:"approve"` → proceed with the plan as-is; `decision:"edit"` → proceed with the
 *  human's `editedPlan` (a TYPED sibling of the plan — NEVER a raw tool `updatedInput`; a plan is not a tool
 *  arg, so this binds NO action and never enters the `policy_ask` boundCallId/boundInputHash machinery);
 *  `decision:"reject"` → the model RE-PLANS (the rejected plan is discarded; a `reason` may steer the
 *  re-plan). Kept DISJOINT from `policy_ask` (approval) AND `dry_run_review` (post-prediction review) so the
 *  resume-side discriminant never confuses the three (the `plan_review`/`plan_review` gateMatch arm). It
 *  binds no TOOL CALL, but since RB-64 it does record a winner: `winnerFromOutcome` returns the decision
 *  (plus `editedPlan` as `updatedInput` and the reviewer `reason`) under the synthetic `gate:plan_review`
 *  marker, so an `env_failed` reopen can replay it. The `boundCallId`/`boundInputHash` TOOL-CALL binding
 *  is what stays `policy_ask`-only. No plan/diff payload (beyond the human's
 *  `editedPlan` text) reaches core — the plan artifact lives in the PROFILE's REF store (design/76 §9). */
 | {
    gate: "plan_review";
    decision: "approve" | "edit" | "reject";
    editedPlan?: string;
    reason?: string;
}
/** design/144 §3 (message-wake): wake a PARKED (non-gate) checkpoint and drive it to continue — a pure
 *  un-park verb, **NOT a gate decision**: it never allows/denies/approves anything. Governance-gate
 *  purity is enforced at the resume entry (fail-closed, pre-CAS): a checkpoint awaiting ANY pending
 *  gate decision (`gate.kind` ∈ {human, irreversible_ask, needs_review, plan_review, resource_limit} —
 *  i.e. the tool_approval / dry_run_review / plan_review / resource_limit decision families) REJECTS a
 *  wake with `wake.gate_pending`, so wake can never become a side door around an approval. Only a
 *  non-gate park (e.g. a deployment-minted approval-timeout park / pure suspend) is wakeable.
 *
 *  `message` (optional): operator content delivered to the resumed run — the ATOMIC equivalent of
 *  `setPendingSteer` + resume in one verb (same `validatePendingSteer` persist-time guards, same
 *  trusted/untrusted delivery framing as {@link CheckpointState.pendingSteerQueue}). It rides the resume
 *  continuation's steer tail as its OWN segment, appended AFTER any already-parked queue entry
 *  (X5: both are delivered — the wake message never displaces a parked, undelivered supervisor steer;
 *  each segment is framed under its own `trusted` semantics, never one merged trust domain). WITHOUT a
 *  `message`, the checkpoint must already carry a parked steer — a wake with nothing to
 *  deliver is rejected (`wake.nothing_to_deliver`, anti spin-wake). Idempotency rides the existing
 *  resolve CAS: a second wake of the same token loses the CAS (`checkpoint.already_resolved`). */
 | {
    gate: "wake";
    message?: PendingSteerInput;
} | {
    gate: "task_done";
    result: TaskResult;
};
/**
 * The pending action a checkpoint suspends *before* — a **discriminated union** keyed by `kind`. Every
 * consumer that reads the tool fields MUST branch on `kind` first: the `resource_limit` arm (design/74)
 * has none (a resource slice suspend has no pending tool to resolve).
 *
 * `tool_approval` (§4): a tool call adjudicated `ask` in durable mode, captured with its **post-hook args**
 * (the design/37 rewrite already applied) so resume executes the exact same call without re-running
 * PreToolUse hooks (§11 M2). `batchToolCallIds` / `completedCallIds` capture the **mid-batch** position
 * (§4.ter): one assistant turn can emit a batch of tool calls; if call #k hits `ask`, calls #1..k-1 already
 * executed (in `completedCallIds`) and #k+1..N are still pending. ID-based (a `Set<toolCallId>`), not
 * positional — immune to reordering / off-by-one (council Question #1), and it doubles as the reconcile
 * suspended-batch discriminant (§15.2 net-add #7) so wake-reconcile never closes a suspended call.
 */
export type PendingAction = {
    kind: "tool_approval";
    toolCallId: string;
    toolName: string;
    /** Post-hook (design/37-rewritten) args to execute on `allow`. */
    args: unknown;
    /** (additive) — the tool's approval display projection of `args`, minted alongside the
     *  row (see AgentTool.approvalPreview; control-char-sanitized + 16KiB-clamped at mint).
     *  **UNTRUSTED, ADVISORY**: inboxes must escape contextually and surface `args` alongside.
     *  NEVER read by the resume path (args + boundInputHash stay the sole execution contract). */
    preview?: unknown;
    /** #457 ② (additive; no checkpoint-version bump — the `hasBidiControls` precedent below: an
     *  optional field an older reader ignores and the resume path never reads) — the PARK twin of
     *  `AskRequest.previewWithheld`: present ⇔ this tool declares an approval preview and the mint
     *  could not present a usable one, `"oversize"` (produced but past the 16KiB display bound — the
     *  stub still rides {@link preview}) or `"unavailable"` (threw / no JSON serialization). ABSENT
     *  covers "no preview declared", "the projection declined" and "it worked" — read presence, never
     *  absence. A DISCLOSURE for the approval inbox's card, never a verdict: {@link args} and
     *  {@link boundInputHash} are carried either way and the resume contract is untouched. Judged over
     *  THIS row's fidelity-projected args snapshot, so it can honestly differ from the synchronous
     *  face on a shape-changing backend — exactly as {@link hasBidiControls} can. */
    previewWithheld?: "oversize" | "unavailable";
    /** design/179 §4, re-formed by design/375 §3.1/§3.3 (BREAKING — replaces the retired
     *  `ruleSuggestions` seat; a pre-375 inbox reading a new row simply finds no offers and renders
     *  no "don't ask again" option, fail toward asking) — the persistable allow-rule OFFERS this
     *  parked call could be covered by, minted from the SAME post-hook args the row executes.
     *  Advisory display metadata for the approval inbox's "stop asking me this" option; never read
     *  by the resume path, and never a rule by itself (minting one goes through the approval-record
     *  protocol). Absent when no rule lane is armed, the command is one the lane cannot speak for,
     *  or the parked ask is one a persisted rule could not clear (mandated marks / a real-approval
     *  demand / a shadowed matching rule).
     *  CONTRACT (same as the synchronous `AskRequest.ruleOffers` — see
     *  {@link import("./permission-rule-model.js").RuleOffer}): at most 2 offers, the whole-string
     *  EXACT single always index 0 when present, the BATCH offer (choosing it is one yes to ALL of
     *  its 1..5 per-segment rules at once) always last; selection indices are keyed against this
     *  order (redemption TICKETS are keyed on the consent record's flat CANDIDATE list instead —
     *  a chosen offer is redeemed via `redeemRuleBatch`), per-element parse with single-row
     *  degrade on an unknown `kind` and
     *  original-index preservation. design/382 B3: a batch offer's members are the
     *  {@link import("./permission-rule-model.js").RuleOfferBatchMember} discriminated union
     *  (`command` | `directoryRead`), and the member-level degrade is the union's own normative
     *  arm — an unknown MEMBER kind drops the WHOLE batch offer (never one member: a conjunction
     *  silently one member short renders "yes to N" as "yes to N−1"), while the sibling single
     *  offers stay rendered. The park→resume redemption chain walks the same consent
     *  protocol as the synchronous card — there is no second form. */
    ruleOffers?: readonly import("./permission-rule-model.js").RuleOffer[];
    /** #490 修② (additive; no checkpoint-version bump — the `previewWithheld`/`hasBidiControls`
     *  precedents above: an optional field an older reader ignores and the resume path never reads)
     *  — the PARK twin of `AskRequest.ruleOffersAbsence`, minted by the SAME one factory as
     *  {@link ruleOffers} so the parked card and the live card cannot answer differently. Present ⇔
     *  the rule-offer lane is in play for this call and has nothing to give, naming which door
     *  closed: `"mandated"` (no persisted rule could ever clear this ask — a real-approval demand,
     *  the tool's own mandate marks, a hook-raised ask, an ancestor's authority, or — #502 — a
     *  demotion this CALL's reversibility probe declared structural, which for the built-in shell
     *  probe means a read outside the directories the session declared; an inbox must NOT point at
     *  rule-writing on this arm — "allow rules silence the classifier's questions, never a mandated
     *  one" — nor at a directory grant: the subpath-rule family exists, but the out-of-root arm has
     *  no clearing configuration in this version, so a grant minted in answer to this line would
     *  never take effect where it was minted; confirming the call is the whole of what a person can
     *  do about it),
     *  `"shadowed"` (a rule the person already wrote is
     *  speaking and does not clear it), `"lane_cannot_speak"` (this card has no rule to offer — the
     *  grammar has no text for the command, or the card's array-order contract declines to present
     *  the one text that exists; it is NOT a claim that no rule could ever be written).
     *  Mutually exclusive with {@link ruleOffers}. ABSENCE is not a claim — it also covers
     *  "there are offers" and the structural doors (no lane wired, another tool, a task that cannot
     *  hold a rule) — so read presence. See the synchronous seat's doc for the full contract. */
    ruleOffersAbsence?: "mandated" | "lane_cannot_speak" | "shadowed";
    /** design/382 §2.4 (adversarial-review r3; additive — no checkpoint-version bump, the
     *  `previewWithheld` precedent: an optional field an older reader ignores and the resume
     *  path never reads) — the PARK twin of `AskRequest.execCwd`, minted by the SAME factory as
     *  {@link ruleOffers}: the relative-cd resolution base those offers were minted with (the
     *  live tracked cwd at park time). An approval inbox reconstructing the authoritative
     *  consent record threads it as `prepareCardApproval`'s `execCwd`. Present only beside
     *  {@link ruleOffers} when the run had a tracked cwd; echo-only, never a control input. */
    execCwd?: string;
    /** #557 (additive; no checkpoint-version bump — the `previewWithheld` precedent: an optional
     *  field an older reader ignores and the resume path never reads) — the PARK twin of
     *  `AskRequest.requiresRealApproval`: present (`true`) ⇔ this parked ask demanded real human
     *  judgment (the gate's `realApproval` bit was minted — the SAME fact, restated where a card
     *  reader looks for it, so an inbox needs no gate-kind decoding to say "only a person can clear
     *  this"). Absent otherwise (never `false`). Display metadata: the resume belts keep reading the
     *  gate's own bit. */
    requiresRealApproval?: true;
    /** #1079 (additive, no checkpoint-version bump — the {@link requiresRealApproval} precedent) — the PARK
     *  twin of `AskRequest.mandated`: present (`true`) ⇔ no persisted allow rule and no standing answer may
     *  retire this parked question; absent otherwise (never `false`; a pre-#1079 row reads absent = unreported).
     *  Minted from the SAME facts object the row's rule-offer factory reads, through
     *  {@link import("./ask-origin.js").askMandateMember} (the live card's derivation). Three independent
     *  facts: {@link requiresRealApproval} (only a person — implies this one), this one (no rule), and
     *  {@link ruleOffersAbsence} (what the offer lane has to give: silent off its tool and wiring, `"shadowed"`
     *  when a person's rule also speaks — never a substitute for this one). Display metadata: no gate, CAS or
     *  resume path reads it. */
    mandated?: true;
    /** The PARK twin of `AskRequest.authoredBy`: the deployment's own governance layer minted this parked question — a
     *  person's under every mode, whatever grant or rule the redeeming host holds. Display / seat metadata. */
    authoredBy?: "deployment";
    /** The PARK twin of `AskRequest.mandate` (same derivation, same precedent): the table word the bit stands on,
     *  present only when it is the whole reason. Written only as a member of the closed set. */
    mandate?: import("./ask-origin.js").PersistedRuleMandate;
    /** The PARK twin of `AskRequest.denialLimitFallback`: present
     *  ⇔ this parked ask is the classifier DENIAL-LIMIT fallback, carrying the counts that tripped
     *  the bound. Its `autoDenyAfterMs` is ALWAYS `0` on a row: the window is a fact of the ask's
     *  route, armed only at a hand-out to a live approver, and nothing counts down on the parked lane
     *  — a park's expiry is the row's own `deadline`/ttl, the engine arms NO timer off this row, and
     *  its settlement never reads as the fallback's auto-deny (see `tool_end.autoDenied`). A durable
     *  card renders the counts and the limit; it has no countdown to render. Echo-only. */
    denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
    /** (additive, same precedent) — the PARK twin of `AskRequest.origin`: WHICH AUTHORITY raised the
     *  parked ask, one word from the closed {@link import("./ask-origin.js").AskOrigin} set, the same
     *  word the synchronous card is rendered by (engine-stamped at the gate, never a policy's claim).
     *  Written only when it is a member of the set; a pre-existing row reads absent (unreported).
     *  Display metadata — the resume belts keep reading the gate's own bit. */
    origin?: import("./ask-origin.js").AskOrigin;
    /** (additive, same precedent) — the PARK twin of `AskRequest.askClass`: the parked ask's class, the first member of
     *  {@link classes}, from the same classification the gate arbitrated it by — so a durable approval seat can answer by
     *  class exactly as a synchronous `onAsk` does. Written by the park mint only as a well-formed pair with
     *  {@link classes} (both or neither). A row parked by an engine before this member existed reads ABSENT
     *  (unclassified), and a resume does not fill it in: a seat answering by class treats absence as a class it will
     *  not answer. Display / seat metadata — no gate, CAS or resume path reads it. */
    askClass?: import("./ask-origin.js").AskClass;
    /** The PARK twin of `AskRequest.classes`: every class of the parked ask, in `ASK_CLASSES` order
     *  (`classes[0] === askClass`). Present exactly when {@link askClass} is. */
    classes?: readonly import("./ask-origin.js").AskClass[];
    /**
     * design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
     * SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
     * suspend-mint and persisted on the row. The operator sees it (surfaced on the pending record) and
     * echoes it back as {@link ResumeOutcome} `boundInputHash`; the resume verifies opaque string equality
     * against THIS value (never re-serializing the args — see the field's doc). Absent only on a legacy
     * pre-1a.2 checkpoint (deserialized without it), in which case the resume skips the hash check.
     */
    boundInputHash: string;
    /** A parked `Skill` call's binding to its card — the skill's name and its two lists ({@link import("./skill-grant.js").skillGrantDigest}, judged by {@link import("./skill-grant.js").parkedSkillApprovalRefusal}); only that row carries it. */
    boundGrantDigest?: string;
    /**
     * design/388 §2.5 boundary ② (B18; additive — no checkpoint-version bump, the `previewWithheld`
     * precedent: an optional group an older reader ignores): the IDENTITY of the row the park minted
     * this action against, read off the leg's tool roster at the suspend station — `capabilityId`
     * (engine-minted over {source, peer, name, contractId}), `contractId`, `shapeDigest`,
     * `implementationRevision` — plus the roster `digest` of the leg that parked. The resume finds the
     * same-name row on ITS leg and compares the FOUR identity members (`judgeParkedToolIdentity`): any
     * mismatch is a replacement wearing the name and the resume refuses it
     * (`resume.tool_contract_mismatch`, the `tool_unavailable` reopen family). All four ABSENT ⇒ a row
     * minted before the members existed ⇒ the name-only path, never widened by their absence.
     */
    capabilityId?: string;
    contractId?: string;
    shapeDigest?: string;
    implementationRevision?: string;
    rosterDigest?: string;
    /**
     * design/388 B6 × B18 (additive; the identity group's precedent): the FACE the parked call was judged under —
     * display `family` + declared `pathTarget` — off the same roster row at the same mint. The pre-CAS resume-edit
     * recheck has no roster yet; it rebuilds the call face from these (`parkedCallFaceOf`) and judges the edit as
     * the live gate judged the original (merge-seam H1: without them a caller slot fell to the catalog's slot for
     * the NAME). Any member absent ⇒ pre-388 row ⇒ the recheck falls to the catalog, never a widened grant — including #635's resolution members, which a row written before them simply lacks (they then read as their documented defaults).
     */
    family?: import("./tool-face.js").ToolFamily;
    pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
    /** The names the parked call answered to, copied from the face the gate judged it under (`ToolCallFace.names`: the
     *  model's spelling first, then the wire name and the other aliases that resolve to it; a row minted before the
     *  spelling led carries the wire name first) — the pre-CAS recheck judges an edit against rules written on any of
     *  them, as the live gate judged the original, and the resume re-mints its face under the spelling while it still
     *  resolves to the row's tool. Absent on a row minted before it ⇒ the row's `toolName` alone. */
    names?: readonly string[];
    /**
     * #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
     * older reader ignores. Unlike that precedent seat — which resume DOES read, through its
     * verify-then-adopt port — nothing reads THIS bit at all, per the echo-only clause below):
     * PRESENCE ONLY — the row's execution
     * payload carries at least one DIRECTIONAL format control. The durable twin of the synchronous
     * {@link import("./tool-policy.js").AskRequest.hasBidiControls} (ONE contract, two faces — that
     * field's doc owns the character class, the hazard and the warning-not-verdict posture; everything
     * there applies here unchanged). What is stated per face:
     *
     *  - JUDGED AT THE PARK MINT over THIS ROW's materialized payload: the fidelity-projected `args`
     *    snapshot (the bytes a human approves and a resume executes) plus the row's own `preview`
     *    projection. The row's bit describes the row's bytes — on a shape-narrowing backend (a Map key
     *    under a JSON codec) a marker the synchronous face detected can be GONE from the row, and the
     *    honest bit for the row is then ABSENT: the inbox renders the row's snapshot, and the bit
     *    agrees with what it renders. The two faces coincide whenever the projection is shape-preserving.
     *  - `true` or ABSENT, never `false` (absence = "not detected": clean, or the bounded scan did not
     *    reach it — a `false` would claim a proof the scan does not offer).
     *  - A DEPLOYMENT-WRITTEN park row (the design/144 convention's deployment half) may carry `true`
     *    (taken at its word — over-warning is the safe direction, and a writer with row access holds
     *    the args themselves already) or omit it (the summary projection then runs its own bounded
     *    backfill scan — see {@link CheckpointSummary.hasBidiControls}). A written `false` is
     *    out-of-contract and is read as ABSENT everywhere; it never reaches a projection.
     *  - ECHO-ONLY, like {@link CheckpointSummary.toolInput}: NO resume / gate / CAS /
     *    `winnerFromOutcome` path reads it — it is a display warning, never a control input.
     */
    hasBidiControls?: true;
    /** All tool-call ids in the suspending assistant message, in emission order. */
    batchToolCallIds: string[];
    /** Ids of calls already executed (results in the session) when the suspend fired — #1..k-1. */
    completedCallIds: string[];
}
/** design/74: suspended at a resource slice boundary — there is NO pending tool to resolve. Consumers
 *  that read the tool-approval fields above MUST branch on `kind` first (this arm has none). */
 | {
    kind: "resource_limit";
    reason: ResourceLimitReason;
}
/** design/76 §2.5: paused at a POST-PREDICTION REVIEW (`CheckpointGate.kind === "needs_review"`). There is NO
 *  pending tool to resolve — a human/judge reviews the buffered predicted state-diff (a PROFILE-held REF, never
 *  stored here) — so this arm has no fields. Its OWN member: before the pause registry a `needs_review` row
 *  borrowed another arm's seat, and the gate × pendingAction pairing lived in a comment. */
 | {
    kind: "review";
}
/** design/80 D-B (r3 — an EXPLICIT arm, NOT a reused `resource_limit` placeholder): paused at a PRE-ACTION
 *  PLAN REVIEW (`CheckpointGate.kind === "plan_review"`). There is NO pending tool to resolve — the human
 *  reviews the PLAN, not a tool call — so this arm has no tool fields. Every consumer that reads the
 *  `tool_approval` fields above MUST branch on `kind` FIRST: a `plan_review` checkpoint must never flow into
 *  a `resource_limit`- or `tool_approval`-shaped continuation (a contract test pins this). The plan artifact
 *  is a PROFILE concern (a REF — design/76 §9); core stores no plan here. */
 | {
    kind: "plan_review";
}
/** design/144 §3 (X2) — the NON-GATE arm: nothing pends a decision. The shape of a deployment-minted
 *  PARK checkpoint (approval-timeout park / pure suspend; paired with `gate.kind === "task_done"`, the
 *  non-gate gate arm) and of a 1C `task_done` handle row. It is the ONLY pendingAction kind the `wake`
 *  resume entry accepts — every other kind above is a pending GATE decision, and the wake gate rejects
 *  it (`wake.gate_pending`) via an EXHAUSTIVE switch (a future kind added here without classifying it
 *  there is a compile error, and an unknown kind on a deserialized row fails closed at runtime). Core
 *  never mints this arm itself (the park convention is the deployment half); the pre-existing doc
 *  references to "a task_done pendingAction" (`getCheckpointSummary` et al.) name exactly this shape. */
 | {
    kind: "task_done";
};
/**
 * The per-task correctness state a checkpoint must carry so a resumed task runs in the **same state
 * space** it suspended in (§4.bis, the jury head must-fix). An **explicit whitelist** of serializable
 * correctness fields — never a blind `JSON.stringify(Prepared)`, which would silently corrupt the 9+
 * non-serializable runtime objects (`harness`/`session`/`abortController`…) it holds (round-2 BUG#1).
 *
 * Round-trip fidelity of every field is covered by tests; runtime objects are **forbidden** here.
 * `cacheFingerprint` (design/31) is deliberately **excluded** — it is observation-only, so resume just
 * suppresses cache-break detection on the first turn rather than persisting it as correctness state.
 */
export interface CheckpointState {
    /** design/36: deferred-tool activation set — else resume re-discloses / diverges from history. */
    activeTools: string[];
    /** 1.41 submit_output: the validated structured output set pre-suspend, else it's lost on resume. */
    outputRef?: {
        value?: unknown;
        set?: boolean;
    };
    /** design/38: nested sub-agent cumulative cost — else pre-suspend child cost evaporates (§4.bis/Q7). */
    nestedStats: NestedUsageAccum;
    /** design/44: the hand's read-file state (content hashes), serialized from the hands-toolkit closure
     *  (NOT part of Prepared — §15.2 net-add #8). Without it a resumed `edit_file` is rejected "not read". */
    readFileState?: Array<[string, ReadEntry]>;
    /**
     * design/199 — the READ-face containment state at suspend (v9,
     * {@link FACE_CHECKPOINT_VERSION}): the RESOLVED face plus the NORMALIZED deny-set ADDITIONS in
     * force (deployment ∪ task layers; the built-in table is engine code under the RESUMING deployment's
     * own tier selection — empty unless it made one — never frozen here). Written ONLY when the posture
     * is non-default (face "open", or additions present) — an absent section IS the default posture and
     * resumes as roots plus whatever built-in tiers the resuming worker selected. Resume folds STRICTER-WINS: face = roots unless the
     * section says open AND the live resolution says open; deny additions = frozen ∪ live (add-only,
     * monotone — a resume can never widen what the cut leg could read).
     *
     * The design's r3/r4 rounds carried a third key (the workspace domain at cut) to evaluate
     * domain-scoped deny entries under their frozen domain; the D-4 ruling (2026-08-13) removed the
     * scoped tier entirely, leaving that key without a consumer — it is deliberately NOT persisted
     * (dead fields in a durable format are the drift surface the liveness gate exists to catch).
     */
    readFace?: {
        face: "open" | "roots";
        denyEntries?: Array<{
            pattern: string;
            caseSensitive: boolean;
        }>;
        /** REDUNDANT governance marker: `true` iff the row's gate was minted WITH a
         *  {@link RealApprovalGateBit}. v8's bit-required invariant was version-keyed and v9 (face)
         *  outranks v8 in the stamp, so without this a v9 GOVERNED row whose `gate.realApproval` member
         *  was dropped in storage would pass pre-CAS and be redeemable on an unwired worker — the exact
         *  #130/#120 corruption/downgrade hole, reopened one version up. The resume pre-CAS ladder
         *  refuses a v9+ row whose section says `realApproval: true` while the gate carries no
         *  well-formed bit (same protection level as v8: a coherent rewrite of BOTH sites remains out of
         *  scope for any in-row invariant). Absent = the row was minted ungoverned. */
        realApproval?: true;
    };
    /**
     * The WRITE-face containment state at suspend: the RESOLVED write face, written ONLY when it is
     * non-default ("open"). An absent section IS "roots" for every reader — which is why it carries NO
     * version stamp: losing it can only make a resume STRICTER. Resume folds STRICTER-WINS, the read seat's
     * law: roots unless the section says open AND the live resolution says open. No deny half and no
     * governance marker.
     */
    writeFace?: {
        face: "open" | "roots";
    };
    /** The grant rows the run grew ({@link import("./skill-grant.js").frozenSkillGrants}); absent = none, damaged = refused pre-CAS. */
    skillGrants?: import("./skill-grant.js").SkillGrantRecord[];
    /**
     * design/319 (A ticket) — the session's reminder provenance mark
     * ({@link import("./reminder-mint.js").mintReminderMark}), persisted so a durable resume keeps
     * minting the SAME mark its transcript's engine reminders and system-prompt declaration carry
     * ("resume reuses" — one declaration, one mark). Additive on the existing checkpoint version —
     * a reader that ignores it is unchanged (the :646 precedent; no version bump). Resume goes
     * through the VERIFY port ({@link import("./reminder-mint.js").isValidReminderMark}): a missing
     * field (pre-319 row) or an unrecognized value-shape (corrupt / foreign / old-scheme after a
     * scheme change) RE-MINTS instead of adopting — the declaration stays strict, and the historic
     * legs' reminders simply read as data (fail-safe downgrade, design/319 a-2).
     */
    reminderMark?: string;
    /**
     * design/78 Slice-1: the SAFE-tier self-repair loop's durable state (`failureTrace`/`diagnostics`/
     * `rejectedHypotheses`/`attemptCount`/`oracleTier` — all JSON/`structuredClone`-safe, no fn/Date). Set ONLY
     * when an orthogonal durable suspend (resource/HITL) interleaves a `runRepairLoop` run — the happy path is
     * in-memory only. On resume it re-seeds `RepairLoopConfig.resumeBundle` so `attemptCount` advances
     * MONOTONICALLY (never reset). `baselinePassTests` is deliberately NOT carried here (grader-computed
     * out-of-process — a worker must not be able to shrink the ratchet). Absent for a run with no repair loop.
     */
    repairBundle?: RepairBundle;
    /**
     * design/49 v1.5: the remote workspace's serializable identity (E2B sandbox id / provider / mount path /
     * snapshot id). Set ONLY when the suspended task ran with a remote, suspendable {@link WorkspaceHandle}
     * (a per-task `executionEnvFactory` env that was `suspendVM()`-paused rather than destroyed). Resume
     * rebuilds the env via the factory and `resumeVM(snapshotId)` so the restored workspace matches the
     * `readFileState` above. All-string fields → JSON/`structuredClone` round-trips safely (consistent with
     * this whitelist's "no runtime objects" rule). Absent for a process-local (non-remote) suspend.
     */
    workspaceHandle?: WorkspaceHandle;
    /**
     * design/171 §5.3 — the BOUNDED, ORDERED queue of parked operator steers for a DURABLY-SUSPENDED task,
     * drained in `seq` order on resume with every entry framed under its own trust semantics. Live
     * `TaskStream.steer` (runtask.ts) is unreachable while the harness is idle, so a supervisor's guidance is
     * parked HERE via {@link CheckpointStore.setPendingSteer} and injected on resume (after the
     * resume-continuation prompt). It is GUIDANCE ONLY — never an approval channel (§3 inv #4) and never
     * parsed into control state (§3 inv #5); a budget/autonomy/gate-threshold is CONFIG, not steer text.
     *
     * `trusted` is FROZEN at `setPendingSteer` from the SERVICE's verified-principal check (an operator-role
     * check, NOT a client header) and NEVER recomputed on resume (§3 inv #1). A `trusted:false` entry reaches
     * the model as a PLAIN user message with NO `<system-reminder>` wrapper (§3 inv #3); a `trusted:true`
     * entry MAY ride the reminder. `text` containing `</system-reminder>` is REJECTED at `setPendingSteer`
     * (`steering.invalid_content`) so a dirty steer never enters this state (§3 inv #2).
     *
     * Delivery is BEST-EFFORT, at-most-meaningfully-once: an entry rides EVERY resume of THIS checkpoint
     * that runs a turn (a faithful reopen→re-resume re-shows it), is NOT carried onto a re-suspend
     * checkpoint (serializeCheckpointState stamps `undefined`), and is DROPPED on a resume that runs no
     * turn. A supervisor re-issues `setPendingSteer` if a steer did not land.
     *
     * Bounds are fail-loud, never lossy: {@link MAX_PENDING_STEER_ENTRIES} entries and
     * {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES} serialized bytes, whichever binds first, reject the
     * APPEND with `steering.queue_full` rather than evicting an already-accepted instruction.
     *
     * Absent when nothing is parked. {@link readPendingSteerQueue} is the single read point; a row that
     * still carries the retired pre-queue single seat (`pendingSteer`) is refused there
     * (`checkpoint.unsupported_version`, `seat_retired`), never folded in.
     */
    pendingSteerQueue?: PendingSteerEntry[];
    /**
     * SR-7 (CC 2.1.198 orphaned-background-task notice: F6c pretty.js:698391-698398, resume leg
     * :707384-707399 under the `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` gate — the cloud-worker restart
     * leg, exactly sema's durable-resume shape): the background tasks (pending/running, this run's
     * owner triple) still ALIVE at suspend. Background processes never survive a suspend (design/103
     * §3.7 unconditional dispose + killed receipts), so on resume any snapshot entry NOT
     * alive in the resume leg's registry is an ORPHAN — aggregated into ONE CC-verbatim "The container
     * was restarted…" reminder appended to the resume continuation (single message: header +
     * `- description (task id)` list + re-create instruction; never a per-task barrage). A survivor
     * (session-resident shell / in-window monitor) is excluded — still reachable via TaskOutput/
     * TaskStop, it needs no obituary. CC's per-orphan `Ku(task_id,"stopped")` status write (:707399)
     * is N/A here: the suspend teardown already settled them killed with receipts. Absent for a run
     * with no live background tasks at suspend, and for pre-1.262 checkpoints (deserialized without
     * the field) — both resume silently, exactly like CC with an empty list.
     */
    runningBackgroundTasks?: Array<{
        id: string;
        description?: string;
    }>;
    /**
     * the hands band's LOGICAL working directory at suspend (`handsCwdRef.current` —
     * moved by `cd` and by EnterWorktree). Without it a resume silently reset the task cwd to the task root:
     * relative Read/Edit/Write paths and Bash commands then operated somewhere else than the model believes.
     * Absent when the task has no tracked cwd (no real shell / read-only hands). The directory itself is
     * plain on-disk state that survives a process-local suspend (worktrees included — the tree stays on disk).
     */
    handsCwd?: string;
    /**
     * the ACTIVE EnterWorktree session at suspend (worktree.ts keeps it in a shared
     * serializable ref, not a closure-only var, precisely so it lands here). Without it a resume LOST the
     * session: ExitWorktree became a no-op (the unchanged worktree could never be removed), a second
     * EnterWorktree was wrongly accepted, and `handsCwd` pointed into a worktree the tooling no longer
     * owned. All-string fields; the worktree directory survives the suspend on disk (managed under the
     * task root, `git worktree` metadata intact).
     */
    activeWorktree?: ActiveWorktreeSession;
    /**
     * Parent effective-policy inheritance (tighten-only): the durable projection of the run's
     * {@link import("./runner/contracts.js").InheritedGate}. Only the serializable DATA half persists —
     * `ancestorRules` (each ancestor's session-rule snapshot: sessionId/principal/rev/rules, re-compiled on
     * resume against the resumed leg's env), `shellGate` (the chain's operator floor) and `permissionMode`. The OPAQUE
     * half (`parentConstraints` — live ToolPolicy/onAsk closures) CANNOT persist; `requiresParentConstraint`
     * records only its PRESENCE, so the resume pre-CAS ladder can fail-loud (`resume.parent_constraint_missing`,
     * checkpoint stays `pending`) when the resuming caller did not re-supply the closures via
     * `resumeStream(..., internals)`. Absent for a task that inherited nothing (pre-1.294 checkpoints included)
     * — distinguishable from `requiresParentConstraint: false` with data-only inheritance, which resumes
     * without any re-supplied internals. All-data fields → JSON/`structuredClone` round-trips safely.
     */
    inheritedGate?: {
        ancestorRules?: ReadonlyArray<{
            sessionId: string;
            principal?: string;
            rev: number;
            gen?: number;
            rules: SessionPermissionRules;
        }>;
        shellGate?: ShellGateDoctrine;
        /** The chain's DECLARED permission mode at suspend (data half, same law as `shellGate`): the suspended leg's
         *  own effective declaration — its request clamped under its parent's ceiling — before the deployment's fold.
         *  A resume leg reads it as the CEILING its re-passed spec is clamped under (tighten-only, the shell floor's
         *  law) and re-judges the deny bits and the classifier face for its own leg: a `bypassPermissions` row does not
         *  revive the mode where the redeeming deployment denies it, and an `auto` row arms only where a face is
         *  wired. Absent on a task that declared nothing and inherited nothing (`default`). Rows written before this
         *  member carried `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; no engine since 7.39.0 reads
         *  them — such a row is refused before the claim ({@link refuseRetiredModeSeed}). */
        permissionMode?: PermissionMode;
        /** Beside a recorded `plan`: the declared word the leg had when a steer or the model's `EnterPlanMode` moved it into
         *  plan — the mode an approved or edited `plan_review` resumes under ({@link import("./mode-policy.js").modeRequestAfterResume}). */
        prePlanMode?: PermissionMode;
        /** Org-memory admission freeze (ruled 2026-08-05): the chain's admitted org-scope set at
         *  suspend (data half, plain strings). The resume leg folds it seed ∩ live (tighten-only) and
         *  re-runs admission under it — a resume must never widen the delegation freeze. Absent on
         *  older checkpoints ⇒ the live channel (or, for a delegated child, the fail-closed ∅) stands.
         *  Cross-VERSION enforcement (an older worker resuming a newer checkpoint ignores fields it
         *  does not know) is the deployment's server/core version-gate concern, not a per-field fence. */
        admittedOrgScopes?: readonly string[];
        /** THIS task's OWN admitted org verdict at suspend (adversarial-review adoption, 2026-08-05):
         *  distinct from {@link admittedOrgScopes} (the incoming PARENT constraint). The resume leg
         *  applies it as the SESSION freeze on the final admitted projection — regardless of origin
         *  (deployment-origin scopes circumvent the chain freeze, never the session freeze) — so a session's
         *  verdict can only NARROW across suspend/resume: re-adjudication still runs every leg (a
         *  revoked grant refuses the resume), but a policy widened mid-session cannot widen THIS
         *  session's mount or the chain its children inherit. Present (possibly EMPTY — an adjudicated
         *  empty verdict is not "org-less") iff the suspend leg adjudicated an org plane; absent on
         *  older checkpoints / org-less tasks. Checkpoints carrying this state are stamped
         *  {@link ORG_ADMISSION_CHECKPOINT_VERSION} so a pre-admission worker rejects them pre-CAS. */
        ownAdmittedOrgScopes?: readonly string[];
        /** The WRITE half of the session freeze: the org write grant this session held at suspend —
         *  `null` = the org write face was read-only (a resume must not gain write authority the
         *  suspend leg did not hold); a scope string = the explicit grant. Present iff
         *  {@link ownAdmittedOrgScopes} is (same adjudication record). */
        ownAdmittedOrgWriteScope?: string | null;
        /** The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of
         *  the suspended tree ran with an org-admission surface (or inherited the bit). A resume worker
         *  missing the surface still treats the leg as governed — org-shaped scopes respelled outside
         *  the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
         *  trees that never had a governance surface. */
        orgAdmissionGoverned?: true;
        requiresParentConstraint: boolean;
        /** How many opaque parent constraints the suspended leg ran under (present iff `requiresParentConstraint`).
         *  The resume pre-CAS ladder rejects a re-supply whose `parentConstraints.length` differs
         *  (`resume.parent_constraint_mismatch`) — a partial re-supply is a typed error, never a silently
         *  thinner chain. Absent on older checkpoints ⇒ only the non-empty check applies. */
        parentConstraintCount?: number;
        /**
         * F-012 L1 — the persisted CONSTRAINT PROJECTIONS of the inherited chain, one ordered entry per
         * `parentConstraints` layer: the layer's deterministic deny data
         * ({@link import("./tool-policy.js").ToolPolicyProjection} content) or an explicit `{opaque:true}`
         * placeholder for a layer that exports none. The resume leg EXECUTES these persisted projections
         * (extra deny-narrowing layers + the pre-CAS edit re-adjudication) rather than trusting a
         * re-supplied policy's execution half for the frozen deny data — a re-supplied closure that
         * matches the digest but behaves differently cannot un-deny what the suspend froze. Present iff
         * `requiresParentConstraint` on new mints; absent on PRE-F-012 checkpoints (⇒ no frozen deny data —
         * the count-only contract governs, and an EDIT resume is refused for want of a validatable chain).
         * "Older" is version-decidable, not guessed: chain, digest and the {@link F012_CHECKPOINT_VERSION}
         * stamp are one write, so on a v7+ row the pair is REQUIRED — its absence there is a damaged row and
         * the resume ladder refuses it pre-CAS rather than falling back to the count-only contract. */
        constraintChain?: ReadonlyArray<import("./tool-policy.js").ConstraintChainEntry>;
        /**
         * F-012 L1 — the content digest over `constraintChain` ({@link import("./tool-policy.js").constraintChainDigest}):
         * binds the ordered chain topology (layer boundaries + opaque placeholders) and each projection's
         * content. The resume pre-CAS ladder recomputes the digest over the RE-SUPPLIED chain's
         * projections and rejects a mismatch (`resume.parent_constraint_mismatch`) — replacing the
         * count-only shape check with a content contract (a same-length chain of different frozen deny
         * data no longer passes). Present iff `constraintChain` is — and on a v7+ row both are required
         * (see {@link constraintChain}: the pair and the version stamp are minted together). */
        constraintDigest?: string;
    };
    /**
     * [c209-C] Q5 — the listing frames' announced NAME-SETS at suspend (agent types / skills the model
     * has actually been shown this run, committed on first-frame delivery or intact boundary survival).
     * A resume leg seeds its producers from these names and DIFFS the current roster/skill set against
     * them, so cross-leg drift (config-center roster change between suspend and resume) is announced as
     * a delta frame at the first boundary instead of silently swallowed — the exact event the listing
     * eviction from the cached prefix exists to serve. Names only, never contents (a description change
     * on a surviving name is deliberately not re-announced — name-keyed diff, agent_listing parity).
     * Schema ADDITION (backward-compatible, B-5): an older checkpoint without the field resumes via the
     * MED-3② transcript probe (delivered ⇒ seeded as the CURRENT entries, drift undetectable there;
     * not found ⇒ full re-announce at the first boundary — conservative direction preserved).
     *
     * [c209-C] R2 C7 — `models`: the model CATALOG names announced (the agent_listing initial frame's
     * "Models available for the 'model' parameter:" tail, Q4). The resume seed feeds it to the
     * producer; a differing current catalog ⇒ the first boundary's agent_listing frame re-carries the
     * full line — without this a catalog swap between legs was never re-disclosed anywhere (the
     * catalog left the tool description with Q4). Absent (older checkpoint / never announced) ⇒ drift
     * undetectable, silent (conservative, same B-5 direction).
     */
    announcedListings?: import("../internal/harness.js").AnnouncedListingSnapshot;
    /**
     * The git-status frame's announced `(kind, hash)` state at suspend (env-tail migration: the
     * run-loop-maintained mirror, advanced at every frame receipt). The LOWEST rung of the resume
     * read ladder — the session branch walk is the authority (a nearest PENDING mirror there, or an
     * announced mirror whose frame entry is off the active branch, forces a re-announce regardless of
     * this field); the checkpoint seeds only when the branch carries no mirror at all, and its
     * `entryId` must itself sit on the active branch to count as announced. Schema ADDITION
     * (additive, old readers ignore it — the Q5 posture): an older checkpoint without the field re-announces
     * conservatively — the frame is a tail append, so a duplicate costs bytes, never a prefix break.
     */
    gitAnnouncement?: {
        kind: "full" | "degraded" | "unavailable" | "non-repo";
        hash: string;
        entryId?: string;
        pending?: true;
    };
    /**
     * design/180 R-4 (R-2 cross-process variant) — the armed delegation child's MONOTONIC runtime-provenance
     * aggregate at suspend ({@link import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate}:
     * version + coverageStartTurn + the two never-cleared bits sawExternal/incomplete). Persisted so a
     * resume — a cross-process one especially, which lost the in-memory ref — CONTINUES accumulating from
     * the recorded bits instead of restarting a fresh aggregate that could prove "clean" on the post-resume
     * segment alone. Plain data → JSON/structuredClone round-trips. Present ONLY when the suspended leg ran
     * with a recorder armed; ABSENT on an unarmed leg AND on an older checkpoint — the resume side reads
     * absence-on-an-armed-child fail-closed as `incomplete`. */
    delegationProvenance?: import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate;
    /**
     * #281 件B follow-up — `true` iff the suspended leg ran as a DELEGATED CHILD (the trusted
     * `RunInternals.isDelegatedChild` fact, read off the leg's own minted
     * {@link import("./runner/contracts.js").Prepared.hookIdentity} so a re-suspend chain carries it
     * forward). Without it a deps-only resume (`resume(token, outcome, config)` — no trusted internals
     * re-supplied) re-minted the resumed leg's identity with `isDelegatedChild: false`, so the
     * delegation-lifecycle observer's emission gate stayed shut and the resumed leg emitted NEITHER
     * frame — a deployment watching only that seat saw a human-approved delegation frozen at its
     * park-leg `"suspended"` terminal forever. Live internals still win when re-supplied (the in-engine
     * resume lanes all do); the checkpoint key is the fallback for the deps-only leg. Only this ONE
     * axis persists — `insideFork`/`agentName`/`parentToolCallId` stay internals-only, honestly absent
     * on a deps-only resume rather than guessed; the accepted residue of the absent `insideFork` is
     * that a FORK child resumed deps-only reads as a plain delegated child on the non-fork consumer
     * facet (subagent persona/context trim) — documented here rather than fixed with a new
     * persistence axis. Schema ADDITION (additive, old readers ignore it —
     * the Q5 posture; no version movement): an older worker resuming a newer row keeps the pre-key
     * behavior, and an older row without the key resumes frame-less by documented honest absence
     * (see {@link import("./types.js").DelegationLifecycleEvent}). Absent = the leg was not a
     * delegated child (never `false` — presence-coded like the other monotonic bits here).
     */
    isDelegatedChild?: true;
    /**
     * design/380 O1③ — the run TREE's placement fixed point at suspend: the SAME three-segment fold the
     * env-factory ctx mint uses (explicit `RunInternals.placementRoot` > the delegation lanes'
     * `rootSessionId` > the leg's own resolved session), stamped from TRUSTED internals so a durable
     * resume can restore it instead of falling back to the parked leg's own session (a target-bound
     * factory keys its placement lookup on the fixed point — the self fallback read a resumed leg as a
     * NEW placement). Restore is the `isDelegatedChild` posture replayed on the placement axis: an
     * EXPLICIT live `internals.placementRoot` wins when re-supplied; otherwise THIS key restores into
     * `internals.placementRoot` at prepare so descendants and the NEXT re-suspend mint inherit it too
     * — outranking a supplied `rootSessionId`, which is DELEGATION IDENTITY (the peer/registry axis a
     * revive leg re-supplies from its row), never a placement claim (reading it as one
     * permanently stuck every parked agent spawned under an explicit placement root). The resume entry
     * ALSO reads this key as an identity record: a resume whose supplied `internals.placementRoot`
     * CONTRADICTS it is refused pre-CAS (`resume.placement_mismatch`); either side absent skips the
     * check (an older row / a claim-less resume — the honest degradation arm), and `""` reads as
     * absence (the identity fields' discipline). Schema ADDITION (additive, old readers ignore it — the Q5 posture;
     * deliberately NOT version-gated: same rolling-deployment account as {@link Checkpoint.principal},
     * whose doc records version-gating as a design candidate — any new stamp is a lattice-wide window
     * rewrite, owned by that ticket, not ad-hoc here). Absent ⇒ minted before the field, or the fold
     * resolved empty on a factory-less run (never persisted as garbage).
     */
    placementRootSessionId?: string;
    /**
     * design/380 O9a — MONOTONIC presence bit: this run's hand-tool content fold was EXTERNAL-TARGET
     * armed (the env declared `ExecutionEnv.externalContentTarget`, or an earlier leg of this suspend
     * chain did). Stamped at the suspend mint from the run's RESOLVED fold state — never from first
     * tool use, so a park landing BEFORE the first hand call is covered too. Presence-coded like
     * {@link isDelegatedChild} (absent = never armed; never `false`). The resume side folds
     * **persisted OR live declaration** (a monotonic join, never a downgrade): a run minted under a
     * declaring adapter keeps its memory fold and its prompt disclosure when resumed on an
     * undeclared/older adapter — the downgrade is closed in the derivation itself, deliberately
     * WITHOUT a new refusal arm (one fewer failure path, same safety). Feeds the memory face and the
     * prompt disclosure only — no gate/policy/roster behavior reads it (the design/378 red line).
     * Schema ADDITION (additive, Q5 posture; the version-gate residual is shared with
     * {@link placementRootSessionId} above and owned by the same version-lattice ticket).
     */
    externalContentTarget?: true;
}
/**
 * {@link CheckpointState} with **every** field made required-PRESENT, while each value keeps its original
 * type (so an absent optional field is still passed explicitly as `undefined`). The suspend-side
 * serialization builds `state` as this type (design/45 P3 / design/51 §P3): adding a new per-task
 * correctness field to {@link CheckpointState} is then a **compile error** at the serialization site until
 * it is explicitly handled — it can never be silently omitted and lost on resume. (Keying off
 * `keyof Required<…>` makes the mapped type non-homomorphic, so it forces presence of every key yet leaves
 * each value's `| undefined` intact — `workspaceHandle: undefined` for a process-local suspend still
 * type-checks.)
 */
export type SerializedCheckpointState = {
    [K in keyof Required<CheckpointState>]: CheckpointState[K];
};
/**
 * The checkpoint FEATURE TABLE — one row per feature that ever raised the row format. A row carries the
 * feature's schema version, the persisted mark by which a reader tells the feature was written on a row
 * (`recordedOn`, the READ face), and what a mint owes when the feature is present (`writes`, the WRITE
 * face — the two faces are deliberately separate columns). Minting is derived from the rows
 * ({@link checkpointVersionFor}: the highest version among the features present), the per-feature
 * constants below ARE the rows' versions, and the resume screen reads the rows — so adding a feature is
 * one row here plus an explicit answer at each mint site (the presence record's keys are mandatory).
 *
 * Because the stamp is a maximum, the version records only the HIGHEST feature on a row; whether a lower
 * feature is present is said by that feature's own mark, never by the number. A resume rule keyed on the
 * version alone is therefore exact to the version at which its feature was the top: `realApproval` is
 * version-recorded at v8 and marker-recorded (`readFace.realApproval`) from v9 on. The same law binds the
 * NEXT feature: once a row can be stamped above v9, "v9+ ⇒ read-face section present" no longer follows
 * from the stamp, and the face needs a mark of its own (or the row a per-feature presence record).
 *
 * v4 is a GAP, deliberately: the retired cross-slice wall-clock allocation. This worker still reads v4
 * rows; a ledger carrying the retired axis is refused by FIELD (`walltime_axis_retired`) at any version.
 */
export declare const CHECKPOINT_FEATURES: {
    /** The baseline shape: a plain human-approval suspend before any feature raised the format. No live
     *  mint stamps it today; it is the floor of the ladder and the reading of a row written before any feature. */
    readonly baseline: {
        readonly version: 1;
        readonly introduced: "design/49";
        readonly recordedOn: (_cp: Checkpoint) => boolean;
        readonly writes: "the row itself";
    };
    /** A `resource_limit` suspend: `resourceLedger` plus a `resource_limit` gate/pendingAction an older worker
     *  cannot handle (it rejects pre-CAS; the row stays `pending`, retryable on a newer worker). */
    readonly resource: {
        readonly version: 2;
        readonly introduced: "design/74";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "resourceLedger; gate/pendingAction kind resource_limit";
    };
    /** A HUMAN-DECISION park (tool approval or plan_review) of the binding era: the decision-action binding
     *  (`boundCallId` + `boundInputHash` on a tool_approval) is enforced ENTIRELY by the resuming worker, so a
     *  pre-binding worker must reject the row rather than execute the pending tool unverified. Like `resource`,
     *  this row is a park-kind FLOOR: a plan_review park carries it with no binding (it was introduced above it);
     *  whether a tool_approval row carries its hash is the resume binding rung's own conjunction. */
    readonly binding: {
        readonly version: 3;
        readonly introduced: "design/80";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "pendingAction.boundCallId + boundInputHash on a tool_approval; a plan_review carries the stamp only";
    };
    /** A ledger carrying a cross-slice TOKEN allocation: the window clamp and the exhausted-resume fail-fast
     *  live in the resuming worker. A ledger with no token total keeps the historic stamps. */
    readonly token: {
        readonly version: 5;
        readonly introduced: "design/164";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "resourceLedger.totalTokens";
    };
    /** Org-memory admission freeze state (the adjudicated-empty record included): the session/delegation
     *  freeze on resume and the tenant-admission door live in the resuming worker. */
    readonly orgAdmission: {
        readonly version: 6;
        readonly introduced: "org-admission";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "inheritedGate.admittedOrgScopes / ownAdmittedOrgScopes / ownAdmittedOrgWriteScope / orgAdmissionGoverned";
    };
    /** The F-012 durable enforcement fields: the frozen ancestor projection chain + its digest (one write, the
     *  resume-edit re-adjudication executes it) and/or the delegation-provenance aggregate a resume re-seeds. */
    readonly f012: {
        readonly version: 7;
        readonly introduced: "F-012";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "inheritedGate.constraintChain + constraintDigest together; delegationProvenance";
    };
    /** A gate carrying {@link RealApprovalGateBit}: the no-org-wiring refusal and the org_unavailable resume
     *  belt live in the resuming worker. Minted at ONE site (the approval park); the two always-mounted
     *  integrity policies mint it on any durable deployment, org-governed or not. */
    readonly realApproval: {
        readonly version: 8;
        readonly introduced: "#130/#120";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "gate.realApproval (a well-formed bit) + pendingAction.requiresRealApproval; from v9: readFace.realApproval = true beside it";
    };
    /** The READ-face section ({@link CheckpointState.readFace}): resolved face + normalized deny-set additions,
     *  written only for a non-default posture; the stricter-wins fold lives in the resuming worker. Carries the
     *  redundant governance marker for `realApproval` (see the table note above). Absent ⇒ default posture. */
    readonly face: {
        readonly version: 9;
        readonly introduced: "design/199";
        readonly recordedOn: (cp: Checkpoint) => boolean;
        readonly writes: "state.readFace { face, denyEntries? } for a non-default posture; realApproval: true only at the mint whose gate carries the bit (a hands-less re-park never carries the seed's marker forward)";
    };
};
/** One row of {@link CHECKPOINT_FEATURES}. */
export interface CheckpointFeatureRow {
    readonly version: number;
    /** Where the feature came from — a design or ticket tag, documentation only. */
    readonly introduced: string;
    /** READ face: true iff the row carries the feature's persisted mark. */
    readonly recordedOn: (cp: Checkpoint) => boolean;
    /** WRITE face: what a mint must write when the feature is present (prose; the mint sites are the code). */
    readonly writes: string;
}
/** The feature words — the row keys of {@link CHECKPOINT_FEATURES}. */
export type CheckpointFeature = keyof typeof CHECKPOINT_FEATURES;
/** The BASELINE {@link Checkpoint} schema version — the `baseline` row. NOT the highest a resume accepts (that
 *  is {@link MAX_SUPPORTED_CHECKPOINT_VERSION}); resume stays forward-compatible by `<=` against MAX, never `===`. */
export declare const CURRENT_CHECKPOINT_VERSION: 1;
/** The `resource` row's version. */
export declare const RESOURCE_CHECKPOINT_VERSION: 2;
/** The `binding` row's version. */
export declare const BINDING_CHECKPOINT_VERSION: 3;
/** The `token` row's version. v4 (the retired wall-clock total) is deliberately not reused. */
export declare const TOKEN_CHECKPOINT_VERSION: 5;
/** The `orgAdmission` row's version. */
export declare const ORG_ADMISSION_CHECKPOINT_VERSION: 6;
/** The `f012` row's version. */
export declare const F012_CHECKPOINT_VERSION: 7;
/** The `realApproval` row's version. */
export declare const REAL_APPROVAL_CHECKPOINT_VERSION: 8;
/** The `face` row's version. */
export declare const FACE_CHECKPOINT_VERSION: 9;
/** The highest {@link Checkpoint.version} `runner.resume` will act on — the top row of the table; a higher one
 *  is rejected pre-CAS with {@link CheckpointError} `unsupported_version` (the row stays `pending`, retryable on
 *  a newer worker). This is the CEILING entrance: the storage-faithfulness probe stamps it directly on a
 *  synthetic row that is never resumed, and does not go through feature detection. */
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION: 9;
/** What a mint site answers for: every row but the baseline, every key MANDATORY — a new row is a compile
 *  error at each mint site until that site says whether its rows carry the feature. */
export type CheckpointFeaturePresence = Record<Exclude<CheckpointFeature, "baseline">, boolean>;
/** The schema version a mint stamps: the highest version among the features PRESENT on the row, the baseline
 *  when none is. One rule for every mint site — the precedence between features is the version numbers. */
export declare function checkpointVersionFor(presence: CheckpointFeaturePresence): number;
/**
 * Read a checkpoint's schema version, defaulting an absent field to **legacy `0`** (a 1.67-era checkpoint
 * written before the field existed — it carries no `workspaceHandle`, so resuming it the v1 way is safe).
 * Compare with `<= MAX_SUPPORTED_CHECKPOINT_VERSION`, never `=== CURRENT_CHECKPOINT_VERSION` (design/49 §3).
 */
export declare function checkpointVersionOf(cp: Pick<Checkpoint, "version">): number;
/**
 * design/74 R3-B: cross-slice resource accounting carried by a `resource_limit` {@link Checkpoint}. The
 * total budget is the human's allocation; `spent*` accumulate across the resume→re-suspend chain (debited
 * at each suspend's `put`). A resumed slice's effective `maxCostUsd` = `min(window, totalBudgetMicroUsd -
 * spentMicroUsd)`; the final {@link TaskResult} stats aggregate `spent* + this slice's stats` so a
 * multi-slice run reports the whole, not just the last segment. Lives ON the checkpoint row so one
 * `resolve`/`put` CAS covers status + ledger atomically (no cross-store split).
 */
export interface ResourceLedger {
    /** The human's total $ allocation in micro-USD; `undefined` = no $ ceiling (only window/walltime bound). */
    totalBudgetMicroUsd?: number;
    /** design/164 — the human's total TOKEN allocation across the whole resume→re-suspend chain;
     *  `undefined` = no token ceiling (only the per-slice window binds). Replaces the retired wall-clock
     *  total: tokens are additive across processes, wall-clock across a suspend chain is not. */
    totalTokens?: number;
    /** Cumulative cost across all slices so far (micro-USD), debited at each suspend's `put`. */
    spentMicroUsd: number;
    /** Cumulative tokens across all slices so far. */
    spentTokens: number;
    /** Cumulative turns across all slices so far. */
    spentTurns: number;
    /** Cumulative wall-clock across all slices so far (ms; each slice measured on the monotonic clock).
     *  design/164: PURE ACCOUNTING — it is no longer a gate on anything (the wall-clock resource axis
     *  retired). Kept because "how long did this chain actually run" is a real observability question.
     *  Absent on checkpoints written before it existed; readers treat missing as 0. */
    spentWalltimeMs?: number;
    /** How many slices have run so far (the resource chain's suspend count). */
    sliceCount: number;
}
export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
    costMicroUsd: number;
    tokens: number;
    turns: number;
    walltimeMs?: number;
}, total?: {
    totalBudgetMicroUsd?: number;
    totalTokens?: number;
}, opts?: {
    countSlice?: boolean;
}): ResourceLedger;
/** design/74 Slice 4: the $ budget (micro-USD) the NEXT slice may still spend = `totalBudgetMicroUsd -
 *  spentMicroUsd`, never negative. `undefined` when no total $ ceiling is set (the run is bounded only by the
 *  per-slice window / walltime). A resumed slice's effective `maxCostUsd` is `min(window, this)`. NB: `0` is a
 *  VALID, exhausted ceiling — NOT "unlimited" (only `undefined` is unlimited). Any spend then immediately trips
 *  `overBudget`, and the run loop fails an exhausted resume fast (runtask). Never falsy-test this value (a
 *  `if (remaining)` would skip a legitimate 0 ceiling and silently circumvent the budget). */
export declare function remainingBudgetMicroUsd(ledger: ResourceLedger | undefined): number | undefined;
/** design/164 (the TOKEN sibling of {@link remainingBudgetMicroUsd}): tokens the NEXT slice may still
 *  spend = `totalTokens − spentTokens`, never negative. `undefined` when no token total is set (the run is
 *  bounded only by the per-slice window / the $ axis). A resumed slice's effective `limits.maxTokens` is
 *  `min(window, this)` — a resume can't refresh the allocation. NB: `0` is a VALID, exhausted ceiling
 *  (only `undefined` is unlimited); the run loop fails an exhausted resume fast — never falsy-test it. */
export declare function remainingTokens(ledger: ResourceLedger | undefined): number | undefined;
/**
 * design/80 D-1 (reopen-by-reason + persist-winner): the WINNING resume binding, recorded ON the
 * checkpoint row the moment a {@link CheckpointStore.resolve} CAS wins (pending → resolved). It is the
 * durable record of "which decision was approved against which pending action" so a later reopen→re-resume
 * can be validated against it. A `policy_ask` resolve and BOTH review-gate resolves record a winner: only
 * `policy_ask` is bound to a specific pending tool call, while `plan_review` / `dry_run_review` record
 * their decision under the synthetic `gate:<kind>` marker instead (RB-64). A `resource_limit` / `wake` /
 * `task_done` resolve records none (there is no decision to bind). `boundInputHash` (binding the exact executed bytes) is a SEPARATE
 * later D-1 slice (1a.2) and is deliberately NOT part of this winner.
 */
export interface ResolvedOutcome {
    /** The pending tool call this decision was bound to (design/80 D-1 slice 1a). A REVIEW gate binds
     *  no tool call, so it records the synthetic `gate:<kind>` marker instead — the replay guard compares the
     *  DECISION for those, never a call id. */
    boundCallId: string;
    /** The adjudication the operator made. RB-64 widens this to the review verdicts (`approve`/`reject`/
     *  `edit`) so a `plan_review`/`dry_run_review` winner can be persisted and replayed by the same
     *  env_failed reopen guard the approval gate has — previously review gates recorded NO winner, which is
     *  what left their reopen path with nothing to check a re-vote against. */
    decision: "allow" | "deny" | "approve" | "reject" | "edit";
    /** A re-written arg payload, if the `allow` rewrote the call (design/37). Carried so an `env_failed`
     *  re-resume (a system retry of the SAME approved action) must replay the identical `updatedInput`. */
    updatedInput?: unknown;
    /** The content-ask ANSWER the `allow` carried, if any (ruled 2026-08-04). Recorded for the same reason
     *  `updatedInput` is: the answer is the SUBSTANCE of a content-ask decision, not a decoration on it, so
     *  an `env_failed` re-resume — a system retry of the already-approved action — must replay the identical
     *  answer. Without it the replay could keep the verdict and swap the answer text going into the model's
     *  context (the shape the plan-edit replay guard closed for `editedPlan`). Omitted when absent so the
     *  replay equality check does not falsely diverge on `undefined`. */
    answer?: QuestionAnswer;
    /** The operator's NOTE, if the decision carried one — the approval lane's deny `reason` and the review
     *  lanes' reject `reason` alike. Recorded for the same reason `updatedInput` and `answer` are: the note
     *  is the substance of the decision, not a decoration on it. It is the text the resume continuation
     *  fences into the model-facing reminder that steers the re-plan, so a replay keeping the verdict and
     *  swapping the note delivers a different instruction under the same word. Omitted when absent.
     *
     *  ADDITIVE and OPTIONAL by contract: rows persisted before this field existed carry no `reason` key,
     *  and the key's absence is read as "this dimension was never recorded" — the replay guard skips it —
     *  NEVER as "recorded as empty", which would refuse every legitimate retry of an existing row. Absence
     *  is therefore indistinguishable from a genuinely note-less decision, which is the accepted price of
     *  the additive shape: the binding starts with the rows that record it.
     *
     *  RECORDED VERBATIM, and independently of the verdict: any supplied note is kept exactly as given —
     *  including `""`, and including a note on a verdict that never relays one (an `allow`, an `approve`, an
     *  `edit`). The winner is a record of the decision that was made, not of the subset of it the
     *  continuation happens to render, and `updatedInput` has carried the same property since D-1 (a rewrite
     *  on a `deny` is recorded and replay-compared although nothing executes it). Normalizing here would
     *  mean an empty note collapsed into the "never recorded" hole above and a note on a non-relaying
     *  verdict became free to change between the resolve and its replay — both weaken "the retry presents
     *  the same decision" for no delivery benefit, since a replay is produced by re-presenting the same
     *  outcome, not by reconstructing an equivalent one. */
    reason?: string;
    /** The settlement record core minted for this decide ({@link import("./gate-outcome.js").GateOutcome}):
     *  the row's own copy of what the resolved call's `tool_end` frame carries, written with the winner so
     *  an audit reading the row reads the same record the stream carried. NOT part of the replay equality
     *  (`when` differs per replay; the record is a consequence of the decision, not its substance). */
    gateOutcome?: import("./gate-outcome.js").GateOutcome;
}
/**
 * design/80 D-1: why a consumed checkpoint was {@link CheckpointStore.reopen | reopened} (resolved →
 * pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
 * RETRY of the ALREADY-APPROVED action — the re-resume MUST replay the persisted {@link ResolvedOutcome}
 * winner, never a new vote — while a `tool_unavailable` reopen (the action could not run, may now be
 * invalid — P-7) lets a human RE-DECIDE with the tool present, so a fresh operator decision IS allowed.
 *
 * `constraint_rejected` (F-012 L3) carries the same FRESH-REDECISION semantics as `tool_unavailable`:
 * a resolve was retracted because its decided INPUT failed a constraint re-adjudication (a frozen
 * ancestor projection / resume-edit policy denied the approver's edit) — the ask itself is unconsumed,
 * so the returned-to-pending row takes a fresh operator decision, never a forced replay of the
 * rejected one. Core's own edit re-adjudication runs PRE-CAS (nothing to reopen there); this reason
 * exists for a deployment lane that discovers the same rejection only after its own resolve landed
 * and must return the row to pending without inventing an out-of-vocabulary reason.
 */
export type ReopenReason = "env_failed" | "tool_unavailable" | "constraint_rejected";
/**
 * design/80 D-1 (atomicity fix): the optimistic-concurrency precondition a {@link CheckpointStore.resolve}
 * caller passes so its CAS is atomic with the validation it did against an earlier `get()` snapshot. The
 * resume-side decision-action guards (boundCallId/boundInputHash) bind to per-token-IMMUTABLE fields, but the
 * **reopen-by-reason** guard reads `reopenReason` + `resolvedOutcome` — which a concurrent
 * {@link CheckpointStore.reopen}/{@link CheckpointStore.resolve} cycle mutates. Passing the monotonic
 * {@link Checkpoint.rev} the caller observed makes `resolve` additionally require the LIVE row's `rev` to still
 * equal it; ANY intervening resolve/reopen bumps `rev`, so a cycle in the get→resolve window loses the CAS
 * (fail-closed), forcing a re-`get` + re-validate.
 *
 * **Why a counter, not the reopenReason value (round-2 BLOCKER fix):** `reopenReason` is a 2-valued enum, so a
 * full `env_failed`→`tool_unavailable`→`env_failed` cycle (the `tool_unavailable` arm permits a fresh P-7
 * re-decision) returns `reopenReason` to the SAME value while `resolvedOutcome` silently changed to a different
 * (e.g. RETRACTED) winner — a classic ABA on the OCC key. A monotonic `rev` bumped on every resolve/reopen is
 * ABA-proof: a returned-to-the-same-value `reopenReason` still has a strictly higher `rev`. Omit `expect`
 * (legacy callers / direct store tests) → no OCC.
 */
export interface ResolveExpectation {
    /** The monotonic {@link Checkpoint.rev} the caller observed at `get()` (absent rev ⇒ legacy `0`). */
    rev: number;
}
/** design/384 slice 2 / S-25-R1 — the terminal intents a claim can carry. Deliberately the
 *  TERMINAL pair only: `resolve` and `expire` are the two verbs that race one pending row to
 *  its end (the design/51 fence law). `reopen` (resolved→pending) is not terminal and `reap`
 *  is a bulk sweep — both stay on their own verbs. */
export type TerminalClaimIntent = {
    kind: "resolve";
    outcome: ResumeOutcome;
    expect?: ResolveExpectation;
} | {
    kind: "expire";
};
/** design/384 slice 2 / S-25-R1 — the single-round-trip answer: EITHER the claim won, OR it lost and
 *  the SAME atomic operation reports what the row is now. `current` reuses the row's own vocabulary
 *  (status / rev / resolvedOutcome) — no new value domain. `status:"pending"` on a loss is the OCC
 *  arm only (`expect.rev` mismatch): the row is still open — re-get + re-validate, zero backoff (a
 *  cleanly KNOWN state is never "unknown"). */
export type TerminalClaimOutcome = {
    claimed: true;
} | {
    claimed: false;
    current: {
        status: "pending" | "resolved" | "expired";
        /** `(cp.rev ?? 0)` — the absent-rev-reads-as-0 rule the OCC key already uses. */
        rev: number;
        /** Row-field PASSTHROUGH on every present status: the row PRESERVES it across `reopen`
         *  (resolved→pending rows carry it), `expire` never clears it, and a winnerless resolve
         *  (resource/wake/task_done) does not overwrite an older value — so even
         *  `(status:"resolved", outcome present)` is only "the row is resolved AND this is its
         *  LAST RECORDED decision winner", never a store-level guarantee that THIS resolve minted
         *  it. Interpreting the pair as a current decision is the CALLER's discipline (the
         *  runner's gate-kind matching; a single-decision row schema like a deployment's ask
         *  rows). See {@link CheckpointStore.claimTerminal} law 6. */
        resolvedOutcome?: ResolvedOutcome;
    }
    /** Row missing — OR wrong scope: the CAS predicate is the shared REF-A4 row match
     *  ({@link checkpointRowMatches}), and a wrong-scope claim must lose WITHOUT reporting the
     *  row's truth (multi-tenant isolation: cross-scope truth in a loss answer would be a read
     *  bypass of the scope WHERE). Absent is a KNOWN state (zero backoff): expire intent ⇒ moot;
     *  resolve intent ⇒ the caller's typed not-found path, no retry. */
     | {
        status: "absent";
    };
};
/** A persisted suspension point: enough to resume a task on any replica. `status` drives the 3-state
 *  machine (pending → resolved | expired) that makes resume idempotent (§5). */
/** A durable checkpoint: the row's fields ({@link CheckpointRow}) plus its registry-bound pause pair. */
export type Checkpoint = CheckpointRow & CheckpointPause;
export interface CheckpointRow {
    token: CheckpointToken;
    /** Non-secret stable identity ({@link mintCheckpointId}) — the display/correlation twin of
     *  the secret `token` (identification vs capability, separate axes). Present on every row minted
     *  since its introduction; absent ⇒ legacy row (read compatibly, no backfill). */
    checkpointId?: string;
    /**
     * Schema version of this checkpoint (design/49 v1.5). Every live suspend stamps the version of the
     * FEATURE it carries (RESOURCE / BINDING / TOKEN / ORG_ADMISSION); {@link CURRENT_CHECKPOINT_VERSION}
     * is the baseline at the bottom of that ladder, not the stamp of any current mint site.
     * absent ⇒ legacy `0` (1.67-era, no `state.workspaceHandle`). Resume rejects `> MAX_SUPPORTED_CHECKPOINT_VERSION`
     * pre-CAS so an old worker can't silently ignore fields a newer format relies on (forward-compatible by `<=`).
     * Read via {@link checkpointVersionOf}.
     */
    version?: number;
    /** Multi-tenant isolation key (forced through `resolve`/`reap` WHERE, §2.1 service [4]). */
    scope: string;
    /** The session to resume (via the SessionStore). */
    sessionId: string;
    /** The session leaf the suspend happened at — the resume point (CAS write base, §5). */
    leafId: string;
    state: CheckpointState;
    /** design/74 R3-B: cross-slice resource accounting. Present for a `resource_limit` gate (debited at the
     *  suspend's `put`; read on resume to size the next slice + aggregate the final stats). **design/80 D-E-core
     *  (A3):** ALSO attached to a human/irreversible_ask APPROVAL suspend — debited with that leg's spend — so a
     *  STATELESS policy can read durable cumulative spend across the resume chain via {@link ToolCallRequest.budget}
     *  (a fresh in-memory counter would reset every leg). On the approval path it is READ-CONTEXT ONLY: the final
     *  -stats aggregate fold (runtask) is gated to the `resource_limit` gate kind, so an approval resume is never
     *  double-counted. Absent for `task_done`. See {@link ResourceLedger}. */
    resourceLedger?: ResourceLedger;
    /**
     * Absolute epoch-ms deadline for an awaiting-human checkpoint; past it, `reap` expires it (§6).
     *
     * **design/80 §D-D semantic contract (the DURABLE FIELD a reaper-loop consumes — read it on
     * {@link CheckpointStore} too):** for an APPROVAL gate (`gate.kind` ∈ {`human`, `irreversible_ask`}) the
     * deployment's reaper-loop treats this as an **SLA resolve-deny** time (FACET A — its fast path: at deadline
     * it `resolve`s the checkpoint as a `deny` so the run ends cleanly), with a LATER `terminalAt` abandonment
     * backstop the loop ALSO honors (crash-safe if the SLA timer never fired). For an UNATTENDED-TTL gate
     * (`gate.kind` ∈ {`resource_limit`, `needs_review`, `plan_review`} — the resource + review-pause family) this
     * is an **abandonment-TTL** → `expire`/{@link reap} ONLY, NEVER a resolve-deny (FACET B — those kinds resume
     * only with their OWN outcome, so a `policy_ask` resolve-deny hits no `gateMatch` arm → `gate_mismatch`). The
     * reaper-loop + the gate.kind policy + minting `terminalAt` are the DEPLOYMENT's job
     * (its store impl + loop — owned service-side); CORE only guarantees this `deadline`
     * field (for `human`/`irreversible_ask`) + the persisted {@link gate}.kind survive the round-trip so any
     * reaper consumer (the service's TiDB loop OR a non-service Pg deployment) implements the SAME contract.
     */
    deadline?: number;
    status: "pending" | "resolved" | "expired";
    createdAt: number;
    /**
     * How many times THIS task has suspended, counting this checkpoint (1 = first suspend; design/72
     * §2.2 (B)). Carried forward across resume→re-suspend (each new checkpoint = the prior count + 1), so
     * a restart-prone model that keeps re-issuing the gated/egress call — re-suspending every resume —
     * is capped: past `maxSuspends` the run fails (`suspend.loop`) instead of minting another checkpoint
     * and looping forever. Absent ⇒ legacy `0` (pre-§2.2 checkpoints; treated as "no prior suspends").
     */
    suspendCount?: number;
    /**
     * design/91 — the wall-clock instant (epoch ms, from {@link import("./types.js").RunnerDeps.now}) this task
     * suspended at a HUMAN-REVIEW gate. Resume derives the human-review latency for this leg as
     * `now() − suspendedAt` and folds it into `TaskResult.stats.humanReview` (the design/89 §2.4 C2 burden axis).
     * The human-review family is the APPROVAL gates (`gate.kind` ∈ {`human`, `irreversible_ask`}) — core stamps
     * this automatically on the approval-suspend mint — AND the REVIEW-PAUSE gates (`needs_review` = a dry-run
     * diff review, `plan_review` = a pre-action PLAN review). Review-pause checkpoints are minted by a PROFILE
     * (core owns only the routing/resume discriminants), so a profile that wants the C2 wait counted MUST stamp
     * `suspendedAt = deps.now()` at mint — the same injectable clock the resume reads, so a test pins the latency.
     * Absent on a `resource_limit` suspend (a machine backstop, not a human wait), on a review-pause checkpoint
     * whose profile did not opt in, and on a legacy pre-design/91 checkpoint (⇒ resume skips the durable latency
     * for that leg — no undercount surprise, just no entry).
     */
    suspendedAt?: number;
    /**
     * design/91 — the accumulated {@link import("./types.js").TaskResult.stats}`.humanReview` from all PRIOR legs,
     * carried forward across resume→re-suspend (like {@link suspendCount} / {@link resourceLedger}) so a
     * multi-leg suspend/resume chain reports the WHOLE human-review burden, not just the last leg's. It holds
     * the gates resolved up to and including the suspend BEFORE this one — the latency for THIS suspend is added
     * by the resume that wakes it (`now() − suspendedAt`). Absent ⇒ no prior human-review time (a first suspend
     * with no earlier synchronous asks). Budget-EXCLUDED side observable — never a cost/gate input.
     */
    humanReview?: {
        count: number;
        totalWaitMs: number;
        gates: import("./task-result.js").HumanReviewGateRow[];
    };
    /**
     * design/80 D-1 (persist-winner): the winning resume binding, recorded by {@link CheckpointStore.resolve}
     * when its CAS wins (pending → resolved) — present after a `policy_ask` resolve and after either REVIEW-gate
     * resolve (`plan_review` / `dry_run_review`, RB-64); absent on a freshly minted pending checkpoint and on
     * `resource_limit` / `wake` / `task_done` resolves. PRESERVED (never cleared) across a
     * {@link CheckpointStore.reopen} so an `env_failed` re-resume can be validated against it. See
     * {@link ResolvedOutcome}.
     */
    resolvedOutcome?: ResolvedOutcome;
    /**
     * design/80 D-1 (reopen-by-reason): why this checkpoint was last reopened (resolved → pending), recorded
     * by {@link CheckpointStore.reopen}. Drives re-resume validation (§3 invariant #1): `env_failed` ⇒ the
     * re-resume must replay the persisted {@link resolvedOutcome} winner (a system retry of the approved
     * action); `tool_unavailable` ⇒ a fresh operator decision is allowed (P-7 re-decide with the tool
     * present). Absent on a never-reopened checkpoint (a first resume is unconstrained by reason).
     */
    reopenReason?: ReopenReason;
    /**
     * design/80 D-1 (atomicity fix, round-2 BLOCKER): a **monotonic revision counter** bumped by EVERY
     * {@link CheckpointStore.resolve} (won) and {@link CheckpointStore.reopen}. It is the optimistic-concurrency
     * key ({@link ResolveExpectation}): a resume reads it at `get()` and `resolve` requires the live `rev` to
     * still equal it, so any resolve/reopen cycle landing in the get→resolve window loses the CAS (fail-closed).
     * Unlike the 2-valued `reopenReason`, it cannot ABA back to a prior value. Absent ⇒ legacy `0` (a checkpoint
     * minted before this field; treated as never mutated).
     */
    rev?: number;
    /**
     * design S1d (source-tag persistence): the issuing task's session id, stamped at the
     * APPROVAL suspend mint so a supervisor inbox can attribute a paused/awaiting checkpoint to the worker that
     * raised it (the durable analog of {@link import("./tool-policy.js").AskRequest}.sourceTaskId — the durable
     * suspend path does NOT invoke `onAsk`, so the synchronous source identity is captured here instead). It is
     * the SAME value the synchronous ask carries: `= sessionId` at the mint.
     *
     * **Security invariant (carried verbatim from the 1.113.0 ask-bubbling contract):** for a Runner-created
     * DELEGATED subagent this is **worker-unforgeable** (a child never sets its own sessionId — the Runner mints
     * it); a top-level caller MAY continue its own session id via {@link import("./types.js").TaskSpec.sessionId},
     * so this is NOT a global "never forgeable" run id — aggregation only aggregates delegated workers, where it
     * holds. **ECHO-ONLY triage metadata, NEVER a gate input:** no gate / CAS / winner / resume-validation path
     * reads it (a contract-test pins this). Absent ⇒ no source task id (a non-ask gate, or a legacy checkpoint).
     */
    sourceTaskId?: string;
    /**
     * design S1d (source-tag persistence): the issuing task's authenticated end-user {@link
     * import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint — and, since
     * #449 G2, at the `resource_limit` mint too — so an aggregating inbox can attribute a paused checkpoint
     * per-user — the durable analog of {@link import("./tool-policy.js").AskRequest}.principal. Caller-set
     * (never a tool/worker arg), and like {@link sourceTaskId} it is **NEVER a gate input**: no gate / CAS /
     * winner path reads it. The RESUME ENTRY however does read it as the row's identity record (#449 G2):
     * a resume config that omits `principal` inherits this value for the resumed leg (usage-ledger bucket,
     * later mints' scope derivation, ask attribution — a dropped field must not silently move the leg onto
     * the SHARED usage bucket), and a resume config carrying a DIFFERENT non-empty principal is refused
     * pre-CAS (`resume.principal_mismatch`). Absent ⇒ no principal (a legacy checkpoint, an unauthenticated
     * task, or a gate kind that predates the stamp). Rolling-deployment note (#449 G2 residual): the field
     * is NOT version-gated — an older worker accepts a principal-bearing row and keeps its historic
     * behavior (no fallback, no mismatch refusal); the identity-continuity guarantee holds once the fleet
     * is on enforcing workers. Version-gating it is a recorded design candidate: the lattice's newest rung
     * (design/199) encodes "v>=9 ⟺ face section present", so ANY new version above it must rewrite that
     * guard's window form and add its own presence invariant — a lattice-wide change, not a stamp.
     */
    principal?: string;
    /**
     * design/153 §7.4 (1.389.0, RB-42②③): the VERBATIM `TaskSpec.durableApproval` opt-in in force at this
     * APPROVAL suspend's mint — the engine-recorded provenance of "this task was durable-approval armed, with
     * this scope and this reaper TTL". Consumed by the parked-resume drive's REVIVE-CONTINUITY re-derivation
     * (subagent settle path): the drive spec carries no forwarding seat, so without this record a resumed leg's
     * SECOND plain ask would headless-deny instead of re-parking. Reading it explicitly (instead of inferring
     * the opt-in from `gate.kind === "human"`) covers a SAFETY-TIER first park (irreversible_ask — the kind
     * alone never proved the spawn opted in) and carries `ttlMs` across the re-park so the re-minted
     * checkpoint re-arms its reaper `deadline` (previously unbounded). Absent ⇒ the mint ran with NO
     * `durableApproval` (an unattended safety suspend, a non-ask gate, or a legacy pre-1.389 checkpoint — the
     * consumer then falls back to the human-kind scope-only inference for legacy rows and injects NOTHING for
     * unattended parks, so a spawn that never opted in can never gain the opt-in through a resume). Engine-
     * minted from the task's own spec — a worker/tool cannot self-declare it (`durableApproval` never rides tool args).
     */
    durableApproval?: {
        scope: string;
        ttlMs?: number;
    };
    /**
     * @contract checkpoint.execution_outcome — the settlement record of how the DECIDED action was
     * finally disposed on the resumed leg: the {@link GateOutcome} the resumed call's `tool_end` frame
     * carried (an executed allow, a person's refusal delivered, or a post-CAS belt's veto of a person's
     * yes), written by {@link CheckpointStore.recordExecutionOutcome} exactly once per row, on every
     * resolution (an `allowed` disposition is recorded too — the column's absence has ONE meaning:
     * the execution outcome is not known, never "it was allowed"). Independent of
     * {@link resolvedOutcome}, which stays the immutable record of the DECISION: a row can read
     * `resolvedOutcome.gateOutcome.disposition.kind === "allowed"` beside
     * `executionOutcome.disposition = { kind: "denied", deniedBy: "policy" }` — the person approved,
     * the deployment's belt refused the edited call — and a reader rebuilding the run from the row
     * sees both facts instead of the pre-execution one alone. The record is physically immutable:
     * a second write of a different record is a conflict, never an overwrite, and a row carrying one
     * cannot be {@link CheckpointStore.reopen | reopened} (the action is settled; a reopen would
     * re-run or re-decide work whose outcome is already on file). Read through
     * `executionVerdict` (checkpoint-execution-record.ts). Present together with {@link executionAtMs}.
     */
    executionOutcome?: GateOutcome;
    /** Epoch ms the store stamped {@link executionOutcome} at. Present iff `executionOutcome` is. */
    executionAtMs?: number;
}
/**
 * design/80 assistant-scheduler seam #1: a LIGHTWEIGHT projection of one PENDING
 * checkpoint, returned in bulk by {@link CheckpointStore.listByScope} so a supervisor scheduler can list a
 * scope's suspended/awaiting tasks in ONE call (no N+1 `get`s, no full-{@link Checkpoint} payloads). It is a
 * read-only DISPLAY/triage summary — every field is DERIVED from the persisted {@link Checkpoint}; nothing
 * here is a new source of truth or a gate input.
 */
export interface CheckpointSummary {
    /** The resume capability token ({@link Checkpoint.token}). */
    token: CheckpointToken;
    /** Non-secret stable identity ({@link Checkpoint.checkpointId}) — absent on legacy rows. */
    checkpointId?: string;
    /** The suspended task / session id ({@link Checkpoint.sessionId}). */
    sessionId: string;
    /** The multi-tenant scope this checkpoint lives in ({@link Checkpoint.scope}) — always equal to the query's. */
    scope: string;
    /** The gate's discriminant ({@link CheckpointGate}.kind): which kind of pause this is. */
    gateKind: CheckpointGate["kind"];
    /** The deterministic risk tier (1..5) when the gate carries a {@link RiskDescriptor} (the `human` /
     *  `irreversible_ask` approval escalations), derived via {@link riskSeverity} from the descriptor's axes —
     *  the SAME value as `gate.riskDescriptor.severity`, recomputed from the single source helper rather than
     *  re-stored. `undefined` for a gate with no descriptor (resource_limit / needs_review / plan_review / a
     *  legacy human ask minted before D-E). The inbox sorts DESC by this. */
    severity?: 1 | 2 | 3 | 4 | 5;
    /** Cumulative spend debited to this suspend chain ({@link ResourceLedger.spentMicroUsd}) in micro-USD —
     *  present whenever the checkpoint carries a {@link Checkpoint.resourceLedger} (every resource_limit suspend,
     *  and every approval suspend on the D-E-core budget-read path); `undefined` when no ledger is attached. */
    spentMicroUsd?: number;
    /** The awaiting-human SLA / abandonment deadline ({@link Checkpoint.deadline}, epoch ms) when present. */
    deadline?: number;
    /** design S1d (source-tag): the issuing worker's session id ({@link Checkpoint.sourceTaskId}) — projected so
     *  a supervisor inbox attributes each pending checkpoint to its worker in ONE `listByScope` call (no N+1
     *  `getCheckpoint`). ECHO-ONLY display/triage; absent on a non-ask gate / legacy checkpoint. */
    sourceTaskId?: string;
    /** design S1d (source-tag): the issuing task's end-user principal ({@link Checkpoint.principal}) for per-user
     *  inbox attribution. ECHO-ONLY display/triage; absent when the checkpoint carries no principal. */
    principal?: string;
    /** The pending tool call's id ({@link PendingAction} `tool_approval.toolCallId`) when this is an approval
     *  suspend — projected so the inbox shows WHICH call awaits a decision without an N+1 `getCheckpoint`.
     *  `undefined` for a non-`tool_approval` pendingAction (resource_limit / plan_review / task_done).
     *
     *  **ABSENCE SHAPE (contract)** — shared by every optional field on this projection: a
     *  tool-less park (`resource_limit` / `plan_review` / `task_done` — none of which binds a tool call)
     *  gets the key **OMITTED**, never `null` and never a present-but-`undefined` property. The
     *  `PendingAction` arms for those kinds declare no tool/binding fields at all, so there is nothing to
     *  project; `summarizeCheckpoint` spreads them conditionally for exactly this reason. A consumer
     *  therefore tests presence (`"toolCallId" in summary`), and a re-projecting service (BFF / SDK wire
     *  face) must not materialize the keys as `null` on the way out. Pinned in
     *  `test/checkpoint-store.test.ts` ("tool-less park …"). */
    toolCallId?: string;
    /** The pending tool's name ({@link PendingAction} `tool_approval.toolName`) when this is an approval suspend.
     *  OMITTED (not `null`) for a non-`tool_approval` pendingAction — see {@link toolCallId}'s absence-shape note. */
    toolName?: string;
    /** design/99 MF-14 (design-review DoR): the DERIVED content-gate classification — `"content_ask"` when this
     *  `tool_approval` gates the reserved AskUserQuestion tool (a question TO the user, not a side-effecting tool).
     *  A TYPED discriminant so a shell renders the question UI without sniffing `toolName` or parsing the bounded
     *  `toolInput` preview. The full typed questions ride the drilled-in Checkpoint's `pendingAction.args` (kept off
     *  this lightweight `listByScope` projection). Deliberately NOT a new `content_ask` checkpoint gate kind (that
     *  would cross the durable-checkpoint once-only-winner / reaper-deadline / batch invariants — the DoR). */
    contentKind?: "content_ask";
    /** design S1e: when the checkpoint was created ({@link Checkpoint.createdAt}, epoch ms) —
     *  projected so a supervisor inbox can sort/age pending entries (oldest-first triage) in ONE `listByScope`
     *  call, no N+1 `getCheckpoint`. ECHO-ONLY display/triage; always present (a Checkpoint always has a
     *  `createdAt`). */
    createdAt?: number;
    /**
     * design S1e: a **BOUNDED, UNREDACTED** raw preview of the pending tool call's input
     * ({@link PendingAction} `tool_approval.args` `JSON.stringify`-ed, truncated to {@link
     * MAX_TOOL_INPUT_PREVIEW_CHARS} chars with a `…` marker when over) — projected so a supervisor inbox shows
     * WHAT a paused tool call will do without an N+1 `getCheckpoint`. `undefined` for a non-`tool_approval`
     * pendingAction (resource_limit / plan_review / task_done — no args to preview).
     *
     * **Contract (load-bearing):**
     *  - **bounded raw preview** — length-capped to {@link MAX_TOOL_INPUT_PREVIEW_CHARS} so it cannot bloat the
     *    `listByScope` payload, but the chars within the cap are the RAW serialized args (no folding/escaping).
     *  - **redaction is the CONSUMER's responsibility** — core gives the inbox the unredacted bounded raw; the
     *    service/inbox decides what (if anything) to mask before showing a human. (Contrast {@link
     *    RiskDescriptor.summary}, which is the NEUTRALIZED+capped descriptor that DOES ride into a render —
     *    `toolInput` is the raw-data sibling for a consumer that wants the actual args.)
     *  - **same data as the full {@link Checkpoint}** — it is a projection of `pendingAction.args`, which the
     *    caller could already read via `get`; this adds NO new exposure, it only saves the round-trip.
     *  - **ECHO-ONLY, like {@link sourceTaskId}** — NO gate / CAS / resume / {@link winnerFromOutcome} path reads
     *    it (a contract test pins this); it is pure display/triage metadata, never a control input.
     */
    toolInput?: string;
    /**
     * HOW this pause's remote workspace comes back, projected from
     * `state.workspaceHandle.restoreMode` so a supervisor inbox can tell the two apart in ONE `listByScope`
     * call. `"snapshot"` = the VM was paused into a snapshot (billing typically stops, memory state
     * captured); `"park_only"` = a non-suspendable target (SSH host / ADB device) was never paused — the
     * machine is still running and still costing, and resume just reconnects. OMITTED (see the
     * {@link toolCallId} absence-shape note) for a process-local suspend, which captured no remote
     * workspace at all. ECHO-ONLY display/triage — nothing gates on it. Mirrors `TaskResult.workspaceRestoreMode`.
     */
    restoreMode?: "snapshot" | "park_only";
    /** the tool's approval display projection ({@link PendingAction} `preview`),
     *  projected so the ONE-CALL inbox (`listByScope`) can render a rich confirmation without an N+1
     *  `get`. Same trust posture as `toolInput`: **UNTRUSTED, advisory display metadata** (strings are
     *  sanitized of control chars at mint but may still carry model-authored markdown — renderers must
     *  contextually escape), ECHO-ONLY (no gate/CAS/resume reads it), and surfaces SHOULD show the
     *  bound raw args (`toolInput`/`pendingAction.args`) alongside — the preview never replaces them. */
    preview?: unknown;
    /** #457 ② — the row's `PendingAction.tool_approval.previewWithheld`, projected BESIDE {@link preview}
     *  for the same reason that one is: without it the ONE-CALL inbox cannot tell "this tool declares no
     *  preview" from "the projection failed", which is the exact collapse the determinant exists to
     *  remove — and re-opening the row with an N+1 `get` just to answer it defeats `listByScope`.
     *  `"oversize"` (produced but past the 16KiB mint bound — the truncation stub still rides
     *  {@link preview}) or `"unavailable"` (threw / no JSON serialization). The {@link toolCallId}
     *  ABSENCE-SHAPE contract applies: OMITTED otherwise, never `null`/`false`/present-but-`undefined`.
     *  A pure echo of the row's own persisted value — unlike {@link hasBidiControls}, there is no
     *  projection-time backfill, because whether a tool DECLARED a preview is not knowable from the
     *  projection's own materials (a pre-#457 row therefore reads absent, which is honest: unreported).
     *  ECHO-ONLY display metadata; nothing gates on it. */
    previewWithheld?: "oversize" | "unavailable";
    /** The row's `PendingAction.tool_approval.requiresRealApproval`, projected so the ONE-CALL
     *  inbox can label "only a person can clear this" without an N+1 `get`. `true` or OMITTED (the
     *  {@link toolCallId} absence-shape contract); pure echo of the persisted bit — a pre-#557 row reads
     *  absent, which is honest (unreported), never a claim that budget may clear it. */
    requiresRealApproval?: true;
    /** The row's `PendingAction.tool_approval.mandated` (#1079), for the same one-call reason: `true` or
     *  OMITTED (the {@link toolCallId} absence-shape contract), screened to the literal `true` — a pre-#1079 row
     *  reads absent (unreported). ECHO-ONLY; nothing gates on it. */
    mandated?: true;
    /** The row's `PendingAction.tool_approval.authoredBy`, echoed as the one literal. ECHO-ONLY. */
    authoredBy?: "deployment";
    /** The row's `PendingAction.tool_approval.mandate`, echoed ONLY as a member of the closed set. ECHO-ONLY. */
    mandate?: import("./ask-origin.js").PersistedRuleMandate;
    /** The row's `PendingAction.tool_approval.denialLimitFallback`, projected BESIDE the preview
     *  for the same one-call reason: a card that shows the fallback's counts and limit must not re-open
     *  the row to learn them (`autoDenyAfterMs` is `0` on every row — see the row member). Pure echo;
     *  OMITTED when the row carries none. */
    denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
    /** The row's `PendingAction.tool_approval.origin`, projected for the same one-call reason: a card
     *  rendered by the ask's origin word must not re-open the row to learn it. Echoed ONLY when the stored
     *  value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
     *  reads as absent, never as a word. OMITTED when the row carries none (a pre-existing row). */
    origin?: import("./ask-origin.js").AskOrigin;
    /**
     * PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
     * format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
     * that doc owns the hazard). Only ever for a `tool_approval` pendingAction; OMITTED for every other
     * kind (the {@link toolCallId} ABSENCE-SHAPE contract applies: never `null`, never present-but-
     * `undefined`, never `false` — a re-projecting BFF must not materialize any of those on the way out).
     *
     * WHERE THE ANSWER COMES FROM: the row's own persisted bit when present (the park mint is the
     * authority — one look, one answer, never re-derived); else a bounded BACKFILL scan run AT PROJECTION
     * TIME over this projection's OWN bounded materials — the truncated {@link toolInput} string just
     * derived plus the row's `preview`, under a small per-row budget — so a pre-#438 row and a
     * deployment-written row still get a bit exactly where the inbox reads. The backfill judges THE BYTES
     * THIS PROJECTION HANDS THE INBOX: a marker buried past the `toolInput` truncation point (or past the
     * per-row scan budget) is honestly reported as absent — new-engine-minted rows carry the mint-time bit
     * (judged over the full parked args) for exactly that reason. The backfill NEVER writes back to the
     * row: `summarizeCheckpoint` stays a pure projection.
     *
     * HOW TO READ IT: `true` = core detected the class in this row's payload (or took a deployment row's
     * own `true` at its word — over-warning is the safe direction); render a visible "the text below can
     * display differently from what it runs" annotation. ABSENT = NOT DETECTED, never "clean" — it covers
     * clean, budget-exhausted, and a `false`-writing out-of-contract producer (read as absent, never
     * projected). The bit changes NO rendering obligation a surface already had: with or without it,
     * `toolInput` / `toolName` / a summary string go through
     * {@link import("./permission-rule-model.js").renderUntrustedCommandText} (or the surface's own
     * screen) before a human reads them; for the structured `args`/`preview` the FIRST-CHOICE display
     * material is the bounded projections core already minted (`toolInput`, the descriptor `summary`) —
     * do not re-serialize the whole args for a list render, and a drill-in that must show everything owes
     * itself a TOTAL, bounded traversal (cycle-safe, per-type output forms, throwing getters to a
     * placeholder, depth/node/char budgets with a truncation marker — never "full `JSON.stringify` then
     * truncate", which does all the work and takes all the throws before the cap). ECHO-ONLY, like
     * {@link sourceTaskId}: a warning to a person, never a control input — resume/gate/CAS never read it.
     */
    hasBidiControls?: true;
}
/**
 * design/80 assistant-scheduler seam #1: project a single PENDING {@link Checkpoint} to its lightweight
 * {@link CheckpointSummary}. Shared by every {@link CheckpointStore} impl so the projection is IDENTICAL
 * across the in-memory, file, and durable backends (the anti-drift guard) — `severity` always derives from the
 * gate's {@link RiskDescriptor} via the single {@link riskSeverity} helper, `spentMicroUsd` always reads
 * {@link ResourceLedger.spentMicroUsd}. Pure; reads no clock/random.
 */
export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
/**
 * WHICH arm of a multi-arm {@link CheckpointError} `code` fired (requested 2026-08-10: a deployment retry
 * policy needs to tell "a newer worker can redeem this row" from "this row is damaged/caller-declared and
 * no worker ever will" — blanket-retriable and gate-shape heuristics were both refuted downstream, so the
 * throw site carries the fact it always knew).
 *
 * A CLOSED SET with one home, registered in `docs/CLOSED-SETS.md`. It is carried in an OPTIONAL member, so
 * the compiler is silent when a throw site omits it, and a site that omits it hands the host a refusal it
 * cannot classify — the registry's mint-obligation leg is what makes the omission loud (every
 * `checkpoint.unsupported_version` construction site must spell one of these words AT the site).
 *
 *  · `unsupported_version` arms: `"version_newer"` (the row's format is newer than this worker's —
 *    retryable on a newer worker), `"env_factory_missing"` (retryable on a factory-wired worker),
 *    `"governed_unwired"` (retryable on an org-wired worker), `"origin_missing"` (a pending approval row
 *    minted before the ask's origin was persisted on the park — terminal for the row's bytes; the host
 *    decides the orphaned card itself, no worker version redeems it), `"ledger_event_unknown"` (a durable
 *    LEDGER carries an event kind this binary does not know, so the replay is refused rather than served
 *    incomplete — the one arm that is not row-scoped: NO row in that data directory is readable here, so
 *    the host rolls the binary forward instead of routing one token elsewhere), `"seat_retired"` (the row
 *    carries a seat this engine no longer reads — terminal for the row's bytes: the retired pre-queue single
 *    steer seat, which no worker version reads and the host decides itself, or a retired mode-seed member of
 *    `inheritedGate`, which an engine at 7.38.x or earlier still reads once).
 *  · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
 *    `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"skill_grants_damaged"`, `"constraint_chain_missing"` (all terminal for the row's
 *    current bytes — no worker version redeems a damaged row).
 */
export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "seat_retired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged"
/** `state.writeFace` is present but not a well-formed section. No `_forged` twin: the section is
 *  legal-and-absent at every version, so there is no version to judge a fabrication against — and a
 *  fabricated "open" still needs the LIVE resolution to say open too. */
 | "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing";
/**
 * The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the four
 * words prepare's mode, output-style and recorded-mode doors throw, held unchanged so one refusal reads the same on a fresh
 * run (`TaskResult.errorCode`) and on a resume (this member). `config.spec_retired_key`: a retired spec key is present;
 * `config.output_style_invalid`: the output-style seat is malformed; `config.permission_mode_invalid`: a mode word is outside
 * the closed set (the config's `permissionMode`, or a mode the row recorded); `config.shell_gate_value_retired`: the shell
 * floor is a retired doctrine word.
 */
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"];
export type CheckpointConfigRefusalCode = (typeof CHECKPOINT_CONFIG_REFUSAL_CODES)[number];
export declare const isCheckpointConfigRefusalCode: (word: unknown) => word is CheckpointConfigRefusalCode;
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
export declare class CheckpointError extends Error {
    readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
    /** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
     *  persisted {@link CheckpointGate} `kind` (council #3 — type confusion guard), or a gate v1
     *  resume does not serve (`task_done`). */
     | "checkpoint.gate_mismatch"
    /** The resume's `TaskSpec.signal` was aborted at the entrance — handed in ALREADY-ABORTED, or aborted
     *  while the wake screen / `resumePreflight` / pre-claim re-adjudication still ran: ONE code for a
     *  cancellation there (the seats' own codes keep what the hook itself did). Refused BEFORE the CAS, so
     *  the checkpoint stays `pending`; consuming the decision on a leg that aborts at once would lose it. */
     | "checkpoint.resume_aborted"
    /** `runner.resume` was handed an outcome whose payload is unsafe to apply — e.g. a deny `reason`
     *  carrying a `</system-reminder>` tag that would escape the model-facing wrapper (round-2 #1). */
     | "checkpoint.invalid_outcome"
    /** The checkpoint's {@link Checkpoint.version} is newer than this worker supports
     *  (`> MAX_SUPPORTED_CHECKPOINT_VERSION`), or it carries a remote `workspaceHandle` but no
     *  `executionEnvFactory` is wired to rebuild the env — either way this worker cannot safely resume it.
     *  Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
     *  (design/49 §2/§3, code-ready council round-2). */
     | "checkpoint.unsupported_version"
    /** The resume's re-supplied config, or a mode the checkpoint row recorded, is one prepare would refuse
     *  (a retired spec key, a malformed output style, a mode word outside the closed set, a retired shell-floor
     *  word). Refused PRE-CAS, so the checkpoint stays `pending` and byte-identical. `message` is prepare's own
     *  sentence; `detail.code` is prepare's own code ({@link CheckpointConfigRefusalCode}) and `detail.field`
     *  says which input carries it: `"taskConfig"` (correct the config and resume the SAME token) or
     *  `"inheritedGate"` (the recorded row — no config reaches it; expire the row). A fresh run of the same
     *  bad config reports the same `detail.code` as its `TaskResult.errorCode`. */
     | "checkpoint.config_refused"
    /** `resume`/`resumeStream` was handed `internals.requestedCwd` for a checkpoint that
     *  carries a remote `workspaceHandle`. The restored workspace's mountPath is authoritative for the
     *  task root, so the cwd would be ignored at best — and verifying it would touch the env BEFORE the
     *  VM restore (a lazy-connect adapter then connects a fresh empty sandbox, the design/49 disease).
     *  Rejected pre-CAS so the checkpoint stays `pending`; re-resume without `requestedCwd`. */
     | "checkpoint.cwd_conflicts_restore"
    /** design/164: the checkpoint was written by a pre-164 worker AND its ledger carries the RETIRED
     *  cross-slice wall-clock allocation (`resourceLedger.totalWalltimeSec`). The axis no longer exists,
     *  so resuming would run the leg with the operator's time ceiling silently unenforced. Refused
     *  PRE-CAS (the row stays `pending`, so a worker of the previous release can still finish it) rather
     *  than migrated: there is no honest conversion from a wall-clock allocation to a token one. */
     | "checkpoint.walltime_axis_retired"
    /** design/80 D-1 (reopen-by-reason): a re-resume of an `env_failed`-reopened checkpoint supplied an
     *  outcome that does NOT equal the persisted {@link ResolvedOutcome} winner. An `env_failed` reopen is
     *  a SYSTEM RETRY of the already-approved action (the infra failed, not the decision) — it must replay
     *  the exact winning binding, never a new vote. Rejected pre-CAS, fail-closed, so the checkpoint stays
     *  `pending` for the correctly-replayed retry. (A `tool_unavailable` reopen — P-7 — is exempt: it lets
     *  a human re-decide with the tool present, so a fresh decision is permitted there.) */
     | "checkpoint.reopen_revote"
    /** design/80 D-1 (atomicity fix): the resume validated its decision-action / reopen-by-reason guards
     *  against a `get()` snapshot, but a concurrent {@link CheckpointStore.resolve}/{@link CheckpointStore.reopen}
     *  cycle advanced the monotonic {@link Checkpoint.rev} in the get→resolve window, so the
     *  {@link CheckpointStore.resolve} CAS's optimistic-concurrency check lost (the validation is stale). The
     *  row is still `pending` — fail-closed; the caller must re-`get` and re-validate against the CURRENT state
     *  (a fresh resume), never blindly retry. */
     | "checkpoint.reopened_concurrently"
    /** A post-CAS compensation {@link CheckpointStore.reopen} returned `false` — the consumed row could not
     *  be flipped back to `pending` (missing, expired, or changed under a concurrent cycle), so no retryable
     *  checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
     *  caller's compensation path settles the token as terminally consumed. */
     | "checkpoint.reopen_failed"
    /** {@link CheckpointStore.recordExecutionOutcome} met a row that already carries a DIFFERENT
     *  execution record — see `ExecutionOutcomeConflictError` (checkpoint-execution-record.ts), the
     *  in-tree form of this code. Never a retry condition. */
     | "checkpoint.execution_outcome_conflict"
    /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
     *  `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
     *  trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
     *  (§3 inv #2). Mirrors the trusted-steer reject at runtask.ts (`steering.invalid_content`). */
     | "steering.invalid_content"
    /** design/171 §6.3: the append would cross the parked-steer queue's entry-count or byte bound
     *  ({@link MAX_PENDING_STEER_ENTRIES} / {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES}). Fail-loud
     *  rather than evicting: an already-accepted operator instruction is never dropped to make room —
     *  the caller delivers or removes a parked steer first. Nothing is written on this path. */
     | "steering.queue_full"
    /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
     *  this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
     *  idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
     *  second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
     *  Nothing is written on this path. */
     | "steering.duplicate_input_id"
    /** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
     *  `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
     *  message discriminates). Pre-CAS: the checkpoint stays `pending` and byte-identical, so the
     *  SAME token is redeemable once the screen clears — a refusal burns nothing. The live steer
     *  verb refuses under the same code word (its own Error face); one vocabulary, two entrances. */
     | "steering.blocked_by_hook"
    /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
     *  checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
     *  resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
     *  the checkpoint through its own decide entry instead (the error message names it). */
     | "wake.gate_pending"
    /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
     *  parked steer either — there is nothing to deliver, so the wake is rejected pre-CAS
     *  (the checkpoint stays pending) instead of burning the token on an empty continuation. */
     | "wake.nothing_to_deliver"
    /** Parent effective-policy inheritance: the checkpoint records `state.inheritedGate.requiresParentConstraint`
     *  (the suspended task ran under inherited OPAQUE parent-policy constraints — live closures that cannot
     *  persist), but the resume supplied no `RunInternals.inheritedGate.parentConstraints` to re-instate them.
     *  Resuming anyway would silently DROP the ancestors' gate (a widening), so this rejects pre-CAS — the
     *  checkpoint stays `pending`; re-resume via `resumeStream(token, outcome, config, internals)` with the
     *  parent chain re-supplied. */
     | "resume.parent_constraint_missing"
    /** Parent effective-policy inheritance (shape check): the resume DID re-supply
     *  `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
     *  `parentConstraintCount` — or (F-012 L1) its projection CONTENT digests differently from the
     *  recorded `constraintDigest` — a partial/mismatched re-supply would silently run the child under a
     *  thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
     *  stays `pending`); re-resume with the full original chain. */
     | "resume.parent_constraint_mismatch"
    /** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
     *  ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
     *  EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
     *  decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
     *  so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
     *  redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
     *  `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
     *  re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
     | "resume.usage_window_exhausted"
    /** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
     *  the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
     *  "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
     *  (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
     *  attribution) — silently running the leg under a different identity is the bad-value direction this
     *  refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
     *  original principal, or omit the field to inherit the recorded one. */
     | "resume.principal_mismatch"
    /** design/380 O1③ (placement identity continuity — the `resume.principal_mismatch` rung's twin
     *  on the placement axis): the resume supplied trusted internals whose EXPLICIT
     *  `placementRoot` differs from the {@link CheckpointState.placementRootSessionId} recorded at
     *  the suspend mint. A target-bound env factory keys its placement lookup on that fixed point,
     *  so silently resuming under a different one would re-place the leg — and every descendant
     *  the child-internals chain hands it to — on another target than the parked workspace lives
     *  on. A supplied `rootSessionId` deliberately does NOT take this refusal: it is
     *  delegation identity, re-supplied by every revive leg, and legitimately differs from the
     *  recorded root whenever the spawner carried an explicit placement — the recorded root simply
     *  outranks it in prepare's restore fold. Rejected PRE-CAS (the checkpoint stays `pending`);
     *  re-resume with the recorded root, or omit `internals.placementRoot` to inherit it. Either
     *  side absent (an older row / a claim-less resume) skips the check, and `""` reads as absence
     *  — the honest degradation arm. */
     | "resume.placement_mismatch"
    /** design/380 O2: the deployment's `RunnerDeps.resumePreflight` refused this resume — an
     *  explicit refusal verdict, a throw, a timeout past its bounded deadline, or an unreadable
     *  answer (all fail-closed). The LAST rung before the CAS, so nothing was consumed. Two arms,
     *  discriminated by the message: the default `retry_later` arm leaves the checkpoint `pending`
     *  and the SAME token redeemable once the obstacle clears (`detail.retryAfterMs` carries the
     *  deployment's wait hint when it gave one — the #449 G1 carrier); the explicit `terminal` arm
     *  has ALREADY settled the row out of `pending` via the single-shot `expire` CAS (reaper
     *  parity) before this throw — the token is NOT redeemable, by the deployment's own permanent
     *  verdict (a dead binding must not be redialed forever). */
     | "resume.preflight_rejected"
    /** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
     *  constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
     *  edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
     *  the same token remains decidable (approve as shown, deny, or edit differently) — the rejection
     *  consumed the decision's INPUT, never the ask itself. */
     | "resume.constraint_rejected"
    /** F-012 L3: the resume carried an approver EDIT but the inherited ancestor chain cannot be
     *  re-adjudicated statically — an OPAQUE layer is present, a layer declares a live remainder beyond
     *  its projection, or the checkpoint predates the projection chain. "Would have been allowed" is not
     *  computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
     *  checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
     | "resume.constraint_unprojectable";
    /** Structured discrimination a caller can branch on where the code alone is ambiguous. Two
     *  members. `field` names WHICH part of the decision payload was rejected on a
     *  `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
     *  re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
     *  action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
     *  decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
     *  `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`/`policy`,
     *  pair the sweep's `sla_timeout` with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
     *  well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
     *  it). On a `checkpoint.config_refused` `field` names which INPUT carries the refusal — `"taskConfig"`
     *  (the re-supplied config: correct it and resume the same token) or `"inheritedGate"` (the mode the row
     *  recorded: no config reaches it; expire the row). The throw
     *  sites always knew which arm fired; this carries that fact instead of dropping it (ruled
     *  2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
     *  discriminant. */
    readonly detail?: {
        field?: "boundCallId" | "boundInputHash" | "boundGrantDigest" | "answer" | "hostDecision" | "approver" | "taskConfig" | "inheritedGate";
        /** On `checkpoint.config_refused` — prepare's own code for the refusal ({@link CheckpointConfigRefusalCode}), the
         *  word a fresh run of the same bad config reports as its `errorCode`. Absent on every other code. */
        code?: CheckpointConfigRefusalCode;
        /** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
         *  (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
         *  retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
         *  gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
         *  every other code. */
        retryAfterMs?: number;
        /** WHICH arm fired, where one `code` covers several — the word list and what each word means to a
         *  host live at {@link CheckpointErrorReason}, its one home. Additive and optional like `field`
         *  (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
         *  is a gate rather than a type: see the closed set's own doc. */
        reason?: CheckpointErrorReason;
    } | undefined;
    constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
    /** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
     *  persisted {@link CheckpointGate} `kind` (council #3 — type confusion guard), or a gate v1
     *  resume does not serve (`task_done`). */
     | "checkpoint.gate_mismatch"
    /** The resume's `TaskSpec.signal` was aborted at the entrance — handed in ALREADY-ABORTED, or aborted
     *  while the wake screen / `resumePreflight` / pre-claim re-adjudication still ran: ONE code for a
     *  cancellation there (the seats' own codes keep what the hook itself did). Refused BEFORE the CAS, so
     *  the checkpoint stays `pending`; consuming the decision on a leg that aborts at once would lose it. */
     | "checkpoint.resume_aborted"
    /** `runner.resume` was handed an outcome whose payload is unsafe to apply — e.g. a deny `reason`
     *  carrying a `</system-reminder>` tag that would escape the model-facing wrapper (round-2 #1). */
     | "checkpoint.invalid_outcome"
    /** The checkpoint's {@link Checkpoint.version} is newer than this worker supports
     *  (`> MAX_SUPPORTED_CHECKPOINT_VERSION`), or it carries a remote `workspaceHandle` but no
     *  `executionEnvFactory` is wired to rebuild the env — either way this worker cannot safely resume it.
     *  Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
     *  (design/49 §2/§3, code-ready council round-2). */
     | "checkpoint.unsupported_version"
    /** The resume's re-supplied config, or a mode the checkpoint row recorded, is one prepare would refuse
     *  (a retired spec key, a malformed output style, a mode word outside the closed set, a retired shell-floor
     *  word). Refused PRE-CAS, so the checkpoint stays `pending` and byte-identical. `message` is prepare's own
     *  sentence; `detail.code` is prepare's own code ({@link CheckpointConfigRefusalCode}) and `detail.field`
     *  says which input carries it: `"taskConfig"` (correct the config and resume the SAME token) or
     *  `"inheritedGate"` (the recorded row — no config reaches it; expire the row). A fresh run of the same
     *  bad config reports the same `detail.code` as its `TaskResult.errorCode`. */
     | "checkpoint.config_refused"
    /** `resume`/`resumeStream` was handed `internals.requestedCwd` for a checkpoint that
     *  carries a remote `workspaceHandle`. The restored workspace's mountPath is authoritative for the
     *  task root, so the cwd would be ignored at best — and verifying it would touch the env BEFORE the
     *  VM restore (a lazy-connect adapter then connects a fresh empty sandbox, the design/49 disease).
     *  Rejected pre-CAS so the checkpoint stays `pending`; re-resume without `requestedCwd`. */
     | "checkpoint.cwd_conflicts_restore"
    /** design/164: the checkpoint was written by a pre-164 worker AND its ledger carries the RETIRED
     *  cross-slice wall-clock allocation (`resourceLedger.totalWalltimeSec`). The axis no longer exists,
     *  so resuming would run the leg with the operator's time ceiling silently unenforced. Refused
     *  PRE-CAS (the row stays `pending`, so a worker of the previous release can still finish it) rather
     *  than migrated: there is no honest conversion from a wall-clock allocation to a token one. */
     | "checkpoint.walltime_axis_retired"
    /** design/80 D-1 (reopen-by-reason): a re-resume of an `env_failed`-reopened checkpoint supplied an
     *  outcome that does NOT equal the persisted {@link ResolvedOutcome} winner. An `env_failed` reopen is
     *  a SYSTEM RETRY of the already-approved action (the infra failed, not the decision) — it must replay
     *  the exact winning binding, never a new vote. Rejected pre-CAS, fail-closed, so the checkpoint stays
     *  `pending` for the correctly-replayed retry. (A `tool_unavailable` reopen — P-7 — is exempt: it lets
     *  a human re-decide with the tool present, so a fresh decision is permitted there.) */
     | "checkpoint.reopen_revote"
    /** design/80 D-1 (atomicity fix): the resume validated its decision-action / reopen-by-reason guards
     *  against a `get()` snapshot, but a concurrent {@link CheckpointStore.resolve}/{@link CheckpointStore.reopen}
     *  cycle advanced the monotonic {@link Checkpoint.rev} in the get→resolve window, so the
     *  {@link CheckpointStore.resolve} CAS's optimistic-concurrency check lost (the validation is stale). The
     *  row is still `pending` — fail-closed; the caller must re-`get` and re-validate against the CURRENT state
     *  (a fresh resume), never blindly retry. */
     | "checkpoint.reopened_concurrently"
    /** A post-CAS compensation {@link CheckpointStore.reopen} returned `false` — the consumed row could not
     *  be flipped back to `pending` (missing, expired, or changed under a concurrent cycle), so no retryable
     *  checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
     *  caller's compensation path settles the token as terminally consumed. */
     | "checkpoint.reopen_failed"
    /** {@link CheckpointStore.recordExecutionOutcome} met a row that already carries a DIFFERENT
     *  execution record — see `ExecutionOutcomeConflictError` (checkpoint-execution-record.ts), the
     *  in-tree form of this code. Never a retry condition. */
     | "checkpoint.execution_outcome_conflict"
    /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
     *  `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
     *  trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
     *  (§3 inv #2). Mirrors the trusted-steer reject at runtask.ts (`steering.invalid_content`). */
     | "steering.invalid_content"
    /** design/171 §6.3: the append would cross the parked-steer queue's entry-count or byte bound
     *  ({@link MAX_PENDING_STEER_ENTRIES} / {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES}). Fail-loud
     *  rather than evicting: an already-accepted operator instruction is never dropped to make room —
     *  the caller delivers or removes a parked steer first. Nothing is written on this path. */
     | "steering.queue_full"
    /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
     *  this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
     *  idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
     *  second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
     *  Nothing is written on this path. */
     | "steering.duplicate_input_id"
    /** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
     *  `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
     *  message discriminates). Pre-CAS: the checkpoint stays `pending` and byte-identical, so the
     *  SAME token is redeemable once the screen clears — a refusal burns nothing. The live steer
     *  verb refuses under the same code word (its own Error face); one vocabulary, two entrances. */
     | "steering.blocked_by_hook"
    /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
     *  checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
     *  resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
     *  the checkpoint through its own decide entry instead (the error message names it). */
     | "wake.gate_pending"
    /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
     *  parked steer either — there is nothing to deliver, so the wake is rejected pre-CAS
     *  (the checkpoint stays pending) instead of burning the token on an empty continuation. */
     | "wake.nothing_to_deliver"
    /** Parent effective-policy inheritance: the checkpoint records `state.inheritedGate.requiresParentConstraint`
     *  (the suspended task ran under inherited OPAQUE parent-policy constraints — live closures that cannot
     *  persist), but the resume supplied no `RunInternals.inheritedGate.parentConstraints` to re-instate them.
     *  Resuming anyway would silently DROP the ancestors' gate (a widening), so this rejects pre-CAS — the
     *  checkpoint stays `pending`; re-resume via `resumeStream(token, outcome, config, internals)` with the
     *  parent chain re-supplied. */
     | "resume.parent_constraint_missing"
    /** Parent effective-policy inheritance (shape check): the resume DID re-supply
     *  `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
     *  `parentConstraintCount` — or (F-012 L1) its projection CONTENT digests differently from the
     *  recorded `constraintDigest` — a partial/mismatched re-supply would silently run the child under a
     *  thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
     *  stays `pending`); re-resume with the full original chain. */
     | "resume.parent_constraint_mismatch"
    /** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
     *  ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
     *  EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
     *  decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
     *  so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
     *  redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
     *  `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
     *  re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
     | "resume.usage_window_exhausted"
    /** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
     *  the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
     *  "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
     *  (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
     *  attribution) — silently running the leg under a different identity is the bad-value direction this
     *  refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
     *  original principal, or omit the field to inherit the recorded one. */
     | "resume.principal_mismatch"
    /** design/380 O1③ (placement identity continuity — the `resume.principal_mismatch` rung's twin
     *  on the placement axis): the resume supplied trusted internals whose EXPLICIT
     *  `placementRoot` differs from the {@link CheckpointState.placementRootSessionId} recorded at
     *  the suspend mint. A target-bound env factory keys its placement lookup on that fixed point,
     *  so silently resuming under a different one would re-place the leg — and every descendant
     *  the child-internals chain hands it to — on another target than the parked workspace lives
     *  on. A supplied `rootSessionId` deliberately does NOT take this refusal: it is
     *  delegation identity, re-supplied by every revive leg, and legitimately differs from the
     *  recorded root whenever the spawner carried an explicit placement — the recorded root simply
     *  outranks it in prepare's restore fold. Rejected PRE-CAS (the checkpoint stays `pending`);
     *  re-resume with the recorded root, or omit `internals.placementRoot` to inherit it. Either
     *  side absent (an older row / a claim-less resume) skips the check, and `""` reads as absence
     *  — the honest degradation arm. */
     | "resume.placement_mismatch"
    /** design/380 O2: the deployment's `RunnerDeps.resumePreflight` refused this resume — an
     *  explicit refusal verdict, a throw, a timeout past its bounded deadline, or an unreadable
     *  answer (all fail-closed). The LAST rung before the CAS, so nothing was consumed. Two arms,
     *  discriminated by the message: the default `retry_later` arm leaves the checkpoint `pending`
     *  and the SAME token redeemable once the obstacle clears (`detail.retryAfterMs` carries the
     *  deployment's wait hint when it gave one — the #449 G1 carrier); the explicit `terminal` arm
     *  has ALREADY settled the row out of `pending` via the single-shot `expire` CAS (reaper
     *  parity) before this throw — the token is NOT redeemable, by the deployment's own permanent
     *  verdict (a dead binding must not be redialed forever). */
     | "resume.preflight_rejected"
    /** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
     *  constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
     *  edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
     *  the same token remains decidable (approve as shown, deny, or edit differently) — the rejection
     *  consumed the decision's INPUT, never the ask itself. */
     | "resume.constraint_rejected"
    /** F-012 L3: the resume carried an approver EDIT but the inherited ancestor chain cannot be
     *  re-adjudicated statically — an OPAQUE layer is present, a layer declares a live remainder beyond
     *  its projection, or the checkpoint predates the projection chain. "Would have been allowed" is not
     *  computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
     *  checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
     | "resume.constraint_unprojectable", message: string, 
    /** Structured discrimination a caller can branch on where the code alone is ambiguous. Two
     *  members. `field` names WHICH part of the decision payload was rejected on a
     *  `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
     *  re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
     *  action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
     *  decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
     *  `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`/`policy`,
     *  pair the sweep's `sla_timeout` with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
     *  well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
     *  it). On a `checkpoint.config_refused` `field` names which INPUT carries the refusal — `"taskConfig"`
     *  (the re-supplied config: correct it and resume the same token) or `"inheritedGate"` (the mode the row
     *  recorded: no config reaches it; expire the row). The throw
     *  sites always knew which arm fired; this carries that fact instead of dropping it (ruled
     *  2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
     *  discriminant. */
    detail?: {
        field?: "boundCallId" | "boundInputHash" | "boundGrantDigest" | "answer" | "hostDecision" | "approver" | "taskConfig" | "inheritedGate";
        /** On `checkpoint.config_refused` — prepare's own code for the refusal ({@link CheckpointConfigRefusalCode}), the
         *  word a fresh run of the same bad config reports as its `errorCode`. Absent on every other code. */
        code?: CheckpointConfigRefusalCode;
        /** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
         *  (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
         *  retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
         *  gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
         *  every other code. */
        retryAfterMs?: number;
        /** WHICH arm fired, where one `code` covers several — the word list and what each word means to a
         *  host live at {@link CheckpointErrorReason}, its one home. Additive and optional like `field`
         *  (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
         *  is a gate rather than a type: see the closed set's own doc. */
        reason?: CheckpointErrorReason;
    } | undefined);
}
/**
 * The pluggable durable seam (design/45 §2.1). Symmetric with `ToolResultStore`/`MemoryStore`:
 * create-once `put`, `get`, an **atomic CAS `resolve`** (the once-only foundation), and a `reap` for TTL
 * expiry. A durable backend (TiDB) makes resume cross-replica; the default in-memory impl is single
 * instance / tests only.
 *
 * **Load-bearing contract (§2.1, service-side report):** `resolve` is the once-only gate. The store guarantees
 * *exactly one* `resolve(token, ...)` wins the CAS (pending → resolved); the **runner** must treat
 * "won the CAS" as the *sole* trigger to execute the pending action — that is what makes a pending tool
 * call run exactly once across retries / double approvals / multiple replicas.
 *
 * **design/80 §D-D expiry semantic contract (gate.kind-aware reap + `terminalAt` backstop):** the
 * reaper-LOOP that calls {@link reap}/{@link expire} is the DEPLOYMENT's (its store impl + loop — OWNED
 * service-side); core's D-D job is ONLY to keep writing the durable FIELDS the loop
 * reads ({@link Checkpoint.deadline} for an approval gate + the persisted {@link Checkpoint.gate}.kind) and
 * to DOCUMENT the contract here so ANY reaper consumer (the service's TiDB loop, or a non-service Pg
 * deployment) implements it IDENTICALLY:
 *  - **FACET A — APPROVAL gates** (`gate.kind` ∈ {`human`, `irreversible_ask`}): the loop's FAST PATH at
 *    `deadline` is an SLA **resolve-deny** (`resolve(token, {gate:"policy_ask", decision:"deny"}, …)` so the
 *    suspended run ends cleanly), with a LATER `terminalAt` **abandonment backstop** the loop ALSO honors
 *    (crash-safe if the SLA timer never fired — then {@link reap}/{@link expire} flips it `expired`).
 *  - **FACET B — UNATTENDED-TTL gates** (`gate.kind` ∈ {`resource_limit`, `needs_review`, `plan_review`} — the
 *    resource slice + the review-pause family): `deadline` is an abandonment-TTL → {@link expire}/{@link reap}
 *    ONLY, **NEVER** a resolve-deny (a resolve-deny on these kinds hits NO `gateMatch` arm → `gate_mismatch`;
 *    their resume outcome is `resource_limit`/`dry_run_review`/`plan_review` respectively, not `policy_ask`).
 *  NO core-minted `terminalAt`, NO core reaper, NO change to {@link reap} logic — minting `terminalAt` and
 *  running the gate.kind-aware loop are the deployment's, by design.
 */
/**
 * design/173 §2.3 — the store DURABILITY declaration vocabulary (declaration, never duck-typing —
 * the exact posture of {@link CheckpointStore.retention}): `"durable"` promises the rows SURVIVE A
 * PROCESS RESTART of the deployment that owns the store's data domain (a durable park can be
 * resumed by a later process); `"process-local"` states they die with the process. **This axis
 * claims restart survival ONLY — it does NOT claim multi-replica coordination** (whether a SECOND
 * concurrent replica can serve the same rows is a separate property of the backend: the file
 * backend is single-instance-per-data-dir by design, Pg/TiDB backends coordinate — both honestly
 * declare `"durable"` on THIS axis; review fold round 1, finding 2). ABSENT is read FAIL-CLOSED as
 * `"process-local"` (the wiring manifest may under-promise durability, never over-promise it); an
 * unparseable value is refused loudly (`config.store_durability_invalid`) rather than folded to
 * either arm. Adapter contract with a conformance obligation: a backend that persists outside the
 * process (file/Pg/TiDB) SHOULD declare `"durable"` — until it does, restart-surviving resume
 * capability is reported as absent.
 */
export type StoreDurability = "durable" | "process-local";
/**
 * The store ENCODING-FIDELITY declaration vocabulary — the third honest-declaration axis, alongside
 * {@link StoreDurability} and {@link CheckpointStore.retention}, and the one that says WHICH VALUES a
 * row can hold unchanged.
 *
 * `"structured-clone"` promises the structured-clone value domain survives a round trip through the
 * backend (a `Date` comes back a `Date`, a `Map` a `Map`, `NaN` stays `NaN`); `"json"` states the
 * backend serialises through JSON, so the row that comes back after a restart carries the JSON
 * PROJECTION of what was filed (`Date` → string, `Map`/`Set` → `{}`, `NaN`/`Infinity` → `null`, `-0`
 * → `0`, an `undefined`-valued key gone).
 *
 * Why this has to be declared rather than inferred: the mint decides what a durable approval row may
 * carry, and it decides it with `structuredClone` — which is WIDER than a JSON codec. A value can pass
 * that check, be filed, degrade on disk, and come back a different value, while the binding hash minted
 * over the pre-serialisation value still matches the one the operator echoes (the resume compares the
 * persisted string opaquely and never re-hashes). The approval is then accepted for arguments that are
 * not the arguments it bound. Only the backend knows its own codec, so only the backend can say.
 *
 * ABSENT is read FAIL-CLOSED as `"json"` — the narrower domain, the same "may under-promise, never
 * over-promise" posture {@link StoreDurability} takes with `"process-local"`: an undeclared backend gets
 * a row it can certainly hold, and a backend that can hold more earns the wider domain by saying so. An
 * unparseable value is refused loudly (`config.store_fidelity_invalid`) rather than folded to either arm.
 */
export type StoreFidelity = "structured-clone" | "json";
/**
 * Resolve a store's declared {@link StoreFidelity} (the read half of the declaration — the exact shape
 * `resolveDeclaredFidelity`'s durability twin has): ABSENT → the fail-closed `"json"`, a recognized
 * value verbatim, anything else refused loudly. Never duck-types the backend.
 */
export declare function resolveDeclaredFidelity(store: {
    readonly fidelity?: StoreFidelity;
} | undefined, storeName: string): StoreFidelity;
/**
 * Project an already-captured value onto what a backend of the given {@link StoreFidelity} can hold, so
 * everything minted from it — the row's args, the approval preview, the binding hash, the risk
 * descriptor — describes the value that comes BACK, not the one that went in.
 *
 * `"structured-clone"` is the identity: the caller's snapshot already is what such a backend stores.
 * `"json"` runs the backend's own codec (`JSON.stringify` → `JSON.parse`, `toJSON` hooks included, since
 * that is exactly what the backend will do to it). The input is expected to be an accessor-free snapshot
 * already, so no property is read a second time here.
 *
 * A value JSON cannot ENCODE AT ALL (a `BigInt`, a cycle) fails instead of degrading — those would throw
 * inside the backend's own write anyway, and failing here means the caller can say why while nothing has
 * been committed. The thrown value rides out on `cause` so the caller decides how to word the refusal.
 */
export declare function encodeAtFidelity(fidelity: StoreFidelity, value: unknown): {
    ok: true;
    value: unknown;
} | {
    ok: false;
    cause: unknown;
};
/**
 * CONSERVATIVE "these two are provably the same value" for deciding whether a {@link encodeAtFidelity}
 * projection moved anything, and whether a policy's rewrite names the very value about to be filed.
 *
 * Deliberately NOT the binding hash, which is the wrong instrument for this question in two directions:
 * it folds `-0` onto `0` (a real projection change the policy may care about, invisible to the hash), and
 * it is a digest, so a collision would read as sameness. `Object.is` at the leaves keeps the `-0`
 * distinction; comparing own-key SETS keeps the dropped `undefined`-valued key visible.
 *
 * Conservative on purpose — the answer it must never give wrongly is TRUE, because both callers read
 * TRUE as "nothing moved / the policy named this very value" and act by filing. So sameness has to be
 * PROVED, not merely un-disproved:
 *  - the PROTOTYPES must match and be the plain-object or array one. `[object Object]` is not proof of
 *    a plain object — a class instance and a null-prototype object both report it, and a tool executing
 *    the arguments can observe inherited behaviour. (Neither input to the projection comparison can be
 *    such a value: `structuredClone` and `JSON.parse` both produce plain data. A policy's rewrite can.)
 *  - own keys are compared with `Reflect.ownKeys` and POSITIONALLY, so a SYMBOL key or a non-enumerable
 *    property on one side is a difference rather than an invisible extra, and so is a different order
 *    (which `Object.keys`, `JSON.stringify` and any tool iterating its arguments can see).
 *  - properties are compared through their DESCRIPTORS — every flag, so a frozen object is not the
 *    mutable value a JSON decode produces — and an accessor on either side answers FALSE without being
 *    read: an approval decision must not be made by running deployment code, and what a getter returns
 *    need not be the same twice.
 *  - reference TOPOLOGY counts: two properties that share one object are not two properties holding
 *    equal objects, and a JSON round trip is precisely what turns the first into the second.
 *  - what it CANNOT prove on its own is that the value it inspected answers ordinary reads the way its
 *    descriptors did — a Proxy reports the target's descriptors and can return anything from a `get`.
 *    A caller comparing a value it did not construct must first establish that it is an inert data
 *    graph (the capture clone does exactly that, and refuses a Proxy outright).
 *  - any brand outside plain object/array that is not already reference-identical answers FALSE (a Date
 *    is never the string a JSON codec leaves behind, a Map is never the `{}`), and so does any
 *    inspection that throws.
 * An uncertain answer therefore costs a policy consultation or a park, never a silently filed value.
 */
export declare function samePlainValue(a: unknown, b: unknown): boolean;
export interface CheckpointStore {
    /**
     * Managed-retention declaration (ruled 2026-08-05, retention.ts): `"managed"` promises the
     * `ManagedRetentionCapability` contract — including the reap/unpin gap this interface documents on
     * {@link reap} (a managed store's scheduled expiry DOES release the session pins). `"none"` (or
     * ABSENT — read fail-closed as `"none"`) states the store cannot delete on schedule; a deployment
     * with a LOCKED retention policy refuses to start over it. Declaration, never duck-typing.
     */
    readonly retention?: import("./retention.js").RetentionDeclaration;
    /**
     * design/173 §2.3 — durability declaration (see {@link StoreDurability}): whether checkpoints
     * this store accepts survive a process restart. Same "declaration, never duck-typing" posture as
     * {@link retention}; the wiring manifest relays it verbatim and reads ABSENT fail-closed as
     * `"process-local"`.
     */
    readonly durability?: StoreDurability;
    /**
     * Encoding-fidelity declaration (see {@link StoreFidelity}): which value domain a row keeps INTACT
     * through this backend. Same "declaration, never duck-typing" posture as {@link durability}; the park
     * mint reads it to decide the width of the snapshot it files, and reads ABSENT fail-closed as
     * `"json"`. A backend that serialises through JSON MUST say so (or leave it absent, which says the
     * same) — a `"structured-clone"` claim it cannot keep is what lets a row degrade under a binding hash
     * that still matches.
     */
    readonly fidelity?: StoreFidelity;
    /**
     * F-012 L2/L3 — the RE-DECISION capability declaration: whether this store's `reopen` (and, when
     * present, a durable validating-lease discipline) actually works — a promise, not a method-presence
     * sniff (`reopen` is optional and a stub that returns `false` still satisfies the type). The
     * sandbox-admission mode's pre-flight REQUIRES `redecision.reopen === true` (its boundary asks park
     * to a human and must be re-decidable); absent reads fail-closed as "no declared re-decision face"
     * and the mode refuses to arm, loudly. Same "declaration, never duck-typing" posture as
     * {@link retention}/{@link durability}/{@link fidelity}.
     */
    readonly redecision?: {
        reopen: boolean;
        validatingLease?: boolean;
    };
    /**
     * The EXECUTION-OUTCOME declaration — REQUIRED, and required to be exactly `{ outcome: true }`: the
     * store promises that {@link recordExecutionOutcome} is the real CAS this contract describes (the
     * predicate, the four words, the conflict throw) and that {@link reopen} honours the record. Same
     * "declaration, never duck-typing" posture as the three optional axes above, but not optional: the
     * durable resume lane records how every decided action was disposed, and a store that cannot keep
     * that record would leave every resolved row it holds silently unreadable on that axis. A store
     * without the declaration is refused at the FIRST read of the store seat
     * ({@link resolveCheckpointStore}, code `config.invalid_checkpoint_store`) — a JavaScript caller that
     * satisfies no type is told at prepare what the type would have told it.
     */
    readonly execution: {
        readonly outcome: true;
    };
    /** Create-once. Throws {@link CheckpointError} `already_exists` on a token collision (never a silent
     *  overwrite — a reused token would clobber a live suspension). */
    put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
    get(token: CheckpointToken): Promise<Checkpoint | null>;
    /**
     * Atomic CAS: `UPDATE … SET status='resolved' WHERE token=? AND scope=? AND status='pending'`.
     * Returns `true` for the single winner (was pending → now resolved), `false` if already resolved/
     * expired (`AlreadyResolved` — the caller treats it as a no-op, never re-executes). `scope` is in the
     * WHERE for multi-tenant isolation (a wrong-scope resolve must not win).
     *
     * `outcome` is supplied so a **durable** backend persists it atomically with the status flip (design/45
     * v2 resumable-resume hook). **design/80 D-1 (persist-winner):** when the CAS wins, the store ALSO records
     * the winning binding as {@link Checkpoint.resolvedOutcome} on the row — so a later {@link reopen} →
     * re-resume can be validated against the approved decision (an `env_failed` reopen must replay the same
     * winner; design/80 §3 inv #1). A `policy_ask` outcome records the decision bound to its pending tool
     * call; the two REVIEW outcomes (`plan_review` / `dry_run_review`) record theirs under a synthetic
     * `gate:` marker; `resource_limit` / `wake` / `task_done` bind nothing and record no winner.
     *
     * **Implementers: derive the winner with {@link winnerFromOutcome} and persist the object WHOLE.** Its
     * field set GROWS (`updatedInput`, then `answer`, then `reason` — each additive and optional), and the
     * replay guard reads an absent optional field as "this backend never recorded it" so that rows written
     * before the field existed still replay. A backend that projects a hand-listed subset of columns is
     * therefore indistinguishable from an old row and silently leaves that dimension UNBOUND for every
     * decision it ever writes — the replay guard cannot tell the two apart, and there is no capability
     * negotiation on this interface to tell it. Round-trip the whole record (opaque JSON is what every
     * in-tree backend does) rather than enumerating its keys.
     *
     * (The crash-recovery semantics of *acting* on a won CAS are runner contract — see
     * {@link Runner.resume}; the store only owns the atomic once-only flip + the durable winner.)
     */
    resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
    /**
     * Inverse of {@link resolve}: CAS `resolved → pending` (`UPDATE … SET status='pending' WHERE token=?
     * AND scope=? AND status='resolved'`). Returns the CAS winner (`true` = was resolved → now pending again).
     *
     * **Compensation for a post-CAS env-restore failure (design/45/49).** `Runner.resumeStream` wins the
     * `resolve` CAS *before* the workspace `resumeVM` runs; if that restore then fails (`resume.env_failed`)
     * the pending action never executed yet the checkpoint is consumed — without this the suspended work is
     * lost to a forced "re-initiate" (fine for a short approval, ruinous for a long unattended task).
     * Reopening lets a retry re-resume the SAME work. Optional: a store that omits it keeps the prior (lossy)
     * behavior, and the runner degrades gracefully. Safe by the `status='resolved'` guard — only the run that
     * consumed the checkpoint (and then failed to restore) can reopen it, and only while it is still `resolved`.
     *
     * **design/80 D-1 (reopen-by-reason):** the `reason` distinguishes the two reopen triggers (both shipped at
     * runtask.ts) so re-resume can validate per reason (§3 inv #1): `env_failed` (the action is still valid,
     * only infra failed → re-resume MUST replay the persisted {@link Checkpoint.resolvedOutcome} winner) vs
     * `tool_unavailable` (P-7: the approved tool vanished, the action may now be invalid → a human re-decides,
     * so a FRESH decision is allowed). The store RECORDS the reason on the row as {@link Checkpoint.reopenReason}
     * and PRESERVES the persisted winner across the reopen (never clears it).
     *
     * **A row that carries an execution record is NOT reopenable** (`… AND execution_outcome IS NULL` in the
     * WHERE; `checkpointExecutionRecorded` is the shared predicate): the decided action's disposition is on
     * file — see {@link CheckpointRow.executionOutcome} — and reopening it would either re-run an action that
     * already ran or re-decide one already refused. Answers `false` like every other predicate miss; the
     * reasons and the winner are untouched.
     */
    reopen?(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
    /**
     * Record how the DECIDED action was finally disposed on the resumed leg — the {@link GateOutcome} its
     * `tool_end` frame carried (see {@link CheckpointRow.executionOutcome} for what the record means and why
     * it is a column of its own beside the immutable decision). REQUIRED (declared by {@link execution}).
     *
     * The CAS: `UPDATE … SET execution_outcome=?, execution_at_ms=? WHERE token=? AND scope=? AND
     * status='resolved' AND execution_outcome IS NULL`. The answer is a word of the closed
     * `ExecutionOutcomeRecordWord` set — `recorded` (the CAS won), `already_recorded` (the row already
     * carries an EQUAL record: a retry, idempotent), `not_resolved` (pending/expired: no decision to be the
     * outcome of), `absent` (no such row in this scope). A row carrying a DIFFERENT record throws
     * `ExecutionOutcomeConflictError` (`checkpoint.execution_outcome_conflict`) — the record is immutable
     * and two accounts of one execution are an engine defect, never a store overwrite. Equality is
     * structural over the record's JSON (key order and freezing are not part of the value —
     * `sameExecutionRecord`); every backend takes its disposition from `executionRecordDisposition`
     * (checkpoint-execution-record.ts) and keeps only the commit half itself.
     *
     * Never touches `status`, `rev`, `resolvedOutcome` or `reopenReason`: the record is a fact about the
     * execution, not a transition of the decision. Its one effect on the row's other verbs is that
     * {@link reopen} refuses a recorded row.
     */
    recordExecutionOutcome(token: CheckpointToken, scope: string, gate: GateOutcome): Promise<ExecutionOutcomeRecordWord>;
    /**
     * design/80 D-A: park a durable mid-task STEER on a DURABLY-SUSPENDED task's checkpoint. CAS-style update
     * `SET state.pendingSteerQueue=? WHERE token=? AND scope=? AND status='pending'`: returns `true` iff the
     * checkpoint is still `pending` (a resolved/expired checkpoint can't be steered → `false`, a no-op). Scope
     * is in the WHERE for multi-tenant isolation, exactly like {@link resolve}/{@link expire}.
     *
     * **design/171 §5.3 — this APPENDS to {@link CheckpointState.pendingSteerQueue}; it no longer overwrites a
     * single seat.** Two people steering one suspended run used to silently destroy the first instruction,
     * with no record that anything was lost. Idempotency moved from last-writer-wins to the `inputId` key:
     * re-appending an `inputId` already queued (a service retrying with the message id it already minted)
     * is a no-op that still returns `true`. Crossing {@link MAX_PENDING_STEER_ENTRIES} or
     * {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES} throws `steering.queue_full` — bounded fail-loud, never
     * by evicting an accepted instruction.
     *
     * A backend must round-trip the queue field verbatim and must build its new queue with
     * {@link appendPendingSteer} so the bounds and the `seq` mint are one implementation rather than one
     * per store.
     *
     * **Trust is FROZEN here** — `steer.trusted` is whatever the SERVICE computed from the verified principal
     * at this call (an operator-role check, NOT a client header — the service's job); the core stores it
     * VERBATIM and NEVER recomputes it on resume (§3 inv #1).
     *
     * **Validation (load-bearing, §3 inv #2):** `steer.text` containing a `</system-reminder>` close tag is
     * REJECTED with a typed {@link CheckpointError} `steering.invalid_content` (mirroring runtask.ts's trusted-
     * steer reject) — a dirty steer NEVER enters {@link CheckpointState}, so the persisted state stays clean
     * regardless of `trusted`. (The untrusted RESUME-injection path ALSO sanitizes the text as untrusted data;
     * this persist-time reject is the belt that keeps a caller-declared close tag out of the durable record on BOTH
     * paths.) Text over {@link MAX_PENDING_STEER_CHARS} is rejected with the same code (HRD-APV-4 — the
     * steer is re-delivered into the model context on every resume, so it is capped like every other
     * persisted model-facing field on this row; rejected rather than truncated because cutting an
     * instruction can invert it). NOT an approval channel: this only writes the parked queue, never
     * touches status / the resolve path / any decision (§3 inv #4).
     */
    setPendingSteer(token: CheckpointToken, scope: string, steer: PendingSteerInput): Promise<boolean>;
    /**
     * CAS-expire a **single** `pending` checkpoint by token: `UPDATE … SET status='expired' WHERE token=?
     * AND scope=? AND status='pending'`. Returns the CAS winner (`true` = was pending → now expired; `false`
     * = already resolved/expired). The same UPDATE as {@link reap} but keyed by **token**, not by deadline.
     *
     * **Load-bearing contract (design/51 §2/§4):** `expire` and {@link resolve} race the **same `pending`
     * row**, so the store serializes them — exactly one wins. This is what makes {@link TaskStream.destroy}
     * a correct *fence-then-reap*: `destroy` calls `expire` FIRST to fence any concurrent `resume` (which
     * goes through `resolve`); only the `expire` winner then destroys the paused env, so the same checkpoint
     * is **never both reaped and resumed**. `scope` is in the WHERE for multi-tenant isolation, like
     * `resolve`/`reap`.
     */
    expire(token: CheckpointToken, scope: string): Promise<boolean>;
    /**
     * design/384 slice 2 / S-25-R1 — the ATOMIC terminal claim: "win the terminal race" and "read the
     * truth on a loss" as ONE store operation. OPTIONAL, probed by presence (the `listByScope`
     * precedent); the runner's resume chain adopts it when present (the loser triages off `current`
     * with zero second read) and keeps the two-step resolve+get path otherwise. This JSDoc is the
     * single normative statement of the claim law — `put`'s create-once/read-back three-state and the
     * suspend saga's commit discipline are the same single-winner law's PUT variant and cross-reference
     * here rather than restate.
     *
     * The laws:
     *  1. **Atomicity** — the win/lose decision and the loser's `current` reading MUST come from the
     *     same atomic unit (one `UPDATE … RETURNING` / one transaction / one single-threaded map
     *     operation). A settle is self-sufficient: a caller holding `claimed:false` NEVER needs a
     *     second read to act.
     *  2. **CAS-predicate identity** — `kind:"resolve"`'s win IS {@link resolve}'s win (same WHERE:
     *     token+scope+pending[+rev]; a win records the winner via {@link winnerFromOutcome} and bumps
     *     `rev` identically); `kind:"expire"` IS {@link expire}. ONE-MACHINE OBLIGATION: a store that
     *     provides `claimTerminal` must route all three verbs through one protected write path
     *     (resolve/expire implemented over claimTerminal, or all three over one shared CAS kernel) —
     *     two predicates would be a drift window, and this law forbids it.
     *  3. **Timeout posture** — no timeout parameter here: transport deadlines belong to the backend,
     *     wall-clock backstops to the caller. The contract states exactly two halves: a SETTLE carries
     *     the truth; a REJECT proves NOTHING (the row may already be terminal — the caller must not
     *     proclaim a terminal state from its own intent; fail-closed projection of "still pending"
     *     stays legal). A LATE settle is as self-sufficient as a prompt one — an answer arriving after
     *     the caller's own deadline still carries the true decision, so the caller settles on truth
     *     with zero follow-up reads.
     *  4. **Recovery discipline — truth is comparable, ownership is not.** A bounded retry after a
     *     reject re-sends the SAME claim (the CAS is idempotent). When the retry answers
     *     `claimed:false`, `current` tells the caller what the row IS — never WHOSE claim made it so:
     *     two claimants writing byte-identical outcomes cannot be told apart by content, and
     *     {@link ResolvedOutcome} carries no claimant identity. Two consumption classes follow:
     *     IDEMPOTENT consumption (settle on the recorded truth — who wrote it is irrelevant) may use
     *     `claimed:false` + the recorded decision directly; RESPONSIBILITY-bearing consumption (only
     *     the winner may execute the pending action — the runner's resume) must NEVER self-attribute
     *     after ambiguity: treat it as a loss (the `already_resolved` shape, nothing executed) — the
     *     same law as the existing crash-after-CAS pin ("retry sees resolved and does NOT re-execute"),
     *     which this claim does not weaken. There is NO replay arm: a burned approval takes the
     *     existing reopen/re-ask compensation route, and this contract opens no "assume approved and
     *     replay" door. (A claimant-identity field would be a schema change; not here.) Expire-class
     *     claims are naturally idempotent (expire-by-me ≡ expire-by-reaper; no attribution needed).
     *  5. **A loss reading is a consistent snapshot at the claim's linearization point, not an eternal
     *     truth** — `resolved` can be reopened back to pending, so "settle is self-sufficient" scopes
     *     to the disposition of THIS loss (settle on truth / re-validate on OCC / close on absent),
     *     never to the row's future; a caller needing current state later still `get`s. `current` is a
     *     projection, not the row (no state / pendingAction payloads).
     *  6. **`resolvedOutcome` reads as the row field it is, passed through verbatim** — the persisted
     *     winner record: written only by decision-bearing resolves ({@link winnerFromOutcome} answers
     *     `undefined` for resource/wake/task_done), PRESERVED by `reopen`, not cleared by later
     *     winnerless resolves. In a loss answer it means "the row's last recorded decision winner",
     *     NOT "the payload of the claim that beat you"; absence means "no decision winner recorded",
     *     not "the winner had no content". For has-a-human-decided consumption this is exactly the
     *     needed semantics; "who beat me / with what" is claimant-identity territory this contract
     *     does not mint.
     *  7. **No declaration bit** — presence of the method IS the contract (an implementation
     *     obligation at the same level as `resolve`'s CAS promise); no `redecision`-style declaration,
     *     because that family guards against a stub satisfying a type, and a `claimTerminal` stub has
     *     no such shape — the return form itself carries the obligation.
     *
     * Deployment correspondence (an ask-row store implementing the same law maps its own vocabulary):
     * token+scope ↔ the ask id (+ its run binding); pending/resolved/expired ↔ the ask row's
     * open/decided/terminal states; intent `resolve(outcome)` ↔ the human-decision verb; intent
     * `expire` ↔ the expire/cancel verbs; the `claimed:false → (status, resolvedOutcome)` PAIRED read
     * ↔ the decided row's recorded decision (reading the pair as the CURRENT decision is the CALLER's
     * discipline here — gate-kind matching — while a single-decision row schema satisfies it
     * structurally). Two shape notes carried from that correspondence: a store whose rows have NO
     * OCC/rev axis simply never produces the `status:"pending"` loss arm (an honestly absent arm is
     * correct — do not fabricate a rev); a store whose domain has NO reopen verb enjoys stronger
     * monotonicity than law 5 assumes — consumption written against law 5's weaker snapshot reading
     * stays correct there unchanged.
     */
    claimTerminal?(token: CheckpointToken, scope: string, intent: TerminalClaimIntent): Promise<TerminalClaimOutcome>;
    /**
     * CAS-expire `pending` checkpoints in `scope` whose `deadline` has passed (`deadline <= cutoff`):
     * `pending → expired`. Returns the count expired (for metrics). Idempotent across replicas (DB
     * serializes; only the first wins each row) — no leader election needed (§6).
     *
     * **Does NOT unpin the sessions of the checkpoints it expires (council finding #2).** A suspend pinned
     * its session against idle eviction; reaping the checkpoint here leaves that pin in place. The reaper
     * that calls `reap` owns releasing the pin. For a **durable** session store this is a no-op anyway
     * (`pin`/`unpin` are no-ops; it never idle-GCs), so reap-without-unpin is harmless — the only store with
     * a real pin is the in-memory `TtlSessionStore` (single-instance / tests), where an un-released pin
     * keeps an abandoned suspended session in the cache until process exit. v1 leaves the runner without an
     * auto-reaper (the service owns the TTL worker); a reaper over the in-memory store should track and
     * `unpin` the expired sessions itself if that leak matters for its deployment. This no-unpin posture
     * is LOCAL to this live-path `reap`: the managed-retention contract's `expireCheckpoints`
     * ({@link import("./retention.js").ManagedRetentionCapability}) deliberately closes the reap/unpin
     * split — a retention-terminal row has its pin released BY that method, so pin release stays
     * exactly-once per row (each row is terminated by exactly one of the two lanes).
     */
    reap(scope: string, cutoff: number): Promise<number>;
    /**
     * design/80 assistant-scheduler seam #1: list a lightweight {@link CheckpointSummary}
     * for EVERY **pending** checkpoint in `scope` (the suspended / awaiting-human tasks — NOT resolved/expired) in
     * ONE call, so a supervisor scheduler can enumerate a scope's open suspensions without N+1 `get`s or hauling
     * full {@link Checkpoint} payloads. Read-only — it never mutates a row, takes no CAS, and has ZERO effect on
     * the once-only resolve. The PENDING predicate is byte-for-byte the {@link resolve} CAS's (`scope === scope &&
     * status === 'pending'`), so the list reflects exactly the rows that are still resumable. Order is unspecified
     * (the inbox sorts by `severity`/`deadline` itself). An empty scope returns `[]`.
     *
     * **OPTIONAL** for backward-compat with external {@link CheckpointStore} impls (a caller probes
     * `store.listByScope?.(scope) ?? []`); ALL THREE first-party impls (in-memory, file, Pg) provide it.
     */
    listByScope?(scope: string): Promise<CheckpointSummary[]>;
    /**
     * Deployment seam: the DISTINCT scopes that currently have at least one PENDING checkpoint — the
     * enumeration face `listByScope` lacks. A host keeping its own scope registry (e.g. a sidecar file for a
     * cross-scope inbox) can rebuild it from the data itself when the registry is lost/corrupt, instead of the
     * enumeration silently going empty and pending approvals turning invisible. Scoped to PENDING deliberately:
     * it matches `listByScope`'s visible surface, so `listScopes() × listByScope()` is exactly the full inbox.
     * **OPTIONAL** like {@link listByScope}; the in-memory and file first-party impls provide it.
     */
    listScopes?(): Promise<string[]>;
}
/**
 * The SINGLE resolution of "which CheckpointStore does this run use" (ruled 2026-08-04; re-homed
 * from prepare-task with the design/173 wiring manifest so the static self-check half shares it
 * without a runtime cycle). The spec seat is three-valued — a store wins, `undefined` falls through
 * to the deployment's `RunnerDeps.checkpointStore`, and the explicit word `"disabled"` means this run
 * has NO store at all (see `TaskSpec.checkpointStore`: the per-run off switch for the durable
 * machine, so a machine-started run cannot mint a checkpoint nobody will ever resolve).
 *
 * The off switch is a WORD, not `null`, on purpose: `??` treats `null` exactly like `undefined`, so
 * the old null sentinel silently fell through a bare coalesce to the very deployment store the
 * caller asked not to have — re-arming every suspend leg while the run looked perfectly normal right
 * up until it parked forever — and every read of the pair had to remember a special `=== null` test.
 * A string is never nullish, so the ordinary coalesce is the whole rule. The retired spelling is
 * REFUSED loudly here (the one read every site goes through) rather than read as either value: a
 * caller still writing `null` is told the word to write, at the first prepare, instead of being
 * handed a store it asked not to have or a silent off switch it did not spell.
 */
export declare function resolveCheckpointStore(spec: {
    checkpointStore?: CheckpointStore | "disabled";
}, deps: {
    checkpointStore?: CheckpointStore;
}): CheckpointStore | undefined;
/** The ONE refusal of a store seat whose {@link CheckpointStore} does not declare `execution = { outcome: true }`
 *  (named by seat and by the store's constructor when it has one). Same door, same code, as the retired-`null`
 *  refusal above: a configuration the run cannot honour is refused at the first read, never carried. */
export declare function checkpointStoreExecutionUndeclared(seat: "TaskSpec.checkpointStore" | "RunnerDeps.checkpointStore", store: object): Error & {
    code: "config.invalid_checkpoint_store";
};
/** The ONE refusal of the retired `checkpointStore: null` spelling (the resolver and the workflow baseline overlay both throw it). */
export declare function retiredCheckpointStoreNull(): Error & {
    code: "config.invalid_checkpoint_store";
};
/**
 * design/80 D-1 (persist-winner): derive the durable {@link ResolvedOutcome} winner from a resume
 * `outcome`, or `undefined` when the outcome binds to no pending action. A `policy_ask` resume names a
 * specific tool call (`boundCallId`) + decision; since RB-64 the REVIEW gates (`plan_review` /
 * `dry_run_review`) record their decision under a `gate:` sentinel too — the env_failed replay guard can
 * only compare what is recorded here. Only `resource_limit` / `task_done` bind nothing and record no
 * winner. Shared by every {@link CheckpointStore} impl so the persisted winner is identical across the
 * in-memory and durable backends.
 */
export declare function winnerFromOutcome(outcome: ResumeOutcome): ResolvedOutcome | undefined;
/**
 * design/80 D-A: the persist-time validation every {@link CheckpointStore.setPendingSteer} impl runs so the
 * reject is IDENTICAL across the in-memory and durable backends. A `steer.text` carrying a `</system-reminder>`
 * close tag would escape the model-facing `<system-reminder>` wrapper a trusted steer rides on resume — reject
 * fail-closed (`steering.invalid_content`) so a dirty steer NEVER enters {@link CheckpointState} (§3 inv #2).
 * Applied for BOTH `trusted` values: the durable record must be clean on either path (the untrusted resume
 * path ALSO sanitizes as untrusted data, but the close tag must never reach the persisted state at all).
 * Returns a copy built from the FROZEN field set ({@link PENDING_STEER_FROZEN_FIELDS} minus the
 * store-minted `seq`), so no extra caller field leaks into the row — and, since design/171 §6.3, an
 * unknown key is a loud rejection rather than a silent drop: a producer one release ahead of this worker
 * must learn that its new field would not survive the parked leg.
 */
export declare function validatePendingSteer(steer: PendingSteerInput): Omit<PendingSteerEntry, "seq">;
/** design/171 §6.1 — the `actor` id/issuer bound. Attribution labels ride the durable row and the model
 *  context, so they are capped like every other persisted model-facing string on this row. */
export declare const MAX_ACTOR_FIELD_CHARS = 256;
/** design/171 §6.3 — bound on the caller-supplied correlation id (a service's own message id). */
export declare const MAX_STEER_INPUT_ID_CHARS = 128;
/** design/171 §6.1 — the frozen field set of an {@link ActorAssertion} (same fail-loud posture as the
 *  entry's own set: an unknown key means a producer's provenance field would vanish silently). */
export declare const ACTOR_ASSERTION_FROZEN_FIELDS: readonly ["id", "hostAsserted", "issuer"];
/**
 * design/171 §5.3 — THE single read point for a checkpoint's parked steers, in delivery (`seq`) order.
 * Every consumer — the resume drain, the wake's nothing-to-deliver guard, a serving layer — goes through
 * here, so no site can grow its own idea of what "the parked steer" is.
 *
 * A row that still carries the RETIRED pre-queue single seat (`state.pendingSteer`, written before the
 * queue existed) is refused here rather than read: the seat has no `inputId`, `seq` or actor, so folding
 * it in would mint a synthetic identity nothing else can name. The refusal is `checkpoint.unsupported_version`
 * with `reason: "seat_retired"` — terminal for the row's bytes; the host decides the parked instruction
 * on its own surface and parks it again through `setPendingSteer` if it still applies.
 */
export declare function readPendingSteerQueue(state: Pick<CheckpointState, "pendingSteerQueue">): PendingSteerEntry[];
/**
 * design/171 §5.3/§6.3 — THE append, shared by every backend so the bounds and the ordering are one
 * implementation rather than one per store.
 *
 * Returns the NEW queue (the caller persists it); throws `steering.queue_full` when either bound would
 * be crossed. Re-appending an entry whose `inputId` is already queued is a NO-OP returning the queue
 * unchanged — that is what keeps `setPendingSteer` retry-safe now that it appends instead of overwriting.
 */
export declare function appendPendingSteer(state: Pick<CheckpointState, "pendingSteerQueue">, entry: Omit<PendingSteerEntry, "seq">): PendingSteerEntry[];
/**
 * REF-A4 — THE row half of every guarded checkpoint transition's CAS predicate, shared by every backend:
 * the row must EXIST, its `scope` must match (multi-tenant isolation, §2.1 — a wrong-scope caller never
 * wins), and it must currently be in `status`. `resolve` requires `"pending"`, `reopen` requires
 * `"resolved"` (the inverse gate), `setPendingSteer`/`expire` require `"pending"`.
 *
 * PURE: it decides nothing about persistence. Each backend keeps its own commit half — the in-memory one
 * flips fields in place, the file one appends+fsyncs the event BEFORE the flip (RB-150: commit ordering is
 * that backend's own correctness core and must never be absorbed into a shared abstraction).
 */
export declare function checkpointRowMatches(cp: Checkpoint | undefined, scope: string, status: Checkpoint["status"]): cp is Checkpoint;
/**
 * REF-A4 — THE optimistic-concurrency half of `resolve`'s CAS predicate (design/80 D-1), shared by every
 * backend. The caller validated its reopen-by-reason guard against a `get()` snapshot; if a concurrent
 * resolve/reopen cycle moved the monotonic `rev` in the get→resolve window, that validation is stale ⇒
 * lose the CAS (fail-closed), forcing a re-get + re-validate. Keyed on `rev`, NOT on `reopenReason` — a
 * 2-valued enum can ABA back to a prior value while the winner changed (round-2 BLOCKER fix). An absent
 * expectation opts out of the guard entirely.
 */
export declare function checkpointOccMatches(cp: Checkpoint, expect?: ResolveExpectation): boolean;
/** Fault-injection mode for {@link InMemoryCheckpointStore.testInjectFault} (council #5). One-shot. */
export type CheckpointFaultMode = 
/** `resolve` commits the CAS (status → resolved) then throws — simulates a crash *after* the commit
 *  but before the caller is acked, so a retry must see `resolved` and NOT re-execute (idempotency). */
"resolve-after-commit"
/** `resolve` throws *before* the CAS — simulates a crash before the commit; the row stays `pending`
 *  so a retry can still win it. */
 | "resolve-before-commit"
/** design/384 slice 2 — `claimTerminal` wins its claim (the commit lands) then throws before the
 *  caller is acked: the claim-law recovery scenario (law 3/4 — a reject proves nothing; the retry's
 *  loss answer carries the truth and a responsibility-bearing consumer must NOT self-attribute). */
 | "claim-after-commit"
/** design/384 slice 2 — `claimTerminal` throws *before* its CAS: the row is untouched (still
 *  pending), so a retry can still win the same claim. */
 | "claim-before-commit";
/**
 * Default in-process {@link CheckpointStore}. Single-instance / tests only — it does NOT survive a
 * restart or span replicas, so it cannot deliver the cross-process guarantee a durable backend does.
 * Single-threaded JS already serializes `resolve`, so the CAS is trivially atomic here;
 * {@link testInjectFault} simulates the crash-recovery races a real backend must survive.
 */
export declare class InMemoryCheckpointStore implements CheckpointStore {
    /** In-memory rows die with the process, but there is no scheduled tenant-scoped deletion contract
     *  here — honestly `"none"` (a locked retention policy must refuse this store). */
    readonly retention: "none";
    /** design/173 §2.3 — honest declaration: in-memory rows die with the process. */
    readonly durability: "process-local";
    /** Honest declaration on the fidelity axis: `put`/`get` are `structuredClone`, so a row keeps the
     *  whole structured-clone domain — a `Date` filed here is a `Date` when it comes back. */
    readonly fidelity: "structured-clone";
    /** F-012 L2: this store's `reopen` is a real resolved→pending CAS (below) — declared, not sniffed. */
    readonly redecision: {
        readonly reopen: true;
    };
    /** The execution-outcome record verb below is the contract's CAS, and `reopen` honours the record. */
    readonly execution: {
        readonly outcome: true;
    };
    private cps;
    private fault;
    put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
    get(token: CheckpointToken): Promise<Checkpoint | null>;
    /** The loser's single-round-trip reading (claim law 1): what the row IS, in its own vocabulary —
     *  or `absent` for a missing row AND a wrong-scope row alike (the isolation rule: cross-scope truth
     *  in a loss answer would be a read bypass of the scope WHERE). Cloned projection, never a live ref. */
    protected claimLossFrom(cp: Checkpoint | undefined, scope: string): TerminalClaimOutcome;
    /**
     * The ONE protected write core for the terminal-resolve claim — `resolve` and
     * `claimTerminal({kind:"resolve"})` both run exactly this (claim law 2's one-machine obligation:
     * one predicate, one commit, no drift window). Single-threaded JS makes the check + flip atomic; a
     * durable backend folds the same predicate into its CAS WHERE clause.
     *
     * ORDER (design/384 slice 2, healing the pre-existing resolve shape): the winner is derived and
     * CLONED — the point where an uncloneable `updatedInput` says so — BEFORE any field is written, and
     * the commit is then one uninterrupted write group (status+rev+winner+reopenReason). The old order
     * cloned AFTER the status/rev flip, so an uncloneable winner threw with the row already resolved
     * and NO winner recorded; now the same throw leaves the row byte-identical (still pending) — the
     * claim REJECTS (a reject proves nothing, law 3) instead of half-committing.
     */
    protected claimResolveCore(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): TerminalClaimOutcome;
    /** The one protected write core for the terminal-expire claim — `expire` and
     *  `claimTerminal({kind:"expire"})` both run exactly this (claim law 2, same as the resolve core). */
    protected claimExpireCore(token: CheckpointToken, scope: string): TerminalClaimOutcome;
    resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
    /** design/384 slice 2 — the atomic terminal claim (the interface JSDoc is the law's single
     *  normative statement). Implemented OVER the same protected cores `resolve`/`expire` run, which is
     *  the one-machine obligation discharged rather than restated. ONE fault plane with the one
     *  machine: a fault armed under the `resolve-*` names fires on the claim route of the SAME CAS too
     *  (`kind:"resolve"` only — expire never consumed resolve faults), so a crash simulation pinned
     *  against the two-step verb keeps firing byte-identically when the runner adopts the claim form.
     *
     *  INHERITANCE OBLIGATION (a subclass provides `claimTerminal` whether it means to or not): a
     *  subclass that overrides `resolve`/`expire` to wrap the CAS (audit hooks, fault injection,
     *  interleave fixtures) must override THIS method consistently. Overriding the `protected` cores
     *  (`claimResolveCore`/`claimExpireCore`) instead covers every COMMIT on every route — the cores
     *  are the one write core all three verbs share — but NOT every ATTEMPT: `resolve`/`expire`
     *  evaluate the shared row predicate themselves and return `false` without entering a core when
     *  the row is not pending (missing, another scope, already terminal), while this claim route
     *  enters the core unconditionally and takes its loss reading from it. A core override therefore
     *  sees every winning claim and every OCC loss, but a row-predicate loss only when it arrived via
     *  the claim route; a hook that must record EVERY attempt, including those losses, on EVERY route
     *  wraps the three public verbs consistently. The same applies to a DELEGATING WRAPPER (Proxy / hand-built object)
     *  that forwards `claimTerminal` while hooking only `resolve`: forwarding IS providing, and the
     *  claim route is then the one that actually runs. A resolve-only wrap goes dark the moment a
     *  consumer prefers the claim route (the runner's resume does), which is a one-machine-law
     *  violation the SUBCLASS created — the base class cannot route through the public verbs
     *  instead, because an `await` between the CAS and the loss reading would break the law-1
     *  atomic unit this method exists to provide. (A wrapper that OMITS the member opts out
     *  cleanly: the consumer's presence probe then keeps the two-step path, hooks intact.) */
    claimTerminal(token: CheckpointToken, scope: string, intent: TerminalClaimIntent): Promise<TerminalClaimOutcome>;
    recordExecutionOutcome(token: CheckpointToken, scope: string, gate: GateOutcome): Promise<ExecutionOutcomeRecordWord>;
    reopen(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
    setPendingSteer(token: CheckpointToken, scope: string, steer: PendingSteerInput): Promise<boolean>;
    expire(token: CheckpointToken, scope: string): Promise<boolean>;
    reap(scope: string, cutoff: number): Promise<number>;
    listByScope(scope: string): Promise<CheckpointSummary[]>;
    listScopes(): Promise<string[]>;
    /** Arm a one-shot fault on the next `resolve` (council #5: makes crash-recovery unit-testable). */
    testInjectFault(mode: CheckpointFaultMode | null): void;
    /** Test/inspection helper: number of stored checkpoints. */
    get size(): number;
}
/**
 * The ONE refusal of a retired mode seed, read PRE-CAS on every resume kind (resume-admission): a row whose `inheritedGate`
 * still carries a retired member is refused as the engine refuses every retired row seat (`readPendingSteerQueue`) —
 * `checkpoint.unsupported_version` with `reason: "seat_retired"` — terminal for the row's bytes on this engine, and
 * nothing consumed: the row stays byte-identical and `pending`. No engine reads the retired keys into a mode any more; an
 * engine at 7.38.x or earlier still does, so the row can be resumed there once (its next suspend writes `permissionMode`), or
 * expired (`CheckpointStore.expire`).
 */
export declare function refuseRetiredModeSeed(state: Pick<CheckpointState, "inheritedGate">): void;
/**
 * The mode seed a row records (the 7.34.0 shape), read at prepare after {@link refuseRetiredModeSeed} has screened the row:
 * `permissionMode` must be a member of the closed set and `prePlanMode` a mode a plan can return to (else
 * `config.permission_mode_invalid`, the row cannot be resumed under a word this engine does not know); the shell floor is
 * `"always"` or absent. Nothing here migrates or guesses.
 */
export declare function recordedModeSeed(gate: Pick<NonNullable<CheckpointState["inheritedGate"]>, "permissionMode" | "prePlanMode" | "shellGate"> | undefined): {
    permissionMode?: PermissionMode;
    prePlanMode?: PermissionMode;
    shellGate?: ShellGateDoctrine;
};