Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
116 lines
4.4 KiB
Python
Executable file
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())
|