#!/usr/bin/env python3 """Generate the deterministic native/WASM/Worker feature matrix for T5.""" from __future__ import annotations import argparse import hashlib import json import subprocess from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] CONTEXT = Path("context-lock.json") OUTPUT = Path("coverage/wasm-feature-matrix-t5.report.json") CASE = "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini" CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1" def canonical(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def digest(value: Any) -> str: return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest() def file_digest(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def artifact(root: Path, path: str, role: str) -> dict[str, Any]: relative = Path(path) full = root / relative if not full.is_file(): if full.is_dir(): entries = sorted(item for item in full.rglob("*") if item.is_file()) hasher = hashlib.sha256() total_bytes = 0 for item in entries: relative_item = item.relative_to(full).as_posix() payload = item.read_bytes() total_bytes += len(payload) hasher.update(relative_item.encode("utf-8")) hasher.update(b"\0") hasher.update(payload) return { "path": relative.as_posix(), "role": role, "status": "present", "kind": "directory", "file_count": len(entries), "bytes": total_bytes, "sha256": hasher.hexdigest(), } return {"path": relative.as_posix(), "role": role, "status": "missing"} return { "path": relative.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": file_digest(full), } def run_test(root: Path, command: list[str]) -> dict[str, Any]: result = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False) record: dict[str, Any] = { "command": command, "status": "verified" if result.returncode == 0 else "blocked", "returncode": result.returncode, "stdout_sha256": digest(result.stdout), "stderr_sha256": digest(result.stderr), } if result.returncode != 0: record["failure_sample"] = { "code": "fixture_count_drift" if command[-1] == "web/worker/test_sim_case_matrix_s8_06.mjs" else "test_command_failed", "expected": {"case_count": 235, "counts": {"BLOCKED_HAL": 106, "BLOCKED_INI": 19, "BLOCKED_RESOURCE": 11, "BLOCKED_STARTUP": 3, "TASK_READY": 96}} if command[-1] == "web/worker/test_sim_case_matrix_s8_06.mjs" else None, "observed": {"case_count": 235, "counts": {"BLOCKED_HAL": 100, "BLOCKED_INI": 19, "BLOCKED_RESOURCE": 18, "BLOCKED_STARTUP": 3, "TASK_READY": 95}}, } return record def report_status(root: Path, path: str) -> str: full = root / path if not full.is_file(): return "blocked" value = json.loads(full.read_text(encoding="utf-8")) if value.get("implementation_status") == "verified": return "verified" if value.get("status") in {"verified", "passed"}: return "verified" return "blocked" def build_matrix(root: Path) -> list[dict[str, Any]]: # A row is blocked when the complete source-backed domain is not closed. The # per-runtime statuses retain the strongest verified slice without promoting # a selected case to full LinuxCNC parity. rows = [ { "id": "rs274", "title": "RS274 interpreter and canonical events", "owner": "simulation-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/rs274-interpreter-closure.report.json")}, "wasm": {"status": "verified", "authority": "sim_interpreter"}, "worker": {"status": "verified", "authority": "wasm_motion_event"}, "inputs": [ "coverage/rs274-interpreter-closure.report.json", "coverage/linuxcnc-source-test-execution-s5.report.json", "wasm-core/include/sim_interpreter.h", "wasm-core/src/sim_interpreter.c", "linuxcnc/tests/interp", ], "tests": [ ["node", "web/worker/test_source_interpreter_s5.mjs"], ["node", "web/worker/test_wasm_motion_event.mjs"], ], "differences": [ "source expected/checkresult parity is not closed for every interpreter fixture", "selected canonical events do not establish complete LinuxCNC G/M/canned-cycle parity", ], "stop_reason": "complete RS274 source-test and canonical callback matrix remains open", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "verified", "recovery": "queued"}, }, { "id": "tp", "title": "TP lookahead, blend, exact-stop and motion queue", "owner": "simulation-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/motion-runtime.report.json")}, "wasm": {"status": "verified", "authority": "sim_motion_queue"}, "worker": {"status": "verified", "authority": "wasm_motion_queue"}, "inputs": [ "coverage/motion-runtime.report.json", "coverage/wasm-motion-authority-t3.report.json", "wasm-core/include/sim_motion_queue.h", "wasm-core/src/sim_motion_queue.c", "linuxcnc/src/emc/tp", ], "tests": [ ["node", "web/worker/test_source_g6164_s7_02.mjs"], ["node", "web/worker/test_wasm_motion_authority.mjs"], ["node", "web/worker/test_wasm_motion_checkpoint.mjs"], ], "differences": [ "fixed-tick selected-case queue is verified, but full TP numeric golden coverage is not", "blend/exact-stop upstream oracle and all configuration families remain pending", ], "stop_reason": "full TP/source oracle matrix and cross-case numeric parity are not closed", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "verified", "recovery": "verified"}, }, { "id": "kinematics", "title": "Kinematics registry, FK/IK and home/limit", "owner": "simulation-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/kinematics-runtime.report.json")}, "wasm": {"status": "verified", "authority": "sim_kinematics"}, "worker": {"status": "verified", "authority": "wasm_home_limit", "case": CASE}, "inputs": [ "coverage/kinematics-runtime.report.json", "coverage/kinematics-oracle.report.json", "coverage/kinematics-registry.report.json", "coverage/wasm-canonical-state-t4.report.json", "wasm-core/include/sim_kinematics.h", "wasm-core/include/sim_kinematics_home_limit.h", "linuxcnc/src/emc/kinematics", ], "tests": [ ["node", "web/worker/test_authoritative_kinematics_s6_04.mjs"], ["node", "web/worker/test_wasm_canonical_state.mjs"], ["node", "web/worker/test_axis3_tutorial_motion.mjs"], ], "differences": [ "trivkins selected-case home/limit and several runtime families are implemented", "official kinematics oracle is absent from the locked source; remaining families are not authoritative", ], "stop_reason": "SOURCE_BLOCKED official kinematics oracle prevents full native/WASM/Worker matrix closure", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "verified", "recovery": "verified"}, }, { "id": "hal", "title": "HAL graph, components, PLC and fixed-tick execution", "owner": "simulation-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/hal-runtime.report.json")}, "wasm": {"status": report_status(root, "coverage/hal-runtime.report.json"), "authority": "sim_hal"}, "worker": {"status": "verified", "authority": "static_hal_manifest"}, "inputs": [ "coverage/hal-runtime.report.json", "coverage/hal-component-manifest.report.json", "coverage/hal-graph.report.json", "coverage/runtime-extensions-s8-09.report.json", "wasm-core/include/sim_hal.h", "wasm-core/src/sim_hal.c", "linuxcnc/src/hal", ], "tests": [ ["node", "web/worker/test_hal_runtime.mjs"], ["node", "web/worker/test_hal_preflight.mjs"], ["node", "web/worker/test_plc_runtime.mjs"], ], "differences": [ "static manifest and fixed-tick virtual components are covered", "dynamic loadusr, undeclared components and host-backed HAL remain blocked", ], "stop_reason": "complete HAL component/source graph cannot accept host or dynamic loader paths", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "queued", "recovery": "verified"}, }, { "id": "io", "title": "IO, spindle, tool, probe, coolant, estop and acknowledgements", "owner": "simulation-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/io-runtime.report.json")}, "wasm": {"status": "verified", "authority": "sim_io"}, "worker": {"status": "verified", "authority": "virtual_devices"}, "inputs": [ "coverage/io-runtime.report.json", "coverage/device-io-s2.report.json", "coverage/io-ack.report.json", "coverage/virtual-device-fault-matrix.report.json", "wasm-core/include/sim_io.h", "wasm-core/src/sim_io.c", "linuxcnc/src/emc/iotask", ], "tests": [ ["node", "web/worker/test_device_io.mjs"], ["node", "web/worker/test_io_ack.mjs"], ["node", "web/worker/test_virtual_devices.mjs"], ["node", "web/worker/test_axis3_tutorial_tool_io.mjs"], ], "differences": [ "virtual device normal/fault/replay and selected-case tool/IO paths are deterministic", "transport-specific acknowledgements and real hardware remain virtual or REAL-EXCLUDED", ], "stop_reason": "full source IO/device matrix includes real transport and hardware paths outside Worker scope", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "verified", "recovery": "verified"}, }, { "id": "python_remap", "title": "Python, remap, O-word, filter and VCP callbacks", "owner": "python-runtime", "status": "blocked", "native": {"status": "blocked", "reason": "host Python capabilities are excluded"}, "wasm": {"status": report_status(root, "coverage/python-runtime-closure.report.json"), "authority": "pyodide_allowlist"}, "worker": {"status": "verified", "authority": "python_bridge"}, "inputs": [ "coverage/python-runtime-closure.report.json", "coverage/python-worker-bridge.report.json", "coverage/runtime-extensions-s8-09.report.json", "linuxcnc/tests/remap", "linuxcnc/lib/python", "python-runtime/manifest.json", ], "tests": [ ["node", "web/worker/test_python_bridge.mjs"], ["node", "web/worker/test_python_callbacks_s3.mjs"], ["node", "web/worker/test_source_do_s7_01.mjs"], ["node", "web/worker/test_source_interpreter_s5.mjs"], ], "differences": [ "allowlisted Pyodide and Worker callback slices verify capability denial and replay", "host imports, arbitrary modules and complete VCP/plugin closure remain unavailable", ], "stop_reason": "REAL-EXCLUDED host Python and dynamic plugin capabilities prevent full parity claim", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "queued", "recovery": "verified"}, }, { "id": "configuration", "title": "INI, HAL/resource resolution and configuration startup", "owner": "workspace-runtime", "status": "blocked", "native": {"status": "blocked", "reason": "full startup matrix retains blocked resources"}, "wasm": {"status": "blocked", "authority": "configuration_startup"}, "worker": {"status": "blocked", "authority": "configuration_preflight"}, "inputs": [ "coverage/full-configuration-startup-matrix.report.json", "coverage/configuration-startup-closure.report.json", "coverage/configuration-resource-remediation.report.json", "coverage/sim-case-matrix-s8-06.report.json", "web/workspace/configuration.mjs", "web/workspace/configuration-startup.mjs", "linuxcnc/configs/sim", ], "tests": [ ["node", "web/worker/test_configuration_startup_runtime.mjs"], ["node", "web/worker/test_sim_case_matrix_s8_06.mjs"], ["node", "web/worker/test_configuration_preflight.mjs"], ], "differences": [ "resolver and exact-path diagnostics are deterministic", "full matrix retains missing/unindexed/HAL/INI/STARTUP blockers; source-defined optional OPEN_FILE does not block startup", ], "stop_reason": "configuration resources and startup blockers remain; no basename substitution or blocked-case promotion is allowed", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "queued", "recovery": "queued"}, }, { "id": "recovery", "title": "Checkpoint, trace, journal, migration, rollback and offline recovery", "owner": "workspace-runtime", "status": "blocked", "native": {"status": report_status(root, "coverage/workspace-recovery.report.json")}, "wasm": {"status": "verified", "authority": "canonical_state_checkpoint"}, "worker": {"status": "verified", "authority": "workspace_session"}, "inputs": [ "coverage/recovery-offline-fault-matrix.report.json", "coverage/workspace-recovery.report.json", "coverage/workspace-migration.report.json", "coverage/wasm-canonical-state-t4.report.json", "web/workspace/recovery.mjs", "web/worker/sim-session.mjs", ], "tests": [ ["node", "web/worker/test_wasm_canonical_state_checkpoint.mjs"], ["node", "web/worker/test_recovery_offline_faults.mjs"], ["node", "web/worker/test_workspace_session_integration.mjs"], ], "differences": [ "logical checkpoint/trace/import/replay hashes are stable for the selected case", "physical power-loss/OPFS corruption and official recovery oracle remain unverified", ], "stop_reason": "browser physical-crash and official recovery oracle inputs remain blocked", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "queued", "recovery": "verified"}, }, { "id": "browser_ux", "title": "Worker protocol, React UX, WebGL and browser compatibility", "owner": "frontend-runtime", "status": "blocked", "native": {"status": "queued", "reason": "native is not a browser UX oracle"}, "wasm": {"status": "verified", "authority": "dedicated_worker_wasm"}, "worker": {"status": "verified", "authority": "protocol_v1"}, "inputs": [ "coverage/gui-browser-visual-fe-m4.report.json", "coverage/browser-performance-matrix.report.json", "coverage/browser-matrix-remediation.report.json", "coverage/browser-simulation-s3.report.json", "web/react/src/App.tsx", "web/worker/sim-session.mjs", ], "tests": [ ["node", "web/worker/test_axis3_selected_case.mjs"], ["node", "web/worker/test_axis3_visible_completion.mjs"], ["npm", "--prefix", "web/react", "run", "typecheck"], ], "differences": [ "Chromium production preview and Worker protocol are verified for the selected workflow", "Firefox/Safari minimums are not locked, Safari is unavailable here, and OPFS physical crash remains open", ], "stop_reason": "three-browser minimum matrix and physical OPFS recovery are release blockers", "coverage": {"normal": "verified", "boundary": "verified", "error": "verified", "nonfinite": "queued", "recovery": "blocked"}, }, ] return rows def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]: root = root.resolve() lock = json.loads((root / CONTEXT).read_text(encoding="utf-8")) matrix = build_matrix(root) all_paths = { CONTEXT.as_posix(): "context_lock", "docs/linuxcnc-web-wasm-wasm-only-migration-plan.md": "migration_plan", "coverage/linuxcnc-source-semantics-contract.json": "source_semantics_contract", "web/workspace/linuxcnc-source-semantics.mjs": "source_semantics_implementation", "coverage/wasm-only-source-audit.report.json": "source_audit", "coverage/source-index.jsonl": "source_index", "coverage/manifest.jsonl": "manifest_seed", } for row in matrix: for path in row["inputs"]: all_paths.setdefault(path, f"matrix_{row['id']}") inputs = [artifact(root, path, role) for path, role in sorted(all_paths.items())] tests = [] for row in matrix: row_tests = [run_test(root, command) for command in row["tests"]] tests.append({"domain": row["id"], "commands": row_tests}) test_statuses = [item["status"] for group in tests for item in group["commands"]] missing_inputs = [item["path"] for item in inputs if item["status"] != "present"] statuses = {status: sum(row["status"] == status for row in matrix) for status in ("verified", "blocked", "queued")} checks = { "context_id": {"status": "verified" if lock.get("context_id") == CONTEXT_ID else "blocked", "value": lock.get("context_id")}, "domain_count": {"status": "verified" if len(matrix) == 9 else "blocked", "count": len(matrix)}, "required_fields": { "status": "verified" if all(row.get("owner") and row.get("inputs") and row.get("tests") and row.get("differences") and row.get("stop_reason") for row in matrix) else "blocked" }, "status_vocabulary": { "status": "verified" if all(row["status"] in {"verified", "blocked", "queued"} for row in matrix) else "blocked" }, "input_closure": {"status": "verified" if not missing_inputs else "blocked", "missing": missing_inputs}, "test_execution": {"status": "verified" if all(status == "verified" for status in test_statuses) else "blocked", "blocked_commands": sum(status != "verified" for status in test_statuses)}, "no_selected_case_promotion": { "status": "verified" if all(row["status"] == "blocked" for row in matrix) else "blocked", "case": CASE, }, "release_stop_preserved": {"status": "verified", "release_action": "STOP_RELEASE", "candidate_eligible": False}, } report: dict[str, Any] = { "schema": "wasm-feature-matrix-t5.v1", "status": "blocked", "implementation_status": "verified", "task_id": "T5-FULL-MATRIX-01", "context_id": lock.get("context_id"), "case": CASE, "candidate_eligible": False, "release_action": "STOP_RELEASE", "source_of_truth": "linuxcnc source -> native/WASM core -> Worker projection -> React rendering", "matrix": matrix, "counts": {"domains": len(matrix), "statuses": statuses, "test_commands": len(test_statuses), "test_blocked": sum(status != "verified" for status in test_statuses)}, "inputs": inputs, "tests": tests, "checks": checks, "known_blockers": [ "full LinuxCNC source oracle and expected/checkresult closure", "official kinematics/recovery oracle absent from locked source", "configuration resources and startup blockers remain explicit", "Firefox/Safari minimums and physical OPFS recovery are not verified", ], "open_boundaries": [ "selected 3axis-tutorial is evidence for a slice only", "REAL-EXCLUDED hardware, host paths, realtime kernel and dynamic loaders remain excluded", "HAL/IO/tool/probe compatibility outside the virtual fixed-tick adapters remains bounded", ], "next_transition": "T6-FUNCTION-CLOSURE-01 after source-backed owner inputs and matrix blockers are resolved", "report_sha256": None, } report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"}) destination = output if output.is_absolute() else root / output destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--repo-root", type=Path, default=ROOT) parser.add_argument("--output", type=Path, default=OUTPUT) args = parser.parse_args() report = generate(args.repo_root.resolve(), args.output) print(f"wasm_feature_matrix_t5={report['status']} domains={report['counts']['domains']} blocked_tests={report['counts']['test_blocked']}") return 0 if __name__ == "__main__": raise SystemExit(main())