/**
 * Durable-execution checkpoint store (DURABLE.1).
 *
 * Every durable primitive call writes one row keyed by `(run_id, step_idx)`.
 * On resume the evaluator queries `lastCompletedStep` + `loadBindings` to
 * restore the `as:` variable scope and skip already-completed steps. The
 * per-primitive boundary (finer than LangGraph's per-node, looser than
 * Temporal's per-activity-with-manual-marks) is the architectural moat:
 * `process:` is already a sequence of atomic primitive calls, so we get
 * resumable execution for free without asking pack authors to annotate
 * anything.
 *
 * Scope of DURABLE.1: STORAGE ONLY. This module ships the table, the
 * `append` / `lastCompletedStep` / `loadBindings` / `pruneOlderThan` API,
 * and canonical-JSON round-trip. It does NOT wire the evaluator. DURABLE.2
 * adds the `durable: boolean` flag to `PrimitiveDescriptor` and wraps each
 * primitive call in the evaluator.
 *
 * Atomicity: every `append` is a single `INSERT OR REPLACE` — SQLite makes
 * single-statement writes atomic, so no explicit BEGIN/COMMIT is needed.
 * `INSERT OR REPLACE` (rather than plain INSERT) gives us the idempotent
 * retry semantics: a primitive that errored at step 3 records
 * `status='errored'`; the resume retries step 3 and overwrites that row
 * with the new outcome. Replay-with-same-input writes the same row a
 * second time without corruption.
 *
 * Fail-mode: any libsql error in `append` re-throws (caller decides). We
 * do NOT swallow errors and continue — silent fail-open on checkpoint
 * write would defeat the entire feature (a crash after the "successful"
 * primitive but before a missing checkpoint would skip the step on
 * resume, breaking exactly-once semantics).
 *
 * Imports from: @libsql/client, ./canonical_json.js.
 * Imported by: DURABLE.2 evaluator wrap, DURABLE.4 resumer, future
 * `opensquid checkpoints` CLI.
 */
import type { Client } from '@libsql/client';
/**
 * Args to `append`. Mirrors the `checkpoints` row 1:1 except `outputs`
 * arrives as an unknown JS value — the store handles canonical-JSON
 * encoding.
 */
export interface CheckpointWrite {
    runId: string;
    stepIdx: number;
    fn: string;
    inputsHash: string;
    outputs: unknown;
    /** Variable name the step's `as:` clause binds to. `undefined` if no binding. */
    asBinding?: string;
    startedAtMs: number;
    completedAtMs: number;
    status: 'completed' | 'errored';
    /** Set when `status === 'errored'`; null otherwise. */
    errorMessage?: string;
}
/**
 * One row read back from the store. `outputs` has already been
 * canonical-JSON-revived (base64 → Buffer; ISO date strings stay strings).
 */
export interface CheckpointRow {
    runId: string;
    stepIdx: number;
    fn: string;
    inputsHash: string;
    outputs: unknown;
    asBinding: string | undefined;
    startedAtMs: number;
    completedAtMs: number;
    status: 'completed' | 'errored';
    errorMessage: string | undefined;
}
/**
 * Identity of a run captured at process entry. The Resumer (DURABLE.4)
 * needs `(packId, skill, ruleId, eventKind, eventPayload, packVersion)` to
 * reconstruct a `ProcessContext` on resume — the checkpoint table only
 * stores the hashed `inputsHash`, never the raw event payload, so the
 * manifest is the load-bearing record for restart.
 *
 * `packVersion` is compared at resume time to detect drift; if the pack
 * version diverges between crash and resume the run is skipped + audited
 * (see Resumer.resume).
 */
