diff --git a/coverage/axis-release-evidence.report.json b/coverage/axis-release-evidence.report.json new file mode 100644 index 0000000..8ba808c --- /dev/null +++ b/coverage/axis-release-evidence.report.json @@ -0,0 +1,110 @@ +{ + "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" + ], + "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", + "implementation_status": "evidence_index_complete_release_blocked", + "integrity": { + "all_report_files_present": true, + "all_report_hashes_present": true, + "release_gate_preserved": true + }, + "ownership": { + "physical_safety": "REAL-EXCLUDED", + "runtime_authority": "Dedicated Worker/WASM", + "source_oracle": "locked LinuxCNC 2.10.0~pre0", + "visual_authority": "Chromium authorized slice only" + }, + "release_decision": { + "candidate_eligible": false, + "global_blocked_count": 6, + "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" + ], + "release_action": "STOP_RELEASE", + "status": "blocked" + }, + "report_sha256": "1a0d3add58fcddf8a6a2a81a7197d91c45a1c94fabb7db75033c4b2f0f3da777", + "reports": [ + { + "file_sha256": "6017dde3a35234ba597dd3ca9bc0551cc380d101d7ab197fb3bdb481f1610542", + "implementation_status": "source_frozen_and_inventory", + "path": "coverage/axis-source-inventory.report.json", + "report_sha256": "0ec92a467ca13700bd12a7f2889ada8aa11588ca50858dbe25f84a5c946b8176", + "schema": "axis-source-inventory.v1", + "status": "verified", + "task_id": "AXIS-INV-01" + }, + { + "file_sha256": "4f3a71108441caa7255d2097650d0b14ae421ef07290d4b544b37d4d9d919486", + "implementation_status": "pure_worker_gate_contract", + "path": "coverage/axis-contract.report.json", + "report_sha256": "5de8d083c7903cf6e4753ff1adcbc26b8a868ef1f911ecbc19f56665e5805a44", + "schema": "axis-contract-report.v1", + "status": "verified", + "task_id": "AXIS-CON-01..04" + }, + { + "file_sha256": "90baa63d72ccdef3d55b9611e130a041ac813fc744d1311465e5dd5a18ddd7d9", + "implementation_status": "worker_backed_operation_controller", + "path": "coverage/axis-control.report.json", + "report_sha256": "cc12b349cc3fc3e7d1882530f123ca92e4a510f3f40fec4c72bcfd4e876f0a3e", + "schema": "axis-control-report.v1", + "status": "verified", + "task_id": "AXIS-CTRL-01..08" + }, + { + "file_sha256": "452a1b13aa4fe75f9fe4cfe54679360756acea7dac87e4abdcb5118fe7ed6bab", + "implementation_status": "authoritative_trajectory_dro_editor", + "path": "coverage/axis-view-edit.report.json", + "report_sha256": "39785b377806de18cb48a1d3c38c129d84a000b9c81d2478cc1a5567ec754e9b", + "schema": "axis-view-edit-report.v1", + "status": "verified", + "task_id": "AXIS-VIEW-01..04+AXIS-EDIT-01" + }, + { + "file_sha256": "3b72163e5ed029b30f4637b3794d404d1f7bf55cdb7a3f6ee0e278b1ddc52211", + "implementation_status": "representative_cases_verified_remaining_families_blocked", + "path": "coverage/axis-case-parity.report.json", + "report_sha256": "a6fcc5eec964a02b821f9a52f30b8e0d3303b8c94507b6d44d6cc75b7a7089e4", + "schema": "axis-case-parity-report.v1", + "status": "partial", + "task_id": "AXIS-CASE-01..04" + }, + { + "file_sha256": "aa7fcb014780e8177b379fc4e660890054456ade5bf9243496a6d2e97e1a4365", + "implementation_status": "evidence_aggregated", + "path": "coverage/axis-test-matrix.report.json", + "report_sha256": "9ba5de25bd8cfbfd61bd0a87bcb282170fa1de44b28a172b1c6ba649f580c357", + "schema": "axis-test-matrix-report.v1", + "status": "partial", + "task_id": "AXIS-TEST-01..06" + }, + { + "file_sha256": "47cca469a22986235ea147333f8b9b6902480793db5c554a6267840c0b572f95", + "implementation_status": "verified", + "path": "coverage/gui-browser-visual-fe-m4.report.json", + "report_sha256": "301fae229574e9e0bb4090330ed6d783da4e9771a1c1666473b1cf011f6330d6", + "schema": "gui-browser-visual-fe-m4-report.v1", + "status": "verified", + "task_id": "FE-M4-GUI-06" + } + ], + "schema": "axis-release-evidence-report.v1", + "status": "partial", + "summary": { + "blocked": 0, + "blocked_case_count": 6, + "chromium_authorized": true, + "partial": 2, + "total_reports": 7, + "verified": 5, + "verified_case_count": 5 + }, + "task_id": "AXIS-REL-01/02" +} diff --git a/docs/linuxcnc-axis-complete-parity-spec.md b/docs/linuxcnc-axis-complete-parity-spec.md index 961b01b..d2ed326 100644 --- a/docs/linuxcnc-axis-complete-parity-spec.md +++ b/docs/linuxcnc-axis-complete-parity-spec.md @@ -558,7 +558,7 @@ M0 的 `AXIS-INV-01…04` 已执行:[`coverage/axis-source-inventory.report.js 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` 已形成案例矩阵:[`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` 已形成测试矩阵:[`coverage/axis-test-matrix.report.json`](../coverage/axis-test-matrix.report.json) 中 DOM/accessibility、Chromium visual、operation replay、Worker case parity 和 Chromium repeat performance 已验证;Firefox/Safari minimum matrix、OPFS recovery 和 6 个案例资源/oracle blocker 仍明确 blocked。报告哈希 `9ba5de25bd8cfbfd61bd0a87bcb282170fa1de44b28a172b1c6ba649f580c357`。 -8. `AXIS-REL-01/02`:只有所有 P0/P1 和报告自哈希通过,才更新 release gate。 +8. `AXIS-REL-01/02` 已完成 AXIS 专用 evidence index:[`coverage/axis-release-evidence.report.json`](../coverage/axis-release-evidence.report.json) 索引 7 份报告、file/report hash、owner、verified/partial 状态和 blocker 分类;release gate 保持 `STOP_RELEASE`,报告哈希 `1a0d3add58fcddf8a6a2a81a7197d91c45a1c94fabb7db75033c4b2f0f3da777`。 第一批可执行命令: diff --git a/nextTask1.md b/nextTask1.md index 166f9a0..7f10f34 100644 --- a/nextTask1.md +++ b/nextTask1.md @@ -49,6 +49,7 @@ direct_inputs: - coverage/axis-view-edit.report.json - coverage/axis-case-parity.report.json - coverage/axis-test-matrix.report.json + - coverage/axis-release-evidence.report.json - coverage/gui-browser-visual-fe-m4.report.json - docs/linuxcnc-axis-complete-parity-spec.md - coverage/browser-matrix-remediation.report.json @@ -86,6 +87,8 @@ non_goals: axis_case_parity_report_sha256: a6fcc5eec964a02b821f9a52f30b8e0d3303b8c94507b6d44d6cc75b7a7089e4 axis_test_matrix: partial axis_test_matrix_report_sha256: 9ba5de25bd8cfbfd61bd0a87bcb282170fa1de44b28a172b1c6ba649f580c357 + axis_release_evidence: partial + axis_release_evidence_report_sha256: 1a0d3add58fcddf8a6a2a81a7197d91c45a1c94fabb7db75033c4b2f0f3da777 browser_matrix: blocked opfs_recovery: blocked prior_completion_evidence: diff --git a/scripts/generate_axis_release_evidence.py b/scripts/generate_axis_release_evidence.py new file mode 100644 index 0000000..38a2ed7 --- /dev/null +++ b/scripts/generate_axis_release_evidence.py @@ -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']}") diff --git a/scripts/test_axis_release_evidence.py b/scripts/test_axis_release_evidence.py new file mode 100644 index 0000000..bfa01a1 --- /dev/null +++ b/scripts/test_axis_release_evidence.py @@ -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()