Add direct WP-0024 owner review interface
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
3f9e4535d1
commit
0d9cebedea
6 changed files with 877 additions and 1 deletions
523
scripts/wp0024-owner-review.py
Normal file
523
scripts/wp0024-owner-review.py
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
#!/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")
|
||||
if not isinstance(owners, dict) or not owners or not isinstance(hashes, dict):
|
||||
raise ReviewError("review contract requires 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")
|
||||
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],
|
||||
}
|
||||
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,
|
||||
"all_approved": artifact_current
|
||||
and all(item["decision"] == "approve" for item in owners.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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue