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

85 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Fail-closed one-time normalization of a mediator publication receipt."""
from __future__ import annotations
import hashlib
import json
from typing import Any
from execution_pool_protocol import truncate_utf8
from publication_retry import receipt_digest
import supervisor_state
def canonical_title(summary: Any) -> str:
"""Apply the broker's character and UTF-8-byte title limits."""
if not isinstance(summary, str) or not (text := summary.strip()):
raise ValueError("publication retry summary is invalid")
return truncate_utf8(text[:240], 507)
def normalize(
board: str, child_task_id: str, run_id: str, raw_result_sha256: str, receipt: Any,
) -> str:
"""Replace only a rejected title on the exact pending mediator receipt."""
if not isinstance(receipt, dict) or not isinstance(raw_result_sha256, str) or len(raw_result_sha256) != 64:
raise ValueError("publication retry normalization input is invalid")
with supervisor_state._connect(board) as connection:
row = connection.execute(
"SELECT receipt_json,issued_run_id,resolved_run_id,reissue_count,last_reissued_run_id FROM publication_retries "
"WHERE board=? AND child_task_id=?", (board, child_task_id)
).fetchone()
provenance = connection.execute(
"SELECT raw_result_sha256,reconstruction FROM publication_retry_provenance "
"WHERE board=? AND child_task_id=?", (board, child_task_id)
).fetchone()
if row is None or provenance is None or provenance[0] != raw_result_sha256:
raise ValueError("publication retry provenance is unavailable")
pending = (run_id, "", 0, "")
released = ("", "", 1, run_id)
if row[1:] not in {pending, released}:
raise ValueError("publication retry is not the exact pending run")
try:
old = json.loads(row[0])
except (TypeError, json.JSONDecodeError) as error:
raise ValueError("sealed publication receipt is malformed") from error
binding = old.get("source") if isinstance(old, dict) else None
if not isinstance(binding, dict):
raise ValueError("sealed publication receipt is malformed")
old, _ordinal, _source_run = supervisor_state._retry_receipt(
board, child_task_id, binding, old, path=None
)
new, _ordinal, _source_run = supervisor_state._retry_receipt(
board, child_task_id, binding, receipt, path=None
)
unchanged = ("source", "baseline_sha", "head", "body", "structured")
if any(old[name] != new[name] for name in unchanged):
raise ValueError("publication retry evidence changed during normalization")
expected = canonical_title(old["structured"].get("summary"))
if new["title"] != expected or new["result_digest"] != receipt_digest(
new["structured"], new["title"], new["body"]
):
raise ValueError("publication retry title normalization is invalid")
encoded = json.dumps(new, separators=(",", ":"), sort_keys=True)
prefix = "normalized-title-from-sealed-receipt-sha256:"
if row[0] == encoded:
sealed_sha = provenance[1][len(prefix):] if isinstance(provenance[1], str) else ""
if not isinstance(provenance[1], str) or not provenance[1].startswith(prefix) or len(sealed_sha) != 64:
raise ValueError("publication retry normalization provenance conflicts")
return sealed_sha
sealed_sha = hashlib.sha256(row[0].encode()).hexdigest()
marker = prefix + sealed_sha
changed = connection.execute(
"UPDATE publication_retries SET receipt_json=? WHERE board=? AND child_task_id=? "
"AND receipt_json=? AND issued_run_id=? AND resolved_run_id='' AND reissue_count=0",
(encoded, board, child_task_id, row[0], run_id),
).rowcount
if changed != 1:
raise ValueError("publication retry changed during normalization")
connection.execute(
"UPDATE publication_retry_provenance SET reconstruction=? WHERE board=? AND child_task_id=?",
(marker, board, child_task_id),
)
return sealed_sha