Record AXIS representative case parity
This commit is contained in:
92
scripts/generate_axis_case_parity_report.py
Normal file
92
scripts/generate_axis_case_parity_report.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/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']}")
|
||||
36
scripts/test_axis_case_parity_report.py
Normal file
36
scripts/test_axis_case_parity_report.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Contract tests for AXIS-CASE-01..04 evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_axis_case_parity_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REPORT = ROOT / "coverage/axis-case-parity.report.json"
|
||||
|
||||
|
||||
class AxisCaseParityReportTests(unittest.TestCase):
|
||||
def test_contract(self) -> None:
|
||||
report = json.loads(REPORT.read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "axis-case-parity-report.v1")
|
||||
self.assertEqual(report["task_id"], "AXIS-CASE-01..04")
|
||||
self.assertEqual(report["status"], "partial")
|
||||
self.assertEqual(report["checks"]["case_01_minimal_xyz"]["status"], "verified")
|
||||
self.assertEqual(report["checks"]["case_02_corexy_dual_path"]["status"], "verified")
|
||||
self.assertEqual(report["checks"]["case_03_lathe_gantry"]["status"], "blocked")
|
||||
self.assertEqual(report["checks"]["case_04_vismach_5axis"]["status"], "blocked")
|
||||
self.assertTrue(report["checks"]["blocked_visibility"]["resource_and_oracle_blockers_retained"])
|
||||
|
||||
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_case_parity_report.canonical(unsigned).encode()).hexdigest())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user