#!/usr/bin/env python3 """Write the RPF-WP-0015-T03 synthetic E2 approval receipt for one founder window. Usage: scripts/rpf-wp-0015-e2-receipt.py OUTPUT.json [--start-in-min N] The window is 15 minutes and starts now, or N minutes from now. Writing the receipt and running the window is the founder GO for that window; D5 (2fd5849c) authorizes the exercise class only. The owner acknowledgements are the owners' State Hub receipts for the *current* review contract (interfaces/RAILIANCE-WP-0024-owner-reviews.json). They are found by subject `WP0024-OWNER-RECEIPT/v1///`, each body validated by the review module. An owner's latest receipt must be `approve`. The script refuses when any pinned artifact differs from the contract, or when a required owner has no current approval for this digest. The receipt holds no credential values, and the script never overwrites an existing file. """ import importlib.util import json import os import sys import urllib.parse import urllib.request from datetime import datetime, timedelta, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OWNERS = ("audit-core", "rapp-postgres", "railiance-platform") def review_module(): spec = importlib.util.spec_from_file_location("wp0024_review", ROOT / "scripts/wp0024-owner-review.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def approvals(review, contract, api_base): digest = review.contract_digest(contract) query = urllib.parse.urlencode({"to_agent": "railiance-platform", "limit": 1000}) with urllib.request.urlopen(f"{api_base}/messages/?{query}", timeout=20) as response: messages = json.load(response) found = {} for owner in OWNERS: # Validate each body with the review module's own parser (subject, sender, # schema). The latest receipt for this digest decides: a request-changes # after an approval withdraws it. receipts = [] for message in messages: receipt = review.parse_receipt_message(message) if receipt and receipt["owner"] == owner and receipt["contract_digest"] == digest: receipts.append((receipt["created_at"], receipt["decision"], message["id"])) if not receipts or max(receipts)[1] != "approve": sys.exit(f"no current {owner} approval for contract {digest[:8]}; run the owner round first") found[owner] = {"acknowledged": True, "message_id": max(receipts)[2]} return digest, found def main(argv): if len(argv) < 2: sys.exit(__doc__) review = review_module() contract = review.load_contract() drift = [p for p, h in contract["artifact_sha256"].items() if review.file_sha256(ROOT / p) != h] if drift: sys.exit("reviewed artifacts differ from the owner-review contract: " + ", ".join(drift)) digest, owners = approvals(review, contract, os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000")) out = argv[1] delay = int(argv[argv.index("--start-in-min") + 1]) if "--start-in-min" in argv else 0 start = datetime.now(timezone.utc).replace(second=0, microsecond=0) + timedelta(minutes=delay) end = start + timedelta(minutes=15) tag = start.strftime("%Y%m%dt%H%M") z = lambda t: t.strftime("%Y-%m-%dT%H:%M:%SZ") # noqa: E731 receipt = { "procedure": "audit-core-database-lease-recovery", "task_id": "RAILIANCE-WP-0024-T03", "status": "approved", "approval_id": f"rpf-wp-0015-t03-e2-{tag}z-founder-go", "window": {"start": z(start), "end": z(end)}, "abort_operator": "founder (platform-root), attended", "owners": owners, "synthetic_load": {"contract_id": "RAILIANCE-WP-0024-T02-SYNTHETIC-01", "driver_revision": "sha256:941ba251f638869626b06e9cbf430c70188c0bdf4715e3eff9c7610366f68662"}, "synthetic_sender": {"identity": f"t03-e2-synthetic-sender-{start:%Y%m%d}", "source": f"t03-e2-synthetic-{start:%Y%m%d}", "tenant": f"tenant:trial:t03-e2-synthetic-{start:%Y%m%d}", "expires_at": z(end)}, } with open(out, "x") as handle: json.dump(receipt, handle, indent=2) handle.write("\n") print(json.dumps({"approval_id": receipt["approval_id"], "window": receipt["window"], "contract_digest": digest, "confirm": f"audit-core-synthetic-sender:{receipt['approval_id']}:attended"})) if __name__ == "__main__": main(sys.argv)