73 lines
3.5 KiB
Python
73 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate mediator-issued, one-shot SCM publication retry receipts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
class PublicationRetryError(ValueError):
|
|
"""A receipt cannot safely bind a fresh continuation run to an old commit."""
|
|
|
|
|
|
SHA = re.compile(r"[0-9a-f]{40,64}\Z")
|
|
RESULT_FIELDS = frozenset(
|
|
{"status", "summary", "changed_files", "tests_run", "artifacts", "findings", "blockers"}
|
|
)
|
|
|
|
|
|
def receipt_digest(structured: dict[str, Any], title: str, body: str) -> str:
|
|
"""Return the exact digest the mediator must retain with completed evidence."""
|
|
value = {"structured": structured, "title": title, "body": body}
|
|
return hashlib.sha256(json.dumps(
|
|
value, ensure_ascii=False, separators=(",", ":"), sort_keys=True
|
|
).encode()).hexdigest()
|
|
|
|
|
|
def validate(
|
|
receipt: Any, binding: dict[str, Any], *, board: str, child_task_id: str,
|
|
root_task_id: str, project: str, branch: str, base_branch: str, live_head: str,
|
|
) -> tuple[dict[str, Any], int, str]:
|
|
"""Return a receipt only when its source and completed evidence are exact."""
|
|
source = receipt.get("source") if isinstance(receipt, dict) else None
|
|
if not isinstance(source, dict) or not all(
|
|
source.get(name) == binding.get(name)
|
|
for name in ("board", "task_id", "run_id", "worker_ordinal", "attempt")
|
|
):
|
|
raise PublicationRetryError("publication retry source binding is invalid")
|
|
expected = {
|
|
"board": board, "task_id": child_task_id, "root_task_id": root_task_id,
|
|
"repo_url": f"https://scm.bstein.dev/titan/{project}.git",
|
|
"branch": branch, "base_branch": base_branch,
|
|
}
|
|
if any(source.get(name) != value for name, value in expected.items()):
|
|
raise PublicationRetryError("publication retry source lineage is invalid")
|
|
run_id, ordinal, attempt = (source["run_id"], source["worker_ordinal"], source["attempt"])
|
|
if (
|
|
not isinstance(run_id, str) or not run_id.isdecimal() or int(run_id) < 1
|
|
or isinstance(ordinal, bool) or not isinstance(ordinal, int) or ordinal not in range(3)
|
|
or isinstance(attempt, bool) or not isinstance(attempt, int) or attempt < 1
|
|
):
|
|
raise PublicationRetryError("publication retry source run is invalid")
|
|
baseline, head, digest = (receipt.get(name) for name in ("baseline_sha", "head", "result_digest"))
|
|
title, body, structured = receipt.get("title"), receipt.get("body"), receipt.get("structured")
|
|
if (
|
|
not all(isinstance(value, str) and SHA.fullmatch(value) for value in (baseline, head))
|
|
or not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest)
|
|
or baseline != live_head or baseline == head
|
|
or not isinstance(title, str) or not title or len(title.encode()) > 512
|
|
or not isinstance(body, str) or len(body.encode()) > 32 * 1024
|
|
or not isinstance(structured, dict) or set(structured) != RESULT_FIELDS
|
|
or structured.get("status") != "completed"
|
|
):
|
|
raise PublicationRetryError("publication retry evidence is invalid")
|
|
if digest != receipt_digest(structured, title, body):
|
|
raise PublicationRetryError("publication retry result digest is invalid")
|
|
for name in RESULT_FIELDS - {"status", "summary"}:
|
|
if not isinstance(structured[name], list) or any(not isinstance(item, str) for item in structured[name]):
|
|
raise PublicationRetryError("publication retry result is invalid")
|
|
return receipt, ordinal, run_id
|