Index AXIS parity release evidence
This commit is contained in:
77
scripts/generate_axis_release_evidence.py
Normal file
77
scripts/generate_axis_release_evidence.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/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']}")
|
||||
36
scripts/test_axis_release_evidence.py
Normal file
36
scripts/test_axis_release_evidence.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Contract tests for AXIS-REL-01/02 evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_axis_release_evidence
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REPORT = ROOT / "coverage/axis-release-evidence.report.json"
|
||||
|
||||
|
||||
class AxisReleaseEvidenceTests(unittest.TestCase):
|
||||
def test_contract(self) -> None:
|
||||
report = json.loads(REPORT.read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "axis-release-evidence-report.v1")
|
||||
self.assertEqual(report["task_id"], "AXIS-REL-01/02")
|
||||
self.assertEqual(report["status"], "partial")
|
||||
self.assertEqual(report["summary"]["total_reports"], 7)
|
||||
self.assertGreaterEqual(report["summary"]["verified"], 5)
|
||||
self.assertEqual(report["release_decision"]["release_action"], "STOP_RELEASE")
|
||||
self.assertFalse(report["release_decision"]["candidate_eligible"])
|
||||
self.assertTrue(report["integrity"]["release_gate_preserved"])
|
||||
|
||||
def test_self_hash(self) -> None:
|
||||
report = json.loads(REPORT.read_text(encoding="utf-8"))
|
||||
unsigned = {key: value for key, value in report.items() if key != "report_sha256"}
|
||||
self.assertEqual(report["report_sha256"], hashlib.sha256(generate_axis_release_evidence.canonical(unsigned).encode()).hexdigest())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user