#!/usr/bin/env python3 """Bounded inode-pinned reads and validation for Hermes terminal records.""" from __future__ import annotations import json import os import stat from pathlib import Path from typing import Any import cli_lane_goal from cli_lane_config import ( MAX_TERMINAL_RECORD_BYTES, QUARANTINE_HASH_BYTES, RESULT_SCHEMA, Route, TerminalIdentity, TerminalRecoverySnapshot, TerminalSnapshot, canonical_run_id, utc_now, ) from cli_lane_files import ( _candidate_path, _terminal_identity, _terminal_path, atomic_json, ) def _read_bounded(descriptor: int, limit: int) -> bytes: """Read at most ``limit`` bytes from a regular file descriptor.""" chunks: list[bytes] = [] remaining = max(0, limit) while remaining: chunk = os.read(descriptor, min(64 * 1024, remaining)) if not chunk: break chunks.append(chunk) remaining -= len(chunk) return b"".join(chunks) def _open_small_json_snapshot(path: Path) -> TerminalSnapshot | None: """Open and pin one bounded, singly-linked JSON document.""" directory_flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) directory_flags |= getattr(os, "O_NOFOLLOW", 0) file_flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) directory = None descriptor = None try: directory = os.open(path.parent, directory_flags) descriptor = os.open(path.name, file_flags, dir_fd=directory) opened = os.fstat(descriptor) if ( not stat.S_ISREG(opened.st_mode) or opened.st_nlink != 1 or opened.st_size > MAX_TERMINAL_RECORD_BYTES ): return None payload = _read_bounded(descriptor, MAX_TERMINAL_RECORD_BYTES + 1) if len(payload) > MAX_TERMINAL_RECORD_BYTES: return None current = os.stat( path.name, dir_fd=directory, follow_symlinks=False, ) if ( current.st_dev != opened.st_dev or current.st_ino != opened.st_ino or current.st_size != opened.st_size or len(payload) != opened.st_size ): return None value = json.loads(payload.decode("utf-8")) if not isinstance(value, dict): return None snapshot = TerminalSnapshot(value, opened, descriptor, directory) descriptor = None directory = None return snapshot except (OSError, UnicodeError, json.JSONDecodeError): return None finally: if descriptor is not None: os.close(descriptor) if directory is not None: os.close(directory) def _open_terminal_recovery_snapshot(path: Path) -> TerminalRecoverySnapshot | None: """Pin the entry recovery inspected, even when its payload is malformed.""" directory_flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) directory_flags |= getattr(os, "O_NOFOLLOW", 0) file_flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) directory = None descriptor = None try: directory = os.open(path.parent, directory_flags) observed = os.stat( path.name, dir_fd=directory, follow_symlinks=False, ) if not stat.S_ISREG(observed.st_mode): snapshot = TerminalRecoverySnapshot( None, observed, None, directory, b"", "non-regular", ) directory = None return snapshot try: descriptor = os.open(path.name, file_flags, dir_fd=directory) except OSError: snapshot = TerminalRecoverySnapshot( None, observed, None, directory, b"", "open-failed", ) directory = None return snapshot opened = os.fstat(descriptor) if opened.st_dev != observed.st_dev or opened.st_ino != observed.st_ino: os.close(descriptor) descriptor = None snapshot = TerminalRecoverySnapshot( None, observed, None, directory, b"", "identity-changed-during-open", ) directory = None return snapshot observed = opened if observed.st_nlink != 1: snapshot = TerminalRecoverySnapshot( None, observed, descriptor, directory, b"", "hardlinked", ) descriptor = None directory = None return snapshot if observed.st_size > MAX_TERMINAL_RECORD_BYTES: prefix = _read_bounded(descriptor, QUARANTINE_HASH_BYTES) snapshot = TerminalRecoverySnapshot( None, observed, descriptor, directory, prefix, "oversized", ) descriptor = None directory = None return snapshot payload = _read_bounded(descriptor, MAX_TERMINAL_RECORD_BYTES + 1) after_read = os.fstat(descriptor) unchanged = ( after_read.st_dev == observed.st_dev and after_read.st_ino == observed.st_ino and after_read.st_size == observed.st_size and after_read.st_mtime_ns == observed.st_mtime_ns and after_read.st_ctime_ns == observed.st_ctime_ns and len(payload) == observed.st_size ) document = None invalid_reason = "unstable-payload" if unchanged: try: parsed = json.loads(payload.decode("utf-8")) if isinstance(parsed, dict): document = parsed invalid_reason = None else: invalid_reason = "non-object-payload" except (UnicodeError, json.JSONDecodeError): invalid_reason = "malformed-payload" snapshot = TerminalRecoverySnapshot( document, after_read, descriptor, directory, payload[:QUARANTINE_HASH_BYTES], invalid_reason, ) descriptor = None directory = None return snapshot except OSError: return None finally: if descriptor is not None: os.close(descriptor) if directory is not None: os.close(directory) def _open_terminal_snapshot( path: Path, identity: TerminalIdentity, ) -> TerminalSnapshot | None: """Open a terminal journal only after its lexical identity is verified.""" if _terminal_identity(path) != identity: return None return _open_small_json_snapshot(path) def _load_terminal_json(path: Path, identity: TerminalIdentity) -> dict[str, Any]: """Read one small, singly-linked journal through its validated directory.""" snapshot = _open_terminal_snapshot(path, identity) if snapshot is None: return {} try: return snapshot.document finally: snapshot.close() def _load_small_json(path: Path) -> dict[str, Any]: """Read one bounded immutable evidence document without following links.""" snapshot = _open_small_json_snapshot(path) if snapshot is None: return {} try: return snapshot.document finally: snapshot.close() def _persist_candidate( state: dict[str, Any], state_file: Path, structured: dict[str, Any], *, route: Route, returncode: int, goal_turn: int, ) -> Path: """Persist every structured response before any judge can supersede it.""" sequence = max(0, int(state.get("candidate_sequence", 0) or 0)) + 1 path = _candidate_path(state_file, state.get("run_id"), sequence) while path.exists(): sequence += 1 path = _candidate_path(state_file, state.get("run_id"), sequence) atomic_json( path, { "board": state.get("board"), "task_id": state.get("task_id"), "expected_run_id": state.get("run_id"), "goal_turn": goal_turn, "provider": route.provider, "model": route.model, "capability": route.capability, "effort": route.effort, "returncode": returncode, "structured": structured, "recorded_at": utc_now(), }, ) state["candidate_sequence"] = sequence state["last_candidate_file"] = str(path) atomic_json(state_file, state) return path def _write_terminal_record( state_file: Path, *, board: str, task_id: str, run_id: Any, structured: dict[str, Any], summary: str, metadata: dict[str, Any], ) -> tuple[Path, dict[str, Any]]: """Journal an accepted result before attempting the Kanban transaction.""" if type(run_id) is not int or canonical_run_id(run_id) is None: raise ValueError("terminal run ID must be a positive SQLite int64") path = _terminal_path(state_file, run_id) record = { "board": board, "task_id": task_id, "expected_run_id": run_id, "result": json.dumps(structured, sort_keys=True), "summary": summary, "metadata": metadata, "kanban_state": "pending", "recorded_at": utc_now(), } atomic_json(path, record) return path, record def _terminal_record_valid( record: dict[str, Any], identity: TerminalIdentity | None = None, ) -> bool: """Reject malformed or non-terminal replay journals without side effects.""" if not isinstance(record, dict): return False required_strings = ("board", "task_id", "result", "summary") if not all( isinstance(record.get(key), str) and record[key] for key in required_strings ): return False expected_run_id = record.get("expected_run_id") if ( type(expected_run_id) is not int or canonical_run_id(expected_run_id) is None ): return False if not isinstance(record.get("metadata"), dict): return False try: structured = json.loads(record["result"]) except (TypeError, json.JSONDecodeError): return False if not isinstance(structured, dict): return False required = set(RESULT_SCHEMA["required"]) if set(structured) != required: return False if ( type(structured["status"]) is not str or structured["status"] not in cli_lane_goal.RESULT_STATUSES or type(structured["summary"]) is not str or not structured["summary"].strip() ): return False for key in ("changed_files", "tests_run", "artifacts", "findings", "blockers"): value = structured[key] if type(value) is not list or any(type(item) is not str for item in value): return False valid = ( structured["status"] == "completed" and structured["blockers"] == [] and cli_lane_goal.unfinished_result_reason(structured) is None and record["summary"] == structured["summary"] and record.get("kanban_state") in {"pending", "prepared", "committed"} ) if not valid or identity is None: return valid return ( record.get("board") == identity.board and record.get("task_id") == identity.task_id and record.get("expected_run_id") == identity.run_id and record.get("kanban_state") == identity.state )