211 lines
8.7 KiB
Python
Executable File
211 lines
8.7 KiB
Python
Executable File
#!/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
|
||
|
||
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
|
||
|
||
|
||
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) -> None:
|
||
"""Require exactly run 10's preserved, unpublished mediator result."""
|
||
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, FAILED_RUN),
|
||
).fetchone()
|
||
active = connection.execute(
|
||
"SELECT 1 FROM assignments WHERE board=? AND task_id=? AND run_id<>? "
|
||
"AND state IN ('assigned','running','result')",
|
||
(BOARD, CHILD, FAILED_RUN),
|
||
).fetchone()
|
||
finally:
|
||
connection.close()
|
||
if row is None or active is not None or tuple(row[3:]) != ("finalized", ORDINAL, 1):
|
||
raise ValueError("run 10 is not the exact terminal publication attempt")
|
||
if not isinstance(row[0], str) or not isinstance(row[1], str):
|
||
raise ValueError("run 10 evidence is malformed")
|
||
try:
|
||
payload, result = json.loads(row[0]), json.loads(row[1])
|
||
except json.JSONDecodeError as error:
|
||
raise ValueError("run 10 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("run 10 is not the retained rejected publication")
|
||
|
||
|
||
def _native_guard() -> None:
|
||
"""Require event 209 to remain the private child’s current blocked outcome."""
|
||
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") != "blocked"
|
||
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 ()) != (FAILED_EVENT_ID, int(FAILED_RUN), "blocked")
|
||
or tuple(latest or ()) != (int(FAILED_RUN), "blocked", "blocked")
|
||
):
|
||
raise ValueError("native task changed after run 10")
|
||
|
||
|
||
def _receipt_guard() -> None:
|
||
"""Verify the sealed source-8 receipt and its normalized title provenance."""
|
||
receipt = supervisor_state.publication_retry(BOARD, CHILD, FAILED_RUN)
|
||
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, FAILED_RUN, "", 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")
|
||
|
||
|
||
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 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)
|
||
_native_guard()
|
||
_receipt_guard()
|
||
_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")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import argparse
|
||
import os
|
||
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
parser.add_argument(
|
||
"--pool-db", type=Path,
|
||
default=Path(os.environ.get("HERMES_HOME", "/opt/data")) / "execution-pool/assignments.db",
|
||
)
|
||
args = parser.parse_args()
|
||
release(args.pool_db)
|
||
print(f"released {BOARD}/{CHILD} failed_run={FAILED_RUN} source_run={SOURCE_RUN}")
|