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

266 lines
12 KiB
Python

#!/usr/bin/env python3
"""Board-local integrity records for Hermes PR continuation cards."""
from __future__ import annotations
import hashlib
import os
import sqlite3
from pathlib import Path
from typing import Any
from supervisor_lineage import Lineage
KANBAN_ROOT = Path("/opt/data/kanban/boards")
class SupervisorStateError(ValueError):
"""A present integrity record cannot be decoded or read safely."""
def _path(board: str, path: Path | None) -> Path:
"""Keep integrity rows beside the board's real Kanban transaction store."""
if path is not None:
return path
if not board or "/" in board or "\\" in board:
raise ValueError("board name is invalid")
return KANBAN_ROOT / board / "kanban.db"
def _connect(board: str, path: Path | None = None) -> sqlite3.Connection:
path = _path(board, path)
if not path.exists() and path is None:
raise ValueError("Kanban board database does not exist")
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
connection = sqlite3.connect(path)
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA foreign_keys=ON")
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS supervisor_roots (
board TEXT NOT NULL, root_task_id TEXT NOT NULL, project TEXT NOT NULL,
branch TEXT NOT NULL, pull_request TEXT NOT NULL, base_branch TEXT NOT NULL,
live_pr_head TEXT NOT NULL, ready_for_human_merge INTEGER NOT NULL DEFAULT 0,
ready_commit TEXT NOT NULL DEFAULT '', PRIMARY KEY (board, root_task_id)
);
CREATE TABLE IF NOT EXISTS supervisor_children (
board TEXT NOT NULL, child_task_id TEXT NOT NULL, root_task_id TEXT NOT NULL,
parent_task_id TEXT NOT NULL, kind TEXT NOT NULL, head_commit TEXT NOT NULL,
objective_digest TEXT NOT NULL, cycle INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (board, child_task_id),
UNIQUE (board, root_task_id, head_commit, objective_digest)
);
"""
)
columns = {row[1] for row in connection.execute("PRAGMA table_info(supervisor_roots)")}
if "ready_for_human_merge" not in columns:
connection.execute(
"ALTER TABLE supervisor_roots ADD COLUMN ready_for_human_merge INTEGER NOT NULL DEFAULT 0"
)
if "ready_commit" not in columns:
connection.execute("ALTER TABLE supervisor_roots ADD COLUMN ready_commit TEXT NOT NULL DEFAULT ''")
child_columns = {row[1] for row in connection.execute("PRAGMA table_info(supervisor_children)")}
if "cycle" not in child_columns:
connection.execute("ALTER TABLE supervisor_children ADD COLUMN cycle INTEGER NOT NULL DEFAULT 1")
try:
if path.name == "state.db":
os.chmod(path, 0o600)
except OSError:
pass
return connection
def _lineage(row: tuple[Any, ...] | None) -> Lineage | None:
if row is None:
return None
try:
values = tuple(str(value) for value in row[:5])
except (TypeError, ValueError):
return None
if not all(values):
return None
return Lineage(*values)
def get_root(board: str, root_task_id: str, *, path: Path | None = None) -> Lineage | None:
"""Return one coordinator-issued root lineage, never task-body claims."""
try:
with _connect(board, path) as connection:
row = connection.execute(
"SELECT root_task_id, branch, pull_request, project, base_branch "
"FROM supervisor_roots WHERE board=? AND root_task_id=?", (board, root_task_id)
).fetchone()
except (OSError, sqlite3.Error) as error:
raise SupervisorStateError("supervisor root state is unreadable") from error
if row is None:
return None
lineage = _lineage(row)
if lineage is None:
raise SupervisorStateError("supervisor root state is malformed")
return lineage
def get_live_head(board: str, root_task_id: str, *, path: Path | None = None) -> str:
"""Return the broker-confirmed head associated with a trusted root."""
try:
with _connect(board, path) as connection:
row = connection.execute(
"SELECT live_pr_head FROM supervisor_roots WHERE board=? AND root_task_id=?",
(board, root_task_id),
).fetchone()
except (OSError, sqlite3.Error) as error:
raise SupervisorStateError("supervisor root state is unreadable") from error
if row is None:
return ""
if not isinstance(row[0], str) or not row[0]:
raise SupervisorStateError("supervisor root state is malformed")
return row[0]
def get_child(board: str, child_task_id: str, *, path: Path | None = None) -> dict[str, Any] | None:
"""Return the only durable authority record for a continuation child."""
try:
with _connect(board, path) as connection:
row = connection.execute(
"SELECT root_task_id, parent_task_id, kind, head_commit, objective_digest, cycle "
"FROM supervisor_children WHERE board=? AND child_task_id=?", (board, child_task_id)
).fetchone()
except (OSError, sqlite3.Error) as error:
raise SupervisorStateError("supervisor child state is unreadable") from error
if row is None:
return None
root, parent, kind, head, digest, cycle = (str(value) for value in row)
lineage = get_root(board, root, path=path)
if lineage is None or kind not in {"repair", "review"} or not all((parent, head, digest)):
raise SupervisorStateError("supervisor child state is malformed")
try:
parsed_cycle = int(cycle)
except (TypeError, ValueError) as error:
raise SupervisorStateError("supervisor child state is malformed") from error
if parsed_cycle < 1:
raise SupervisorStateError("supervisor child state is malformed")
return {"lineage": lineage, "root_task_id": root, "parent_task_id": parent,
"kind": kind, "head_commit": head, "objective_digest": digest,
"cycle": parsed_cycle}
def record_submission(
board: str, task_id: str, lineage: Lineage, live_pr_head: str, *, path: Path | None = None
) -> None:
"""Record a coordinator-verified submission against its immutable root.
``task_id`` identifies the completing root or child. The coordinator has
already authenticated that task's signed assignment before calling this
function; the record deliberately remains keyed by ``root_task_id``.
"""
if not board or not task_id or not live_pr_head:
raise ValueError("submission does not name a task and live head")
with _connect(board, path) as connection:
existing = connection.execute(
"SELECT project,branch,pull_request,base_branch,live_pr_head FROM supervisor_roots "
"WHERE board=? AND root_task_id=?", (board, lineage.root_task_id)
).fetchone()
identity = (lineage.project, lineage.branch, lineage.pull_request, lineage.base_branch)
if existing is None:
connection.execute(
"INSERT INTO supervisor_roots(board,root_task_id,project,branch,pull_request,base_branch,live_pr_head) "
"VALUES(?,?,?,?,?,?,?)",
(board, lineage.root_task_id, *identity, live_pr_head),
)
elif tuple(existing[:4]) != identity:
raise ValueError("submission attempts to rewrite immutable root lineage")
elif existing[4] != live_pr_head:
connection.execute(
"UPDATE supervisor_roots SET live_pr_head=?, ready_for_human_merge=0, ready_commit='' "
"WHERE board=? AND root_task_id=?",
(live_pr_head, board, lineage.root_task_id),
)
def record_live_head(
board: str, root_task_id: str, live_pr_head: str, *, path: Path | None = None
) -> None:
"""Refresh a root's head only after the continuation CLI verified its PR."""
if not board or not root_task_id or not live_pr_head:
raise ValueError("root and live head are required")
with _connect(board, path) as connection:
changed = connection.execute(
"UPDATE supervisor_roots SET live_pr_head=?, ready_for_human_merge=0, ready_commit='' "
"WHERE board=? AND root_task_id=?", (live_pr_head, board, root_task_id)
).rowcount
if changed != 1:
raise ValueError("unknown continuation root")
def set_ready(
board: str, root_task_id: str, head_commit: str, *, path: Path | None = None
) -> None:
"""Publish human-ready state only for the verified current root head."""
with _connect(board, path) as connection:
changed = connection.execute(
"UPDATE supervisor_roots SET ready_for_human_merge=1, ready_commit=? "
"WHERE board=? AND root_task_id=? AND live_pr_head=?",
(head_commit, board, root_task_id, head_commit),
).rowcount
if changed != 1:
raise ValueError("ready head is not the trusted live root head")
def clear_ready(board: str, root_task_id: str, *, path: Path | None = None) -> None:
"""Invalidate approval whenever a continuation is queued or head changes."""
with _connect(board, path) as connection:
connection.execute(
"UPDATE supervisor_roots SET ready_for_human_merge=0, ready_commit='' "
"WHERE board=? AND root_task_id=?", (board, root_task_id)
)
def record_child(
board: str, child_task_id: str, root_task_id: str, parent_task_id: str, kind: str,
head_commit: str, objective: str, cycle: int = 1, *, path: Path | None = None
) -> None:
"""Bind a created child to an existing trusted root and exact continuation."""
if not get_root(board, root_task_id, path=path) or kind not in {"repair", "review"}:
raise ValueError("continuation root or kind is invalid")
digest = objective_digest(objective)
if cycle < 1:
raise ValueError("continuation cycle is invalid")
values = (root_task_id, parent_task_id, kind, head_commit, digest, cycle)
with _connect(board, path) as connection:
existing = connection.execute(
"SELECT root_task_id,parent_task_id,kind,head_commit,objective_digest,cycle "
"FROM supervisor_children WHERE board=? AND child_task_id=?", (board, child_task_id)
).fetchone()
if existing is None:
collision = connection.execute(
"SELECT child_task_id FROM supervisor_children WHERE board=? AND root_task_id=? "
"AND head_commit=? AND objective_digest=?",
(board, root_task_id, head_commit, digest),
).fetchone()
if collision is not None:
raise ValueError("continuation already maps this root/head/objective to another task")
connection.execute(
"INSERT INTO supervisor_children VALUES(?,?,?,?,?,?,?,?)", (board, child_task_id, *values)
)
elif tuple(existing) != values:
raise ValueError("continuation child lineage conflicts with its existing record")
def objective_digest(objective: str) -> str:
"""Make duplicate user follow-ups idempotent without retaining extra prose."""
return hashlib.sha256(objective.strip().encode()).hexdigest()
def existing_child(
board: str, root_task_id: str, head_commit: str, objective: str, *, path: Path | None = None
) -> str:
"""Return an existing same-objective continuation, if one was created."""
with _connect(board, path) as connection:
row = connection.execute(
"SELECT child_task_id FROM supervisor_children WHERE board=? AND root_task_id=? "
"AND head_commit=? AND objective_digest=?",
(board, root_task_id, head_commit, objective_digest(objective)),
).fetchone()
return str(row[0]) if row else ""