"""Durable authoritative state for an autonomous penetration-test run."""

from __future__ import annotations

import json
import sqlite3
import time
import uuid
from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
from typing import TYPE_CHECKING, Any

from .identifiers import validate_opaque_id

if TYPE_CHECKING:
    from .execution import ValidExecution
    from .plan import TaskRecord, ValidPlan


class RunStatus(StrEnum):
    RUNNING = "running"
    COMPLETED = "completed"
    PAUSED = "paused"
    FAILED = "failed"


class AttemptStatus(StrEnum):
    ACTIVE = "active"
    DONE = "done"
    PROGRESS = "progress"
    BLOCKED = "blocked"
    FAILED = "failed"
    ERROR = "error"


@dataclass(frozen=True)
class RunSpec:
    run_id: str
    goal: str
    allowed_targets: tuple[str, ...]
    max_attempts_per_task: int = 2


@dataclass(frozen=True)
class RunSnapshot:
    run_id: str
    goal: str
    allowed_targets: tuple[str, ...]
    status: RunStatus
    revision: int
    max_attempts_per_task: int
    tasks: tuple[TaskRecord, ...] = ()
    observations: tuple[ObservationRecord, ...] = ()
    attempts: tuple[AttemptRecord, ...] = ()
    transitions: tuple[TransitionRecord, ...] = ()


@dataclass(frozen=True)
class ObservationRecord:
    id: str
    task_id: str
    attempt_id: str
    statement: str
    trace_episode_id: str
    evidence_sequences: tuple[int, ...]
    created_revision: int


@dataclass(frozen=True)
class AttemptRecord:
    id: str
    task_id: str
    status: AttemptStatus
    started_revision: int
    finished_revision: int | None
    trace_episode_id: str | None
    summary: str | None
    failure_kind: str | None
    failure_message: str | None


@dataclass(frozen=True)
class TransitionRecord:
    revision: int
    kind: str
    task_id: str | None
    attempt_id: str | None
    detail: dict[str, Any]
    created_at: float


@dataclass(frozen=True)
class TaskLease:
    run_id: str
    task_id: str
    attempt_id: str
    revision: int
    trace_episode_id: str | None = None


@dataclass(frozen=True)
class PlanCommit:
    revision: int
    lease: TaskLease | None


@dataclass(frozen=True)
class AttemptCommit:
    snapshot: RunSnapshot
    retry_lease: TaskLease | None = None


class RunNotFoundError(LookupError):
    pass


class RunSpecMismatchError(ValueError):
    pass


class StaleRevisionError(RuntimeError):
    pass


def validate_run_spec(spec: RunSpec) -> None:
    validate_opaque_id(spec.run_id, label="run_id")
    if not spec.goal.strip() or len(spec.goal) > 4_000:
        raise ValueError("goal must be 1-4000 characters")
    if not spec.allowed_targets or len(spec.allowed_targets) > 16:
        raise ValueError("allowed_targets must contain at most 16 nonempty targets")
    if len(spec.allowed_targets) != len(set(spec.allowed_targets)):
        raise ValueError("allowed_targets must be unique")
    if any(not target.strip() or len(target) > 2_048 for target in spec.allowed_targets):
        raise ValueError("each allowed target must be 1-2048 characters")
    if not 1 <= spec.max_attempts_per_task <= 10:
        raise ValueError("max_attempts_per_task must be between 1 and 10")


