#!/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)