iop/scripts/agent_benchmark/attempts.py
toki 91dec43635 feat(benchmark): 비교 파이프라인 기반을 구축한다
caller와 모델 조합을 반복 비교할 때 실행·격리·재개 근거가 흔들리지 않도록 manifest, workspace, lifecycle, append-only attempt 기반과 project-local 진입점을 함께 고정한다.
2026-08-09 23:49:04 +09:00

751 lines
38 KiB
Python

"""Durable, append-only benchmark run and attempt state.
Attempt-directory creation is the allocation marker. The directory remains
empty until the workspace layer has prepared it; only then is the identity
bound running record published. This preserves the workspace API's
empty-root contract while retaining a crash-recoverable allocation boundary.
"""
from __future__ import annotations
import contextlib
import datetime as _datetime
import fcntl
import hashlib
import json
import os
import re
import secrets
import stat
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Iterator
from scripts.agent_benchmark.lifecycle import (
COMPLETION_MODES,
EVENT_FINISH,
EVENT_IDLE,
EVENT_QUIET,
EVENT_SUBMITTED,
InvocationResult,
LifecycleRecoveryError,
RECEIPT_VERSION,
SUBMISSION_MODES,
SupervisorLocator,
TERMINAL_REASONS,
recover_invocation,
)
from scripts.agent_benchmark.manifest import Manifest, validate_manifest_bytes
from scripts.agent_benchmark.workspace import AttemptIdentity
RUN_ID_RE = re.compile(r"^run-[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}$")
ATTEMPT_RE = re.compile(r"^attempt-([0-9]{6})$")
DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
TERMINAL_STATES = frozenset(("success", "failed", "timed_out", "cancelled", "interrupted"))
NONTERMINAL_STATE = "running"
SUCCESS_EVIDENCE_KINDS = (EVENT_SUBMITTED, EVENT_FINISH, EVENT_IDLE, EVENT_QUIET)
RECEIPT_FIELDS = {
"receipt_version", "supervisor_pid", "challenge_digest", "reason", "exit_code",
"signal", "caller_launched", "cleanup_complete", "process_group_alive", "completed_at",
}
class AttemptError(Exception):
"""Base error whose message is safe to present to a benchmark caller."""
class RunPathError(AttemptError):
pass
class RunBusyError(AttemptError):
pass
class AttemptStateError(AttemptError):
pass
class CapabilityUnavailable(AttemptError):
pass
@dataclass(frozen=True)
class RunIdentity:
run_id: str
manifest_digest: str
root: str
@dataclass(frozen=True)
class Slot:
cell_id: str
repetition: int
@dataclass(frozen=True)
class Attempt:
identity: AttemptIdentity
root: str
state: str
def _json_bytes(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8") + b"\n"
def _fsync_dir(path: Path) -> None:
fd = os.open(path, os.O_RDONLY)
try:
os.fsync(fd)
finally:
os.close(fd)
def _write_new(path: Path, data: bytes) -> None:
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, data)
os.fsync(fd)
finally:
os.close(fd)
_fsync_dir(path.parent)
def _replace(path: Path, data: bytes) -> None:
fd, tmp = tempfile.mkstemp(prefix=".attempt-", dir=path.parent)
try:
with os.fdopen(fd, "wb") as handle:
handle.write(data)
handle.flush()
os.fsync(handle.fileno())
os.chmod(tmp, 0o600)
os.replace(tmp, path)
_fsync_dir(path.parent)
finally:
try:
os.unlink(tmp)
except FileNotFoundError:
pass
def _open_regular(path: Path, label: str, flags: int = os.O_RDONLY) -> int:
"""Open one durable file without following links, blocking or trusting its type."""
try:
fd = os.open(path, flags | os.O_NOFOLLOW | os.O_NONBLOCK)
except OSError as exc:
raise AttemptStateError(f"{label} is unavailable") from exc
try:
opened = os.fstat(fd)
if not stat.S_ISREG(opened.st_mode):
raise AttemptStateError(f"{label} must be a regular file")
except BaseException:
os.close(fd)
raise
return fd
def _read_regular_bytes(path: Path, label: str) -> bytes:
"""Read only the no-follow descriptor that was verified as regular."""
fd = _open_regular(path, label)
try:
chunks: list[bytes] = []
while True:
chunk = os.read(fd, 1 << 20)
if not chunk:
return b"".join(chunks)
chunks.append(chunk)
finally:
os.close(fd)
def _utc_timestamp(clock: Callable[[], _datetime.datetime]) -> str:
return clock().astimezone(_datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
def _directory(path: Path, label: str) -> None:
try:
mode = os.lstat(path).st_mode
except OSError as exc:
raise RunPathError(f"{label} is unavailable") from exc
if not stat.S_ISDIR(mode):
raise RunPathError(f"{label} is invalid")
def _contained(path: Path, root: Path) -> bool:
try:
path.resolve(strict=False).relative_to(root.resolve())
return True
except ValueError:
return False
class RunStore:
"""Filesystem-backed run store rooted at a validated manifest output root."""
def __init__(
self,
repo_root: str | Path,
*,
clock: Callable[[], _datetime.datetime] = lambda: _datetime.datetime.now(_datetime.timezone.utc),
token_hex: Callable[[int], str] = secrets.token_hex,
) -> None:
self.repo_root = Path(repo_root).resolve()
self._clock = clock
self._token_hex = token_hex
def _output_root(self, manifest: Manifest, *, create: bool) -> Path:
root = self.repo_root / manifest.output_root
if not _contained(root, self.repo_root):
raise RunPathError("output root is invalid")
if create:
root.mkdir(mode=0o700, parents=True, exist_ok=True)
_directory(root, "output root")
if root.is_symlink():
raise RunPathError("output root is invalid")
return root.resolve()
def _run_path(self, manifest: Manifest, run_id: str, *, create_root: bool) -> Path:
if not RUN_ID_RE.fullmatch(run_id):
raise RunPathError("run id is invalid")
root = self._output_root(manifest, create=create_root)
path = root / run_id
if path.parent != root or path.is_symlink():
raise RunPathError("run path is invalid")
return path
def create(self, manifest: Manifest, manifest_bytes: bytes) -> RunIdentity:
"""Create a run and atomically persist immutable source bytes first."""
loaded = validate_manifest_bytes(manifest_bytes, repo_root=self.repo_root)
if loaded.digest != manifest.digest:
raise AttemptStateError("manifest snapshot does not match manifest digest")
run_id = f"run-{_utc_timestamp(self._clock)}-{self._token_hex(6)}"
if not RUN_ID_RE.fullmatch(run_id):
raise AttemptStateError("generated run id is invalid")
path = self._run_path(manifest, run_id, create_root=True)
try:
path.mkdir(mode=0o700)
except FileExistsError as exc:
raise AttemptStateError("generated run id already exists") from exc
_write_new(path / "manifest.json", manifest_bytes)
_write_new(path / "run.json", _json_bytes({"run_id": run_id, "manifest_digest": manifest.digest}))
_write_new(path / "run.lock", b"")
_fsync_dir(path)
return RunIdentity(run_id, manifest.digest, str(path))
def open(self, manifest: Manifest, run_id: str, manifest_bytes: bytes | None = None) -> RunIdentity:
path = self._run_path(manifest, run_id, create_root=False)
_directory(path, "run")
try:
record = json.loads(_read_regular_bytes(path / "run.json", "run record").decode("utf-8"))
snapshot = _read_regular_bytes(path / "manifest.json", "manifest snapshot")
_read_regular_bytes(path / "run.lock", "run lock")
except (OSError, json.JSONDecodeError) as exc:
raise AttemptStateError("run state is unavailable") from exc
if record != {"run_id": run_id, "manifest_digest": manifest.digest}:
raise AttemptStateError("run identity does not match manifest")
if manifest_bytes is not None and snapshot != manifest_bytes:
raise AttemptStateError("manifest bytes do not match run snapshot")
if validate_manifest_bytes(snapshot, repo_root=self.repo_root).digest != manifest.digest:
raise AttemptStateError("run manifest digest is invalid")
return RunIdentity(run_id, manifest.digest, str(path))
@contextlib.contextmanager
def writer(self, run: RunIdentity) -> Iterator[None]:
root = Path(run.root)
_directory(root, "run")
lock_path = root / "run.lock"
fd = _open_regular(lock_path, "run lock", os.O_RDWR)
try:
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise RunBusyError("run-busy") from exc
yield
finally:
try:
fcntl.flock(fd, fcntl.LOCK_UN)
finally:
os.close(fd)
@staticmethod
def slots(manifest: Manifest) -> tuple[Slot, ...]:
return tuple(Slot(cell.id, repetition) for cell in manifest.matrix for repetition in range(1, manifest.repetitions + 1))
def _slot_path(self, run: RunIdentity, slot: Slot) -> Path:
if not re.fullmatch(r"[a-z0-9][a-z0-9_-]{0,63}", slot.cell_id) or slot.repetition < 1:
raise AttemptStateError("slot is invalid")
return Path(run.root) / "cells" / slot.cell_id / f"repetition-{slot.repetition:04d}"
def _ensure_slot_parent(self, run: RunIdentity, slot: Slot) -> Path:
current = Path(run.root)
for segment in ("cells", slot.cell_id, f"repetition-{slot.repetition:04d}"):
current = current / segment
if current.exists() or current.is_symlink():
_directory(current, "slot state")
if current.is_symlink():
raise AttemptStateError("slot state is invalid")
continue
try:
current.mkdir(mode=0o700)
except FileExistsError:
_directory(current, "slot state")
if current.is_symlink():
raise AttemptStateError("slot state is invalid")
return current
@staticmethod
def _expected_record(run: RunIdentity, identity: AttemptIdentity, state: str) -> dict[str, Any]:
return {
"run_id": run.run_id,
"manifest_digest": run.manifest_digest,
"cell_id": identity.cell_id,
"repetition": identity.repetition,
"attempt": identity.attempt,
"state": state,
}
def _attempt_record(self, root: Path, run: RunIdentity, identity: AttemptIdentity, *, absent_ok: bool = False) -> dict[str, Any] | None:
path = root / "attempt.json"
try:
record = json.loads(_read_regular_bytes(path, "attempt record").decode("utf-8"))
except AttemptStateError:
if absent_ok:
try:
os.lstat(path)
except FileNotFoundError:
return None
raise
except (OSError, json.JSONDecodeError) as exc:
raise AttemptStateError("attempt record is unavailable") from exc
if not isinstance(record, dict):
raise AttemptStateError("attempt record is invalid")
expected = self._expected_record(run, identity, str(record.get("state", "")))
if record.get("state") not in TERMINAL_STATES | {NONTERMINAL_STATE}:
raise AttemptStateError("attempt record is invalid")
for key, value in expected.items():
if record.get(key) != value:
raise AttemptStateError("attempt record identity is invalid")
allowed = set(expected) | {"locator", "spec_digest", "lifecycle"}
if set(record) - allowed:
raise AttemptStateError("attempt record schema is invalid")
if record["state"] == NONTERMINAL_STATE and "lifecycle" in record:
raise AttemptStateError("running attempt has terminal evidence")
if ("locator" in record) != ("spec_digest" in record):
raise AttemptStateError("attempt invocation identity is invalid")
if "locator" in record:
self._locator_from_record(root, record["locator"])
if not isinstance(record["spec_digest"], str) or not DIGEST_RE.fullmatch(record["spec_digest"]):
raise AttemptStateError("attempt invocation digest is invalid")
if "lifecycle" in record and (not isinstance(record["lifecycle"], dict) or set(record["lifecycle"]) != {"terminal_reason"} or not isinstance(record["lifecycle"]["terminal_reason"], str)):
raise AttemptStateError("attempt lifecycle is invalid")
return record
def attempts(self, run: RunIdentity, slot: Slot) -> tuple[Attempt, ...]:
parent = self._slot_path(run, slot)
if not parent.exists() and not parent.is_symlink():
return ()
_directory(parent, "slot state")
found: list[Attempt] = []
for child in sorted(parent.iterdir(), key=lambda item: item.name):
match = ATTEMPT_RE.fullmatch(child.name)
if not match or child.is_symlink() or not child.is_dir():
raise AttemptStateError("slot contains invalid state")
number = int(match.group(1))
identity = AttemptIdentity(run.run_id, slot.cell_id, slot.repetition, number)
record = self._attempt_record(child, run, identity, absent_ok=True)
state = NONTERMINAL_STATE if record is None else str(record["state"])
found.append(Attempt(identity, str(child), state))
return tuple(found)
def allocate(self, run: RunIdentity, slot: Slot) -> Attempt:
"""Create the exclusive, deliberately empty attempt root."""
existing = self.attempts(run, slot)
if any(item.state not in TERMINAL_STATES for item in existing):
raise AttemptStateError("slot already has a nonterminal attempt")
number = max((item.identity.attempt for item in existing), default=0) + 1
parent = self._ensure_slot_parent(run, slot)
root = parent / f"attempt-{number:06d}"
try:
root.mkdir(mode=0o700)
_fsync_dir(parent)
except FileExistsError as exc:
raise AttemptStateError("attempt allocation collision") from exc
return Attempt(AttemptIdentity(run.run_id, slot.cell_id, slot.repetition, number), str(root), NONTERMINAL_STATE)
def _bound_attempt(self, attempt: Attempt) -> tuple[RunIdentity, Path]:
root = Path(attempt.root)
try:
run_root = root.parents[3]
except IndexError as exc:
raise AttemptStateError("attempt path is invalid") from exc
_directory(run_root, "run")
try:
run_record = json.loads(_read_regular_bytes(run_root / "run.json", "run record").decode("utf-8"))
snapshot = _read_regular_bytes(run_root / "manifest.json", "manifest snapshot")
_read_regular_bytes(run_root / "run.lock", "run lock")
manifest = validate_manifest_bytes(snapshot, repo_root=self.repo_root)
except Exception as exc:
raise AttemptStateError("attempt run binding is invalid") from exc
if not isinstance(run_record, dict) or set(run_record) != {"run_id", "manifest_digest"}:
raise AttemptStateError("attempt run binding is invalid")
run = RunIdentity(str(run_record["run_id"]), str(run_record["manifest_digest"]), str(run_root))
if not RUN_ID_RE.fullmatch(run.run_id) or run.manifest_digest != manifest.digest:
raise AttemptStateError("attempt run binding is invalid")
if run_root.resolve() != self._run_path(manifest, run.run_id, create_root=False).resolve():
raise AttemptStateError("attempt run binding is invalid")
expected = self._slot_path(run, Slot(attempt.identity.cell_id, attempt.identity.repetition)) / f"attempt-{attempt.identity.attempt:06d}"
if root.resolve() != expected.resolve() or attempt.identity.run_id != run.run_id or root.is_symlink():
raise AttemptStateError("attempt identity is invalid")
_directory(root, "attempt root")
return run, root
def _initial_record(self, run: RunIdentity, attempt: Attempt, state: str, *, reason: str | None = None) -> dict[str, Any]:
record = self._expected_record(run, attempt.identity, state)
if reason is not None:
record["lifecycle"] = {"terminal_reason": reason}
return record
def publish_terminal(self, attempt: Attempt, state: str, *, result: dict[str, Any] | None = None) -> Attempt:
if state not in TERMINAL_STATES:
raise AttemptStateError("terminal state is invalid")
run, root = self._bound_attempt(attempt)
record = self._attempt_record(root, run, attempt.identity, absent_ok=True)
if record is None:
reason = str((result or {}).get("terminal_reason") or state)
_write_new(root / "attempt.json", _json_bytes(self._initial_record(run, attempt, state, reason=reason)))
return Attempt(attempt.identity, attempt.root, state)
if record["state"] in TERMINAL_STATES:
if record["state"] != state:
raise AttemptStateError("terminal attempt is immutable")
return Attempt(attempt.identity, attempt.root, state)
if record["state"] != NONTERMINAL_STATE:
raise AttemptStateError("attempt transition is invalid")
record["state"] = state
record["lifecycle"] = {"terminal_reason": str((result or {}).get("terminal_reason") or state)}
_replace(root / "attempt.json", _json_bytes(record))
return Attempt(attempt.identity, attempt.root, state)
def _locator_from_record(self, root: Path, raw: Any) -> SupervisorLocator:
fields = {"supervisor_pid", "start_identity", "socket_path", "challenge", "control_dir", "created_at"}
if not isinstance(raw, dict) or set(raw) != fields:
raise AttemptStateError("locator is invalid")
try:
locator = SupervisorLocator(**raw)
except TypeError as exc:
raise AttemptStateError("locator is invalid") from exc
if not isinstance(locator.supervisor_pid, int) or locator.supervisor_pid < 1 or any(not isinstance(value, str) or not value for value in (locator.start_identity, locator.socket_path, locator.challenge, locator.control_dir, locator.created_at)):
raise AttemptStateError("locator is invalid")
control = Path(locator.control_dir)
socket = Path(locator.socket_path)
if not _contained(control, root) or not _contained(socket, control) or socket.parent != control:
raise AttemptStateError("locator escapes attempt root")
return locator
def record_locator(self, attempt: Attempt, locator: SupervisorLocator, invocation_digest: str) -> None:
run, root = self._bound_attempt(attempt)
record = self._attempt_record(root, run, attempt.identity)
if record is None or record["state"] != NONTERMINAL_STATE or "locator" in record:
raise AttemptStateError("locator transition is invalid")
if not isinstance(invocation_digest, str) or not DIGEST_RE.fullmatch(invocation_digest):
raise AttemptStateError("invocation digest is invalid")
raw = {
"supervisor_pid": locator.supervisor_pid,
"start_identity": locator.start_identity,
"socket_path": locator.socket_path,
"challenge": locator.challenge,
"control_dir": locator.control_dir,
"created_at": locator.created_at,
}
self._locator_from_record(root, raw)
registered = self._read_json_file(Path(locator.control_dir), "locator.json")
if registered != raw:
raise AttemptStateError("registered locator is invalid")
record["locator"] = raw
record["spec_digest"] = invocation_digest
_replace(root / "attempt.json", _json_bytes(record))
@staticmethod
def _state_for_reason(reason: str) -> str:
if reason == "success":
return "success"
if reason == "timed_out":
return "timed_out"
if reason == "cancelled":
return "cancelled"
return "failed"
def _read_json_file(self, root: Path, name: str) -> dict[str, Any]:
path = root / name
try:
value = json.loads(_read_regular_bytes(path, name).decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise AttemptStateError(f"{name} is invalid") from exc
if not isinstance(value, dict):
raise AttemptStateError(f"{name} is invalid")
return value
@staticmethod
def _exact_fields(value: Any, fields: set[str], label: str) -> dict[str, Any]:
if not isinstance(value, dict) or set(value) != fields:
raise AttemptStateError(f"{label} schema is invalid")
return value
@staticmethod
def _optional_int(value: Any) -> bool:
return value is None or (isinstance(value, int) and not isinstance(value, bool))
def _validate_result_record(self, result: dict[str, Any], locator: SupervisorLocator, expected_digest: str) -> None:
fields = {
"record", "success", "terminal_reason", "exit_code", "signal", "submitted",
"finish_then_idle_then_quiet", "cleanup_complete", "process_group_alive",
"submission_mode", "completion_mode", "spec_digest", "locator", "started_at",
"ended_at", "duration_ns", "stdout", "stderr", "events",
}
self._exact_fields(result, fields, "lifecycle result")
required_bools = ("success", "submitted", "finish_then_idle_then_quiet", "cleanup_complete", "process_group_alive")
if result["record"] != "result" or any(not isinstance(result[key], bool) for key in required_bools):
raise AttemptStateError("lifecycle result is invalid")
if result["terminal_reason"] not in TERMINAL_REASONS or result["spec_digest"] != expected_digest:
raise AttemptStateError("lifecycle result identity is invalid")
if not self._optional_int(result["exit_code"]) or not self._optional_int(result["signal"]):
raise AttemptStateError("lifecycle result is invalid")
if result["submission_mode"] not in SUBMISSION_MODES or result["completion_mode"] not in COMPLETION_MODES:
raise AttemptStateError("lifecycle result is invalid")
if not all(isinstance(result[key], str) for key in ("started_at", "ended_at")) or not isinstance(result["duration_ns"], int) or isinstance(result["duration_ns"], bool) or result["duration_ns"] < 0:
raise AttemptStateError("lifecycle result is invalid")
expected_public = self._public_locator(locator)
if result["locator"] != expected_public:
raise AttemptStateError("lifecycle terminal locator is invalid")
for name, stream in (("stdout", "stdout"), ("stderr", "stderr")):
capture = self._exact_fields(result[name], {"stream", "text", "line_count", "byte_count", "truncated"}, f"{name} capture")
if capture["stream"] != stream or not isinstance(capture["text"], str) or not isinstance(capture["line_count"], int) or not isinstance(capture["byte_count"], int) or isinstance(capture["line_count"], bool) or isinstance(capture["byte_count"], bool) or capture["line_count"] < 0 or capture["byte_count"] < 0 or not isinstance(capture["truncated"], bool):
raise AttemptStateError("lifecycle capture is invalid")
if not isinstance(result["events"], list):
raise AttemptStateError("lifecycle events are invalid")
for event in result["events"]:
checked = self._exact_fields(event, {"record", "kind", "source", "stream", "monotonic_ns", "source_monotonic_ns", "observed_at", "detail"}, "lifecycle event")
if checked["record"] != "event" or not all(isinstance(checked[key], str) for key in ("kind", "source", "stream", "observed_at", "detail")) or not all(isinstance(checked[key], int) and not isinstance(checked[key], bool) and checked[key] >= 0 for key in ("monotonic_ns", "source_monotonic_ns")):
raise AttemptStateError("lifecycle event is invalid")
if not result["cleanup_complete"] or result["process_group_alive"] or result["success"] != (result["terminal_reason"] == "success"):
raise AttemptStateError("lifecycle terminal outcome is invalid")
if result["success"] and not result["finish_then_idle_then_quiet"]:
raise AttemptStateError("lifecycle terminal outcome is invalid")
@staticmethod
def _instant(value: Any, label: str) -> _datetime.datetime:
"""Parse one produced ISO-8601 instant into a comparable UTC value."""
try:
parsed = _datetime.datetime.fromisoformat(str(value))
except (TypeError, ValueError) as exc:
raise AttemptStateError(f"{label} timestamp is invalid") from exc
return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=_datetime.timezone.utc)
@staticmethod
def _validate_terminal_events(events: list[Any]) -> dict[str, int]:
"""Return the unique ordinal of every success-evidence event kind."""
positions: dict[str, int] = {}
for index, event in enumerate(events):
kind = event["kind"]
if kind not in SUCCESS_EVIDENCE_KINDS:
continue
if kind in positions:
raise AttemptStateError("lifecycle events are invalid")
positions[kind] = index
return positions
def _validate_terminal_coherence(self, result: dict[str, Any], receipt: dict[str, Any], positions: dict[str, int]) -> None:
"""Bind result, ordered events and cleanup receipt to one terminal projection."""
submitted, finish, idle, quiet = (positions.get(kind) for kind in SUCCESS_EVIDENCE_KINDS)
ordered = finish is not None and idle is not None and quiet is not None and finish < idle < quiet
if ordered != result["finish_then_idle_then_quiet"]:
raise AttemptStateError("lifecycle ordered evidence is invalid")
if result["exit_code"] != receipt["exit_code"] or result["signal"] != receipt["signal"]:
raise AttemptStateError("lifecycle terminal outcome is invalid")
if result["submitted"] and not receipt["caller_launched"]:
raise AttemptStateError("lifecycle terminal outcome is invalid")
if result["success"] and (not result["submitted"] or submitted is None or finish is None or submitted > finish):
raise AttemptStateError("lifecycle success evidence is invalid")
started = self._instant(result["started_at"], "lifecycle result")
ended = self._instant(result["ended_at"], "lifecycle result")
completed = self._instant(receipt["completed_at"], "cleanup receipt")
if completed < started or ended < completed:
raise AttemptStateError("lifecycle terminal chronology is invalid")
def _validate_receipt_record(self, receipt: dict[str, Any], locator: SupervisorLocator) -> dict[str, Any]:
"""Validate one cleanup receipt against the registered supervisor identity."""
self._exact_fields(receipt, RECEIPT_FIELDS, "cleanup receipt")
if receipt["receipt_version"] != RECEIPT_VERSION or receipt["supervisor_pid"] != locator.supervisor_pid or receipt["challenge_digest"] != self._public_locator(locator)["challenge_digest"] or receipt["reason"] not in TERMINAL_REASONS or not self._optional_int(receipt["exit_code"]) or not self._optional_int(receipt["signal"]) or not isinstance(receipt["caller_launched"], bool) or receipt["cleanup_complete"] is not True or receipt["process_group_alive"] is not False or not isinstance(receipt["completed_at"], str):
raise AttemptStateError("cleanup receipt is invalid")
return receipt
@staticmethod
def _public_locator(locator: SupervisorLocator) -> dict[str, Any]:
return {
"supervisor_pid": locator.supervisor_pid,
"start_identity": locator.start_identity,
"socket_path": locator.socket_path,
"control_dir": locator.control_dir,
"challenge_digest": hashlib.sha256(locator.challenge.encode("utf-8")).hexdigest(),
"created_at": locator.created_at,
}
def _validate_journal_record(self, root: Path, result: dict[str, Any], expected_digest: str) -> None:
"""Validate the append-only journal against the published result record."""
journal = root / "lifecycle-journal.jsonl"
try:
lines = [json.loads(line) for line in _read_regular_bytes(journal, "lifecycle journal").decode("utf-8").splitlines()]
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise AttemptStateError("lifecycle journal is invalid") from exc
if len(lines) < 2:
raise AttemptStateError("lifecycle journal is invalid")
header, terminal = lines[0], lines[-1]
self._exact_fields(header, {"record", "journal_version", "spec_digest", "submission_mode", "completion_mode", "started_at"}, "lifecycle journal header")
self._exact_fields(terminal, {"record", "terminal_reason", "success", "cleanup_complete", "process_group_alive", "ended_at"}, "lifecycle journal terminal")
if header["record"] != "header" or header["journal_version"] != 1 or header["spec_digest"] != expected_digest or header["submission_mode"] != result["submission_mode"] or header["completion_mode"] != result["completion_mode"] or header["started_at"] != result["started_at"] or not isinstance(header["started_at"], str) or terminal["record"] != "terminal" or terminal["terminal_reason"] != result["terminal_reason"] or terminal["success"] != result["success"] or terminal["cleanup_complete"] is not True or terminal["process_group_alive"] is not False or terminal["ended_at"] != result["ended_at"] or not isinstance(terminal["ended_at"], str):
raise AttemptStateError("lifecycle journal is invalid")
for event in lines[1:-1]:
self._exact_fields(event, {"record", "kind", "source", "stream", "monotonic_ns", "source_monotonic_ns", "observed_at", "detail"}, "lifecycle journal event")
if lines[1:-1] != result["events"]:
raise AttemptStateError("lifecycle journal events are invalid")
def _read_bound_lifecycle_terminal(self, root: Path, locator: SupervisorLocator, expected_digest: str) -> dict[str, Any] | None:
result_path = root / "lifecycle-result.json"
if not result_path.exists() and not result_path.is_symlink():
return None
result = self._read_json_file(root, "lifecycle-result.json")
self._validate_result_record(result, locator, expected_digest)
self._validate_journal_record(root, result, expected_digest)
receipt = Path(locator.control_dir) / "cleanup-receipt.json"
if receipt.parent != Path(locator.control_dir) or not _contained(receipt, root):
raise AttemptStateError("cleanup receipt escapes attempt root")
receipt_data = self._validate_receipt_record(self._read_json_file(receipt.parent, receipt.name), locator)
if receipt_data["reason"] != result["terminal_reason"]:
raise AttemptStateError("cleanup receipt is invalid")
events = self._validate_terminal_events(result["events"])
self._validate_terminal_coherence(result, receipt_data, events)
return result
def validate_invocation_terminal(self, attempt: Attempt, invocation: InvocationResult) -> dict[str, Any]:
"""Validate direct lifecycle output against the identity committed before launch."""
if not isinstance(invocation, InvocationResult):
raise AttemptStateError("invocation result is invalid")
run, root = self._bound_attempt(attempt)
record = self._attempt_record(root, run, attempt.identity)
if record is None or record["state"] != NONTERMINAL_STATE:
raise AttemptStateError("invocation transition is invalid")
raw_locator = record.get("locator")
expected_digest = record.get("spec_digest")
if raw_locator is None or not isinstance(expected_digest, str):
raise AttemptStateError("invocation identity was not committed")
locator = self._locator_from_record(root, raw_locator)
expected_result_path = root / "lifecycle-result.json"
expected_journal_path = root / "lifecycle-journal.jsonl"
if invocation.locator != locator or invocation.spec_digest != expected_digest or Path(invocation.result_path).resolve(strict=False) != expected_result_path.resolve() or Path(invocation.journal_path).resolve(strict=False) != expected_journal_path.resolve():
raise AttemptStateError("invocation result identity is invalid")
terminal = self._read_bound_lifecycle_terminal(root, locator, expected_digest)
if terminal is None:
raise AttemptStateError("lifecycle terminal is unavailable")
fields = ("success", "terminal_reason", "exit_code", "signal", "submitted", "finish_then_idle_then_quiet", "cleanup_complete", "process_group_alive", "spec_digest", "started_at", "ended_at", "duration_ns")
if any(getattr(invocation, field) != terminal[field] for field in fields):
raise AttemptStateError("invocation result does not match durable terminal")
return terminal
def reconcile(self, attempt: Attempt) -> Attempt:
"""Commit only authenticated terminal evidence before recovery cleanup."""
run, root = self._bound_attempt(attempt)
record = self._attempt_record(root, run, attempt.identity, absent_ok=True)
if record is None:
return self.publish_terminal(attempt, "interrupted", result={"terminal_reason": "interrupted"})
if record["state"] in TERMINAL_STATES:
return Attempt(attempt.identity, attempt.root, str(record["state"]))
raw_locator = record.get("locator")
if raw_locator is None:
return self.publish_terminal(attempt, "interrupted", result={"terminal_reason": "interrupted"})
locator = self._locator_from_record(root, raw_locator)
expected_digest = record.get("spec_digest")
if not isinstance(expected_digest, str) or not DIGEST_RE.fullmatch(expected_digest):
raise AttemptStateError("recovery identity is invalid")
terminal = self._read_bound_lifecycle_terminal(root, locator, expected_digest)
if terminal is not None:
return self.publish_terminal(attempt, self._state_for_reason(terminal["terminal_reason"]), result=terminal)
try:
outcome = recover_invocation(locator, stop=True)
except LifecycleRecoveryError as exc:
raise AttemptStateError("recovery is unverified") from exc
receipt = Path(outcome.receipt_path)
if not outcome.cleanup_complete or outcome.process_group_alive or receipt.parent != Path(locator.control_dir) or not _contained(receipt, root):
raise AttemptStateError("recovery cleanup is unverified")
try:
self._validate_receipt_record(self._read_json_file(receipt.parent, receipt.name), locator)
except AttemptStateError as exc:
raise AttemptStateError("recovery cleanup is unverified") from exc
return self.publish_terminal(attempt, "interrupted", result={"terminal_reason": "interrupted"})
def execute_attempt(
self,
attempt: Attempt,
*,
prepare: Callable[[Attempt], Any],
invoke: Callable[[Attempt, Callable[[SupervisorLocator, str], None]], InvocationResult],
) -> Attempt:
"""Prepare once, publish running identity, then invoke lifecycle once."""
run, root = self._bound_attempt(attempt)
if self._attempt_record(root, run, attempt.identity, absent_ok=True) is not None:
raise AttemptStateError("attempt has already been prepared")
try:
prepare(attempt)
except Exception:
_write_new(root / "attempt.json", _json_bytes(self._initial_record(run, attempt, "failed", reason="preparation_failed")))
raise
_write_new(root / "attempt.json", _json_bytes(self._initial_record(run, attempt, NONTERMINAL_STATE)))
result = invoke(attempt, lambda locator, digest: self.record_locator(attempt, locator, digest))
terminal = self.validate_invocation_terminal(attempt, result)
return self.publish_terminal(attempt, self._state_for_reason(terminal["terminal_reason"]), result=terminal)
def status(self, run: RunIdentity, manifest: Manifest) -> dict[str, Any]:
"""Read-only deterministic status; it neither creates nor reconciles."""
bound_run = self.open(manifest, run.run_id)
if bound_run != run:
raise AttemptStateError("run identity is invalid")
states = {name: 0 for name in sorted(TERMINAL_STATES | {NONTERMINAL_STATE})}
for slot in self.slots(manifest):
for attempt in self.attempts(bound_run, slot):
states[attempt.state] += 1
return {"run_id": bound_run.run_id, "manifest_digest": bound_run.manifest_digest, "attempts": states}
def run_slots(
store: RunStore,
run: RunIdentity,
manifest: Manifest,
*,
adapters: dict[str, Callable[[Attempt, Callable[[SupervisorLocator, str], None]], InvocationResult]],
prepare: Callable[[Attempt], Any],
retry_failed: bool = False,
) -> tuple[Attempt, ...]:
"""Execute pending slots using injected adapters; never resolves a real CLI."""
missing = {cell.caller for cell in manifest.matrix if cell.caller not in adapters}
if missing:
raise CapabilityUnavailable("capability-unavailable: caller-adapter")
bound_run = store.open(manifest, run.run_id)
if bound_run != run:
raise AttemptStateError("run identity is invalid")
completed: list[Attempt] = []
with store.writer(bound_run):
for slot in store.slots(manifest):
existing = store.attempts(bound_run, slot)
if existing and existing[-1].state == "success":
continue
if existing and existing[-1].state in {"failed", "timed_out", "cancelled"} and not retry_failed:
continue
if existing and existing[-1].state not in TERMINAL_STATES:
store.reconcile(existing[-1])
existing = store.attempts(bound_run, slot)
if existing[-1].state == "success" or (existing[-1].state in {"failed", "timed_out", "cancelled"} and not retry_failed):
continue
attempt = store.allocate(bound_run, slot)
adapter = next(cell.caller for cell in manifest.matrix if cell.id == slot.cell_id)
completed.append(store.execute_attempt(attempt, prepare=prepare, invoke=adapters[adapter]))
return tuple(completed)