export interface RunManifest {
    runId: string;
    packId: string;
    packVersion: string;
    skill: string;
    ruleId: string;
    eventKind: string;
    eventPayload: unknown;
    startedAtMs: number;
}
export declare class CheckpointStore {
    private readonly db;
    private initialized;
    constructor(db: Client);
    /**
     * Idempotent DDL — `CREATE TABLE IF NOT EXISTS` + indexes. Same posture
     * as `RateLimiter.init` (AUTO.2) and the libsql RAG backends. Safe to
     * call repeatedly; only the first call hits libsql.
     */
    init(): Promise<void>;
    /**
     * Atomic write of a single checkpoint row. `INSERT OR REPLACE` makes
     * the call idempotent on `(run_id, step_idx)` — a retried step
     * overwrites its prior errored row with the new outcome, and a
     * replay-with-same-inputs writes the same row twice without corruption.
     *
     * Throws on libsql error. The caller (evaluator in DURABLE.2) is the
     * right place to decide fail-closed vs fail-open semantics; the store
     * never silently swallows.
     */
    append(write: CheckpointWrite): Promise<void>;
    /**
     * Highest step_idx for which the run has a `status='completed'` row.
     * Errored rows are excluded so the resume cursor naturally re-runs
     * errored steps (the spec's retry-not-skip rule).
     *
     * Returns `null` when the run has no completed steps yet — caller starts
     * the process from `step_idx = 0`.
     */
    lastCompletedStep(runId: string): Promise<CheckpointRow | null>;
    /**
     * Replay every `status='completed'` checkpoint that owns an `as:`
     * binding into a name→value map. The evaluator hydrates its
     * `ctx.bindings` from this map before resuming.
     *
     * Errored steps and steps without `asBinding` are skipped — they
     * contribute nothing to the rule's variable scope.
     *
     * Order: ascending `step_idx`. If two completed steps somehow share a
     * binding name (a primitive author bug — the same `as:` reused in two
     * places), the later step's value wins, matching the in-memory
     * evaluator semantics.
     */
    loadBindings(runId: string): Promise<Record<string, unknown>>;
    /**
     * Fetch every row for a run in ascending `step_idx` order. Used by the
     * evaluator's checkpoint wrap (DURABLE.2) to build a per-step lookup map
     * once at process entry, then dispatch each durable step against the
     * pre-fetched row without per-step libsql queries.
     *
     * Returns BOTH completed and errored rows — the evaluator decides what to
     * do with each: completed + matching inputsHash → skip; everything else
     * → re-execute and overwrite via `INSERT OR REPLACE`.
     *
     * Returns `[]` when the run has no checkpoints (fresh run).
     */
    fetchRun(runId: string): Promise<CheckpointRow[]>;
    /**
     * Delete every row whose `completed_at_ms` falls before `now - ms`.
     * Returns the affected-row count for audit-log surfacing. Used by the
     * future `opensquid checkpoints clean --older-than 7d` CLI verb.
     *
     * `nowMs` is optional and injected for tests; production callers pass
     * `Date.now()`.
     */
    pruneOlderThan(ms: number, nowMs?: number): Promise<number>;
    /**
     * Record a run-start manifest. Called by the rule dispatcher BEFORE
     * `evaluateProcess` so that a crash mid-process leaves the manifest
     * behind for the Resumer (DURABLE.4) to find.
     *
     * `INSERT OR REPLACE` — idempotent on `runId`. A retried run with the
     * same identity overwrites the prior manifest (timestamps refresh,
     * event payload re-canonicalizes to the same JSON anyway). The Resumer
     * uses the latest manifest in any case.
     */
    recordRunStart(manifest: RunManifest): Promise<void>;
    /**
     * Look up the manifest for a runId. Returns `null` when the run was
     * never registered via `recordRunStart` — the Resumer treats those
     * runs as orphan checkpoints and skips them with audit reason
     * `manifest_missing`.
     */
    getRunManifest(runId: string): Promise<RunManifest | null>;
    /**
     * Record terminal outcome for a run. Called by the rule dispatcher
     * AFTER `evaluateProcess` returns (regardless of `verdict` / `no_verdict`
     * / `error`). The Resumer treats a row in `terminal_markers` as
     * "this run finished — do not resume".
     *
     * `outcome` is stored as free-form text for audit visibility
     * (verdict / no_verdict / error). The Resumer doesn't switch on it —
     * presence of the row is what gates resume.
     */
    recordRunTerminal(runId: string, outcome: string, nowMs: number): Promise<void>;
    /**
     * Has the run terminated? `true` when a `terminal_markers` row exists
     * for the runId. Used by tests + the Resumer's own scan to short-circuit
     * cleanly without a second SELECT.
     */
    hasTerminalMarker(runId: string): Promise<boolean>;
    /**
     * Scan for runs the Resumer (DURABLE.4) should consider resuming.
     *
     * A run is "interrupted" when:
     *   - It has at least one row in `checkpoints` (we ran something), AND
     *   - It has NO row in `terminal_markers` (process didn't finish), AND
     *   - The most-recent completed_at_ms is within `withinMs` of now
     *     (the resume window — older runs are "stale" and skipped by the
     *     Resumer with audit reason `stale`).
     *
     * Returns rows in ascending-age order (most recent first) so the
     * Resumer drains the freshest interrupted runs before stale ones in
     * pathological 100+-interrupted-runs cases.
     *
     * `withinMs <= 0` disables the window — useful for explicit
     * `opensquid checkpoints resume <run_id>` which bypasses the default.
     * The Resumer's own implementation passes `Number.POSITIVE_INFINITY`
     * for that path; this method clamps to a "no window" semantic when
     * the value is non-finite or non-positive.
     */
    scanInterrupted(withinMs: number, nowMs?: number): Promise<InterruptedSummary[]>;
}
/**
 * One row returned by `CheckpointStore.scanInterrupted` — the resumable
 * runs in the store. The Resumer joins this with `getRunManifest` to
 * build the full `InterruptedRun` shape (with packId / skill / ruleId /
 * eventPayload).
 */
export interface InterruptedSummary {
    runId: string;
    lastCompletedStep: number;
    lastCompletedAtMs: number;
}
//# sourceMappingURL=checkpoint_store.d.ts.map