Files
CosmicClash/scripts/verify_release_gate.py
2026-09-01 18:26:56 +01:00

58 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""Fail-closed validation for a multiplayer promotion evidence report."""
import json
import sys
from typing import Any
STAGES = ("development", "internal", "casual-canary", "casual", "provisional-ranked", "ranked")
def _required_true(report: dict[str, Any], key: str) -> None:
value: Any = report
for part in key.split("."):
if not isinstance(value, dict) or part not in value:
raise ValueError(f"missing gate: {key}")
value = value[part]
if value is not True:
raise ValueError(f"gate did not pass: {key}")
def validate_release_report(report: dict[str, Any]) -> tuple[str, str]:
if not isinstance(report, dict):
raise ValueError("release report must be an object")
source = report.get("from_stage")
target = report.get("to_stage")
if source not in STAGES or target not in STAGES:
raise ValueError("from_stage and to_stage must be known release stages")
if STAGES.index(target) != STAGES.index(source) + 1:
raise ValueError(f"promotion must advance exactly one stage: {source!r} -> {target!r}")
if not isinstance(report.get("release_id"), str) or not report["release_id"].strip():
raise ValueError("release_id is required")
for gate in (
"slo_passed", "security_passed", "cost_passed", "rollback_rehearsed",
"playtests.eu_passed", "playtests.na_passed", "legacy.phase6_passed",
"legacy.enet_passed",
):
_required_true(report, gate)
return source, target
def main() -> int:
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} report.json", file=sys.stderr)
return 2
try:
with open(sys.argv[1], encoding="utf-8") as handle:
source, target = validate_release_report(json.load(handle))
except (OSError, ValueError, json.JSONDecodeError) as error:
print(f"release gate failed: {error}", file=sys.stderr)
return 1
print(f"release gate passed: {source} -> {target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())