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

175 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""Bind coordinator publication blocks to one immutable native event."""
from __future__ import annotations
import json
from typing import Any
import supervisor_state
PREFIX = "[hermes-publication-retry-fence:"
def marker(receipt: Any, run_id: str) -> str:
"""Return a bounded marker derived only from a validated retry receipt."""
digest = receipt.get("result_digest") if isinstance(receipt, dict) else None
if not isinstance(digest, str) or len(digest) != 64 or not run_id.isdecimal():
raise ValueError("publication retry fence is invalid")
return f"{PREFIX}{run_id}:{digest}]"
def _event(connection: Any, task_id: str, run_id: str, value: str) -> int:
"""Read the just-written native block event before recording its fence."""
row = connection.execute(
"SELECT id,run_id,kind,payload FROM task_events WHERE task_id=? ORDER BY id DESC LIMIT 1", (task_id,)
).fetchone()
try:
payload = json.loads(row[3] or "{}")
except (TypeError, ValueError, json.JSONDecodeError) as error:
raise ValueError("publication retry block event is malformed") from error
if (row is None or int(row[0]) < 1 or int(row[1] or 0) != int(run_id)
or row[2] != "blocked" or not isinstance(payload, dict)):
raise ValueError("publication retry block event is unavailable")
if value not in str(payload.get("reason") or ""):
raise ValueError("publication retry block event lacks its fence")
return int(row[0])
def record(board: str, task_id: str, run_id: str, connection: Any, value: str) -> None:
"""Persist a coordinator-owned event identity only after native blocking."""
event_id = _event(connection, task_id, run_id, value)
with supervisor_state._connect(board) as state:
state.execute(
"CREATE TABLE IF NOT EXISTS publication_retry_fences("
"board TEXT NOT NULL,child_task_id TEXT NOT NULL,run_id TEXT NOT NULL,"
"event_id INTEGER NOT NULL,marker TEXT NOT NULL,PRIMARY KEY(board,child_task_id,run_id))"
)
existing = state.execute(
"SELECT event_id,marker FROM publication_retry_fences WHERE board=? AND child_task_id=? AND run_id=?",
(board, task_id, run_id),
).fetchone()
if existing is None:
state.execute("INSERT INTO publication_retry_fences VALUES(?,?,?,?,?)", (board, task_id, run_id, event_id, value))
elif tuple(existing) != (event_id, value):
raise ValueError("publication retry fence conflicts with native history")
def read(board: str, task_id: str, run_id: str) -> tuple[int, str] | None:
"""Return one exact coordinator block fence, never a task-body claim."""
try:
with supervisor_state._connect(board) as state:
row = state.execute(
"SELECT event_id,marker FROM publication_retry_fences WHERE board=? AND child_task_id=? AND run_id=?",
(board, task_id, run_id),
).fetchone()
except Exception:
return None
return (int(row[0]), str(row[1])) if row and int(row[0]) > 0 and str(row[1]).startswith(PREFIX) else None
def terminal_receipt(record: Any) -> dict[str, Any] | None:
"""Return only a signed-pool publication failure that may reconstruct a fence."""
if not isinstance(record, dict):
return None
result, assignment = record.get("result"), record.get("payload")
if not isinstance(result, dict) or not isinstance(assignment, dict):
return None
structured = result.get("structured")
if (not isinstance(structured, dict) or structured.get("status") != "blocked"
or result.get("capacity_failure") is not True or result.get("scm_submission") is not None):
return None
receipt = result.get("scm_resume")
if receipt is None and result.get("publication_retry_transient") is True:
receipt = assignment.get("scm_resume")
source = receipt.get("source") if isinstance(receipt, dict) else None
if (not isinstance(source, dict) or source.get("board") != record.get("board")
or source.get("task_id") != record.get("task_id")
or source.get("worker_ordinal") != record.get("worker_ordinal")):
return None
return receipt
def recover(board: str, task_id: str, run_id: str, connection: Any, receipt: Any) -> tuple[int, str] | None:
"""Reconstruct a missing sidecar fence only from the current native event."""
try:
with supervisor_state._connect(board) as state:
sealed = state.execute(
"SELECT receipt_json FROM publication_retries WHERE board=? AND child_task_id=?",
(board, task_id),
).fetchone()
if sealed is None or json.loads(sealed[0]) != receipt:
return None
value = marker(receipt, run_id)
record(board, task_id, run_id, connection, value)
except (OSError, TypeError, ValueError, json.JSONDecodeError):
return None
return read(board, task_id, run_id)
def guarded_unblock(kanban_db: Any, connection: Any, task_id: str, root_task_id: str, run_id: str, fence: tuple[int, str]) -> bool:
"""Atomically reopen only the current coordinator-owned native block event."""
if (not isinstance(fence, tuple) or len(fence) != 2 or not isinstance(fence[0], int)
or fence[0] < 1 or not isinstance(fence[1], str) or not fence[1].startswith(PREFIX)
or not str(run_id).isdecimal() or not root_task_id):
return False
transaction = getattr(kanban_db, "write_txn", None)
append_event = getattr(kanban_db, "_append_event", None)
if not callable(transaction) or not callable(append_event):
return False
try:
with transaction(connection):
event = connection.execute(
"SELECT id,run_id,kind,payload FROM task_events WHERE task_id=? ORDER BY id DESC LIMIT 1",
(task_id,),
).fetchone()
try:
payload = json.loads(event[3] or "{}") if event is not None else {}
except (TypeError, ValueError, json.JSONDecodeError):
return False
if (event is None or int(event[0]) != fence[0] or int(event[1] or 0) != int(run_id)
or event[2] != "blocked" or not isinstance(payload, dict)
or fence[1] not in str(payload.get("reason") or "")):
return False
task = connection.execute(
"SELECT status,current_run_id FROM tasks WHERE id=?", (task_id,)
).fetchone()
parent = connection.execute(
"SELECT 1 FROM task_links WHERE child_id=? AND parent_id=?", (task_id, root_task_id)
).fetchone()
if task is None or task[0] != "blocked" or task[1] is not None or parent is None:
return False
unfinished = connection.execute(
"SELECT 1 FROM task_links l JOIN tasks p ON p.id=l.parent_id "
"WHERE l.child_id=? AND p.status != 'done' LIMIT 1", (task_id,)
).fetchone()
status = "todo" if unfinished is not None else "ready"
changed = connection.execute(
"UPDATE tasks SET status=?,current_run_id=NULL,consecutive_failures=0,last_failure_error=NULL "
"WHERE id=? AND status='blocked' AND current_run_id IS NULL", (status, task_id),
).rowcount
if changed != 1:
return False
append_event(connection, task_id, "unblocked", {"status": status, "publication_retry_fence": fence[1]})
return True
except (AttributeError, TypeError, ValueError):
return False
def block(kanban_db: Any, connection: Any, board: str, task_id: str, run_id: str, reason: str, kind: str, receipt: Any) -> bool:
"""Block normally, then retain a native event fence for an automatic retry."""
try:
value = marker(receipt, run_id) if receipt is not None else ""
except ValueError:
# A malformed retained receipt must still park its exact live run, but
# can never acquire automatic-retry ownership.
value = ""
changed = kanban_db.block_task(
connection, task_id, reason=f"{reason}\n{value}" if value else reason, kind=kind,
expected_run_id=int(run_id),
)
if changed and value:
record(board, task_id, run_id, connection, value)
return bool(changed)