713 lines
29 KiB
Python
713 lines
29 KiB
Python
#!/usr/bin/env python3
|
|
"""Export exact private replay material for one guarded applied proposal.
|
|
|
|
The exporter is intentionally read-only. It validates an existing worker replay
|
|
receipt, reads the approved and applied proposal rows through one gate-owned
|
|
function, and atomically publishes deterministic genesis-ledger input.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
import apply_proposal as apply_engine # noqa: E402
|
|
import kb_apply_replay_receipt as replay_receipt # noqa: E402
|
|
|
|
MATERIAL_ARTIFACT = "teleo_strict_proposal_replay_material"
|
|
MATERIAL_CONTRACT_VERSION = 1
|
|
MATERIAL_FIELDS = frozenset(
|
|
{
|
|
"artifact",
|
|
"contract_version",
|
|
"sequence",
|
|
"approved_proposal",
|
|
"approval_snapshot",
|
|
"applied_proposal",
|
|
"replay_receipt",
|
|
}
|
|
)
|
|
PROPOSAL_FIELDS = frozenset(
|
|
{
|
|
"id",
|
|
"proposal_type",
|
|
"status",
|
|
"proposed_by_handle",
|
|
"proposed_by_agent_id",
|
|
"channel",
|
|
"source_ref",
|
|
"rationale",
|
|
"payload",
|
|
"reviewed_by_handle",
|
|
"reviewed_by_agent_id",
|
|
"reviewed_at",
|
|
"review_note",
|
|
"applied_by_handle",
|
|
"applied_by_agent_id",
|
|
"applied_at",
|
|
"created_at",
|
|
"updated_at",
|
|
}
|
|
)
|
|
APPROVAL_FIELDS = frozenset(
|
|
{
|
|
"proposal_id",
|
|
"proposal_type",
|
|
"payload",
|
|
"reviewed_by_handle",
|
|
"reviewed_by_agent_id",
|
|
"reviewed_by_db_role",
|
|
"reviewed_at",
|
|
"review_note",
|
|
}
|
|
)
|
|
PROPOSAL_TRANSITION_FIELDS = frozenset(
|
|
{"status", "applied_by_handle", "applied_by_agent_id", "applied_at", "updated_at"}
|
|
)
|
|
PROPOSAL_UUID_FIELDS = frozenset({"id", "proposed_by_agent_id", "reviewed_by_agent_id", "applied_by_agent_id"})
|
|
PROPOSAL_TIMESTAMP_FIELDS = frozenset({"reviewed_at", "applied_at", "created_at", "updated_at"})
|
|
APPROVAL_UUID_FIELDS = frozenset({"proposal_id", "reviewed_by_agent_id"})
|
|
APPROVAL_TIMESTAMP_FIELDS = frozenset({"reviewed_at"})
|
|
RECEIPT_PROPOSAL_FIELDS = (
|
|
"id",
|
|
"proposal_type",
|
|
"status",
|
|
"payload",
|
|
"reviewed_by_handle",
|
|
"reviewed_by_agent_id",
|
|
"reviewed_at",
|
|
"review_note",
|
|
"applied_by_handle",
|
|
"applied_by_agent_id",
|
|
"applied_at",
|
|
)
|
|
RECEIPT_PROPOSAL_FIELD_SET = frozenset(RECEIPT_PROPOSAL_FIELDS)
|
|
LEGACY_RECEIPT_TIMESTAMP_FIELDS = frozenset({"captured_at", "created_at", "updated_at", "reviewed_at", "applied_at"})
|
|
LEGACY_POSTGRES_UTC_TIMESTAMP = re.compile(
|
|
r"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,6})?\+00$"
|
|
)
|
|
|
|
|
|
class ExportError(RuntimeError):
|
|
"""A fail-closed error that contains no proposal payload material."""
|
|
|
|
|
|
def _canonical_json(value: Any) -> str:
|
|
return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
|
|
|
|
|
|
def _same_json(left: Any, right: Any) -> bool:
|
|
return _canonical_json(left) == _canonical_json(right)
|
|
|
|
|
|
def _sha256_json(value: Any) -> str:
|
|
return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest()
|
|
|
|
|
|
def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
result: dict[str, Any] = {}
|
|
for key, value in pairs:
|
|
if key in result:
|
|
raise ExportError("JSON input contains duplicate object fields")
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def _reject_nonfinite_number(value: str) -> None:
|
|
raise ExportError(f"JSON input contains invalid numeric constant {value}")
|
|
|
|
|
|
def _parse_json_object(raw: str, *, label: str) -> dict[str, Any]:
|
|
try:
|
|
value = json.loads(
|
|
raw,
|
|
object_pairs_hook=_reject_duplicate_keys,
|
|
parse_constant=_reject_nonfinite_number,
|
|
)
|
|
except (json.JSONDecodeError, UnicodeError) as exc:
|
|
raise ExportError(f"{label} is not valid UTF-8 JSON") from exc
|
|
if not isinstance(value, dict):
|
|
raise ExportError(f"{label} must contain one JSON object")
|
|
return value
|
|
|
|
|
|
def _absolute_path(value: str | Path) -> Path:
|
|
path = Path(value).expanduser()
|
|
if not path.is_absolute():
|
|
path = Path.cwd() / path
|
|
return path
|
|
|
|
|
|
def _load_private_receipt(path: Path) -> dict[str, Any]:
|
|
path = _absolute_path(path)
|
|
try:
|
|
metadata = path.lstat()
|
|
except OSError as exc:
|
|
raise ExportError("worker replay receipt is unavailable") from exc
|
|
if path.is_symlink() or not stat.S_ISREG(metadata.st_mode):
|
|
raise ExportError("worker replay receipt must be a regular non-symlink file")
|
|
if stat.S_IMODE(metadata.st_mode) != 0o600:
|
|
raise ExportError("worker replay receipt permissions must be exactly 0600")
|
|
try:
|
|
raw = path.read_text(encoding="utf-8")
|
|
except (OSError, UnicodeError) as exc:
|
|
raise ExportError("worker replay receipt is unreadable") from exc
|
|
return _parse_json_object(raw, label="worker replay receipt")
|
|
|
|
|
|
def _require_exact_fields(value: Any, expected: frozenset[str], *, label: str) -> dict[str, Any]:
|
|
if not isinstance(value, dict):
|
|
raise ExportError(f"{label} must be an object")
|
|
observed = frozenset(value)
|
|
if observed != expected:
|
|
raise ExportError(
|
|
f"{label} has invalid fields; missing_count={len(expected - observed)}, "
|
|
f"extra_count={len(observed - expected)}"
|
|
)
|
|
return value
|
|
|
|
|
|
def _canonical_uuid(value: Any, *, label: str, nullable: bool = False) -> str | None:
|
|
if value is None and nullable:
|
|
return None
|
|
try:
|
|
return str(uuid.UUID(str(value)))
|
|
except (AttributeError, TypeError, ValueError) as exc:
|
|
raise ExportError(f"{label} must be a UUID") from exc
|
|
|
|
|
|
def _canonical_timestamp(value: Any, *, label: str, nullable: bool = False) -> str | None:
|
|
if value is None and nullable:
|
|
return None
|
|
try:
|
|
return replay_receipt.canonical_utc_timestamp(value, path=label)
|
|
except ValueError as exc:
|
|
raise ExportError(f"{label} must be a timezone-aware timestamp") from exc
|
|
|
|
|
|
def _canonical_legacy_postgres_timestamp(value: Any, *, label: str) -> str:
|
|
if not isinstance(value, str) or not LEGACY_POSTGRES_UTC_TIMESTAMP.fullmatch(value):
|
|
raise ExportError(f"{label} must use the exact legacy PostgreSQL UTC timestamp format")
|
|
timestamp_format = "%Y-%m-%d %H:%M:%S.%f%z" if "." in value else "%Y-%m-%d %H:%M:%S%z"
|
|
try:
|
|
parsed = datetime.strptime(value + ":00", timestamp_format)
|
|
except ValueError as exc:
|
|
raise ExportError(f"{label} must be a valid legacy PostgreSQL UTC timestamp") from exc
|
|
return parsed.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
|
|
|
|
def _normalize_proposal(value: Any, *, label: str) -> dict[str, Any]:
|
|
row = dict(_require_exact_fields(value, PROPOSAL_FIELDS, label=label))
|
|
for field in PROPOSAL_UUID_FIELDS:
|
|
row[field] = _canonical_uuid(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
nullable=field != "id",
|
|
)
|
|
for field in PROPOSAL_TIMESTAMP_FIELDS:
|
|
row[field] = _canonical_timestamp(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
nullable=field in {"reviewed_at", "applied_at"},
|
|
)
|
|
if not isinstance(row["proposal_type"], str) or not row["proposal_type"]:
|
|
raise ExportError(f"{label}.proposal_type must be a non-empty string")
|
|
if not isinstance(row["status"], str) or not row["status"]:
|
|
raise ExportError(f"{label}.status must be a non-empty string")
|
|
if not isinstance(row["payload"], dict):
|
|
raise ExportError(f"{label}.payload must be an object")
|
|
for field in (
|
|
PROPOSAL_FIELDS
|
|
- PROPOSAL_UUID_FIELDS
|
|
- PROPOSAL_TIMESTAMP_FIELDS
|
|
- {
|
|
"payload",
|
|
}
|
|
):
|
|
if field in {"proposal_type", "status"}:
|
|
continue
|
|
if row[field] is not None and not isinstance(row[field], str):
|
|
raise ExportError(f"{label}.{field} must be text or null")
|
|
return row
|
|
|
|
|
|
def _normalize_approval(value: Any, *, label: str) -> dict[str, Any]:
|
|
row = dict(_require_exact_fields(value, APPROVAL_FIELDS, label=label))
|
|
for field in APPROVAL_UUID_FIELDS:
|
|
row[field] = _canonical_uuid(row[field], label=f"{label}.{field}")
|
|
for field in APPROVAL_TIMESTAMP_FIELDS:
|
|
row[field] = _canonical_timestamp(row[field], label=f"{label}.{field}")
|
|
if not isinstance(row["payload"], dict):
|
|
raise ExportError(f"{label}.payload must be an object")
|
|
for field in APPROVAL_FIELDS - APPROVAL_UUID_FIELDS - APPROVAL_TIMESTAMP_FIELDS - {"payload"}:
|
|
if not isinstance(row[field], str) or not row[field].strip():
|
|
raise ExportError(f"{label}.{field} must be non-empty text")
|
|
return row
|
|
|
|
|
|
def _normalize_receipt_proposal(value: Any, *, label: str) -> dict[str, Any]:
|
|
row = dict(_require_exact_fields(value, RECEIPT_PROPOSAL_FIELD_SET, label=label))
|
|
for field in RECEIPT_PROPOSAL_FIELD_SET & PROPOSAL_UUID_FIELDS:
|
|
row[field] = _canonical_uuid(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
nullable=field != "id",
|
|
)
|
|
for field in RECEIPT_PROPOSAL_FIELD_SET & PROPOSAL_TIMESTAMP_FIELDS:
|
|
row[field] = _canonical_timestamp(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
nullable=False,
|
|
)
|
|
return row
|
|
|
|
|
|
def _normalize_legacy_receipt_proposal(value: Any, *, label: str) -> dict[str, Any]:
|
|
row = dict(_require_exact_fields(value, RECEIPT_PROPOSAL_FIELD_SET, label=label))
|
|
for field in RECEIPT_PROPOSAL_FIELD_SET & PROPOSAL_UUID_FIELDS:
|
|
row[field] = _canonical_uuid(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
nullable=field != "id",
|
|
)
|
|
for field in RECEIPT_PROPOSAL_FIELD_SET & PROPOSAL_TIMESTAMP_FIELDS:
|
|
row[field] = _canonical_legacy_postgres_timestamp(
|
|
row[field],
|
|
label=f"{label}.{field}",
|
|
)
|
|
return row
|
|
|
|
|
|
def _normalize_legacy_receipt_rows(value: Any) -> dict[str, list[dict[str, Any]]]:
|
|
if not isinstance(value, dict):
|
|
raise ExportError("worker replay receipt canonical rows must be an object")
|
|
normalized: dict[str, list[dict[str, Any]]] = {}
|
|
for table, rows in value.items():
|
|
if not isinstance(table, str) or not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
|
|
raise ExportError("worker replay receipt canonical rows must map table names to row arrays")
|
|
normalized_rows: list[dict[str, Any]] = []
|
|
for index, row in enumerate(rows):
|
|
normalized_row: dict[str, Any] = {}
|
|
for field, field_value in row.items():
|
|
path = f"replay_receipt.canonical_rows.{table}[{index}].{field}"
|
|
if field in LEGACY_RECEIPT_TIMESTAMP_FIELDS and field_value is not None:
|
|
normalized_row[field] = _canonical_legacy_postgres_timestamp(field_value, label=path)
|
|
else:
|
|
try:
|
|
normalized_row[field] = replay_receipt.normalize_typed_value(
|
|
field,
|
|
field_value,
|
|
path=path,
|
|
)
|
|
except ValueError as exc:
|
|
raise ExportError("worker replay receipt contains an invalid typed row value") from exc
|
|
normalized_rows.append(normalized_row)
|
|
normalized[table] = normalized_rows
|
|
return normalized
|
|
|
|
|
|
def _has_legacy_postgres_timestamp(proposal: dict[str, Any], rows: Any) -> bool:
|
|
values = [proposal.get("reviewed_at"), proposal.get("applied_at")]
|
|
if isinstance(rows, dict):
|
|
values.extend(
|
|
row.get(field)
|
|
for table_rows in rows.values()
|
|
if isinstance(table_rows, list)
|
|
for row in table_rows
|
|
if isinstance(row, dict)
|
|
for field in LEGACY_RECEIPT_TIMESTAMP_FIELDS
|
|
)
|
|
return any(isinstance(value, str) and LEGACY_POSTGRES_UTC_TIMESTAMP.fullmatch(value) for value in values)
|
|
|
|
|
|
def _validate_legacy_worker_replay_receipt(
|
|
receipt: dict[str, Any],
|
|
*,
|
|
expected_proposal_id: str | None,
|
|
) -> dict[str, Any]:
|
|
proposal = receipt.get("proposal")
|
|
rows = receipt.get("canonical_rows")
|
|
apply_metadata = receipt.get("apply_engine")
|
|
if not isinstance(proposal, dict) or not isinstance(rows, dict) or not isinstance(apply_metadata, dict):
|
|
raise ExportError("worker replay receipt is missing proposal, rows, or apply metadata")
|
|
if not _has_legacy_postgres_timestamp(proposal, rows):
|
|
raise ExportError("worker replay receipt is not a supported legacy PostgreSQL rendering")
|
|
|
|
normalized_proposal = _normalize_legacy_receipt_proposal(proposal, label="replay_receipt.proposal")
|
|
if any(
|
|
proposal[field] is not None and proposal[field] != normalized_proposal[field]
|
|
for field in RECEIPT_PROPOSAL_FIELD_SET & PROPOSAL_UUID_FIELDS
|
|
):
|
|
raise ExportError("legacy worker replay receipt proposal UUIDs are not canonical")
|
|
if expected_proposal_id is not None and normalized_proposal["id"] != _canonical_uuid(
|
|
expected_proposal_id,
|
|
label="expected_proposal_id",
|
|
):
|
|
raise ExportError("worker replay receipt proposal id does not match")
|
|
normalized_rows = _normalize_legacy_receipt_rows(rows)
|
|
if any(
|
|
row.get(field) is not None and row.get(field) != normalized_rows[table][index].get(field)
|
|
for table, table_rows in rows.items()
|
|
for index, row in enumerate(table_rows)
|
|
for field in row
|
|
if field not in LEGACY_RECEIPT_TIMESTAMP_FIELDS
|
|
):
|
|
raise ExportError("legacy worker replay receipt row UUIDs are not canonical")
|
|
exported_at_utc = _canonical_timestamp(
|
|
receipt.get("exported_at_utc"),
|
|
label="replay_receipt.exported_at_utc",
|
|
)
|
|
try:
|
|
normalized_receipt = replay_receipt.build_replay_receipt(
|
|
normalized_proposal,
|
|
normalized_rows,
|
|
apply_sql_sha256=apply_metadata.get("apply_sql_sha256"),
|
|
apply_sql_source=apply_metadata.get("source"),
|
|
exported_at_utc=exported_at_utc,
|
|
)
|
|
except (KeyError, TypeError, ValueError) as exc:
|
|
raise ExportError("legacy worker replay receipt failed strict payload and row validation") from exc
|
|
|
|
expected_counts = normalized_receipt["expected_row_counts"]
|
|
if any(not isinstance(rows.get(table), list) for table in expected_counts):
|
|
raise ExportError("legacy worker replay receipt is missing an expected row array")
|
|
raw_rows = {table: sorted(rows.get(table, []), key=_canonical_json) for table in expected_counts}
|
|
actual_counts = {table: len(table_rows) for table, table_rows in raw_rows.items()}
|
|
raw_apply_metadata = {
|
|
"apply_sql_sha256": apply_metadata.get("apply_sql_sha256"),
|
|
"source": apply_metadata.get("source"),
|
|
}
|
|
replay_material = {
|
|
"receipt_contract_version": replay_receipt.RECEIPT_CONTRACT_VERSION,
|
|
"apply_engine": raw_apply_metadata,
|
|
"proposal": proposal,
|
|
"canonical_rows": raw_rows,
|
|
}
|
|
raw_apply_payload = proposal.get("payload", {}).get("apply_payload")
|
|
expected_receipt = {
|
|
**replay_material,
|
|
"artifact": "kb_apply_replay_receipt",
|
|
"exported_at_utc": receipt.get("exported_at_utc"),
|
|
"expected_row_counts": expected_counts,
|
|
"actual_row_counts": actual_counts,
|
|
"checks": normalized_receipt["checks"],
|
|
"hashes": {
|
|
"apply_payload_sha256": _sha256_json(raw_apply_payload),
|
|
"proposal_payload_sha256": _sha256_json(proposal.get("payload")),
|
|
"canonical_rows_sha256": _sha256_json(raw_rows),
|
|
"row_sha256": {table: [_sha256_json(row) for row in table_rows] for table, table_rows in raw_rows.items()},
|
|
"table_sha256": {table: _sha256_json(table_rows) for table, table_rows in raw_rows.items()},
|
|
"replay_material_sha256": _sha256_json(replay_material),
|
|
},
|
|
"replay_ready": True,
|
|
"privacy": "private receipt; may contain claim bodies and source excerpts; do not commit",
|
|
}
|
|
if not _same_json(receipt, expected_receipt):
|
|
raise ExportError("legacy worker replay receipt hashes or contract fields are inconsistent")
|
|
return normalized_receipt
|
|
|
|
|
|
def validate_worker_replay_receipt(
|
|
receipt: dict[str, Any],
|
|
*,
|
|
expected_proposal_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Validate a current receipt or one exact legacy PostgreSQL timestamp rendering."""
|
|
try:
|
|
return replay_receipt.validate_replay_receipt(
|
|
receipt,
|
|
expected_proposal_id=expected_proposal_id,
|
|
)
|
|
except ValueError:
|
|
return _validate_legacy_worker_replay_receipt(
|
|
receipt,
|
|
expected_proposal_id=expected_proposal_id,
|
|
)
|
|
|
|
|
|
def _parse_timestamp(value: str) -> datetime:
|
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
|
|
|
|
def validate_material(
|
|
material: dict[str, Any],
|
|
*,
|
|
expected_proposal_id: str | None = None,
|
|
expected_sequence: int | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Validate and canonicalize the exact seven-field replay material."""
|
|
_require_exact_fields(material, MATERIAL_FIELDS, label="replay material")
|
|
if material["artifact"] != MATERIAL_ARTIFACT:
|
|
raise ExportError("replay material artifact is unsupported")
|
|
if type(material["contract_version"]) is not int or material["contract_version"] != MATERIAL_CONTRACT_VERSION:
|
|
raise ExportError("replay material contract version is unsupported")
|
|
sequence = material["sequence"]
|
|
if type(sequence) is not int or sequence <= 0:
|
|
raise ExportError("replay material sequence must be a positive integer")
|
|
if expected_sequence is not None and sequence != expected_sequence:
|
|
raise ExportError("replay material sequence does not match the requested sequence")
|
|
|
|
approved = _normalize_proposal(material["approved_proposal"], label="approved_proposal")
|
|
approval = _normalize_approval(material["approval_snapshot"], label="approval_snapshot")
|
|
applied = _normalize_proposal(material["applied_proposal"], label="applied_proposal")
|
|
proposal_id = _canonical_uuid(applied["id"], label="applied_proposal.id")
|
|
if expected_proposal_id is not None and proposal_id != _canonical_uuid(
|
|
expected_proposal_id, label="expected_proposal_id"
|
|
):
|
|
raise ExportError("database proposal id does not match the requested proposal")
|
|
if approved["id"] != proposal_id or approval["proposal_id"] != proposal_id:
|
|
raise ExportError("approved, approval, and applied proposal ids do not match")
|
|
if approved["status"] != "approved" or applied["status"] != "applied":
|
|
raise ExportError("replay material requires exact approved and applied proposal states")
|
|
if any(approved[field] is not None for field in ("applied_by_handle", "applied_by_agent_id", "applied_at")):
|
|
raise ExportError("approved proposal snapshot already contains apply metadata")
|
|
if not applied["applied_by_handle"] or not applied["applied_by_agent_id"] or not applied["applied_at"]:
|
|
raise ExportError("applied proposal row lacks exact apply metadata")
|
|
if not approved["reviewed_by_handle"] or not approved["reviewed_by_agent_id"] or not approved["reviewed_at"]:
|
|
raise ExportError("approved proposal row lacks exact review metadata")
|
|
|
|
changed_fields = {field for field in PROPOSAL_FIELDS if not _same_json(approved[field], applied[field])}
|
|
if changed_fields != PROPOSAL_TRANSITION_FIELDS:
|
|
raise ExportError("proposal rows differ outside or omit the exact guarded apply transition")
|
|
|
|
approval_projection = {
|
|
"proposal_id": approved["id"],
|
|
"proposal_type": approved["proposal_type"],
|
|
"payload": approved["payload"],
|
|
"reviewed_by_handle": approved["reviewed_by_handle"],
|
|
"reviewed_by_agent_id": approved["reviewed_by_agent_id"],
|
|
"reviewed_at": approved["reviewed_at"],
|
|
"review_note": approved["review_note"],
|
|
}
|
|
for field, expected in approval_projection.items():
|
|
if not _same_json(approval[field], expected):
|
|
raise ExportError(f"approval snapshot does not match approved proposal field {field}")
|
|
|
|
receipt = material["replay_receipt"]
|
|
if not isinstance(receipt, dict):
|
|
raise ExportError("replay_receipt must be an object")
|
|
try:
|
|
normalized_receipt = validate_worker_replay_receipt(receipt, expected_proposal_id=proposal_id)
|
|
except (ExportError, ValueError) as exc:
|
|
raise ExportError("worker replay receipt failed its exact row-level contract") from exc
|
|
receipt_proposal = receipt.get("proposal")
|
|
if not isinstance(receipt_proposal, dict):
|
|
raise ExportError("worker replay receipt has no proposal envelope")
|
|
normalized_receipt_proposal = _normalize_receipt_proposal(
|
|
normalized_receipt.get("proposal"),
|
|
label="replay_receipt.proposal",
|
|
)
|
|
applied_envelope = {field: applied[field] for field in RECEIPT_PROPOSAL_FIELDS}
|
|
if not _same_json(normalized_receipt_proposal, applied_envelope):
|
|
raise ExportError("worker replay receipt does not match the exact applied proposal row")
|
|
|
|
created_at = _parse_timestamp(approved["created_at"])
|
|
reviewed_at = _parse_timestamp(approved["reviewed_at"])
|
|
approved_updated_at = _parse_timestamp(approved["updated_at"])
|
|
applied_at = _parse_timestamp(applied["applied_at"])
|
|
applied_updated_at = _parse_timestamp(applied["updated_at"])
|
|
if not created_at <= reviewed_at <= approved_updated_at < applied_at <= applied_updated_at:
|
|
raise ExportError("proposal transition timestamps are not in exact lifecycle order")
|
|
|
|
return {
|
|
"artifact": MATERIAL_ARTIFACT,
|
|
"contract_version": MATERIAL_CONTRACT_VERSION,
|
|
"sequence": sequence,
|
|
"approved_proposal": approved,
|
|
"approval_snapshot": approval,
|
|
"applied_proposal": applied,
|
|
"replay_receipt": receipt,
|
|
}
|
|
|
|
|
|
def fetch_transition_rows(args: argparse.Namespace, password: str, proposal_id: str) -> dict[str, Any]:
|
|
sql = f"""begin transaction isolation level repeatable read read only;
|
|
select kb_stage.export_applied_proposal_replay_rows(
|
|
{apply_engine.sql_literal(proposal_id)}::uuid
|
|
)::text;
|
|
rollback;
|
|
"""
|
|
raw = apply_engine.run_psql(args, sql, password, redact_output_on_error=True).strip()
|
|
if not raw or "\n" in raw:
|
|
raise ExportError("applied transition export returned an invalid row count")
|
|
return _parse_json_object(raw, label="applied transition export")
|
|
|
|
|
|
def _resolved_output(path: Path) -> Path:
|
|
absolute = _absolute_path(path)
|
|
if absolute.name in {"", ".", ".."}:
|
|
raise ExportError("output path must name a file")
|
|
return absolute.parent.resolve(strict=False) / absolute.name
|
|
|
|
|
|
def _paths_alias(left: Path, right: Path) -> bool:
|
|
if left.resolve(strict=False) == right.resolve(strict=False):
|
|
return True
|
|
try:
|
|
return left.exists() and right.exists() and os.path.samefile(left, right)
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def _assert_output_safe(path: Path, protected_paths: tuple[Path, ...]) -> None:
|
|
for protected in protected_paths:
|
|
if _paths_alias(path, protected):
|
|
raise ExportError("output path aliases a protected input or engine file")
|
|
try:
|
|
metadata = path.lstat()
|
|
except FileNotFoundError:
|
|
return
|
|
if path.is_symlink() or not stat.S_ISREG(metadata.st_mode):
|
|
raise ExportError("output path must be a regular non-symlink file")
|
|
if metadata.st_nlink != 1:
|
|
raise ExportError("output path has unsafe hard-link aliases")
|
|
if stat.S_IMODE(metadata.st_mode) != 0o600:
|
|
raise ExportError("existing output permissions must be exactly 0600")
|
|
|
|
|
|
def _sync_directory(path: Path) -> None:
|
|
directory_fd = os.open(path, os.O_RDONLY)
|
|
try:
|
|
os.fsync(directory_fd)
|
|
finally:
|
|
os.close(directory_fd)
|
|
|
|
|
|
def write_private_material(
|
|
path: Path,
|
|
material: dict[str, Any],
|
|
*,
|
|
protected_paths: tuple[Path, ...],
|
|
) -> tuple[Path, str]:
|
|
"""Publish once, or accept an exact 0600 rerun without overwriting drift."""
|
|
output = _resolved_output(path)
|
|
output.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
_assert_output_safe(output, protected_paths)
|
|
payload = (json.dumps(material, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode("utf-8")
|
|
if output.exists():
|
|
if output.read_bytes() != payload:
|
|
raise ExportError("existing output contains different replay material")
|
|
return output, "unchanged"
|
|
|
|
fd, temporary_name = tempfile.mkstemp(prefix=f".{output.name}.", dir=str(output.parent))
|
|
temporary = Path(temporary_name)
|
|
published = False
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
with os.fdopen(fd, "wb") as handle:
|
|
handle.write(payload)
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
try:
|
|
os.link(temporary, output)
|
|
published = True
|
|
except FileExistsError:
|
|
_assert_output_safe(output, protected_paths)
|
|
if output.read_bytes() != payload:
|
|
raise ExportError("output changed concurrently during replay material export") from None
|
|
_sync_directory(output.parent)
|
|
finally:
|
|
try:
|
|
temporary.unlink()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
_assert_output_safe(output, protected_paths)
|
|
if output.read_bytes() != payload:
|
|
raise ExportError("published replay material failed exact readback")
|
|
return output, "created" if published else "unchanged"
|
|
|
|
|
|
def _positive_sequence(value: str) -> int:
|
|
try:
|
|
sequence = int(value)
|
|
except ValueError as exc:
|
|
raise argparse.ArgumentTypeError("sequence must be a positive integer") from exc
|
|
if sequence <= 0:
|
|
raise argparse.ArgumentTypeError("sequence must be a positive integer")
|
|
return sequence
|
|
|
|
|
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("proposal_id", help="UUID of the applied proposal")
|
|
parser.add_argument("--replay-receipt", required=True, help="private 0600 worker replay receipt")
|
|
parser.add_argument("--sequence", required=True, type=_positive_sequence, help="positive ledger sequence")
|
|
parser.add_argument("--output", required=True, help="private replay material output path")
|
|
parser.add_argument("--secrets-file", default=apply_engine.DEFAULT_SECRETS_FILE)
|
|
parser.add_argument("--container", default=apply_engine.DEFAULT_CONTAINER)
|
|
parser.add_argument("--db", default=apply_engine.DEFAULT_DB)
|
|
parser.add_argument("--host", default=apply_engine.DEFAULT_HOST)
|
|
parser.add_argument("--role", default=apply_engine.DEFAULT_ROLE)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def run(args: argparse.Namespace) -> int:
|
|
proposal_id = _canonical_uuid(args.proposal_id, label="proposal_id")
|
|
receipt_path = _absolute_path(args.replay_receipt)
|
|
receipt = _load_private_receipt(receipt_path)
|
|
try:
|
|
replay_receipt.validate_replay_receipt(receipt, expected_proposal_id=proposal_id)
|
|
except ValueError as exc:
|
|
raise ExportError("worker replay receipt failed its exact row-level contract") from exc
|
|
|
|
password = apply_engine.load_password(args.secrets_file)
|
|
rows = fetch_transition_rows(args, password, proposal_id)
|
|
material = validate_material(
|
|
{
|
|
"artifact": MATERIAL_ARTIFACT,
|
|
"contract_version": MATERIAL_CONTRACT_VERSION,
|
|
"sequence": args.sequence,
|
|
"approved_proposal": rows.get("approved_proposal"),
|
|
"approval_snapshot": rows.get("approval_snapshot"),
|
|
"applied_proposal": rows.get("applied_proposal"),
|
|
"replay_receipt": receipt,
|
|
},
|
|
expected_proposal_id=proposal_id,
|
|
expected_sequence=args.sequence,
|
|
)
|
|
protected_paths = tuple(
|
|
_absolute_path(path)
|
|
for path in (
|
|
receipt_path,
|
|
args.secrets_file,
|
|
Path(__file__),
|
|
Path(apply_engine.__file__),
|
|
Path(replay_receipt.__file__),
|
|
)
|
|
)
|
|
_, disposition = write_private_material(
|
|
Path(args.output),
|
|
material,
|
|
protected_paths=protected_paths,
|
|
)
|
|
print(f"replay material {disposition}: proposal={proposal_id} sequence={args.sequence}")
|
|
return 0
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
try:
|
|
return run(parse_args(sys.argv[1:] if argv is None else argv))
|
|
except ExportError as exc:
|
|
print(f"replay material export refused: {exc}", file=sys.stderr)
|
|
return 1
|
|
except OSError:
|
|
print("replay material export refused: private file operation failed", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|