239 lines
16 KiB
Python
239 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate source-backed T6 closure evidence without promoting blocked rows."""
|
|
|
|
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]
|
|
OUTPUT = Path("coverage/wasm-function-closure-t6.report.json")
|
|
CONTEXT = Path("context-lock.json")
|
|
CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"
|
|
CASE = "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini"
|
|
|
|
|
|
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 full.is_dir():
|
|
entries = sorted(item for item in full.rglob("*") if item.is_file())
|
|
hasher = hashlib.sha256()
|
|
total = 0
|
|
for item in entries:
|
|
payload = item.read_bytes()
|
|
total += len(payload)
|
|
hasher.update(item.relative_to(full).as_posix().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, "sha256": hasher.hexdigest()}
|
|
if not full.is_file():
|
|
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)
|
|
return {"command": command, "status": "verified" if result.returncode == 0 else "blocked", "returncode": result.returncode, "stdout_sha256": digest(result.stdout), "stderr_sha256": digest(result.stderr)}
|
|
|
|
|
|
def rows() -> list[dict[str, Any]]:
|
|
return [
|
|
{
|
|
"id": "rs274",
|
|
"owner": "simulation-runtime",
|
|
"title": "advanced RS274/G-M/O and canonical callback closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["S1-01A interpreter state/modal/error ABI", "S5-04 source interpreter adapters", "S7-01 DO/ENDDO/BREAK", "S7-02 G64/G61"],
|
|
"source_refs": ["linuxcnc/src/emc/rs274ngc", "linuxcnc/tests/interp", "coverage/rs274-interpreter-closure.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "sim_interpreter"},
|
|
"tests": [["node", "web/worker/test_source_interpreter_s5.mjs"], ["node", "web/worker/test_source_do_s7_01.mjs"], ["node", "web/worker/test_source_g6164_s7_02.mjs"]],
|
|
"difference": "complete G/M/canned-cycle/coordinate-compensation oracle is not present in the locked closure",
|
|
"stop_reason": "SOURCE_ORACLE_BLOCKED: S1-01B expected/canonical callback inputs are not approved",
|
|
"next_action": "provide exact upstream expected/checkresult and canonical callback golden before extending interpreter",
|
|
},
|
|
{
|
|
"id": "tp",
|
|
"owner": "simulation-runtime",
|
|
"title": "TP blend/exact-stop/accel/jerk closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["fixed-tick sim_motion_queue", "G64/G61 Worker modal state", "checkpoint/replay endpoint parity"],
|
|
"source_refs": ["linuxcnc/src/emc/tp", "linuxcnc/unit_tests/tp", "coverage/motion-runtime.report.json", "coverage/wasm-motion-authority-t3.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "sim_motion_queue"},
|
|
"tests": [["node", "web/worker/test_wasm_motion_authority.mjs"], ["node", "web/worker/test_wasm_motion_checkpoint.mjs"], ["node", "web/worker/test_source_g6164_s7_02.mjs"]],
|
|
"difference": "queue state is deterministic but full upstream blend and dynamics numeric golden is not closed",
|
|
"stop_reason": "SOURCE_ORACLE_BLOCKED: TP numeric oracle and all configuration-family samples are unavailable",
|
|
"next_action": "import approved TP golden with fixed logical tick and first-divergent-field rules",
|
|
},
|
|
{
|
|
"id": "kinematics",
|
|
"owner": "simulation-runtime",
|
|
"title": "kinematics family FK/IK and home/limit closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["trivkins selected-case home/limit", "registered fixed-tick kinematics families"],
|
|
"source_refs": ["linuxcnc/src/emc/kinematics", "coverage/kinematics-runtime.report.json", "coverage/kinematics-oracle.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "sim_kinematics_home_limit"},
|
|
"tests": [["node", "web/worker/test_authoritative_kinematics_s6_04.mjs"], ["node", "web/worker/test_axis3_tutorial_motion.mjs"], ["node", "web/worker/test_wasm_canonical_state.mjs"]],
|
|
"difference": "runtime supports deterministic families, but official kinematics unit-test root is absent",
|
|
"stop_reason": "SOURCE_ORACLE_BLOCKED: linuxcnc/unit_tests/kinematics is missing from locked source",
|
|
"next_action": "obtain approved official FK/IK/recovery oracle or an explicit contract replacement",
|
|
},
|
|
{
|
|
"id": "hal_io",
|
|
"owner": "simulation-runtime",
|
|
"title": "HAL, PLC, IO and virtual device closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["static HAL manifest/graph", "fixed-tick virtual devices", "IO ack/fault/recovery"],
|
|
"source_refs": ["linuxcnc/src/hal", "linuxcnc/src/emc/iotask", "coverage/hal-component-manifest.report.json", "coverage/virtual-device-fault-matrix.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "sim_hal_and_sim_io"},
|
|
"tests": [["node", "web/worker/test_hal_runtime.mjs"], ["node", "web/worker/test_device_io.mjs"], ["node", "web/worker/test_io_ack.mjs"], ["node", "web/worker/test_virtual_devices.mjs"]],
|
|
"difference": "static/virtual paths are replayable; dynamic loadusr and real transport/device paths remain excluded",
|
|
"stop_reason": "REAL_EXCLUDED: host drivers, dynamic loaders and physical device timing cannot be accepted as Worker parity",
|
|
"next_action": "keep virtual adapter boundary explicit; require an approved pure-simulation fixture for any new component",
|
|
},
|
|
{
|
|
"id": "python_remap",
|
|
"owner": "python-runtime",
|
|
"title": "Python/remap/filter/VCP closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["Pyodide allowlist", "virtual linuxcnc/hal API", "remap/O-word callback samples"],
|
|
"source_refs": ["linuxcnc/lib/python", "linuxcnc/tests/remap", "coverage/python-worker-bridge.report.json", "python-runtime/manifest.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "python_allowlist"},
|
|
"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"]],
|
|
"difference": "allowlisted callbacks are deterministic; host imports and arbitrary plugin modules are denied",
|
|
"stop_reason": "REAL_EXCLUDED: host Python, dynamic imports and unrestricted filesystem/network capabilities are outside Worker scope",
|
|
"next_action": "add individually hashed allowlisted module contract only after source and license inputs are approved",
|
|
},
|
|
{
|
|
"id": "configuration",
|
|
"owner": "workspace-runtime",
|
|
"title": "configuration/resource/startup closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["exact-path resolver", "INI/HAL diagnostics", "TASK_READY selected fixtures"],
|
|
"source_refs": ["linuxcnc/configs/sim", "coverage/full-configuration-startup-matrix.report.json", "coverage/configuration-resource-remediation.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "configuration_startup"},
|
|
"tests": [["node", "web/worker/test_configuration_startup_runtime.mjs"], ["node", "web/worker/test_configuration_preflight.mjs"]],
|
|
"difference": "resolver is fail-closed, but the full matrix retains missing/unindexed/HAL/INI/STARTUP resources",
|
|
"stop_reason": "RESOURCE_BLOCKED: exact source resources are absent or unresolved; basename substitution is forbidden",
|
|
"next_action": "supply exact-path resource fixtures and regenerate the startup matrix without promotion",
|
|
},
|
|
{
|
|
"id": "recovery",
|
|
"owner": "workspace-runtime",
|
|
"title": "checkpoint/journal/migration/offline recovery closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["logical checkpoint/trace import", "workspace migration/rollback", "offline missing-resource diagnostics"],
|
|
"source_refs": ["coverage/recovery-offline-fault-matrix.report.json", "coverage/workspace-recovery.report.json", "coverage/wasm-canonical-state-t4.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "workspace_session"},
|
|
"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"]],
|
|
"difference": "logical recovery is deterministic; physical power-loss/OPFS corruption and official recovery oracle are open",
|
|
"stop_reason": "SOURCE_ORACLE_BLOCKED: official recovery oracle and physical filesystem crash input are absent",
|
|
"next_action": "obtain approved crash/oracle fixture before claiming recovery closure",
|
|
},
|
|
{
|
|
"id": "browser_ux",
|
|
"owner": "frontend-runtime",
|
|
"title": "browser UX, WebGL and multi-engine closure",
|
|
"status": "blocked",
|
|
"verified_slice": ["Chromium Worker/React workflow", "Three.js nonblank canvas", "protocol error/recovery UX"],
|
|
"source_refs": ["coverage/browser-performance-matrix.report.json", "coverage/gui-browser-visual-fe-m4.report.json", "coverage/browser-matrix-remediation.report.json"],
|
|
"capability": {"wasm": "verified", "worker": "verified", "authority": "dedicated_worker_protocol_v1"},
|
|
"tests": [["node", "web/worker/test_axis3_selected_case.mjs"], ["node", "web/worker/test_axis3_visible_completion.mjs"]],
|
|
"difference": "Chromium slice is verified; Firefox/Safari minimums and physical OPFS recovery are not",
|
|
"stop_reason": "BROWSER_BLOCKED: minimum versions are TBD and Safari/physical OPFS are unavailable in the locked environment",
|
|
"next_action": "pin reviewed browser minimums and execute the same workflow/fault matrix in each engine",
|
|
},
|
|
]
|
|
|
|
|
|
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
|
|
root = root.resolve()
|
|
lock = json.loads((root / CONTEXT).read_text(encoding="utf-8"))
|
|
matrix = rows()
|
|
input_paths = {
|
|
"context-lock.json": "context_lock",
|
|
"docs/linuxcnc-web-wasm-wasm-only-migration-plan.md": "migration_plan",
|
|
"coverage/wasm-feature-matrix-t5.report.json": "t5_matrix",
|
|
"coverage/full-linuxcnc-simulation-gap-audit.report.json": "gap_audit",
|
|
"coverage/source-index.jsonl": "source_inventory",
|
|
"coverage/test-entries.jsonl": "test_inventory",
|
|
}
|
|
for row in matrix:
|
|
for path in row["source_refs"]:
|
|
input_paths.setdefault(path, f"source_{row['id']}")
|
|
inputs = [artifact(root, path, role) for path, role in sorted(input_paths.items())]
|
|
tests = [{"domain": row["id"], "commands": [run_test(root, command) for command in row["tests"]]} for row in matrix]
|
|
test_commands = [item for group in tests for item in group["commands"]]
|
|
failure_samples = [
|
|
{"code": "official_kinematics_oracle_missing", "source": "linuxcnc/unit_tests/kinematics", "observed": "missing_from_locked_source"},
|
|
{"code": "configuration_resource_blocked", "source": "coverage/full-configuration-startup-matrix.report.json", "observed": {"blocked_entries": 208, "entrypoints": 233}},
|
|
{"code": "browser_minimum_unpinned", "source": "context-lock.json", "observed": {"chromium": "TBD", "firefox": "TBD", "safari": "TBD"}},
|
|
{"code": "host_capability_excluded", "source": "python-runtime/manifest.json", "observed": ["host_paths", "socket", "subprocess", "device_paths"]},
|
|
]
|
|
checks = {
|
|
"context_match": {"status": "verified" if lock.get("context_id") == CONTEXT_ID else "blocked", "context_id": lock.get("context_id")},
|
|
"row_count": {"status": "verified" if len(matrix) == 8 else "blocked", "count": len(matrix)},
|
|
"source_refs": {"status": "verified" if all(row["source_refs"] for row in matrix) else "blocked"},
|
|
"closure_status_vocabulary": {"status": "verified" if all(row["status"] in {"verified", "blocked", "queued"} for row in matrix) else "blocked"},
|
|
"no_js_fallback": {"status": "verified", "policy": "blocked rows never fall back to JS authority"},
|
|
"failure_samples": {"status": "verified" if len(failure_samples) >= 5 else "blocked", "count": len(failure_samples)},
|
|
"test_execution": {"status": "verified" if all(item["status"] == "verified" for item in test_commands) else "blocked", "blocked_commands": sum(item["status"] != "verified" for item in test_commands)},
|
|
"selected_case_boundary": {"status": "verified", "case": CASE, "promotion": "forbidden"},
|
|
"release_stop_preserved": {"status": "verified", "release_action": "STOP_RELEASE", "candidate_eligible": False},
|
|
}
|
|
report: dict[str, Any] = {
|
|
"schema": "wasm-function-closure-t6.v1",
|
|
"status": "blocked",
|
|
"implementation_status": "verified",
|
|
"task_id": "T6-FUNCTION-CLOSURE-01",
|
|
"context_id": lock.get("context_id"),
|
|
"case": CASE,
|
|
"candidate_eligible": False,
|
|
"release_action": "STOP_RELEASE",
|
|
"matrix": matrix,
|
|
"counts": {"rows": len(matrix), "blocked": sum(row["status"] == "blocked" for row in matrix), "verified_slices": sum(bool(row["verified_slice"]) for row in matrix), "test_commands": len(test_commands), "test_blocked": sum(item["status"] != "verified" for item in test_commands)},
|
|
"inputs": inputs,
|
|
"tests": tests,
|
|
"failure_samples": failure_samples,
|
|
"checks": checks,
|
|
"stop_reason": "all complete closure rows retain source/oracle/resource/host/browser blockers; no selected-case or JS authority promotion",
|
|
"next_transition": "T7-RELEASE-GATE-01 only after owner-approved source/oracle/resource/browser inputs",
|
|
"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_function_closure_t6={report['status']} rows={report['counts']['rows']} blocked={report['counts']['blocked']}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|