#!/usr/bin/env python3 """Generate the auditable AXIS parity evidence index and release decision.""" from __future__ import annotations import hashlib import json from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] REPORT = ROOT / "coverage/axis-release-evidence.report.json" def canonical(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def load(path: str) -> dict[str, Any]: return json.loads((ROOT / path).read_text(encoding="utf-8")) def sha(path: str) -> str: return hashlib.sha256((ROOT / path).read_bytes()).hexdigest() def entry(path: str) -> dict[str, Any]: value = load(path) return {"path": path, "schema": value.get("schema"), "task_id": value.get("task_id"), "status": value.get("status"), "implementation_status": value.get("implementation_status"), "report_sha256": value.get("report_sha256"), "file_sha256": sha(path)} def generate() -> dict[str, Any]: reports = [ "coverage/axis-source-inventory.report.json", "coverage/axis-contract.report.json", "coverage/axis-control.report.json", "coverage/axis-view-edit.report.json", "coverage/axis-case-parity.report.json", "coverage/axis-test-matrix.report.json", "coverage/gui-browser-visual-fe-m4.report.json", ] gate = load("coverage/release-candidate-gate.report.json") entries = [entry(path) for path in reports] counts = {status: sum(row["status"] == status for row in entries) for status in ("verified", "partial", "blocked")} report: dict[str, Any] = { "schema": "axis-release-evidence-report.v1", "task_id": "AXIS-REL-01/02", "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "status": "partial", "implementation_status": "evidence_index_complete_release_blocked", "reports": entries, "summary": {"total_reports": len(entries), "verified": counts["verified"], "partial": counts["partial"], "blocked": counts["blocked"], "chromium_authorized": True, "verified_case_count": 5, "blocked_case_count": 6}, "release_decision": { "status": "blocked", "release_action": gate.get("release_action", "STOP_RELEASE"), "candidate_eligible": gate.get("candidate_eligible", False), "global_blocked_count": gate.get("blocked_count"), "reasons": [ "Firefox/Safari minimum browser matrix remains TBD/unavailable", "OPFS worker timeout and crash/restart Playwright evidence unavailable", "lathe/gantry/VisMach-5-axis exact resource closure remains blocked", "official kinematics oracle remains separately blocked", ], }, "ownership": {"source_oracle": "locked LinuxCNC 2.10.0~pre0", "runtime_authority": "Dedicated Worker/WASM", "visual_authority": "Chromium authorized slice only", "physical_safety": "REAL-EXCLUDED"}, "integrity": {"all_report_files_present": all((ROOT / path).is_file() for path in reports), "all_report_hashes_present": all(row["report_sha256"] for row in entries), "release_gate_preserved": gate.get("release_action") == "STOP_RELEASE" and gate.get("candidate_eligible") is False}, "boundaries": ["index does not promote partial reports", "Chromium authorization is scoped to DOM/visual/performance evidence", "release gate remains STOP_RELEASE until all blockers are separately closed"], } report["report_sha256"] = hashlib.sha256(canonical(report).encode()).hexdigest() REPORT.parent.mkdir(parents=True, exist_ok=True) REPORT.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report if __name__ == "__main__": result = generate() print(f"axis_release_evidence={result['status']} reports={result['summary']['total_reports']} release={result['release_decision']['release_action']} report_sha256={result['report_sha256']}")