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

275 lines
12 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Release Soteria #3's failed publication run after a reviewed mediator fix."""
from __future__ import annotations
import hashlib
import json
import sqlite3
from pathlib import Path
from typing import Any
from gitea_api_policy import _draft_title
import scm_broker_client
import supervisor_state
BOARD = "soteria"
CHILD = "t_4095fe0d"
ROOT = "t_c7c42600"
SOURCE_RUN = "8"
FAILED_RUN = "10"
ORDINAL = 0
BRANCH = "hermes-repair/sonar-AZ9pTqVcN0JrBQvDGDs3"
REMOTE_HEAD = "51133b559ad62f324e45bc61587900f08156b733"
PRESERVED_HEAD = "f21833e78768e7e4045a6304e311989196846ae4"
RECEIPT_DIGEST = "32f99b7ff27b2251e55cfe6fe2ff38fd3054bb781a8cc5e06b010ef855eb6c82"
SOURCE_RAW_SHA = "13de550c15ecfdf6684022b07a823da1c51586f959e9d9d2b3aea67433a9085d"
FAILED_RESULT_SHA = "0395eaed346eab364990de9b6d6c9a791c3d015a6b026f2473a9c80e8a1d2548"
FAILED_EVENT_ID = 209
RETRY_AFTER = 1789346240
CONFIRM_RUN = "11"
CONFIRM_EVENT_ID = 213
def _value(task: Any, name: str) -> Any:
"""Read one scalar from either native task representation."""
return task.get(name) if isinstance(task, dict) else getattr(task, name, None)
def _pool_guard(database: Path, run_id: str) -> None:
"""Require one exact terminal mediator result and no competing assignment."""
connection = sqlite3.connect(f"file:{database}?mode=ro", uri=True)
try:
row = connection.execute(
"SELECT payload_json,result_json,result_digest,state,worker_ordinal,attempt "
"FROM assignments WHERE board=? AND task_id=? AND run_id=?",
(BOARD, CHILD, run_id),
).fetchone()
active = connection.execute(
"SELECT 1 FROM assignments WHERE board=? AND task_id=? AND run_id<>? "
"AND state IN ('assigned','running','result')",
(BOARD, CHILD, run_id),
).fetchone()
finally:
connection.close()
if row is None or active is not None or tuple(row[3:]) != ("finalized", ORDINAL, 1):
raise ValueError("publication attempt is not the exact terminal run")
if not isinstance(row[0], str) or not isinstance(row[1], str):
raise ValueError("publication attempt evidence is malformed")
try:
payload, result = json.loads(row[0]), json.loads(row[1])
except json.JSONDecodeError as error:
raise ValueError("publication attempt evidence is malformed") from error
resume = payload.get("scm_resume") if isinstance(payload, dict) else None
structured = result.get("structured") if isinstance(result, dict) else None
source = resume.get("source") if isinstance(resume, dict) else None
if (
hashlib.sha256(row[1].encode()).hexdigest() != FAILED_RESULT_SHA
or row[2] != FAILED_RESULT_SHA
or not isinstance(source, dict)
or source.get("run_id") != SOURCE_RUN
or source.get("worker_ordinal") != ORDINAL
or resume.get("head") != PRESERVED_HEAD
or not isinstance(structured, dict)
or structured.get("status") != "blocked"
or result.get("returncode") != 1
or result.get("capacity_failure") is not False
or result.get("scm_submission") is not None
):
raise ValueError("publication attempt is not the retained rejected publication")
def _native_guard(run_id: str, event_id: int, status: str, event_kind: str) -> None:
"""Require one exact native terminal event and private root/child chain."""
from hermes_cli import kanban_db
with kanban_db.scoped_current_board(BOARD):
connection = kanban_db.connect(board=BOARD)
try:
task = kanban_db.get_task(connection, CHILD)
parents = kanban_db.parent_ids(connection, CHILD)
event = connection.execute(
"SELECT id,run_id,kind FROM task_events WHERE task_id=? ORDER BY id DESC LIMIT 1",
(CHILD,),
).fetchone()
latest = connection.execute(
"SELECT id,status,outcome FROM task_runs WHERE task_id=? ORDER BY id DESC LIMIT 1",
(CHILD,),
).fetchone()
finally:
connection.close()
if (
task is None
or _value(task, "status") != status
or _value(task, "current_run_id") is not None
or _value(task, "block_kind") != "capability"
or ROOT not in {str(parent) for parent in parents}
or tuple(event or ()) != (event_id, int(run_id), event_kind)
or tuple(latest or ()) != (int(run_id), "blocked", "blocked")
):
raise ValueError("native task changed after run 10")
def _receipt_guard(run_id: str) -> dict[str, Any]:
"""Verify the sealed source-8 receipt and its normalized title provenance."""
receipt = supervisor_state.publication_retry(BOARD, CHILD, run_id)
if not isinstance(receipt, dict):
raise ValueError("publication receipt is unavailable")
source = receipt.get("source")
if (
receipt.get("head") != PRESERVED_HEAD
or receipt.get("result_digest") != RECEIPT_DIGEST
or not isinstance(source, dict)
or source.get("board") != BOARD
or source.get("task_id") != CHILD
or source.get("root_task_id") != ROOT
or source.get("run_id") != SOURCE_RUN
or source.get("worker_ordinal") != ORDINAL
):
raise ValueError("publication receipt changed after normalization")
with supervisor_state._connect(BOARD) as connection:
retry = connection.execute(
"SELECT source_run_id,source_ordinal,issued_run_id,resolved_run_id,reissue_count,retry_after,last_reissued_run_id "
"FROM publication_retries WHERE board=? AND child_task_id=?", (BOARD, CHILD)
).fetchone()
provenance = connection.execute(
"SELECT raw_result_sha256,reconstruction FROM publication_retry_provenance "
"WHERE board=? AND child_task_id=?", (BOARD, CHILD)
).fetchone()
pending = (SOURCE_RUN, ORDINAL, run_id, "", 1, RETRY_AFTER, "9")
released = (SOURCE_RUN, ORDINAL, "", "", 1, RETRY_AFTER, "9")
if (
tuple(retry or ()) not in {pending, released}
or provenance is None
or provenance[0] != SOURCE_RAW_SHA
or not isinstance(provenance[1], str)
or not provenance[1].startswith("normalized-title-from-sealed-receipt-sha256:")
):
raise ValueError("publication retry state changed after run 10")
return receipt
def _remote_guard() -> None:
"""Require the continuing pull request to remain open at its old broker head."""
try:
pull = json.loads(scm_broker_client.read("/api/v1/repos/titan/soteria/pulls/3"))
except (json.JSONDecodeError, OSError) as error:
raise ValueError("pull request metadata is unavailable") from error
head = pull.get("head") if isinstance(pull, dict) else None
base = pull.get("base") if isinstance(pull, dict) else None
if (
not isinstance(pull, dict)
or pull.get("state") != "open"
or pull.get("merged") is not False
or not isinstance(head, dict)
or head.get("ref") != BRANCH
or head.get("sha") != REMOTE_HEAD
or not isinstance(base, dict)
or base.get("ref") != "main"
):
raise ValueError("pull request changed after run 10")
def _published_remote_guard(receipt: dict[str, Any]) -> None:
"""Require the already-published PR head and prose to equal sealed evidence."""
try:
pull = json.loads(scm_broker_client.read("/api/v1/repos/titan/soteria/pulls/3"))
except (json.JSONDecodeError, OSError) as error:
raise ValueError("published pull request metadata is unavailable") from error
head = pull.get("head") if isinstance(pull, dict) else None
base = pull.get("base") if isinstance(pull, dict) else None
title, body = receipt.get("title"), receipt.get("body")
if (
not isinstance(title, str)
or not isinstance(body, str)
or not isinstance(pull, dict)
or pull.get("state") != "open"
or pull.get("merged") is not False
or not isinstance(head, dict)
or head.get("ref") != BRANCH
or head.get("sha") != PRESERVED_HEAD
or not isinstance(base, dict)
or base.get("ref") != "main"
or pull.get("title") != _draft_title(title)
or pull.get("body") != body
):
raise ValueError("published pull request changed after run 11")
def release(pool_database: Path) -> bool:
"""CAS-release run 10’s issue fence without changing its retry budget/history.
A retry after a crash before native unblock accepts the already-released
state. Any other state is evidence that another actor changed the card.
"""
_pool_guard(pool_database, FAILED_RUN)
_native_guard(FAILED_RUN, FAILED_EVENT_ID, "blocked", "blocked")
_receipt_guard(FAILED_RUN)
_remote_guard()
with supervisor_state._connect(BOARD) as connection:
changed = connection.execute(
"UPDATE publication_retries SET issued_run_id='' WHERE board=? AND child_task_id=? "
"AND source_run_id=? AND source_ordinal=? AND issued_run_id=? AND resolved_run_id='' "
"AND reissue_count=1 AND retry_after=? AND last_reissued_run_id='9'",
(BOARD, CHILD, SOURCE_RUN, ORDINAL, FAILED_RUN, RETRY_AFTER),
).rowcount
if changed == 1:
return True
with supervisor_state._connect(BOARD) as connection:
row = connection.execute(
"SELECT source_run_id,source_ordinal,issued_run_id,resolved_run_id,reissue_count,retry_after,last_reissued_run_id "
"FROM publication_retries WHERE board=? AND child_task_id=?", (BOARD, CHILD)
).fetchone()
if tuple(row or ()) == (SOURCE_RUN, ORDINAL, "", "", 1, RETRY_AFTER, "9"):
return False
raise ValueError("publication retry state changed before CAS release")
def confirm_published_release(pool_database: Path) -> bool:
"""Release run 11 only after its PR publish is independently visible.
This intentionally does not reopen native work. The caller must separately
use the native triage specification API after a fixed mediator is deployed.
"""
_pool_guard(pool_database, CONFIRM_RUN)
_native_guard(CONFIRM_RUN, CONFIRM_EVENT_ID, "triage", "block_loop_detected")
receipt = _receipt_guard(CONFIRM_RUN)
_published_remote_guard(receipt)
with supervisor_state._connect(BOARD) as connection:
changed = connection.execute(
"UPDATE publication_retries SET issued_run_id='' WHERE board=? AND child_task_id=? "
"AND source_run_id=? AND source_ordinal=? AND issued_run_id=? AND resolved_run_id='' "
"AND reissue_count=1 AND retry_after=? AND last_reissued_run_id='9'",
(BOARD, CHILD, SOURCE_RUN, ORDINAL, CONFIRM_RUN, RETRY_AFTER),
).rowcount
if changed == 1:
return True
with supervisor_state._connect(BOARD) as connection:
row = connection.execute(
"SELECT source_run_id,source_ordinal,issued_run_id,resolved_run_id,reissue_count,retry_after,last_reissued_run_id "
"FROM publication_retries WHERE board=? AND child_task_id=?", (BOARD, CHILD)
).fetchone()
if tuple(row or ()) == (SOURCE_RUN, ORDINAL, "", "", 1, RETRY_AFTER, "9"):
return False
raise ValueError("published confirmation changed before CAS release")
if __name__ == "__main__":
import argparse
import os
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--confirm-published", action="store_true")
parser.add_argument(
"--pool-db", type=Path,
default=Path(os.environ.get("HERMES_HOME", "/opt/data")) / "execution-pool/assignments.db",
)
args = parser.parse_args()
if args.confirm_published:
confirm_published_release(args.pool_db)
print(f"released {BOARD}/{CHILD} published_run={CONFIRM_RUN} source_run={SOURCE_RUN}")
else:
release(args.pool_db)
print(f"released {BOARD}/{CHILD} failed_run={FAILED_RUN} source_run={SOURCE_RUN}")