mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
58 lines
2.0 KiB
Python
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())
|