diff --git a/coverage/axis-case-parity.report.json b/coverage/axis-case-parity.report.json new file mode 100644 index 0000000..9beeac2 --- /dev/null +++ b/coverage/axis-case-parity.report.json @@ -0,0 +1,246 @@ +{ + "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" + ], + "cases": [ + { + "evidence": [ + "coverage/minimal-xyz-vertical-s8-04.report.json", + "AXIS-CTRL-01..08", + "AXIS-VIEW-01..04" + ], + "family": "cartesian/trivkins", + "path": "linuxcnc/configs/sim/axis/minimal_xyz.ini", + "source_sha256": "63923bfb90f4aba843cb89f8dccc34732d803e2bfd5598ed23a2ba22f360d00a", + "status": "verified" + }, + { + "evidence": [ + "coverage/corexy-dual-path-s8-05.report.json", + "AXIS-VIEW-01..04" + ], + "family": "corexy/kins", + "path": "linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini", + "source_sha256": "b6682cc5186944a44c2aa78501c4f350e56b4da1151348fa9fc3ece5f0eeea3a", + "status": "verified" + }, + { + "evidence": [ + "coverage/corexy-dual-path-s8-05.report.json", + "AXIS-VIEW-01..04" + ], + "family": "corexy/hal", + "path": "linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini", + "source_sha256": "e838da30ce5fba2d049487d48bd2aeff46b1390a5a448f31980d9ce06121740f", + "status": "verified" + }, + { + "evidence": [ + "coverage/machine-families-s8-08.report.json", + "AXIS-VIEW-01..04" + ], + "family": "lineardelta", + "path": "linuxcnc/configs/sim/axis/ldelta.ini", + "source_sha256": "1567138c3d54b4d9712b5031f6a0b060edadd8d1442d1eff8d93c2beefbb5e0c", + "status": "verified" + }, + { + "evidence": [ + "coverage/machine-families-s8-08.report.json", + "AXIS-VIEW-01..04" + ], + "family": "rotarydelta", + "path": "linuxcnc/configs/sim/axis/rdelta.ini", + "source_sha256": "53510733b8fd0230547f48f76ebd35dc5c98f58fe1dba1246777e227ef82c508", + "status": "verified" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/lathe.ini", + "reason": "linuxcnc/configs/sim/axis/sim-lathe.var", + "source_sha256": "e1ce3667979cf2ea4813aa492c2061727d3f02a4d9592db0e4889f4fa53a6447", + "status": "blocked" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/lathe-fanucy/lathe-fanucy.ini", + "reason": "linuxcnc/configs/sim/axis/lathe-fanucy/sim-lathe.var", + "source_sha256": "be3724c6093b9d507e40d41ef1f5047c3244e8a95acba3aaa72176e15752675c", + "status": "blocked" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/gantry/gantry.ini", + "reason": "linuxcnc/configs/sim/axis/gantry/sim.tbl, linuxcnc/configs/sim/axis/gantry/sim.var", + "source_sha256": "acc7db8d6a38a64598ef277dfea3480f0e7400d461784a0fa0139bded93d5a68", + "status": "blocked" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/gantry/gantry_mm.ini", + "reason": "linuxcnc/configs/sim/axis/gantry/gantry_mm.var", + "source_sha256": "0692df9758234ff4cb29b22ee58688fe2bf450d93cc386ae096e0a943ef475e3", + "status": "blocked" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/vismach/5axis/bridgemill/5axis.ini", + "reason": "linuxcnc/configs/sim/axis/vismach/5axis/bridgemill/5axis.var", + "source_sha256": "789ba656f8ee3e97feba07b58eb242305898e745795891b1a7e536289bffe6bf", + "status": "blocked" + }, + { + "evidence": [ + "coverage/sim-case-matrix-s8-06.report.json" + ], + "family": "lathe/gantry/vismach-5axis", + "path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", + "reason": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/emc2.gif", + "source_sha256": "9ee1d96962e7a44b25468f452543cfc3ca318585269e26227bb5b6e17b1e42f1", + "status": "blocked" + } + ], + "checks": { + "blocked_visibility": { + "resource_and_oracle_blockers_retained": true, + "status": "verified" + }, + "case_01_minimal_xyz": { + "controls": true, + "endpoint_recovery": true, + "frontend_authority": true, + "status": "verified" + }, + "case_02_corexy_dual_path": { + "hal": true, + "kins": true, + "mode_parity": true, + "recovery_parity": true, + "status": "verified" + }, + "case_03_lathe_gantry": { + "oracle": "separate", + "resource_matrix": "BLOCKED_RESOURCE", + "status": "blocked" + }, + "case_04_vismach_5axis": { + "oracle": "official_kinematics_oracle_blocked", + "resource_matrix": "BLOCKED_RESOURCE", + "status": "blocked" + }, + "exact_paths": { + "basename_substitution": false, + "matrix_case_count": { + "BLOCKED_HAL": 4, + "BLOCKED_INI": 3, + "BLOCKED_RESOURCE": 216, + "BLOCKED_STARTUP": 2, + "TASK_READY": 10 + }, + "status": "verified" + }, + "frontend_mapping": { + "reports": [ + "coverage/axis-control.report.json", + "coverage/axis-view-edit.report.json", + "coverage/gui-browser-visual-fe-m4.report.json" + ], + "status": "verified", + "worker_snapshot_owner": true + } + }, + "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", + "evidence_reports": [ + { + "path": "coverage/minimal-xyz-vertical-s8-04.report.json", + "schema": "minimal-xyz-vertical-s8-04-report.v1", + "sha256": "093555ed13ad742a90a621bf876e1106cd0ef79a449ea13e2ae38eaa75290d12", + "status": "verified", + "task_id": "S8-04" + }, + { + "path": "coverage/corexy-dual-path-s8-05.report.json", + "schema": "corexy-dual-path-s8-05-report.v1", + "sha256": "f5de18eb8596dd3998b25bfee924eef01342e56ab9881bb7bf92ef5e9aae0bc5", + "status": "verified", + "task_id": "S8-05" + }, + { + "path": "coverage/machine-families-s8-08.report.json", + "schema": "machine-families-s8-08-report.v1", + "sha256": "9622d9551e759b3bf8cba0ed5f48a962affd56ce0c855eab4594e896c96c8a96", + "status": "verified", + "task_id": "S8-08" + }, + { + "path": "coverage/sim-case-matrix-s8-06.report.json", + "schema": "sim-case-matrix-s8-06-report.v1", + "sha256": "010b00aea1f37f1f91572d1618f455ed08a886aaca2b09f26fbde7b0fb545ff6", + "status": "verified", + "task_id": "S8-06" + }, + { + "path": "coverage/axis-control.report.json", + "schema": "axis-control-report.v1", + "sha256": "90baa63d72ccdef3d55b9611e130a041ac813fc744d1311465e5dd5a18ddd7d9", + "status": "verified", + "task_id": "AXIS-CTRL-01..08" + }, + { + "path": "coverage/axis-view-edit.report.json", + "schema": "axis-view-edit-report.v1", + "sha256": "452a1b13aa4fe75f9fe4cfe54679360756acea7dac87e4abdcb5118fe7ed6bab", + "status": "verified", + "task_id": "AXIS-VIEW-01..04+AXIS-EDIT-01" + }, + { + "path": "coverage/gui-browser-visual-fe-m4.report.json", + "schema": "gui-browser-visual-fe-m4-report.v1", + "sha256": "47cca469a22986235ea147333f8b9b6902480793db5c554a6267840c0b572f95", + "status": "verified", + "task_id": "FE-M4-GUI-06" + } + ], + "family_summary": { + "cartesian_trivkins": { + "cases": 1, + "status": "verified" + }, + "corexy_dual_path": { + "cases": 2, + "status": "verified" + }, + "delta_representatives": { + "cases": 2, + "status": "verified" + }, + "lathe_gantry_vismach_5axis": { + "cases": 6, + "reason": "exact resource/startup blockers remain in S8-06; official kinematics oracle separately blocked", + "status": "blocked" + } + }, + "implementation_status": "representative_cases_verified_remaining_families_blocked", + "report_sha256": "a6fcc5eec964a02b821f9a52f30b8e0d3303b8c94507b6d44d6cc75b7a7089e4", + "schema": "axis-case-parity-report.v1", + "status": "partial", + "task_id": "AXIS-CASE-01..04" +} diff --git a/docs/linuxcnc-axis-complete-parity-spec.md b/docs/linuxcnc-axis-complete-parity-spec.md index 1f5418a..649cc4b 100644 --- a/docs/linuxcnc-axis-complete-parity-spec.md +++ b/docs/linuxcnc-axis-complete-parity-spec.md @@ -556,7 +556,7 @@ M0 的 `AXIS-INV-01…04` 已执行:[`coverage/axis-source-inventory.report.js 3. `AXIS-UI-01…07` 已完成 Chromium 结构证据:[`coverage/gui-browser-visual-fe-m4.report.json`](../coverage/gui-browser-visual-fe-m4.report.json) 包含 26 个 `data-axis-feature` 标记、4 tab/tabpanel 关联、20 个原始 toolbar icon 和 aria 统计;专属浏览器契约仍只关闭 Chromium 授权切片。 4. `AXIS-CTRL-01…08` 已完成:[`coverage/axis-control.report.json`](../coverage/axis-control.report.json) 固化 Worker-backed command mapping、单调 operation ID、pending/result/error/blocked trace、门控前置检查和 F1/F2/F3/F5/R/T/P/S/Escape/Ctrl-K 快捷键;报告哈希 `cc12b349cc3fc3e7d1882530f123ca92e4a510f3f40fec4c72bcfd4e876f0a3e`。 5. `AXIS-VIEW-01…04` 和 `AXIS-EDIT-01` 已完成:[`coverage/axis-view-edit.report.json`](../coverage/axis-view-edit.report.json) 固化 Worker authoritative planner trajectory、6 camera modes、8 preview layers、DRO coordinate/display/unit modes、current-line/aria-current/diagnostic editor evidence;报告哈希 `39785b377806de18cb48a1d3c38c129d84a000b9c81d2478cc1a5567ec754e9b`。 -6. `AXIS-CASE-01…04`:按 minimal XYZ → CoreXY dual path → lathe/gantry/9-axis → VisMach/5-axis 取证。 +6. `AXIS-CASE-01…04` 已形成案例矩阵:[`coverage/axis-case-parity.report.json`](../coverage/axis-case-parity.report.json) 中 Minimal XYZ、CoreXY kins/HAL、Linear/Rotary Delta representative verified;lathe/gantry/VisMach-5-axis 仍按 exact resource/startup 与 official kinematics oracle 保持 blocked。报告哈希 `a6fcc5eec964a02b821f9a52f30b8e0d3303b8c94507b6d44d6cc75b7a7089e4`。 7. `AXIS-TEST-01…06`:Chromium visual/interaction/performance/recovery,外加明确的 Firefox/Safari/OPFS blocker。 8. `AXIS-REL-01/02`:只有所有 P0/P1 和报告自哈希通过,才更新 release gate。 diff --git a/nextTask1.md b/nextTask1.md index 16eb397..c5ed9a7 100644 --- a/nextTask1.md +++ b/nextTask1.md @@ -47,6 +47,7 @@ direct_inputs: - coverage/axis-contract.report.json - coverage/axis-control.report.json - coverage/axis-view-edit.report.json + - coverage/axis-case-parity.report.json - coverage/gui-browser-visual-fe-m4.report.json - docs/linuxcnc-axis-complete-parity-spec.md - coverage/browser-matrix-remediation.report.json @@ -80,6 +81,8 @@ non_goals: axis_control_report_sha256: cc12b349cc3fc3e7d1882530f123ca92e4a510f3f40fec4c72bcfd4e876f0a3e axis_view_edit_contract: verified axis_view_edit_report_sha256: 39785b377806de18cb48a1d3c38c129d84a000b9c81d2478cc1a5567ec754e9b + axis_case_parity: partial + axis_case_parity_report_sha256: a6fcc5eec964a02b821f9a52f30b8e0d3303b8c94507b6d44d6cc75b7a7089e4 browser_matrix: blocked opfs_recovery: blocked prior_completion_evidence: diff --git a/scripts/generate_axis_case_parity_report.py b/scripts/generate_axis_case_parity_report.py new file mode 100644 index 0000000..2ac7057 --- /dev/null +++ b/scripts/generate_axis_case_parity_report.py @@ -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']}") diff --git a/scripts/test_axis_case_parity_report.py b/scripts/test_axis_case_parity_report.py new file mode 100644 index 0000000..9e5b2ae --- /dev/null +++ b/scripts/test_axis_case_parity_report.py @@ -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()