201 lines
20 KiB
Python
201 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the bounded SRC-00 source-slice inventory for the XYZAC case."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"
|
|
CASE_DIR = ROOT / "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting"
|
|
CASE = CASE_DIR / "xyzac-trt.ini"
|
|
REPORT = ROOT / "coverage/xyzac-trt-source-slice-inventory.report.json"
|
|
SLICE_MODULE = ROOT / "web/workspace/xyzac-trt-source-slices.mjs"
|
|
CONTRACT_MODULE = ROOT / "web/workspace/xyzac-trt-case-contract.mjs"
|
|
|
|
DIRECT_SOURCES = [
|
|
"doc-rtcp/xyzac-trt-web-wasm-source-parity-contract.md",
|
|
"doc-rtcp/xyzac-trt-web-wasm-implementation-plan.md",
|
|
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
|
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.xml",
|
|
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal",
|
|
"linuxcnc/src/emc/usr_intf/axis/scripts/axis.py",
|
|
"linuxcnc/share/axis/tcl/axis.tcl",
|
|
"linuxcnc/lib/python/rs274/glcanon.py",
|
|
"linuxcnc/lib/python/gremlin.py",
|
|
"linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py",
|
|
"linuxcnc/src/emc/kinematics/xyzac-trt-kins.c",
|
|
"linuxcnc/src/emc/kinematics/trtfuncs.c",
|
|
"linuxcnc/src/emc/kinematics/switchkins.c",
|
|
"linuxcnc/src/emc/rs274ngc/interp_read.cc",
|
|
"linuxcnc/src/emc/rs274ngc/interp_execute.cc",
|
|
"linuxcnc/src/emc/tp/tp.c",
|
|
"linuxcnc/src/emc/motion/command.c",
|
|
"linuxcnc/src/emc/motion/control.c",
|
|
"context-lock.json",
|
|
]
|
|
|
|
DIRECT_SOURCE_DIRECTORIES = [
|
|
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos",
|
|
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs",
|
|
]
|
|
|
|
WASM_EVIDENCE = [
|
|
{"path": "web/worker/test_panel_snapshot_projection.mjs", "authority": "wasm", "status": "verified"},
|
|
{"path": "web/worker/test_wasm_motion_authority.mjs", "authority": "wasm", "status": "verified"},
|
|
{"path": "web/worker/test_wasm_canonical_state.mjs", "authority": "wasm", "status": "verified"},
|
|
{"path": "web/worker/test_wasm_motion_checkpoint.mjs", "authority": "wasm", "status": "verified"},
|
|
{"path": "web/worker/test_wasm_session.mjs", "authority": "wasm", "status": "verified"},
|
|
]
|
|
|
|
|
|
def sha256(path: Path) -> str | None:
|
|
if not path.is_file():
|
|
return None
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def canonical(value: Any) -> str:
|
|
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
|
|
|
|
def source(path: str, role: str) -> dict[str, Any]:
|
|
absolute = ROOT / path
|
|
return {"path": path, "role": role, "bytes": absolute.stat().st_size if absolute.is_file() else None,
|
|
"sha256": sha256(absolute), "status": "indexed" if absolute.is_file() else "SOURCE_BLOCKED"}
|
|
|
|
|
|
def line_span(path: str, pattern: str) -> str | None:
|
|
absolute = ROOT / path
|
|
if not absolute.is_file():
|
|
return None
|
|
lines = absolute.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
hits = [i + 1 for i, line in enumerate(lines) if re.search(pattern, line)]
|
|
return f"{min(hits)}-{max(hits)}" if hits else None
|
|
|
|
|
|
def axis_controls() -> dict[str, Any]:
|
|
path = "linuxcnc/share/axis/tcl/axis.tcl"
|
|
text = (ROOT / path).read_text(encoding="utf-8", errors="replace")
|
|
menus = []
|
|
for menu, label in re.findall(r"setup_menu_accel (\.menu(?:\.[^ ]+)*) end \[_ ([^\n]+)", text):
|
|
clean = re.sub(r"[\[\]\\\"]", "", label).strip().replace(" ", "_").replace("'", "")
|
|
menus.append({"control_id": f"AXIS-MENU-{menu[6:].replace('.', '-')}-{clean}".upper(), "slice_id": "AXIS-XYZAC-SHELL-MENU", "label_source": label.strip(), "source": {"path": path, "symbol": "setup_menu_accel", "lines": line_span(path, re.escape(label.strip()))}})
|
|
toolbar = []
|
|
for widget in re.findall(r"^(?:Button|button) (\.toolbar\.[^\s\\]+)", text, re.MULTILINE):
|
|
toolbar.append({"control_id": f"AXIS-TOOLBAR-{widget.rsplit('.', 1)[-1]}".upper(), "slice_id": "AXIS-XYZAC-TOOLBAR", "widget": widget, "source": {"path": path, "symbol": widget, "lines": line_span(path, re.escape(widget))}})
|
|
bindings = [{"control_id": f"AXIS-KEY-{binding.upper().replace('-', '_').replace('<', '').replace('>', '')}", "slice_id": "AXIS-XYZAC-KEYBOARD", "binding": binding, "source": {"path": path, "symbol": "bind", "lines": line_span(path, rf"bind [^\\n]*<{re.escape(binding)}>")}} for binding in sorted(set(re.findall(r"^bind [^\s]+ <([^>]+)>", text, re.MULTILINE)))]
|
|
return {"menus": menus, "toolbar": toolbar, "keyboard_bindings": bindings,
|
|
"menu_count": len(menus), "toolbar_count": len(toolbar), "keyboard_count": len(bindings),
|
|
"source_sha256": sha256(ROOT / path)}
|
|
|
|
|
|
def exact_resources() -> list[dict[str, Any]]:
|
|
resources = [
|
|
("xyzac-trt.ini", "case-entrypoint"), ("xyzac-trt.xml", "PYVCP"), ("switchkins_postgui.hal", "POSTGUI_HALFILE"),
|
|
("xyzac-trt.tbl", "TOOL_TABLE"), ("xyzac.var", "PARAMETER_FILE"), ("demos/xyzac_switchkins.ngc", "OPEN_FILE"),
|
|
("remap_subs/428remap.ngc", "REMAP:M428"), ("remap_subs/429remap.ngc", "REMAP:M429"),
|
|
("remap_subs/430remap.ngc", "REMAP:M430"), ("remap_subs/xyzac_switchkins_sub.ngc", "NGCGUI_SUBFILE"),
|
|
("remap_subs/centering.ngc", "NGCGUI_SUBFILE"), ("remap_subs/helix_ac.ngc", "RTCP-SUBROUTINE"),
|
|
("../../../../../../lib/hallib/basic_sim.tcl", "HALFILE:LIB:basic_sim.tcl"),
|
|
("linuxcnc.gif", "INTRO_GRAPHIC"),
|
|
]
|
|
rows = []
|
|
for relative, role in resources:
|
|
absolute = ROOT / "linuxcnc/linuxcnc.gif" if role == "INTRO_GRAPHIC" else CASE_DIR / relative
|
|
path = absolute.resolve().relative_to(ROOT) if absolute.exists() else CASE_DIR.joinpath(relative)
|
|
rows.append({"path": str(path), "role": role,
|
|
"status": "resolved" if absolute.is_file() else ("BLOCKED_RESOURCE" if role == "INTRO_GRAPHIC" else "resolved-via-host-prefix" if role.startswith("HALFILE") else "BLOCKED_RESOURCE"),
|
|
"sha256": sha256(absolute)})
|
|
known = {row["path"] for row in rows}
|
|
for directory, role in ((CASE_DIR / "demos", "DEMO_PROGRAM"), (CASE_DIR / "remap_subs", "REMAP_SUBROUTINE")):
|
|
for path in sorted(directory.glob("*.ngc")):
|
|
rel = str(path.relative_to(ROOT))
|
|
if rel not in known:
|
|
rows.append({"path": rel, "role": role, "status": "resolved", "sha256": sha256(path)})
|
|
return rows
|
|
|
|
|
|
def slice_row(slice_id: str, path: str, symbol: str, domain: str, inputs: list[str], native: list[str], wasm: list[str], outputs: list[str], controls: list[str], gates: list[str], errors: list[str], recovery: list[str], status: str = "SOURCE_MAPPED") -> dict[str, Any]:
|
|
return {"slice_id": slice_id, "domain": domain, "source": {"path": path, "symbol": symbol, "lines": line_span(path, re.escape(symbol)), "sha256": sha256(ROOT / path)},
|
|
"inputs": inputs, "native_commands": native, "wasm_commands": wasm, "authoritative_outputs": outputs, "ui_controls": controls, "gates": gates,
|
|
"normal_cases": ["not executed: SRC-00 inventory scope"], "boundary_cases": ["fixed logical tick and finite values required"],
|
|
"error_cases": errors, "recovery_cases": recovery, "wasm_evidence": WASM_EVIDENCE[:2], "status": status,
|
|
"authority": "linuxcnc-wasm-worker"}
|
|
|
|
|
|
def build() -> dict[str, Any]:
|
|
controls = axis_controls()
|
|
direct_source_paths = list(DIRECT_SOURCES)
|
|
for directory in DIRECT_SOURCE_DIRECTORIES:
|
|
direct_source_paths.extend(str(path.relative_to(ROOT)) for path in sorted((ROOT / directory).glob("*.ngc")))
|
|
sources = [source(path, "direct-input") for path in direct_source_paths]
|
|
slices = [
|
|
slice_row("AXIS-XYZAC-SHELL-MENU", "linuxcnc/share/axis/tcl/axis.tcl", "setup_menu_accel", "axis-shell", ["menu event"], ["AXIS Tcl command"], ["WORKER_COMMAND"], ["task/mode/error snapshot"], ["AXIS-MENU-*"], ["estop/off/mode gate"], ["SOURCE_BLOCKED: no per-control WASM trace"], ["operation ledger reconcile"]),
|
|
slice_row("AXIS-XYZAC-TOOLBAR", "linuxcnc/share/axis/tcl/axis.tcl", ".toolbar.machine_estop", "axis-toolbar", ["toolbar click"], ["estop_clicked/task_run/task_pause/task_stop"], ["ESTOP", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_ABORT"], ["axis_state", "state_hash", "command_result"], ["AXIS-TOOLBAR-*"], ["task_ready", "mode", "fault"], ["SOURCE_BLOCKED: native handler matrix not runtime compared"], ["checkpoint/reconcile"]),
|
|
slice_row("AXIS-XYZAC-KEYBOARD", "linuxcnc/share/axis/tcl/axis.tcl", "bind", "axis-keyboard", ["keyboard binding"], ["jog/task/mode binding"], ["JOG_START", "JOG_STOP", "PROGRAM_RUN", "PROGRAM_ABORT"], ["authoritative snapshot", "gate reason"], ["AXIS-KEY-*"], ["focus/input protection", "manual mode"], ["SOURCE_BLOCKED: keyboard Worker evidence pending"], ["pending operation reconcile"]),
|
|
slice_row("AXIS-XYZAC-PROGRAM-MDI", "linuxcnc/src/emc/usr_intf/axis/scripts/axis.py", "send_mdi_command", "program-mdi", ["program/MDI text"], ["commands.send_mdi_command", "commands.task_run"], ["PROGRAM_LOAD", "PROGRAM_RUN", "MDI_EXECUTE", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT"], ["program", "canonical trajectory", "task.interp_state", "state_hash"], ["AXIS-PROGRAM", "AXIS-MDI"], ["task_idle", "file_loaded", "mode_mdi/auto", "no_fault"], ["SOURCE_BLOCKED: exact case WASM execution not proven"], ["checkpoint/replay/no-duplicate-side-effect"]),
|
|
slice_row("AXIS-XYZAC-JOG-HOME", "linuxcnc/src/emc/usr_intf/axis/scripts/axis.py", "jog_on", "manual-motion", ["axis/joint/direction/velocity"], ["jog_on", "jog_off", "home_joint", "home_all_joints"], ["JOG_START", "JOG_STOP", "HOME"], ["motion.axes", "motion.joints", "home_limit", "state_hash"], ["AXIS-JOG", "AXIS-HOME"], ["manual_ok", "axis_enabled", "homing_allowed", "limit/fault"], ["SOURCE_BLOCKED: exact XYZAC Worker comparison pending"], ["pause/resume/abort"]),
|
|
slice_row("AXIS-XYZAC-PREVIEW-VISUAL", "linuxcnc/lib/python/rs274/glcanon.py", "GlCanon", "preview-vismach", ["parsed program", "canonical events"], ["canon/gremlin/vismach"], ["PROGRAM_LOAD", "SNAPSHOT_REQUEST"], ["wasm canonical trajectory", "visual.pose", "motion.joints"], ["AXIS-PREVIEW", "AXIS-VISMACH"], ["program_parse_ok", "canonical_source_present"], ["SOURCE_BLOCKED: JS preview forbidden; exact case evidence pending"], ["clear visual only; no motion mutation"]),
|
|
slice_row("AXIS-XYZAC-PYVCP-SWITCHKINS", "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal", "type0-button/type1-button/type2-button", "pyvcp-hal", ["PyVCP button/HAL net"], ["halui.mdi-command-00/01/02"], ["MDI_EXECUTE:M429", "MDI_EXECUTE:M428", "MDI_EXECUTE:M430"], ["motion.switchkins-type", "kinstype_is_0/1/2", "state_hash"], ["AXIS-RTCP-TYPE-0", "AXIS-RTCP-TYPE-1", "AXIS-RTCP-TYPE-2"], ["interp_idle", "HAL_PIN_VARS", "switchkins_net_present"], ["BLOCKED_HAL: native HAL load not proven in WASM case"], ["ledger reconcile/no duplicate M-code"]),
|
|
slice_row("AXIS-XYZAC-M428-M429-M430", "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc", "428remap/429remap/430remap", "switchkins-rtcp", ["MDI/G-code M428/M429/M430", "motion.switchkins-type"], ["M68 E3 Q{0,1,2}", "M66 E0 L0"], ["MDI_EXECUTE", "KINEMATICS_SELECT"], ["kinematics.active_type", "motion.switchkins-type", "state_hash"], ["AXIS-RTCP-M428", "AXIS-RTCP-M429", "AXIS-RTCP-M430"], ["interp_idle", "HAL_PIN_VARS", "switchkins_net", "finite_offsets"], ["BLOCKED_STARTUP: native kins startup did not reach verified state"], ["checkpoint/reconcile; no blind retry"]),
|
|
slice_row("AXIS-XYZAC-KINEMATICS", "linuxcnc/src/emc/kinematics/trtfuncs.c", "xyzacKinematicsForward", "kinematics", ["five joints", "A/C degrees", "tool/axis offsets"], ["kinematicsForward", "kinematicsInverse"], ["KINEMATICS_FORWARD", "KINEMATICS_INVERSE"], ["pose", "joints", "limits", "state_hash"], ["AXIS-XYZAC-POSE"], ["active_type", "five_joint_mapping", "finite_offsets", "limits"], ["SOURCE_BLOCKED: exact XYZAC native/WASM trace unavailable"], ["checkpoint pose/joint/hash recovery"]),
|
|
slice_row("AXIS-XYZAC-TASK-TP-MOTION", "linuxcnc/src/emc/motion/control.c", "control", "task-tp-motion", ["task/interpreter/TP input"], ["task cycle", "TP queue", "motion control"], ["SIM_STEP", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT"], ["task", "program", "planner", "motion", "simulation_tick", "state_hash"], ["AXIS-STATE"], ["ESTOP/OFF/ON/HOME/IDLE/RUNNING/PAUSED/limit/fault"], ["SOURCE_BLOCKED: no exact case Worker trace attached"], ["checkpoint/replay/reconcile"]),
|
|
slice_row("AXIS-XYZAC-ERROR-RECOVERY", "linuxcnc/src/emc/motion/command.c", "command", "error-recovery", ["command result/error"], ["command dispatch/abort"], ["COMMAND_RESULT", "ERROR", "CHECKPOINT", "RECOVER"], ["error", "ledger", "checkpoint", "recovery endpoint", "state_hash"], ["AXIS-DIAGNOSTICS", "AXIS-RECOVERY"], ["operation_id", "seq", "ack/result/commit"], ["SOURCE_BLOCKED: exact case crash/restart evidence pending"], ["reconcile before retry"]),
|
|
]
|
|
resources = exact_resources()
|
|
source_hashes = {row["path"]: row["sha256"] for row in sources}
|
|
unresolved = [row for row in resources if row["status"].startswith("BLOCKED")]
|
|
command_contract = [{"command": command, "slice_id": slice_id} for command, slice_id in [
|
|
("PROGRAM_LOAD", "AXIS-XYZAC-PROGRAM-MDI"), ("PROGRAM_RUN", "AXIS-XYZAC-PROGRAM-MDI"), ("PROGRAM_PAUSE", "AXIS-XYZAC-PROGRAM-MDI"), ("PROGRAM_RESUME", "AXIS-XYZAC-PROGRAM-MDI"), ("PROGRAM_ABORT", "AXIS-XYZAC-PROGRAM-MDI"), ("MDI_EXECUTE", "AXIS-XYZAC-PROGRAM-MDI"), ("JOG_START", "AXIS-XYZAC-JOG-HOME"), ("JOG_STOP", "AXIS-XYZAC-JOG-HOME"), ("HOME", "AXIS-XYZAC-JOG-HOME"), ("M428", "AXIS-XYZAC-M428-M429-M430"), ("M429", "AXIS-XYZAC-M428-M429-M430"), ("M430", "AXIS-XYZAC-M428-M429-M430"), ("SIM_STEP", "AXIS-XYZAC-TASK-TP-MOTION"), ("CHECKPOINT", "AXIS-XYZAC-ERROR-RECOVERY"), ("RECOVER", "AXIS-XYZAC-ERROR-RECOVERY")]]
|
|
state_contract = [{"state": state, "slice_id": slice_id} for state, slice_id in [
|
|
("task.state", "AXIS-XYZAC-TASK-TP-MOTION"), ("task.mode", "AXIS-XYZAC-TASK-TP-MOTION"), ("task.interp_state", "AXIS-XYZAC-PROGRAM-MDI"), ("program.current_line", "AXIS-XYZAC-PROGRAM-MDI"), ("motion.joints", "AXIS-XYZAC-KINEMATICS"), ("kinematics.active_type", "AXIS-XYZAC-M428-M429-M430"), ("motion.switchkins-type", "AXIS-XYZAC-PYVCP-SWITCHKINS"), ("pose", "AXIS-XYZAC-KINEMATICS"), ("state_hash", "AXIS-XYZAC-ERROR-RECOVERY")]]
|
|
error_contract = [{"error": error, "slice_id": slice_id} for error, slice_id in [("axis_gate", "AXIS-XYZAC-SHELL-MENU"), ("M428_HAL_PIN_MISSING", "AXIS-XYZAC-M428-M429-M430"), ("kinematics_singular", "AXIS-XYZAC-KINEMATICS"), ("following_error", "AXIS-XYZAC-TASK-TP-MOTION"), ("ledger_reconcile_required", "AXIS-XYZAC-ERROR-RECOVERY")]]
|
|
visual_contract = [{"visual": visual, "slice_id": "AXIS-XYZAC-PREVIEW-VISUAL"} for visual in ["canonical_toolpath", "wasm_pose", "wasm_joints", "vismach_clear"]]
|
|
recovery_contract = [{"recovery": recovery, "slice_id": "AXIS-XYZAC-ERROR-RECOVERY"} for recovery in ["pause_resume", "abort", "checkpoint", "worker_restart_reconcile", "no_duplicate_mcode"]]
|
|
wasm_authority = json.loads((ROOT / "coverage/wasm-only-source-audit.report.json").read_text(encoding="utf-8")) if (ROOT / "coverage/wasm-only-source-audit.report.json").is_file() else {}
|
|
report: dict[str, Any] = {
|
|
"schema": "xyzac-trt-source-slice-inventory.v1", "task_id": "SRC-00", "context_id": CONTEXT_ID,
|
|
"case": {"path": str(CASE.relative_to(ROOT)), "sha256": sha256(CASE), "status": "indexed"},
|
|
"status": "blocked", "implementation_status": "source_frozen_slice_inventory",
|
|
"source_hashes": source_hashes, "direct_input_specs": [{"path": path, "kind": "file", "status": "indexed" if (ROOT / path).is_file() else "SOURCE_BLOCKED"} for path in DIRECT_SOURCES] + [{"path": path, "kind": "directory", "resolved_files": [row["path"] for row in sources if row["path"].startswith(path + "/")], "status": "indexed" if (ROOT / path).is_dir() else "SOURCE_BLOCKED"} for path in DIRECT_SOURCE_DIRECTORIES], "direct_sources": sources, "exact_resources": resources,
|
|
"axis_controls": controls, "slices": slices, "command_contract": command_contract, "state_contract": state_contract, "error_contract": error_contract, "visual_contract": visual_contract, "recovery_contract": recovery_contract,
|
|
"counts": {"direct_sources": len(sources), "slices": len(slices), "axis_menus": controls["menu_count"], "axis_toolbar": controls["toolbar_count"], "axis_keyboard": controls["keyboard_count"], "unregistered_slices": 0, "blocked_resources": len(unresolved)},
|
|
"authority": {"js_authority": 0, "authority_owner": "linuxcnc-wasm-worker", "existing_audit": {"path": "coverage/wasm-only-source-audit.report.json", "status": wasm_authority.get("status", "missing"), "open_boundaries": wasm_authority.get("open_tasks", [])}},
|
|
"checks": {"all_direct_sources_have_sha256": all(row["sha256"] for row in sources), "all_controls_commands_states_have_slice_id": all(row.get("slice_id") for group in (controls["menus"], controls["toolbar"], controls["keyboard_bindings"], command_contract, state_contract, error_contract, visual_contract, recovery_contract) for row in group) and all(row.get("slice_id") for row in slices), "exact_resources_resolved_or_explicitly_blocked": all(row["status"] in {"resolved", "resolved-via-host-prefix", "BLOCKED_RESOURCE", "BLOCKED_STARTUP", "BLOCKED_HAL"} for row in resources), "unregistered_slice_count": 0, "js_authority": 0},
|
|
"runtime_evidence": {"native": {"status": "pending-parity-report", "commands": ["./run-5axis-rtcp.sh", "./run-5axis-rtcp.sh xyzbc-trt.ini"]}, "wasm": {"status": "REUSED_EXISTING_EVIDENCE", "tests": [row["path"] for row in WASM_EVIDENCE], "authority": "wasm"}},
|
|
"blockers": ["SOURCE_BLOCKED: run scripts/test_xyzac_trt_parity.py to promote the native/WASM exact-case evidence"],
|
|
"next_task": "WASM-00", "release_status": "STOP_RELEASE",
|
|
}
|
|
parity_path = ROOT / "coverage/xyzac-trt-native-wasm-parity.report.json"
|
|
if parity_path.is_file():
|
|
parity = json.loads(parity_path.read_text(encoding="utf-8"))
|
|
report["runtime_evidence"] = {"native": parity.get("native", {}), "wasm": parity.get("wasm", {}), "worker": parity.get("worker", {}), "trace": parity.get("trace", {})}
|
|
if parity.get("status") == "verified" and not unresolved:
|
|
report["status"] = "verified"
|
|
report["implementation_status"] = "source_frozen_and_worker_verified"
|
|
report["blockers"] = []
|
|
report["report_sha256"] = hashlib.sha256(canonical(report).encode()).hexdigest()
|
|
return report
|
|
|
|
|
|
def write() -> dict[str, Any]:
|
|
report = build()
|
|
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")
|
|
public = {"schema": report["schema"], "context_id": CONTEXT_ID, "case": report["case"], "axis_controls": report["axis_controls"], "slices": report["slices"], "command_contract": report["command_contract"], "state_contract": report["state_contract"], "error_contract": report["error_contract"], "visual_contract": report["visual_contract"], "recovery_contract": report["recovery_contract"], "counts": report["counts"], "authority": report["authority"], "report_sha256": report["report_sha256"]}
|
|
SLICE_MODULE.write_text("export const XYZAC_TRT_SOURCE_SLICES = Object.freeze(" + json.dumps(public, ensure_ascii=False, sort_keys=True, indent=2) + ");\n", encoding="utf-8")
|
|
contract = {"schema": "xyzac-trt-case-contract.v1", "context_id": CONTEXT_ID, "case": report["case"], "resources": report["exact_resources"], "commands": ["MDI_EXECUTE", "PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT", "JOG_START", "JOG_STOP", "HOME", "KINEMATICS_SELECT", "SNAPSHOT_REQUEST"], "authority": "linuxcnc-wasm-worker", "js_authority": 0, "report_sha256": report["report_sha256"]}
|
|
CONTRACT_MODULE.write_text("export const XYZAC_TRT_CASE_CONTRACT = Object.freeze(" + json.dumps(contract, ensure_ascii=False, sort_keys=True, indent=2) + ");\n", encoding="utf-8")
|
|
return report
|
|
|
|
|
|
if __name__ == "__main__":
|
|
result = write()
|
|
print(json.dumps({"status": result["status"], "slices": result["counts"]["slices"], "sources": result["counts"]["direct_sources"], "report_sha256": result["report_sha256"]}))
|