railiance-platform/scripts/rpf-wp-0015-e2-receipt.py
codex dad91e8c44 Route the sender lane's cluster writes through AUDIT_KUBECONFIG too.
Round-10 review (railiance-platform) found that ContainedOperator inherited
an ssh-only kubectl, so an E2 would write OpenBao and then fail its sync,
restart and rollback. Also: a bad AUDIT_KUBECONFIG now leaves window-error
evidence, and the receipt script validates bodies and lets a later
request-changes withdraw an approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 236506@bnt-lap001
Assistant-Session: 09087018-91e1-48c5-ab2f-acfbb62c92e4
2026-11-03 15:09:59 +01:00

99 lines
4.5 KiB
Python
Executable file

#!/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/<owner>/<decision>/<contract digest>`, 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)