Record AXIS representative case parity

This commit is contained in:
2026-08-23 10:01:21 +08:00
parent 26c95e1400
commit c96c48a0eb
5 changed files with 378 additions and 1 deletions

View File

@@ -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"
}

View File

@@ -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。

View File

@@ -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:

View 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']}")

View 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()