Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
551 lines
20 KiB
Python
551 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Direct, value-safe owner review interface for RAILIANCE-WP-0024.
|
|
|
|
The executable surface is intentionally closed: contract files select named
|
|
read-only checks, never shell commands. Approvals are bound to the canonical
|
|
contract digest and the SHA-256 digests of the owner's reviewed artifacts.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from typing import Any, Callable
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DEFAULT_CONTRACT = ROOT / "interfaces" / "RAILIANCE-WP-0024-owner-reviews.json"
|
|
DEFAULT_API_BASE = os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000")
|
|
RECEIPT_PREFIX = "WP0024-OWNER-RECEIPT"
|
|
KNOWN_CHECKS = {
|
|
"focused-unit-tests",
|
|
"database-lease-preflight",
|
|
"node-reboot-preflight",
|
|
}
|
|
|
|
|
|
class ReviewError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def canonical_json(value: Any) -> str:
|
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
|
|
|
|
def sha256_bytes(value: bytes) -> str:
|
|
return hashlib.sha256(value).hexdigest()
|
|
|
|
|
|
def file_sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def load_contract(path: Path = DEFAULT_CONTRACT) -> dict[str, Any]:
|
|
try:
|
|
value = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError) as exc:
|
|
raise ReviewError(f"review contract is unavailable or invalid: {path}") from exc
|
|
if not isinstance(value, dict):
|
|
raise ReviewError("review contract must be a JSON object")
|
|
if value.get("interface") != "railiance.owner-review" or value.get("version") != 1:
|
|
raise ReviewError("unsupported owner-review interface/version")
|
|
owners = value.get("owners")
|
|
hashes = value.get("artifact_sha256")
|
|
task_owners = value.get("task_owners")
|
|
if (
|
|
not isinstance(owners, dict)
|
|
or not owners
|
|
or not isinstance(hashes, dict)
|
|
or not isinstance(task_owners, dict)
|
|
or not task_owners
|
|
):
|
|
raise ReviewError(
|
|
"review contract requires owners, task_owners and artifact_sha256 maps"
|
|
)
|
|
for owner, review in owners.items():
|
|
if not isinstance(owner, str) or not isinstance(review, dict):
|
|
raise ReviewError("invalid owner review entry")
|
|
artifacts = review.get("artifacts")
|
|
checks = review.get("checks")
|
|
assertions = review.get("assertions")
|
|
if not all(isinstance(item, str) for item in artifacts or []):
|
|
raise ReviewError(f"{owner}: artifacts must be strings")
|
|
if not artifacts or any(item not in hashes for item in artifacts):
|
|
raise ReviewError(f"{owner}: every artifact requires a pinned digest")
|
|
if not checks or any(item not in KNOWN_CHECKS for item in checks):
|
|
raise ReviewError(f"{owner}: unknown or missing read-only check")
|
|
if not assertions or not all(isinstance(item, str) for item in assertions):
|
|
raise ReviewError(f"{owner}: assertions must be non-empty strings")
|
|
expected_tasks = sorted(
|
|
task for task, required in task_owners.items() if owner in required
|
|
)
|
|
if sorted(review.get("tasks") or []) != expected_tasks:
|
|
raise ReviewError(f"{owner}: tasks disagree with task_owners")
|
|
for task, required in task_owners.items():
|
|
if not isinstance(task, str) or not isinstance(required, list) or not required:
|
|
raise ReviewError("task_owners entries must be non-empty owner lists")
|
|
if len(set(required)) != len(required) or any(owner not in owners for owner in required):
|
|
raise ReviewError(f"{task}: task_owners contains unknown or duplicate owners")
|
|
return value
|
|
|
|
|
|
def contract_digest(contract: dict[str, Any]) -> str:
|
|
return sha256_bytes(canonical_json(contract).encode("utf-8"))
|
|
|
|
|
|
def owner_review(contract: dict[str, Any], owner: str) -> dict[str, Any]:
|
|
review = contract["owners"].get(owner)
|
|
if not isinstance(review, dict):
|
|
choices = ", ".join(sorted(contract["owners"]))
|
|
raise ReviewError(f"unknown owner {owner!r}; choose one of: {choices}")
|
|
return review
|
|
|
|
|
|
def artifact_state(
|
|
contract: dict[str, Any], owner: str, *, root: Path = ROOT
|
|
) -> dict[str, dict[str, Any]]:
|
|
review = owner_review(contract, owner)
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for relative in review["artifacts"]:
|
|
expected = contract["artifact_sha256"][relative]
|
|
target = (root / relative).resolve()
|
|
try:
|
|
target.relative_to(root.resolve())
|
|
except ValueError as exc:
|
|
raise ReviewError(f"artifact escapes repository root: {relative}") from exc
|
|
try:
|
|
observed = file_sha256(target)
|
|
except OSError:
|
|
observed = None
|
|
result[relative] = {
|
|
"expected_sha256": expected,
|
|
"observed_sha256": observed,
|
|
"matches": observed == expected,
|
|
}
|
|
return result
|
|
|
|
|
|
def check_command(check_id: str) -> list[str]:
|
|
if check_id == "focused-unit-tests":
|
|
return [
|
|
sys.executable,
|
|
"-m",
|
|
"unittest",
|
|
"tests/test_audit_core_recovery_preflight.py",
|
|
"tests/test_audit_core_database_lease_recovery.py",
|
|
"tests/test_wp0024_owner_review.py",
|
|
]
|
|
if check_id == "database-lease-preflight":
|
|
return [sys.executable, "scripts/audit-core-recovery-preflight.py", "database-lease"]
|
|
if check_id == "node-reboot-preflight":
|
|
return [sys.executable, "scripts/audit-core-recovery-preflight.py", "node-reboot"]
|
|
raise ReviewError(f"unknown check id: {check_id}")
|
|
|
|
|
|
def run_named_check(
|
|
check_id: str,
|
|
*,
|
|
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
|
) -> dict[str, Any]:
|
|
completed = runner(
|
|
check_command(check_id),
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
result: dict[str, Any] = {
|
|
"id": check_id,
|
|
"passed": completed.returncode == 0,
|
|
"exit_code": completed.returncode,
|
|
"read_only": True,
|
|
}
|
|
if check_id.endswith("-preflight") and completed.returncode == 0:
|
|
try:
|
|
payload = json.loads(completed.stdout)
|
|
except json.JSONDecodeError:
|
|
result["passed"] = False
|
|
result["detail"] = "preflight returned invalid JSON"
|
|
else:
|
|
safe = (
|
|
isinstance(payload, dict)
|
|
and payload.get("automated_checks_passed") is True
|
|
and payload.get("secret_values_observed") is False
|
|
)
|
|
result["passed"] = bool(safe)
|
|
result["automated_checks_passed"] = payload.get("automated_checks_passed")
|
|
result["ready_for_live_execution"] = payload.get("ready_for_live_execution")
|
|
result["secret_values_observed"] = payload.get("secret_values_observed")
|
|
if not safe:
|
|
result["detail"] = "preflight safety/automated gate did not pass"
|
|
elif completed.returncode != 0:
|
|
result["detail"] = "check failed; run the named interface verify command locally for diagnosis"
|
|
return result
|
|
|
|
|
|
def verify(
|
|
contract: dict[str, Any],
|
|
owner: str,
|
|
*,
|
|
root: Path = ROOT,
|
|
check_runner: Callable[[str], dict[str, Any]] = run_named_check,
|
|
) -> dict[str, Any]:
|
|
artifacts = artifact_state(contract, owner, root=root)
|
|
checks: list[dict[str, Any]] = []
|
|
if all(item["matches"] for item in artifacts.values()):
|
|
checks = [check_runner(item) for item in owner_review(contract, owner)["checks"]]
|
|
return {
|
|
"interface": contract["interface"],
|
|
"version": contract["version"],
|
|
"workplan_id": contract["workplan_id"],
|
|
"owner": owner,
|
|
"contract_digest": contract_digest(contract),
|
|
"artifacts": artifacts,
|
|
"checks": checks,
|
|
"passed": bool(checks)
|
|
and all(item["matches"] for item in artifacts.values())
|
|
and all(item["passed"] for item in checks),
|
|
"secret_values_observed": False,
|
|
}
|
|
|
|
|
|
def validate_reviewer(value: str) -> str:
|
|
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:@/+\-]{1,127}", value):
|
|
raise ReviewError("reviewer must be a stable 2-128 character identifier")
|
|
return value
|
|
|
|
|
|
def validate_note(value: str) -> str:
|
|
note = value.strip()
|
|
if not note or len(note) > 2000:
|
|
raise ReviewError("request-changes requires a note of 1-2000 characters")
|
|
forbidden = ("BEGIN PRIVATE KEY", "BEGIN OPENSSH PRIVATE KEY", "AGE-SECRET-KEY-1", "hvs.")
|
|
if any(marker in note for marker in forbidden):
|
|
raise ReviewError("note appears to contain credential material; do not submit it")
|
|
return note
|
|
|
|
|
|
def receipt_subject(receipt: dict[str, Any]) -> str:
|
|
return "/".join(
|
|
(
|
|
RECEIPT_PREFIX,
|
|
f"v{receipt['version']}",
|
|
receipt["owner"],
|
|
receipt["decision"],
|
|
receipt["contract_digest"],
|
|
)
|
|
)
|
|
|
|
|
|
def build_receipt(
|
|
contract: dict[str, Any],
|
|
owner: str,
|
|
decision: str,
|
|
reviewer: str,
|
|
*,
|
|
verification: dict[str, Any] | None = None,
|
|
note: str | None = None,
|
|
now: datetime | None = None,
|
|
) -> dict[str, Any]:
|
|
if decision not in contract["decisions"]:
|
|
raise ReviewError(f"unsupported decision: {decision}")
|
|
if decision == "approve" and (not verification or verification.get("passed") is not True):
|
|
raise ReviewError("approval requires a passing direct verification")
|
|
if decision == "request-changes":
|
|
note = validate_note(note or "")
|
|
review = owner_review(contract, owner)
|
|
timestamp = (now or datetime.now(UTC)).astimezone(UTC).isoformat().replace("+00:00", "Z")
|
|
receipt: dict[str, Any] = {
|
|
"interface": contract["interface"],
|
|
"version": contract["version"],
|
|
"workplan_id": contract["workplan_id"],
|
|
"workplan_uuid": contract["workplan_uuid"],
|
|
"owner": owner,
|
|
"reviewer": validate_reviewer(reviewer),
|
|
"decision": decision,
|
|
"created_at": timestamp,
|
|
"contract_digest": contract_digest(contract),
|
|
"artifact_sha256": {
|
|
path: contract["artifact_sha256"][path] for path in review["artifacts"]
|
|
},
|
|
"assertion_count": len(review["assertions"]),
|
|
"secret_values_observed": False,
|
|
}
|
|
if verification:
|
|
receipt["checks"] = [
|
|
{"id": item["id"], "passed": item["passed"], "read_only": True}
|
|
for item in verification["checks"]
|
|
]
|
|
if note is not None:
|
|
receipt["note"] = note
|
|
return receipt
|
|
|
|
|
|
def http_json(
|
|
method: str,
|
|
url: str,
|
|
payload: dict[str, Any] | None = None,
|
|
*,
|
|
opener: Callable[..., Any] = urllib.request.urlopen,
|
|
) -> Any:
|
|
data = canonical_json(payload).encode("utf-8") if payload is not None else None
|
|
request = urllib.request.Request(
|
|
url,
|
|
data=data,
|
|
method=method,
|
|
headers={"Content-Type": "application/json"} if data else {},
|
|
)
|
|
try:
|
|
with opener(request, timeout=10) as response:
|
|
return json.loads(response.read().decode("utf-8"))
|
|
except (OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
|
|
raise ReviewError(f"State Hub request failed: {method} {url}") from exc
|
|
|
|
|
|
def post_receipt(receipt: dict[str, Any], api_base: str) -> dict[str, Any]:
|
|
payload = {
|
|
"from_agent": receipt["owner"],
|
|
"to_agent": "railiance-platform",
|
|
"subject": receipt_subject(receipt),
|
|
"body": canonical_json(receipt),
|
|
}
|
|
response = http_json("POST", f"{api_base.rstrip('/')}/messages/", payload)
|
|
if not isinstance(response, dict):
|
|
raise ReviewError("State Hub returned an invalid receipt response")
|
|
return response
|
|
|
|
|
|
def parse_receipt_message(message: dict[str, Any]) -> dict[str, Any] | None:
|
|
subject = message.get("subject")
|
|
body = message.get("body")
|
|
if not isinstance(subject, str) or not subject.startswith(RECEIPT_PREFIX + "/"):
|
|
return None
|
|
if not isinstance(body, str):
|
|
return None
|
|
try:
|
|
receipt = json.loads(body)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
if not isinstance(receipt, dict):
|
|
return None
|
|
required = {
|
|
"interface": str,
|
|
"version": int,
|
|
"workplan_id": str,
|
|
"workplan_uuid": str,
|
|
"owner": str,
|
|
"reviewer": str,
|
|
"decision": str,
|
|
"created_at": str,
|
|
"contract_digest": str,
|
|
"artifact_sha256": dict,
|
|
"assertion_count": int,
|
|
"secret_values_observed": bool,
|
|
}
|
|
if any(not isinstance(receipt.get(key), kind) for key, kind in required.items()):
|
|
return None
|
|
if (
|
|
receipt["interface"] != "railiance.owner-review"
|
|
or receipt["version"] != 1
|
|
or receipt["workplan_id"] != "RAILIANCE-WP-0024"
|
|
or receipt["decision"] not in {"approve", "request-changes"}
|
|
or receipt["secret_values_observed"] is not False
|
|
or not re.fullmatch(r"[0-9a-f]{64}", receipt["contract_digest"])
|
|
):
|
|
return None
|
|
try:
|
|
datetime.fromisoformat(receipt["created_at"].replace("Z", "+00:00"))
|
|
expected_subject = receipt_subject(receipt)
|
|
except (KeyError, TypeError, ValueError):
|
|
return None
|
|
if expected_subject != subject:
|
|
return None
|
|
if message.get("from_agent") != receipt.get("owner"):
|
|
return None
|
|
return receipt
|
|
|
|
|
|
def aggregate_status(
|
|
contract: dict[str, Any], messages: list[dict[str, Any]]
|
|
) -> dict[str, Any]:
|
|
digest = contract_digest(contract)
|
|
candidates: dict[str, list[dict[str, Any]]] = {
|
|
owner: [] for owner in contract["owners"]
|
|
}
|
|
stale: dict[str, int] = {owner: 0 for owner in contract["owners"]}
|
|
for message in messages:
|
|
receipt = parse_receipt_message(message)
|
|
if not receipt or receipt.get("owner") not in candidates:
|
|
continue
|
|
owner = receipt["owner"]
|
|
if receipt.get("contract_digest") != digest:
|
|
stale[owner] += 1
|
|
continue
|
|
expected = {
|
|
path: contract["artifact_sha256"][path]
|
|
for path in contract["owners"][owner]["artifacts"]
|
|
}
|
|
review = contract["owners"][owner]
|
|
try:
|
|
validate_reviewer(receipt["reviewer"])
|
|
except ReviewError:
|
|
stale[owner] += 1
|
|
continue
|
|
if (
|
|
receipt.get("interface") != contract["interface"]
|
|
or receipt.get("version") != contract["version"]
|
|
or receipt.get("workplan_id") != contract["workplan_id"]
|
|
or receipt.get("workplan_uuid") != contract["workplan_uuid"]
|
|
or receipt.get("artifact_sha256") != expected
|
|
or receipt.get("assertion_count") != len(review["assertions"])
|
|
):
|
|
stale[owner] += 1
|
|
continue
|
|
if receipt["decision"] == "approve":
|
|
checks = receipt.get("checks")
|
|
if (
|
|
not isinstance(checks, list)
|
|
or [item.get("id") for item in checks if isinstance(item, dict)]
|
|
!= review["checks"]
|
|
or any(
|
|
not isinstance(item, dict)
|
|
or item.get("passed") is not True
|
|
or item.get("read_only") is not True
|
|
for item in checks
|
|
)
|
|
):
|
|
stale[owner] += 1
|
|
continue
|
|
candidates[owner].append(receipt)
|
|
owners: dict[str, Any] = {}
|
|
for owner, receipts in candidates.items():
|
|
latest = max(receipts, key=lambda item: item.get("created_at", ""), default=None)
|
|
owners[owner] = {
|
|
"decision": latest.get("decision") if latest else "missing",
|
|
"reviewer": latest.get("reviewer") if latest else None,
|
|
"created_at": latest.get("created_at") if latest else None,
|
|
"stale_receipt_count": stale[owner],
|
|
}
|
|
tasks = {
|
|
task: {
|
|
"required_owners": required,
|
|
"decisions": {owner: owners[owner]["decision"] for owner in required},
|
|
"all_approved": all(owners[owner]["decision"] == "approve" for owner in required),
|
|
}
|
|
for task, required in contract["task_owners"].items()
|
|
}
|
|
try:
|
|
artifact_current = all(
|
|
file_sha256(ROOT / path) == expected
|
|
for path, expected in contract["artifact_sha256"].items()
|
|
)
|
|
except OSError:
|
|
artifact_current = False
|
|
return {
|
|
"interface": contract["interface"],
|
|
"version": contract["version"],
|
|
"workplan_id": contract["workplan_id"],
|
|
"contract_digest": digest,
|
|
"contract_artifacts_current": artifact_current,
|
|
"owners": owners,
|
|
"tasks": tasks,
|
|
"all_approved": artifact_current
|
|
and all(item["all_approved"] for item in tasks.values()),
|
|
}
|
|
|
|
|
|
def fetch_status(contract: dict[str, Any], api_base: str) -> dict[str, Any]:
|
|
query = urllib.parse.urlencode({"to_agent": "railiance-platform", "limit": 500})
|
|
value = http_json("GET", f"{api_base.rstrip('/')}/messages/?{query}")
|
|
if not isinstance(value, list):
|
|
raise ReviewError("State Hub messages response is not a list")
|
|
return aggregate_status(contract, value)
|
|
|
|
|
|
def parser() -> argparse.ArgumentParser:
|
|
result = argparse.ArgumentParser(description=__doc__)
|
|
result.add_argument("--contract", type=Path, default=DEFAULT_CONTRACT)
|
|
result.add_argument("--api-base", default=DEFAULT_API_BASE)
|
|
subparsers = result.add_subparsers(dest="command", required=True)
|
|
for command in ("show", "verify"):
|
|
child = subparsers.add_parser(command)
|
|
child.add_argument("--owner", required=True)
|
|
approve = subparsers.add_parser("approve")
|
|
approve.add_argument("--owner", required=True)
|
|
approve.add_argument("--reviewer", required=True)
|
|
changes = subparsers.add_parser("request-changes")
|
|
changes.add_argument("--owner", required=True)
|
|
changes.add_argument("--reviewer", required=True)
|
|
changes.add_argument("--note", required=True)
|
|
subparsers.add_parser("status")
|
|
return result
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parser().parse_args(argv)
|
|
try:
|
|
contract = load_contract(args.contract)
|
|
if args.command == "show":
|
|
review = owner_review(contract, args.owner)
|
|
output = {
|
|
"interface": contract["interface"],
|
|
"version": contract["version"],
|
|
"workplan_id": contract["workplan_id"],
|
|
"owner": args.owner,
|
|
"contract_digest": contract_digest(contract),
|
|
**review,
|
|
"artifacts_state": artifact_state(contract, args.owner),
|
|
"decisions": contract["decisions"],
|
|
}
|
|
elif args.command == "verify":
|
|
output = verify(contract, args.owner)
|
|
elif args.command == "approve":
|
|
verification = verify(contract, args.owner)
|
|
receipt = build_receipt(
|
|
contract, args.owner, "approve", args.reviewer, verification=verification
|
|
)
|
|
posted = post_receipt(receipt, args.api_base)
|
|
output = {
|
|
"submitted": True,
|
|
"message_id": posted.get("id"),
|
|
"receipt": receipt,
|
|
}
|
|
elif args.command == "request-changes":
|
|
receipt = build_receipt(
|
|
contract,
|
|
args.owner,
|
|
"request-changes",
|
|
args.reviewer,
|
|
note=args.note,
|
|
)
|
|
posted = post_receipt(receipt, args.api_base)
|
|
output = {
|
|
"submitted": True,
|
|
"message_id": posted.get("id"),
|
|
"receipt": receipt,
|
|
}
|
|
else:
|
|
output = fetch_status(contract, args.api_base)
|
|
except ReviewError as exc:
|
|
print(canonical_json({"ok": False, "error": str(exc)}), file=sys.stderr)
|
|
return 2
|
|
print(json.dumps(output, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|