rein-aharness/scripts/verify_runtime_lock.py
tegwick 4310c15eac feat(deploy): gate claims on pinned runtime readiness
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
2026-09-04 20:08:37 +02:00

116 lines
4.4 KiB
Python
Executable file

#!/usr/bin/env python3
"""Verify production sibling sources against deploy/runtime-contract-lock.json."""
from __future__ import annotations
import json
import re
import subprocess
import sys
import tomllib
from pathlib import Path
from typing import Any
def _git(path: Path, *args: str) -> str:
return subprocess.check_output(
["git", "-C", str(path), *args],
text=True,
stderr=subprocess.DEVNULL,
timeout=10,
).strip()
def verify(lock_path: Path) -> dict[str, Any]:
lock_path = lock_path.resolve()
repo_root = lock_path.parent.parent
payload = json.loads(lock_path.read_text(encoding="utf-8"))
if set(payload) != {"schema_version", "contracts", "dependencies"}:
raise ValueError("runtime lock has invalid top-level fields")
if payload.get("schema_version") != "1":
raise ValueError("unsupported runtime lock schema")
contracts = payload.get("contracts")
if not isinstance(contracts, dict) or set(contracts) != {
"activity_core_contract_commit",
"activity_core_schema",
"glas_contract_version",
}:
raise ValueError("runtime lock has invalid contract fields")
activity_commit = str(contracts["activity_core_contract_commit"])
if re.fullmatch(r"[0-9a-f]{40}", activity_commit) is None:
raise ValueError("Activity Core contract commit must be a full Git object id")
activity_schema = str(contracts["activity_core_schema"])
if re.fullmatch(r"[0-9]{4}", activity_schema) is None:
raise ValueError("Activity Core schema must be a four-digit migration id")
glas_contract = str(contracts["glas_contract_version"])
if re.fullmatch(r"[0-9]+\.[0-9]+", glas_contract) is None:
raise ValueError("Glas contract version must pin major.minor")
dependencies = payload.get("dependencies")
if not isinstance(dependencies, list) or not dependencies:
raise ValueError("runtime lock has no dependencies")
results: list[dict[str, Any]] = []
for entry in dependencies:
if not isinstance(entry, dict) or set(entry) != {
"commit",
"distribution",
"source",
"version",
}:
raise ValueError("runtime dependency entry has invalid fields")
source = (repo_root / str(entry["source"])).resolve()
if not source.is_dir():
raise ValueError(f"missing source checkout for {entry['distribution']}")
head = _git(source, "rev-parse", "HEAD")
if head != entry["commit"]:
raise ValueError(
f"{entry['distribution']} revision mismatch: "
f"expected {entry['commit']} actual {head}"
)
if _git(source, "status", "--porcelain"):
raise ValueError(f"{entry['distribution']} source checkout is dirty")
project = tomllib.loads(
(source / "pyproject.toml").read_text(encoding="utf-8")
).get("project", {})
if project.get("name") != entry["distribution"]:
raise ValueError(f"{entry['distribution']} package name mismatch")
if project.get("version") != entry["version"]:
raise ValueError(f"{entry['distribution']} package version mismatch")
results.append(
{
"distribution": entry["distribution"],
"version": entry["version"],
"commit": head,
"clean": True,
}
)
return {"ok": True, "schema_version": "1", "dependencies": results}
def main(argv: list[str] | None = None) -> int:
args = list(sys.argv[1:] if argv is None else argv)
if len(args) > 1:
print("usage: verify_runtime_lock.py [lock-file]", file=sys.stderr)
return 2
default = Path(__file__).resolve().parents[1] / "deploy/runtime-contract-lock.json"
lock_path = Path(args[0]) if args else default
try:
report = verify(lock_path)
except (OSError, ValueError, subprocess.SubprocessError, json.JSONDecodeError) as exc:
print(
json.dumps(
{
"ok": False,
"error": f"runtime lock verification failed ({type(exc).__name__})",
"detail": str(exc)[:500],
},
sort_keys=True,
)
)
return 1
print(json.dumps(report, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())