atlas-iac/services/hermes/scripts/cli_lane_files.py

175 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Durable path and filesystem primitives for Hermes CLI lane journals."""
from __future__ import annotations
import json
import os
import re
import stat
import uuid
from pathlib import Path
from typing import Any
from cli_lane_config import STATE_ROOT, TerminalIdentity, canonical_run_id
def atomic_json(path: Path, value: dict[str, Any], mode: int = 0o600) -> None:
"""Durably replace a small state document without following temp symlinks."""
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(path.parent, 0o700, follow_symlinks=False)
temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
flags |= getattr(os, "O_NOFOLLOW", 0)
descriptor = None
try:
descriptor = os.open(temporary, flags, mode)
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
descriptor = None
json.dump(value, stream, indent=2, sort_keys=True)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.chmod(temporary, mode, follow_symlinks=False)
os.replace(temporary, path)
_fsync_directory(path.parent)
finally:
if descriptor is not None:
os.close(descriptor)
try:
temporary.unlink()
except FileNotFoundError:
pass
def _fsync_directory(directory: Path) -> None:
"""Persist directory-entry changes after an atomic rename or quarantine."""
flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0)
flags |= getattr(os, "O_NOFOLLOW", 0)
descriptor = os.open(directory, flags)
try:
os.fsync(descriptor)
finally:
os.close(descriptor)
def load_json(path: Path) -> dict[str, Any]:
try:
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
descriptor = os.open(path, flags)
with os.fdopen(descriptor, "r", encoding="utf-8") as stream:
value = json.load(stream)
except (OSError, UnicodeError, json.JSONDecodeError):
return {}
return value if isinstance(value, dict) else {}
def state_path(board: str, task_id: str) -> Path:
safe_board = re.sub(r"[^a-zA-Z0-9_.-]+", "-", board)
safe_task = re.sub(r"[^a-zA-Z0-9_.-]+", "-", task_id)
return STATE_ROOT / safe_board / f"{safe_task}.json"
def _result_path(state_file: Path, run_id: Any, sequence: int) -> Path:
"""Return a unique provider-result path; completed turns are never reused."""
safe_run = re.sub(r"[^a-zA-Z0-9_.-]+", "-", str(run_id or "unknown"))
return state_file.with_name(
f"{state_file.stem}.run-{safe_run}.provider-{sequence}.result.json"
)
def _candidate_path(state_file: Path, run_id: Any, sequence: int) -> Path:
"""Return the durable path for one exact structured worker response."""
safe_run = re.sub(r"[^a-zA-Z0-9_.-]+", "-", str(run_id or "unknown"))
return state_file.with_name(
f"{state_file.stem}.run-{safe_run}.candidate-{sequence}.json"
)
def _terminal_path(
state_file: Path,
run_id: Any,
state: str = "pending",
) -> Path:
"""Return the identity-bound replay path for an accepted terminal response."""
if state not in {"pending", "committed"}:
raise ValueError(f"invalid terminal journal state: {state}")
canonical = canonical_run_id(run_id)
if type(run_id) is not int or canonical is None:
raise ValueError("terminal run ID must be a positive SQLite int64")
return state_file.with_name(
f"{state_file.stem}.run-{canonical}.terminal.{state}.json"
)
_TERMINAL_NAME = re.compile(
r"^(?P<task>[a-zA-Z0-9_.-]+)\.run-(?P<run>[0-9]+)\."
r"terminal\.(?P<state>pending|committed)\.json$"
)
_TERMINAL_EVIDENCE_NAME = re.compile(
r"^(?P<task>[a-zA-Z0-9_.-]+)\.run-(?P<run>[0-9]+)\."
r"terminal\.(?P<state>prepared|conflict)-(?P<digest>[a-f0-9]{32})\.json$"
)
_RETIRE_NAME = re.compile(
r"^\.retire\.(?P<path_digest>[a-f0-9]{16})\."
r"(?P<source_digest>[a-f0-9]{16})\.(?P<sequence>[0-9]+)$"
)
_LEGACY_RETIRE_NAME = re.compile(r"^\.retire\.[a-f0-9]{32}\.[0-9]+$")
_SAFE_BOARD = re.compile(r"^[a-zA-Z0-9_.-]+$")
def _terminal_identity(path: Path) -> TerminalIdentity | None:
"""Parse replay authority lexically before opening a journal or board."""
try:
relative = path.relative_to(STATE_ROOT)
except ValueError:
return None
if len(relative.parts) != 2:
return None
board, filename = relative.parts
match = _TERMINAL_NAME.fullmatch(filename)
run_id = canonical_run_id(match.group("run")) if match else None
try:
board_stat = path.parent.stat(follow_symlinks=False)
except OSError:
return None
if (
not match
or run_id is None
or not stat.S_ISDIR(board_stat.st_mode)
or not _SAFE_BOARD.fullmatch(board)
or board in {".", ".."}
or match.group("task") in {".", ".."}
):
return None
return TerminalIdentity(
board=board,
task_id=match.group("task"),
run_id=run_id,
state=match.group("state"),
)
def _terminal_evidence_identity(path: Path) -> TerminalIdentity | None:
"""Parse immutable prepared/conflict evidence authority from its path."""
try:
relative = path.relative_to(STATE_ROOT)
except ValueError:
return None
if len(relative.parts) != 2:
return None
board, filename = relative.parts
match = _TERMINAL_EVIDENCE_NAME.fullmatch(filename)
run_id = canonical_run_id(match.group("run")) if match else None
try:
board_stat = path.parent.stat(follow_symlinks=False)
except OSError:
return None
if (
not match
or run_id is None
or not stat.S_ISDIR(board_stat.st_mode)
or not _SAFE_BOARD.fullmatch(board)
or board in {".", ".."}
or match.group("task") in {".", ".."}
):
return None
return TerminalIdentity(
board=board,
task_id=match.group("task"),
run_id=run_id,
state=match.group("state"),
)