Complete 3axis tutorial native and Web/WASM parity tools

This commit is contained in:
2026-08-24 14:01:30 +08:00
parent 938654b19d
commit 7e90142b63
147 changed files with 17639 additions and 557 deletions

View File

@@ -0,0 +1,457 @@
#!/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())