class MemoryKernel:
    """SQLite-backed source of truth with a deliberately small interface."""

    def __init__(self, database: str | Path) -> None:
        self.database = Path(database)
        self.database.parent.mkdir(parents=True, exist_ok=True)
        with self._connect() as connection:
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS runs (
                    run_id TEXT PRIMARY KEY,
                    goal TEXT NOT NULL,
                    allowed_targets TEXT NOT NULL,
                    status TEXT NOT NULL,
                    revision INTEGER NOT NULL,
                    max_attempts_per_task INTEGER NOT NULL DEFAULT 2
                )
                """
            )
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS transitions (
                    run_id TEXT NOT NULL,
                    revision INTEGER NOT NULL,
                    kind TEXT NOT NULL,
                    task_id TEXT,
                    attempt_id TEXT,
                    detail TEXT NOT NULL,
                    created_at REAL NOT NULL,
                    PRIMARY KEY (run_id, revision),
                    FOREIGN KEY (run_id) REFERENCES runs(run_id)
                )
                """
            )
            self._ensure_column(
                connection,
                "runs",
                "max_attempts_per_task",
                "INTEGER NOT NULL DEFAULT 2",
            )
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS attempts (
                    attempt_id TEXT PRIMARY KEY,
                    run_id TEXT NOT NULL,
                    task_id TEXT NOT NULL,
                    status TEXT NOT NULL,
                    started_revision INTEGER NOT NULL,
                    finished_revision INTEGER,
                    trace_episode_id TEXT,
                    summary TEXT,
                    failure_kind TEXT,
                    failure_message TEXT,
                    FOREIGN KEY (run_id, task_id) REFERENCES tasks(run_id, task_id)
                )
                """
            )
            self._ensure_column(connection, "attempts", "failure_kind", "TEXT")
            self._ensure_column(connection, "attempts", "failure_message", "TEXT")
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS observations (
                    observation_id TEXT PRIMARY KEY,
                    run_id TEXT NOT NULL,
                    task_id TEXT NOT NULL,
                    attempt_id TEXT NOT NULL,
                    statement TEXT NOT NULL,
                    trace_episode_id TEXT NOT NULL,
                    evidence_sequences TEXT NOT NULL,
                    created_revision INTEGER NOT NULL,
                    FOREIGN KEY (run_id, task_id) REFERENCES tasks(run_id, task_id),
                    FOREIGN KEY (attempt_id) REFERENCES attempts(attempt_id)
                )
                """
            )
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS tasks (
                    run_id TEXT NOT NULL,
                    task_id TEXT NOT NULL,
                    kind TEXT NOT NULL,
                    target TEXT NOT NULL,
                    objective TEXT NOT NULL,
                    done_when TEXT NOT NULL,
                    basis_ids TEXT NOT NULL,
                    depends_on TEXT NOT NULL,
                    status TEXT NOT NULL,
                    created_revision INTEGER NOT NULL,
                    PRIMARY KEY (run_id, task_id),
                    FOREIGN KEY (run_id) REFERENCES runs(run_id)
                )
                """
            )

    def create_run(self, spec: RunSpec) -> RunSnapshot:
        validate_run_spec(spec)
        with self._connect() as connection:
            connection.execute(
                """
                INSERT INTO runs(
                    run_id, goal, allowed_targets, status, revision, max_attempts_per_task
                ) VALUES (?, ?, ?, ?, 0, ?)
                """,
                (
                    spec.run_id,
                    spec.goal,
                    json.dumps(spec.allowed_targets),
                    RunStatus.RUNNING.value,
                    spec.max_attempts_per_task,
                ),
            )
            self._record_transition(
                connection,
                run_id=spec.run_id,
                revision=0,
                kind="run_created",
                detail={"allowed_targets": list(spec.allowed_targets)},
            )
        return self.snapshot(spec.run_id)

    def open_run(self, spec: RunSpec) -> RunSnapshot:
        """Create a run or reopen it only when its durable identity is unchanged."""
        validate_run_spec(spec)
        try:
            snapshot = self.snapshot(spec.run_id)
        except RunNotFoundError:
            try:
                return self.create_run(spec)
            except sqlite3.IntegrityError:
                snapshot = self.snapshot(spec.run_id)
        persisted = (
            snapshot.goal,
            snapshot.allowed_targets,
            snapshot.max_attempts_per_task,
        )
        requested = (
            spec.goal,
            spec.allowed_targets,
            spec.max_attempts_per_task,
        )
        if persisted != requested:
            raise RunSpecMismatchError(
                f"persisted run {spec.run_id!r} does not match requested spec"
            )
        return snapshot

    def snapshot(self, run_id: str) -> RunSnapshot:
        validate_opaque_id(run_id, label="run_id")
        from .plan import TaskKind, TaskRecord, TaskStatus

        with self._connect() as connection:
            connection.execute("BEGIN")
            row = connection.execute(
                """
                SELECT run_id, goal, allowed_targets, status, revision, max_attempts_per_task
                FROM runs
                WHERE run_id = ?
                """,
                (run_id,),
            ).fetchone()
            task_rows = connection.execute(
                """
                SELECT task_id, kind, target, objective, done_when, basis_ids,
                       depends_on, status, created_revision
                FROM tasks
                WHERE run_id = ?
                ORDER BY created_revision, task_id
                """,
                (run_id,),
            ).fetchall()
            observation_rows = connection.execute(
                """
                SELECT observation_id, task_id, attempt_id, statement,
                       trace_episode_id, evidence_sequences, created_revision
                FROM observations
                WHERE run_id = ?
                ORDER BY created_revision, observation_id
                """,
                (run_id,),
            ).fetchall()
            attempt_rows = connection.execute(
                """
                SELECT attempt_id, task_id, status, started_revision, finished_revision,
                       trace_episode_id, summary, failure_kind, failure_message
                FROM attempts
                WHERE run_id = ?
                ORDER BY started_revision, attempt_id
                """,
                (run_id,),
            ).fetchall()
            transition_rows = connection.execute(
                """
                SELECT revision, kind, task_id, attempt_id, detail, created_at
                FROM transitions
                WHERE run_id = ?
                ORDER BY revision
                """,
                (run_id,),
            ).fetchall()
        if row is None:
            raise RunNotFoundError(run_id)
        tasks = tuple(
            TaskRecord(
                id=str(task_row["task_id"]),
                kind=TaskKind(task_row["kind"]),
                target=str(task_row["target"]),
                objective=str(task_row["objective"]),
                done_when=str(task_row["done_when"]),
                basis_ids=tuple(json.loads(task_row["basis_ids"])),
                depends_on=tuple(json.loads(task_row["depends_on"])),
                status=TaskStatus(task_row["status"]),
                created_revision=int(task_row["created_revision"]),
            )
            for task_row in task_rows
        )
        observations = tuple(
            ObservationRecord(
                id=str(observation_row["observation_id"]),
                task_id=str(observation_row["task_id"]),
                attempt_id=str(observation_row["attempt_id"]),
                statement=str(observation_row["statement"]),
                trace_episode_id=str(observation_row["trace_episode_id"]),
                evidence_sequences=tuple(json.loads(observation_row["evidence_sequences"])),
                created_revision=int(observation_row["created_revision"]),
            )
            for observation_row in observation_rows
        )
        attempts = tuple(
            AttemptRecord(
                id=str(attempt_row["attempt_id"]),
                task_id=str(attempt_row["task_id"]),
                status=AttemptStatus(attempt_row["status"]),
                started_revision=int(attempt_row["started_revision"]),
                finished_revision=(
                    int(attempt_row["finished_revision"])
                    if attempt_row["finished_revision"] is not None
                    else None
                ),
                trace_episode_id=(
                    str(attempt_row["trace_episode_id"])
                    if attempt_row["trace_episode_id"] is not None
                    else None
                ),
                summary=(
                    str(attempt_row["summary"]) if attempt_row["summary"] is not None else None
                ),
                failure_kind=(
                    str(attempt_row["failure_kind"])
                    if attempt_row["failure_kind"] is not None
                    else None
                ),
                failure_message=(
                    str(attempt_row["failure_message"])
                    if attempt_row["failure_message"] is not None
                    else None
                ),
            )
            for attempt_row in attempt_rows
        )
        transitions = tuple(
            TransitionRecord(
                revision=int(transition_row["revision"]),
                kind=str(transition_row["kind"]),
                task_id=(
                    str(transition_row["task_id"])
                    if transition_row["task_id"] is not None
                    else None
                ),
                attempt_id=(
                    str(transition_row["attempt_id"])
                    if transition_row["attempt_id"] is not None
                    else None
                ),
                detail=dict(json.loads(transition_row["detail"])),
                created_at=float(transition_row["created_at"]),
            )
            for transition_row in transition_rows
        )
        return RunSnapshot(
            run_id=str(row["run_id"]),
            goal=str(row["goal"]),
            allowed_targets=tuple(json.loads(row["allowed_targets"])),
            status=RunStatus(row["status"]),
            revision=int(row["revision"]),
            max_attempts_per_task=int(row["max_attempts_per_task"]),
            tasks=tasks,
            observations=observations,
            attempts=attempts,
            transitions=transitions,
        )

    def commit_plan(self, plan: ValidPlan) -> PlanCommit:
        from .plan import TaskStatus

        new_revision = plan.base_revision + 1
        lease: TaskLease | None = None
        with self._connect() as connection:
            connection.execute("BEGIN IMMEDIATE")
            row = connection.execute(
                "SELECT revision, status FROM runs WHERE run_id = ?",
                (plan.run_id,),
            ).fetchone()
            if row is None:
                raise RunNotFoundError(plan.run_id)
            if int(row["revision"]) != plan.base_revision:
                raise StaleRevisionError(plan.run_id)

            for task in plan.new_tasks:
                dependencies_done = True
                for dependency_id in task.depends_on:
                    dependency = connection.execute(
                        """
                        SELECT status
                        FROM tasks
                        WHERE run_id = ? AND task_id = ?
                        """,
                        (plan.run_id, dependency_id),
                    ).fetchone()
                    if dependency is None or dependency["status"] != TaskStatus.DONE.value:
                        dependencies_done = False
                        break
                status = TaskStatus.READY if dependencies_done else TaskStatus.BLOCKED
                connection.execute(
                    """
                    INSERT INTO tasks(
                        run_id, task_id, kind, target, objective, done_when,
                        basis_ids, depends_on, status, created_revision
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        plan.run_id,
                        task.id,
                        task.kind.value,
                        task.target,
                        task.objective,
                        task.done_when,
                        json.dumps(task.basis_ids),
                        json.dumps(task.depends_on),
                        status.value,
                        new_revision,
                    ),
                )

            if plan.next_task_id is not None:
                updated = connection.execute(
                    """
                    UPDATE tasks
                    SET status = ?
                    WHERE run_id = ? AND task_id = ? AND status = ?
                    """,
                    (
                        TaskStatus.ACTIVE.value,
                        plan.run_id,
                        plan.next_task_id,
                        TaskStatus.READY.value,
                    ),
                ).rowcount
                if updated != 1:
                    raise StaleRevisionError(f"task is no longer ready: {plan.next_task_id}")
                attempt_id = uuid.uuid4().hex
                connection.execute(
                    """
                    INSERT INTO attempts(
                        attempt_id, run_id, task_id, status, started_revision, trace_episode_id
                    ) VALUES (?, ?, ?, ?, ?, ?)
                    """,
                    (
                        attempt_id,
                        plan.run_id,
                        plan.next_task_id,
                        AttemptStatus.ACTIVE.value,
                        new_revision,
                        f"executor-{attempt_id}",
                    ),
                )
                lease = TaskLease(
                    run_id=plan.run_id,
                    task_id=plan.next_task_id,
                    attempt_id=attempt_id,
                    revision=new_revision,
                    trace_episode_id=f"executor-{attempt_id}",
                )

            run_status = RunStatus.COMPLETED if plan.finish else RunStatus.RUNNING
            updated_run = connection.execute(
                """
                UPDATE runs
                SET revision = ?, status = ?
                WHERE run_id = ? AND revision = ?
                """,
                (new_revision, run_status.value, plan.run_id, plan.base_revision),
            ).rowcount
            if updated_run != 1:
                raise StaleRevisionError(plan.run_id)

            self._record_transition(
                connection,
                run_id=plan.run_id,
                revision=new_revision,
                kind="plan_committed",
                task_id=plan.next_task_id,
                attempt_id=lease.attempt_id if lease is not None else None,
                detail={
                    "finish": plan.finish,
                    "finish_basis_ids": list(plan.finish_basis_ids),
                    "new_task_ids": [task.id for task in plan.new_tasks],
                    "next_task_id": plan.next_task_id,
                    "summary": plan.summary,
                },
            )

        return PlanCommit(revision=new_revision, lease=lease)

    def commit_execution(self, execution: ValidExecution) -> RunSnapshot:
        from .execution import ExecutionOutcome
        from .plan import TaskStatus

        new_revision = execution.lease_revision + 1
        task_status = {
            ExecutionOutcome.DONE: TaskStatus.DONE,
            ExecutionOutcome.PROGRESS: TaskStatus.READY,
            ExecutionOutcome.BLOCKED: TaskStatus.FAILED,
            ExecutionOutcome.FAILED: TaskStatus.FAILED,
        }[execution.outcome]
        with self._connect() as connection:
            connection.execute("BEGIN IMMEDIATE")
            row = connection.execute(
                "SELECT revision, max_attempts_per_task FROM runs WHERE run_id = ?",
                (execution.run_id,),
            ).fetchone()
            if row is None:
                raise RunNotFoundError(execution.run_id)
            if int(row["revision"]) != execution.lease_revision:
                raise StaleRevisionError(execution.run_id)

            if execution.observation is not None and execution.reused_observation_id is not None:
                raise ValueError("an execution cannot create and reuse an observation")
            if execution.reused_observation_id is not None:
                reused_observation = connection.execute(
                    """
                    SELECT task_id
                    FROM observations
                    WHERE run_id = ? AND observation_id = ?
                    """,
                    (execution.run_id, execution.reused_observation_id),
                ).fetchone()
                if reused_observation is None or reused_observation["task_id"] != execution.task_id:
                    raise ValueError("a reused observation must belong to the same run and task")

            attempt_limit_reached = False
            if execution.outcome is ExecutionOutcome.PROGRESS:
                attempt_count = int(
                    connection.execute(
                        "SELECT COUNT(*) FROM attempts WHERE run_id = ? AND task_id = ?",
                        (execution.run_id, execution.task_id),
                    ).fetchone()[0]
                )
                attempt_limit_reached = attempt_count >= int(row["max_attempts_per_task"])
                if attempt_limit_reached:
                    task_status = TaskStatus.FAILED

            updated_task = connection.execute(
                """
                UPDATE tasks
                SET status = ?
                WHERE run_id = ? AND task_id = ? AND status = ?
                """,
                (
                    task_status.value,
                    execution.run_id,
                    execution.task_id,
                    TaskStatus.ACTIVE.value,
                ),
            ).rowcount
            if updated_task != 1:
                raise StaleRevisionError(f"task is no longer active: {execution.task_id}")

            updated_attempt = connection.execute(
                """
                UPDATE attempts
                SET status = ?, finished_revision = ?, trace_episode_id = ?, summary = ?
                WHERE attempt_id = ? AND run_id = ? AND task_id = ? AND status = 'active'
                """,
                (
                    execution.outcome.value,
                    new_revision,
                    execution.trace_episode_id,
                    execution.summary,
                    execution.attempt_id,
                    execution.run_id,
                    execution.task_id,
                ),
            ).rowcount
            if updated_attempt != 1:
                raise StaleRevisionError(f"attempt is no longer active: {execution.attempt_id}")

            observation_id = execution.reused_observation_id
            if execution.observation is not None:
                observation_id = f"{execution.attempt_id}:observation"
                connection.execute(
                    """
                    INSERT INTO observations(
                        observation_id, run_id, task_id, attempt_id, statement,
                        trace_episode_id, evidence_sequences, created_revision
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        observation_id,
                        execution.run_id,
                        execution.task_id,
                        execution.attempt_id,
                        execution.observation,
                        execution.trace_episode_id,
                        json.dumps(execution.evidence_sequences),
                        new_revision,
                    ),
                )

            blocked_rows = connection.execute(
                """
                SELECT task_id, depends_on
                FROM tasks
                WHERE run_id = ? AND status = ? AND task_id != ?
                """,
                (execution.run_id, TaskStatus.BLOCKED.value, execution.task_id),
            ).fetchall()
            for blocked_row in blocked_rows:
                dependency_ids = tuple(json.loads(blocked_row["depends_on"]))
                if not dependency_ids:
                    continue
                placeholders = ",".join("?" for _ in dependency_ids)
                dependency_rows = connection.execute(
                    f"""
                    SELECT task_id, status
                    FROM tasks
                    WHERE run_id = ? AND task_id IN ({placeholders})
                    """,
                    (execution.run_id, *dependency_ids),
                ).fetchall()
                statuses = {
                    str(dependency["task_id"]): str(dependency["status"])
                    for dependency in dependency_rows
                }
                if all(
                    statuses.get(dependency_id) == TaskStatus.DONE.value
                    for dependency_id in dependency_ids
                ):
                    connection.execute(
                        """
                        UPDATE tasks
                        SET status = ?
                        WHERE run_id = ? AND task_id = ? AND status = ?
                        """,
                        (
                            TaskStatus.READY.value,
                            execution.run_id,
                            blocked_row["task_id"],
                            TaskStatus.BLOCKED.value,
                        ),
                    )

            updated_run = connection.execute(
                """
                UPDATE runs
                SET revision = ?
                WHERE run_id = ? AND revision = ?
                """,
                (new_revision, execution.run_id, execution.lease_revision),
            ).rowcount
            if updated_run != 1:
                raise StaleRevisionError(execution.run_id)

            self._record_transition(
                connection,
                run_id=execution.run_id,
                revision=new_revision,
                kind="attempt_committed",
                task_id=execution.task_id,
                attempt_id=execution.attempt_id,
                detail={
                    "observation_id": observation_id,
                    "outcome": execution.outcome.value,
                    "summary": execution.summary,
                    **({"attempt_limit_reached": True} if attempt_limit_reached else {}),
                    **(
                        {"recovered_transport_failure": True}
                        if execution.recovered_transport_failure
                        else {}
                    ),
                    **({"evidence_span_widened": True} if execution.evidence_span_widened else {}),
                    **({"evidence_projected": True} if execution.evidence_projected else {}),
                    **({"evidence_fallback": True} if execution.evidence_fallback else {}),
                    **({"evidence_truncated": True} if execution.evidence_truncated else {}),
                    **({"evidence_unresolved": True} if execution.evidence_unresolved else {}),
                    **(
                        {"reused_observation_id": execution.reused_observation_id}
                        if execution.reused_observation_id is not None
                        else {}
                    ),
                },
            )

        return self.snapshot(execution.run_id)

    def commit_attempt_failure(
        self,
        lease: TaskLease,
        *,
        trace_episode_id: str,
        failure_kind: str,
        failure_message: str,
        retryable: bool = False,
    ) -> AttemptCommit:
        """Atomically close an unsafe operational failure without committing evidence."""
        from .plan import TaskStatus

        new_revision = lease.revision + 1
        message = failure_message.strip()[:1000] or "Executor attempt failed"
        with self._connect() as connection:
            connection.execute("BEGIN IMMEDIATE")
            row = connection.execute(
                "SELECT revision, max_attempts_per_task FROM runs WHERE run_id = ?",
                (lease.run_id,),
            ).fetchone()
            if row is None:
                raise RunNotFoundError(lease.run_id)
            if int(row["revision"]) != lease.revision:
                raise StaleRevisionError(lease.run_id)

            updated_attempt = connection.execute(
                """
                UPDATE attempts
                SET status = ?, finished_revision = ?, trace_episode_id = ?, summary = ?,
                    failure_kind = ?, failure_message = ?
                WHERE attempt_id = ? AND run_id = ? AND task_id = ? AND status = ?
                """,
                (
                    AttemptStatus.ERROR.value,
                    new_revision,
                    trace_episode_id,
                    message,
                    failure_kind,
                    message,
                    lease.attempt_id,
                    lease.run_id,
                    lease.task_id,
                    AttemptStatus.ACTIVE.value,
                ),
            ).rowcount
            if updated_attempt != 1:
                raise StaleRevisionError(f"attempt is no longer active: {lease.attempt_id}")

            attempt_count = int(
                connection.execute(
                    "SELECT COUNT(*) FROM attempts WHERE run_id = ? AND task_id = ?",
                    (lease.run_id, lease.task_id),
                ).fetchone()[0]
            )
            may_retry = retryable and attempt_count < int(row["max_attempts_per_task"])
            retry_lease: TaskLease | None = None
            if may_retry:
                task = connection.execute(
                    """
                    SELECT status FROM tasks
                    WHERE run_id = ? AND task_id = ?
                    """,
                    (lease.run_id, lease.task_id),
                ).fetchone()
                if task is None or task["status"] != TaskStatus.ACTIVE.value:
                    raise StaleRevisionError(f"task is no longer active: {lease.task_id}")
                retry_attempt_id = uuid.uuid4().hex
                connection.execute(
                    """
                    INSERT INTO attempts(
                        attempt_id, run_id, task_id, status, started_revision, trace_episode_id
                    ) VALUES (?, ?, ?, ?, ?, ?)
                    """,
                    (
                        retry_attempt_id,
                        lease.run_id,
                        lease.task_id,
                        AttemptStatus.ACTIVE.value,
                        new_revision,
                        f"executor-{retry_attempt_id}",
                    ),
                )
                retry_lease = TaskLease(
                    run_id=lease.run_id,
                    task_id=lease.task_id,
                    attempt_id=retry_attempt_id,
                    revision=new_revision,
                    trace_episode_id=f"executor-{retry_attempt_id}",
                )
            else:
                updated_task = connection.execute(
                    """
                    UPDATE tasks
                    SET status = ?
                    WHERE run_id = ? AND task_id = ? AND status = ?
                    """,
                    (
                        TaskStatus.FAILED.value,
                        lease.run_id,
                        lease.task_id,
                        TaskStatus.ACTIVE.value,
                    ),
                ).rowcount
                if updated_task != 1:
                    raise StaleRevisionError(f"task is no longer active: {lease.task_id}")

            updated_run = connection.execute(
                """
                UPDATE runs
                SET revision = ?, status = ?
                WHERE run_id = ? AND revision = ?
                """,
                (
                    new_revision,
                    (RunStatus.RUNNING if may_retry else RunStatus.FAILED).value,
                    lease.run_id,
                    lease.revision,
                ),
            ).rowcount
            if updated_run != 1:
                raise StaleRevisionError(lease.run_id)

            self._record_transition(
                connection,
                run_id=lease.run_id,
                revision=new_revision,
                kind="attempt_failed",
                task_id=lease.task_id,
                attempt_id=lease.attempt_id,
                detail={
                    "failure_kind": failure_kind,
                    "failure_message": message,
                    "retry_attempt_id": (
                        retry_lease.attempt_id if retry_lease is not None else None
                    ),
                    "terminal": not may_retry,
                },
            )

        return AttemptCommit(
            snapshot=self.snapshot(lease.run_id),
            retry_lease=retry_lease,
        )

    def commit_run_failure(
        self,
        run_id: str,
        base_revision: int,
        *,
        failure_kind: str,
        failure_message: str,
        transition_kind: str = "supervisor_failed",
    ) -> RunSnapshot:
        """Atomically settle a run-level failure when no task lease exists."""
        new_revision = base_revision + 1
        message = failure_message.strip()[:1000] or "Run failed"
        with self._connect() as connection:
            connection.execute("BEGIN IMMEDIATE")
            active_attempts = int(
                connection.execute(
                    "SELECT COUNT(*) FROM attempts WHERE run_id = ? AND status = ?",
                    (run_id, AttemptStatus.ACTIVE.value),
                ).fetchone()[0]
            )
            if active_attempts:
                raise StaleRevisionError("cannot fail a run while an attempt is active")
            updated = connection.execute(
                """
                UPDATE runs
                SET revision = ?, status = ?
                WHERE run_id = ? AND revision = ? AND status = ?
                """,
                (
                    new_revision,
                    RunStatus.FAILED.value,
                    run_id,
                    base_revision,
                    RunStatus.RUNNING.value,
                ),
            ).rowcount
            if updated != 1:
                raise StaleRevisionError(run_id)
            self._record_transition(
                connection,
                run_id=run_id,
                revision=new_revision,
                kind=transition_kind,
                detail={
                    "failure_kind": failure_kind,
                    "failure_message": message,
                },
            )
        return self.snapshot(run_id)

    def _connect(self) -> sqlite3.Connection:
        connection = sqlite3.connect(self.database)
        connection.row_factory = sqlite3.Row
        connection.execute("PRAGMA foreign_keys = ON")
        connection.execute("PRAGMA journal_mode = WAL")
        connection.execute("PRAGMA synchronous = FULL")
        return connection

    @staticmethod
    def _ensure_column(
        connection: sqlite3.Connection,
        table: str,
        column: str,
        declaration: str,
    ) -> None:
        columns = {str(row["name"]) for row in connection.execute(f"PRAGMA table_info({table})")}
        if column not in columns:
            connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {declaration}")

    @staticmethod
    def _record_transition(
        connection: sqlite3.Connection,
        *,
        run_id: str,
        revision: int,
        kind: str,
        detail: dict[str, Any],
        task_id: str | None = None,
        attempt_id: str | None = None,
    ) -> None:
        connection.execute(
            """
            INSERT INTO transitions(
                run_id, revision, kind, task_id, attempt_id, detail, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                run_id,
                revision,
                kind,
                task_id,
                attempt_id,
                json.dumps(detail, sort_keys=True),
                time.time(),
            ),
        )


__all__ = [
    "AttemptCommit",
    "AttemptRecord",
    "AttemptStatus",
    "MemoryKernel",
    "ObservationRecord",
    "PlanCommit",
    "RunNotFoundError",
    "RunSnapshot",
    "RunSpec",
    "RunSpecMismatchError",
    "RunStatus",
    "StaleRevisionError",
    "TaskLease",
    "TransitionRecord",
    "validate_run_spec",
]
