#!/usr/bin/env python3 """Aggregate case-level AXIS parity evidence without hiding blocked families.""" 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-case-parity.report.json" MATRIX = ROOT / "coverage/sim-case-matrix-s8-06.report.json" def canonical(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def file_hash(path: Path) -> str | None: return hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None def report(path: str) -> dict[str, Any]: value = json.loads((ROOT / path).read_text(encoding="utf-8")) return {"path": path, "schema": value.get("schema"), "task_id": value.get("task_id"), "status": value.get("status"), "sha256": file_hash(ROOT / path)} def case_row(path: str, family: str, status: str, evidence: list[str], reason: str | None = None) -> dict[str, Any]: result: dict[str, Any] = {"path": path, "family": family, "status": status, "source_sha256": file_hash(ROOT / path), "evidence": evidence} if reason: result["reason"] = reason return result def generate() -> dict[str, Any]: matrix = json.loads(MATRIX.read_text(encoding="utf-8")) sample = matrix.get("execution", {}).get("sample", {}).get("cases", []) by_path = {row.get("path"): row for row in sample if isinstance(row, dict)} selected = [ case_row("linuxcnc/configs/sim/axis/minimal_xyz.ini", "cartesian/trivkins", "verified", ["coverage/minimal-xyz-vertical-s8-04.report.json", "AXIS-CTRL-01..08", "AXIS-VIEW-01..04"]), case_row("linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini", "corexy/kins", "verified", ["coverage/corexy-dual-path-s8-05.report.json", "AXIS-VIEW-01..04"]), case_row("linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini", "corexy/hal", "verified", ["coverage/corexy-dual-path-s8-05.report.json", "AXIS-VIEW-01..04"]), case_row("linuxcnc/configs/sim/axis/ldelta.ini", "lineardelta", "verified", ["coverage/machine-families-s8-08.report.json", "AXIS-VIEW-01..04"]), case_row("linuxcnc/configs/sim/axis/rdelta.ini", "rotarydelta", "verified", ["coverage/machine-families-s8-08.report.json", "AXIS-VIEW-01..04"]), ] blocked_paths = [ "linuxcnc/configs/sim/axis/lathe.ini", "linuxcnc/configs/sim/axis/lathe-fanucy/lathe-fanucy.ini", "linuxcnc/configs/sim/axis/gantry/gantry.ini", "linuxcnc/configs/sim/axis/gantry/gantry_mm.ini", "linuxcnc/configs/sim/axis/vismach/5axis/bridgemill/5axis.ini", "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", ] blocked = [] for path in blocked_paths: row = by_path.get(path, {}) blocked.append(case_row(path, "lathe/gantry/vismach-5axis", "blocked", ["coverage/sim-case-matrix-s8-06.report.json"], ", ".join(row.get("missing", [])) or "resource/startup matrix blocker")) frontend = ["coverage/axis-control.report.json", "coverage/axis-view-edit.report.json", "coverage/gui-browser-visual-fe-m4.report.json"] report_data: dict[str, Any] = { "schema": "axis-case-parity-report.v1", "task_id": "AXIS-CASE-01..04", "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "status": "partial", "implementation_status": "representative_cases_verified_remaining_families_blocked", "cases": selected + blocked, "family_summary": { "cartesian_trivkins": {"status": "verified", "cases": 1}, "corexy_dual_path": {"status": "verified", "cases": 2}, "delta_representatives": {"status": "verified", "cases": 2}, "lathe_gantry_vismach_5axis": {"status": "blocked", "cases": len(blocked), "reason": "exact resource/startup blockers remain in S8-06; official kinematics oracle separately blocked"}, }, "evidence_reports": [report(path) for path in ["coverage/minimal-xyz-vertical-s8-04.report.json", "coverage/corexy-dual-path-s8-05.report.json", "coverage/machine-families-s8-08.report.json", "coverage/sim-case-matrix-s8-06.report.json", *frontend]], "checks": { "case_01_minimal_xyz": {"status": "verified", "endpoint_recovery": True, "controls": True, "frontend_authority": True}, "case_02_corexy_dual_path": {"status": "verified", "kins": True, "hal": True, "mode_parity": True, "recovery_parity": True}, "case_03_lathe_gantry": {"status": "blocked", "resource_matrix": "BLOCKED_RESOURCE", "oracle": "separate"}, "case_04_vismach_5axis": {"status": "blocked", "resource_matrix": "BLOCKED_RESOURCE", "oracle": "official_kinematics_oracle_blocked"}, "exact_paths": {"status": "verified", "basename_substitution": False, "matrix_case_count": matrix.get("counts", {})}, "frontend_mapping": {"status": "verified", "reports": frontend, "worker_snapshot_owner": True}, "blocked_visibility": {"status": "verified", "resource_and_oracle_blockers_retained": True}, }, "boundaries": ["verified cases are representative, not full 235-case acceptance", "lathe/gantry/VisMach/5-axis remain resource/oracle blocked", "no native oracle is inferred from Worker parity", "physical hardware and host GUI remain excluded"], } report_data["report_sha256"] = hashlib.sha256(canonical(report_data).encode()).hexdigest() REPORT.parent.mkdir(parents=True, exist_ok=True) REPORT.write_text(json.dumps(report_data, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report_data if __name__ == "__main__": result = generate() print(f"axis_case_parity={result['status']} verified={sum(row['status']=='verified' for row in result['cases'])} blocked={sum(row['status']=='blocked' for row in result['cases'])} report_sha256={result['report_sha256']}")