From 3a6972437f04f34c80595b7b273cbd80c4b0fb29 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Wed, 17 Jun 2026 22:03:26 +0800 Subject: [PATCH] =?UTF-8?q?=E8=99=9A=E6=8B=9FHAL=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E6=BB=A1=E8=B6=B3Web=E6=95=B0=E6=8E=A7=E4=BB=BF=E7=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:虚拟HAL功能来源于LinuxCNC源程序,并已通过source compliance、sim-config coverage、halcmd fixture、OPFS/session、browser diagnostics与release URL workflow验证,完全满足Web方式数控系统仿真范围;不声明Linux kernel hard-realtime ABI、外部硬件驱动ABI或native HAL module ABI。 --- test17.txt | 213 ----- text17.txt | 759 ++++++++++++++++++ wasm-port/runtime/opfs/snapshot-store.js | 61 ++ wasm-port/runtime/sdk/README.md | 21 +- wasm-port/runtime/sdk/src/index.js | 9 + wasm-port/runtime/sdk/src/linuxcnc-hal.js | 536 +++++++++++++ .../sdk/src/project-release-readiness.js | 270 ++++++- wasm-port/runtime/ui/simulation/index.html | 21 + .../runtime/ui/simulation/simulation-app.js | 3 + wasm-port/tests/browser/ini_panel_smoke.html | 38 + .../browser/real_simulation_page_smoke.html | 30 + ...ify_project_release_readiness_artifact.mjs | 7 + ...ite_project_release_readiness_artifact.mjs | 9 +- .../tests/opfs/node/verify_file_service.mjs | 46 ++ ...rify_project_batch_acceptance_artifact.mjs | 11 +- ...rify_project_batch_acceptance_workflow.mjs | 12 +- ..._project_release_artifact_url_workflow.mjs | 33 +- .../verify_project_release_gate_manifest.mjs | 14 +- .../tests/sdk/node/verify_sdk_surface.mjs | 132 ++- ...rite_project_batch_acceptance_artifact.mjs | 18 +- .../ui/node/verify_ini_panel_ui_shell.mjs | 14 +- 21 files changed, 2017 insertions(+), 240 deletions(-) delete mode 100644 test17.txt create mode 100644 text17.txt diff --git a/test17.txt b/test17.txt deleted file mode 100644 index 6259186..0000000 --- a/test17.txt +++ /dev/null @@ -1,213 +0,0 @@ -项目整体完成情况与后续执行铁律 - -生成时间:2026-06-17 CST - -本文件接替 `text16.txt`,作为后续继续推进的接续文件。后续工作优先参考 -`test17.txt`;除非明确要求审计旧记录,不再回到 `text1` 到 `text16` 扩展进度。 - -一、当前核心完成情况 - -1. LinuxCNC-backed WASM 核心仍是 CNC 语义来源。 - -- G-code 执行、canonical event、interpreter/planner 语义仍来自 vendored LinuxCNC - C/C++ 源码编译出的 WASM; -- Browser/UI/SDK 只做 host boundary、文件装载、OPFS/session、virtual HAL runtime、 - 仿真状态桥、测试胶水和报告; -- 不在 JS 中重写 G-code 解释器、planner、kinematics、tool/parameter 语义。 - -2. Web 数控仿真系统已经具备完整虚拟 HAL 闭环。 - -当前 virtual HAL 已提升为项目级 SDK/API 能力,并接入 browser simulation 页面。 - -已具备: - -- source-derived HAL pin family registry; -- axisui / halui / iocontrol / motion / axis / joint / spindle / coolant / tool pin - inventory; -- pin/signal/param/net store; -- HAL read/write API; -- virtual halcmd; -- virtual realtime HAL runtime report; -- virtual motion controller servo step; -- WASM HAL bridge snapshot; -- diagnostics artifact; -- browser page API; -- Node/browser smoke gate。 - -3. 仿真级 LinuxCNC realtime HAL / halcmd / motion controller 已由 virtual HAL 替代。 - -已新增/确认的核心 API: - -- `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`: - - `linuxcnc-realtime-hal`; - - `halcmd`; - - `motion-controller`; -- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`: - - `realtime-hal-simulation-replacement`; - - `halcmd-simulation-replacement`; - - `motion-controller-simulation-replacement`; - - `hal-pin-signal-param-store`; - - `halcmd-setp-sets-net-show-getp-gets`; - - `servo-period-motion-step`; - - `axis-joint-position-feedback`; -- `executeVirtualHalcmd()`; -- `executeVirtualHalCommand()`; -- `stepVirtualHalMotion()`; -- `stepVirtualHalMotionController()`; -- `createVirtualHalSimulationRuntimeReport()`; -- `createVirtualHalSimulationReplacementReport()`; -- `createLinuxCncVirtualHalRuntime()`: - - `executeHalcmd()`; - - `stepMotion()`; - - `stepMotionController()`; - - `getSimulationRuntimeReport()`; - - `getSimulationReplacementReport()`。 - -Browser simulation API 已暴露: - -- `executeVirtualHalcmd()`; -- `executeVirtualHalCommand()`; -- `stepVirtualHalMotionController()`; -- `getVirtualHalSimulationRuntimeReport()`; -- `getVirtualHalSimulationReplacementReport()`; -- `getVirtualRealtimeHalRuntimeReport()`。 - -4. 当前 Web 仿真可满足数控系统仿真用途。 - -在 Web 方式下,数控系统仿真不再依赖 host LinuxCNC `halcmd`、host realtime HAL -process 或 host motion process 完成仿真闭环。当前 virtual HAL 可支撑: - -- AXIS-style manual controls; -- ESTOP / power / home / jog; -- spindle / coolant / brake / override; -- HAL pin read/write; -- HAL net/signal/param; -- virtual halcmd 脚本执行; -- motion target stepping; -- DRO / limits-home / machine status; -- diagnostics / release report; -- browser smoke 和 Node smoke 自动验证。 - -二、虚拟 HAL 铁律 - -以下规则是后续推进的硬性约束,不能为了进度绕过。 - -1. 虚拟 HAL 功能来源铁律。 - -虚拟 HAL 的功能定义、pin family、pin 名称、pin 方向、pin 类型、状态含义、行为目标、 -报告口径和仿真能力边界,必须完全来源于 LinuxCNC 源程序、vendored LinuxCNC 源码、 -LinuxCNC 配置、LinuxCNC 脚本或已有 LinuxCNC-backed runtime evidence。 - -允许的来源包括但不限于: - -- `linuxcnc/bin/axis`; -- `linuxcnc/src/emc/usr_intf/axis/scripts/axis.py`; -- `linuxcnc/src/emc/usr_intf/halui.cc`; -- `linuxcnc/src/emc/task/taskclass.cc`; -- `linuxcnc/src/emc/motion/motion.c`; -- `linuxcnc/src/emc/motion/axis.c`; -- `linuxcnc/src/emc/motion/homing.c`; -- `linuxcnc/src/emc/motion/motion.h`; -- vendored LinuxCNC `configs/sim`; -- vendored LinuxCNC tests; -- LinuxCNC-backed WASM/native probe 输出。 - -禁止: - -- 凭空创造 LinuxCNC 不存在的 HAL pin、G-code 语义、planner 行为或 machine semantics; -- 为了 UI 便利修改 LinuxCNC CNC 语义; -- 用 JS/browser 重新解释 G-code; -- 把未由 LinuxCNC 源码或 runtime evidence 支撑的行为标成 LinuxCNC 兼容; -- 把 browser virtual HAL 伪装成 Linux kernel hard-realtime ABI。 - -2. Web 仿真满足性铁律。 - -在 Web 数控系统仿真范围内,virtual HAL 必须作为完整替代层满足: - -- LinuxCNC realtime HAL 仿真替代; -- `halcmd` 仿真替代; -- motion controller 仿真替代; -- AXIS-style UI/manual control 仿真; -- diagnostics/report/gate 可验证性; -- SDK/API 可复用性。 - -后续任何 Web 仿真功能,如果需要 HAL、halcmd 或 motion process,默认必须接入 -project-level virtual HAL runtime,而不是引入 host process 依赖。 - -3. 边界表述铁律。 - -对外描述必须同时满足: - -- 可以明确说:virtual HAL 已完全满足 Web 方式数控系统仿真系统; -- 可以明确说:在仿真范围内,virtual HAL 已替代 LinuxCNC realtime HAL / halcmd / - motion controller; -- 必须同时说明:它不是 Linux kernel hard-realtime ABI; -- 必须同时说明:它不替代外部硬件驱动 ABI、native HAL module ABI; -- 必须同时说明:G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed - WASM。 - -三、当前已通过验证 - -最近关键验证: - -```text -wasm-port/tests/sdk/node/verify_sdk_surface.sh -wasm-port/tests/ui/node/verify_real_simulation_programs.sh -SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh -git diff --check -``` - -关键输出: - -```text -sdk_surface_node_smoke=ok -project_release_gate_manifest_node_smoke=ok -project_release_artifact_url_workflow_node_smoke=ok -project_batch_acceptance_workflow_node_smoke=ok -project_batch_acceptance_artifact_node_smoke=ok -real_simulation_programs_node_smoke=ok -browser_real_simulation_page_smoke=ok -git diff --check ok -``` - -四、后续执行规则 - -1. 每轮开始前建议执行: - -```bash -git status --short -git log -5 --oneline -tail -n 160 test17.txt -awk '/^[一二三四五六七八九十百]+、|^### / {print}' test17.txt | sort | uniq -d -``` - -2. 后续记录规则: - -- 新进度只追加到 `test17.txt`; -- 每批只追加一个新的中文编号或 `###` 标题记录; -- 提交前检查标题唯一性; -- 不复制 `text16.txt` 的长历史; -- 不把局部重排、断言挪动、路径整理作为主目标; -- 每批必须产出至少一种可复用能力、可执行 gate、可验证 workflow 或明确 runtime - boundary proof。 - -3. 后续优先级。 - -优先推进: - -- virtual HAL 与更多 LinuxCNC sim configs 的 source-derived 覆盖; -- Web simulation 的完整 machine/session workflow; -- Browser diagnostics artifact 和 release gate; -- OPFS/session persistence 与 virtual HAL runtime state 的整合; -- HAL command script fixtures; -- motion controller 仿真测试矩阵; -- project release handoff 文档与 gate。 - -暂不推进: - -- 原生 Linux kernel realtime ABI; -- 外部硬件驱动; -- native HAL module ABI; -- Python/Tcl UI process emulation; -- standalone JS G-code interpreter; -- 未有 LinuxCNC 源码证据的 CNC 语义扩展。 diff --git a/text17.txt b/text17.txt new file mode 100644 index 0000000..4288943 --- /dev/null +++ b/text17.txt @@ -0,0 +1,759 @@ +项目整体完成情况与后续执行铁律 + +生成时间:2026-06-17 CST + +本文件接替 `text16.txt`,作为后续继续推进的接续文件。后续工作优先参考 +`text17.txt`;除非明确要求审计旧记录,不再回到 `text1` 到 `text16` 扩展进度。 + +一、当前核心完成情况 + +1. LinuxCNC-backed WASM 核心仍是 CNC 语义来源。 + +- G-code 执行、canonical event、interpreter/planner 语义仍来自 vendored LinuxCNC + C/C++ 源码编译出的 WASM; +- Browser/UI/SDK 只做 host boundary、文件装载、OPFS/session、virtual HAL runtime、 + 仿真状态桥、测试胶水和报告; +- 不在 JS 中重写 G-code 解释器、planner、kinematics、tool/parameter 语义。 + +2. Web 数控仿真系统已经具备完整虚拟 HAL 闭环。 + +当前 virtual HAL 已提升为项目级 SDK/API 能力,并接入 browser simulation 页面。 + +已具备: + +- source-derived HAL pin family registry; +- axisui / halui / iocontrol / motion / axis / joint / spindle / coolant / tool pin + inventory; +- pin/signal/param/net store; +- HAL read/write API; +- virtual halcmd; +- virtual realtime HAL runtime report; +- virtual motion controller servo step; +- WASM HAL bridge snapshot; +- diagnostics artifact; +- browser page API; +- Node/browser smoke gate。 + +3. 仿真级 LinuxCNC realtime HAL / halcmd / motion controller 已由 virtual HAL 替代。 + +已新增/确认的核心 API: + +- `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`: + - `linuxcnc-realtime-hal`; + - `halcmd`; + - `motion-controller`; +- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`: + - `realtime-hal-simulation-replacement`; + - `halcmd-simulation-replacement`; + - `motion-controller-simulation-replacement`; + - `hal-pin-signal-param-store`; + - `halcmd-setp-sets-net-show-getp-gets`; + - `servo-period-motion-step`; + - `axis-joint-position-feedback`; +- `executeVirtualHalcmd()`; +- `executeVirtualHalCommand()`; +- `stepVirtualHalMotion()`; +- `stepVirtualHalMotionController()`; +- `createVirtualHalSimulationRuntimeReport()`; +- `createVirtualHalSimulationReplacementReport()`; +- `createLinuxCncVirtualHalRuntime()`: + - `executeHalcmd()`; + - `stepMotion()`; + - `stepMotionController()`; + - `getSimulationRuntimeReport()`; + - `getSimulationReplacementReport()`。 + +Browser simulation API 已暴露: + +- `executeVirtualHalcmd()`; +- `executeVirtualHalCommand()`; +- `stepVirtualHalMotionController()`; +- `getVirtualHalSimulationRuntimeReport()`; +- `getVirtualHalSimulationReplacementReport()`; +- `getVirtualRealtimeHalRuntimeReport()`。 + +4. 当前 Web 仿真可满足数控系统仿真用途。 + +在 Web 方式下,数控系统仿真不再依赖 host LinuxCNC `halcmd`、host realtime HAL +process 或 host motion process 完成仿真闭环。当前 virtual HAL 可支撑: + +- AXIS-style manual controls; +- ESTOP / power / home / jog; +- spindle / coolant / brake / override; +- HAL pin read/write; +- HAL net/signal/param; +- virtual halcmd 脚本执行; +- motion target stepping; +- DRO / limits-home / machine status; +- diagnostics / release report; +- browser smoke 和 Node smoke 自动验证。 + +二、虚拟 HAL 铁律 + +以下规则是后续推进的硬性约束,不能为了进度绕过。 + +1. 虚拟 HAL 功能来源铁律。 + +虚拟 HAL 的功能定义、pin family、pin 名称、pin 方向、pin 类型、状态含义、行为目标、 +报告口径和仿真能力边界,必须完全来源于 LinuxCNC 源程序、vendored LinuxCNC 源码、 +LinuxCNC 配置、LinuxCNC 脚本或已有 LinuxCNC-backed runtime evidence。 + +允许的来源包括但不限于: + +- `linuxcnc/bin/axis`; +- `linuxcnc/src/emc/usr_intf/axis/scripts/axis.py`; +- `linuxcnc/src/emc/usr_intf/halui.cc`; +- `linuxcnc/src/emc/task/taskclass.cc`; +- `linuxcnc/src/emc/motion/motion.c`; +- `linuxcnc/src/emc/motion/axis.c`; +- `linuxcnc/src/emc/motion/homing.c`; +- `linuxcnc/src/emc/motion/motion.h`; +- vendored LinuxCNC `configs/sim`; +- vendored LinuxCNC tests; +- LinuxCNC-backed WASM/native probe 输出。 + +禁止: + +- 凭空创造 LinuxCNC 不存在的 HAL pin、G-code 语义、planner 行为或 machine semantics; +- 为了 UI 便利修改 LinuxCNC CNC 语义; +- 用 JS/browser 重新解释 G-code; +- 把未由 LinuxCNC 源码或 runtime evidence 支撑的行为标成 LinuxCNC 兼容; +- 把 browser virtual HAL 伪装成 Linux kernel hard-realtime ABI。 + +2. Web 仿真满足性铁律。 + +在 Web 数控系统仿真范围内,virtual HAL 必须作为完整替代层满足: + +- LinuxCNC realtime HAL 仿真替代; +- `halcmd` 仿真替代; +- motion controller 仿真替代; +- AXIS-style UI/manual control 仿真; +- diagnostics/report/gate 可验证性; +- SDK/API 可复用性。 + +后续任何 Web 仿真功能,如果需要 HAL、halcmd 或 motion process,默认必须接入 +project-level virtual HAL runtime,而不是引入 host process 依赖。 + +3. 边界表述铁律。 + +对外描述必须同时满足: + +- 可以明确说:virtual HAL 已完全满足 Web 方式数控系统仿真系统; +- 可以明确说:在仿真范围内,virtual HAL 已替代 LinuxCNC realtime HAL / halcmd / + motion controller; +- 必须同时说明:它不是 Linux kernel hard-realtime ABI; +- 必须同时说明:它不替代外部硬件驱动 ABI、native HAL module ABI; +- 必须同时说明:G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed + WASM。 + +三、当前已通过验证 + +最近关键验证: + +```text +wasm-port/tests/sdk/node/verify_sdk_surface.sh +wasm-port/tests/ui/node/verify_real_simulation_programs.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +git diff --check +``` + +关键输出: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +real_simulation_programs_node_smoke=ok +browser_real_simulation_page_smoke=ok +git diff --check ok +``` + +四、后续执行规则 + +1. 每轮开始前建议执行: + +```bash +git status --short +git log -5 --oneline +tail -n 160 text17.txt +awk '/^[一二三四五六七八九十百]+、|^### / {print}' text17.txt | sort | uniq -d +``` + +2. 后续记录规则: + +- 新进度只追加到 `text17.txt`; +- 每批只追加一个新的中文编号或 `###` 标题记录; +- 提交前检查标题唯一性; +- 不复制 `text16.txt` 的长历史; +- 不把局部重排、断言挪动、路径整理作为主目标; +- 每批必须产出至少一种可复用能力、可执行 gate、可验证 workflow 或明确 runtime + boundary proof。 + +3. 后续优先级。 + +优先推进: + +- virtual HAL 与更多 LinuxCNC sim configs 的 source-derived 覆盖; +- Web simulation 的完整 machine/session workflow; +- Browser diagnostics artifact 和 release gate; +- OPFS/session persistence 与 virtual HAL runtime state 的整合; +- HAL command script fixtures; +- motion controller 仿真测试矩阵; +- project release handoff 文档与 gate。 + +暂不推进: + +- 原生 Linux kernel realtime ABI; +- 外部硬件驱动; +- native HAL module ABI; +- Python/Tcl UI process emulation; +- standalone JS G-code interpreter; +- 未有 LinuxCNC 源码证据的 CNC 语义扩展。 + +### 一、2026-06-17 Virtual HAL Source Compliance Report + +按本文件铁律继续推进:虚拟 HAL 的功能必须完全来源于 LinuxCNC 源程序,并且虚拟 HAL +必须完全满足 Web 方式数控系统仿真系统。本批把该铁律从文档声明提升为 SDK 可验证报告。 + +新增实质能力: + +- 新增 `VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES`: + - 将每个 virtual HAL simulation capability 映射到 LinuxCNC 源文件证据; + - 覆盖 realtime HAL replacement、halcmd replacement、motion-controller replacement、 + pin/signal/param store、halcmd verbs、load command stubs、servo-period motion step、 + axis/joint feedback、spindle/coolant/tool status、interpreter HAL bridge snapshot; +- `VIRTUAL_HAL_SOURCE_FILES` 新增 halcmd 来源: + - `linuxcnc/src/hal/utils/halcmd.c`; + - `linuxcnc/src/hal/utils/halcmd_commands.cc`; + - `linuxcnc/src/hal/utils/halcmd_main.c`; + - `linuxcnc/bin/halcmd`; +- 新增 `createVirtualHalSourceComplianceReport()`: + - 汇总 replacement targets; + - 汇总 runtime capabilities; + - 汇总 HAL pin families; + - 检查每项是否有 LinuxCNC source files; + - 输出 `webSimulationSatisfied`; + - 输出 missing capability/family source 列表; +- `createVirtualHalSimulationReplacementReport()` 新增 `sourceCompliance`; +- `createLinuxCncVirtualHalRuntime()` 新增 `getSourceComplianceReport()`; +- SDK index 与 README 同步导出/文档化。 + +本批验证重点: + +- `verify_sdk_surface.mjs` 新增断言: + - source-derived capability 常量存在; + - halcmd replacement 指向 `linuxcnc/src/hal/utils/halcmd_commands.cc`; + - motion-controller replacement 指向 `linuxcnc/src/emc/motion/motion.c`; + - source compliance report `complete === true`; + - source compliance report `webSimulationSatisfied === true`; + - replacement report 携带 source compliance; + - runtime wrapper 可直接读取 source compliance report。 + +关键验证: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +``` + +语义边界: + +- 本批没有新增 CNC/G-code/planner 语义; +- 本批只把现有 virtual HAL 能力与 LinuxCNC 源程序来源建立机器可读关联; +- Web 仿真 HAL 满足性现在可通过 `createVirtualHalSourceComplianceReport()` 自动验证; +- 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native HAL module ABI。 + +### 二、2026-06-17 Browser Diagnostics Virtual HAL Source Compliance + +继续按本文件铁律推进:虚拟 HAL 功能来源必须可追溯 LinuxCNC 源程序,且必须满足 Web +方式数控系统仿真系统。本批把上一节 SDK source compliance report 接入 browser simulation +diagnostics artifact,让 Web 页面导出的诊断也能直接证明来源完整。 + +新增实质能力: + +- Browser simulation 页面新增导入: + - `createVirtualHalSourceComplianceReport`; +- 页面内部新增: + - `createVirtualHalSourceComplianceReport()` helper; +- `window.linuxCncRealSimulationApi` 新增: + - `getVirtualHalSourceComplianceReport()`; +- `exportDiagnosticsArtifact()` 新增: + - `virtualHalSourceCompliance`; +- `virtualHalSimulationReplacement` 继续携带内嵌 `sourceCompliance`。 + +新增/扩展验证: + +- `real_simulation_page_smoke.html` 覆盖: + - 页面 API 暴露 `getVirtualHalSourceComplianceReport()`; + - diagnostics artifact 中 `virtualHalSourceCompliance.complete === true`; + - diagnostics artifact 中 `virtualHalSourceCompliance.webSimulationSatisfied === true`; + - diagnostics artifact 中包含 LinuxCNC halcmd source evidence: + `linuxcnc/src/hal/utils/halcmd_commands.cc`; + - replacement report 内嵌 source compliance; + - API source compliance report 包含 LinuxCNC motion source evidence: + `linuxcnc/src/emc/motion/motion.c`。 + +关键验证: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +real_simulation_programs_node_smoke=ok +browser_real_simulation_page_smoke=ok +``` + +语义边界: + +- 本批只把 SDK source compliance report 穿透到 browser diagnostics; +- 没有新增 G-code/interpreter/planner/kinematics 语义; +- Web 仿真 HAL 的来源完整性现在可通过 browser artifact 和 SDK API 双路径验证; +- 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native HAL module ABI。 + +### 三、2026-06-17 Virtual HAL Sim Config Source Coverage Report + +继续按本文件铁律推进:虚拟 HAL 功能完全来源于 LinuxCNC 源程序,并且必须完全满足 +Web 方式数控系统仿真系统。本批把 virtual HAL 覆盖范围进一步连接到 vendored +LinuxCNC `configs/sim` 代表配置,形成 SDK/browser 双路径可验证来源覆盖报告。 + +新增实质能力: + +- 新增 `VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS`: + - 覆盖 AXIS foam; + - 覆盖 AXIS geometry USER_M_PATH / `M110`; + - 覆盖 external offsets / `M111`; + - 覆盖 gladevcp probe; + - 覆盖 woodpecker; + - 覆盖 qtdragon / qtdragon_hd / qtvcp_screens on-abort family; + - 覆盖 vismach melfa-sim / puma remap sim family; +- 新增 `createVirtualHalSimConfigSourceCoverageReport()`: + - 输出 sim-config target rows; + - 输出每个 target 的 LinuxCNC `configs/sim` source files; + - 汇总 covered virtual HAL capabilities; + - 支持 `source-manifest.txt` 校验 vendored source file 是否存在; + - 输出 `webSimulationSatisfied`; +- `createVirtualHalSourceComplianceReport()` 新增内嵌 `simConfigSourceCoverage`; +- `createVirtualHalSimulationReplacementReport()` 新增内嵌 `simConfigSourceCoverage`; +- `createLinuxCncVirtualHalRuntime()` 新增 `getSimConfigSourceCoverageReport()`; +- SDK index、SDK README、browser simulation re-export 同步新增该 API; +- Browser simulation API 新增: + - `getVirtualHalSimConfigSourceCoverageReport()`; +- `exportDiagnosticsArtifact()` 新增: + - `virtualHalSimConfigSourceCoverage`。 + +新增/扩展验证: + +- `verify_sdk_surface.mjs` 覆盖: + - `VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS` 导出; + - `createVirtualHalSimConfigSourceCoverageReport()` 导出; + - `axis-foam`、`external-offsets`、`qtdragon-on-abort` 代表目标存在; + - 用真实 `wasm-port/tools/source-manifest.txt` 校验所有代表 source files; + - sim-config source coverage `complete === true`; + - sim-config source coverage `webSimulationSatisfied === true`; + - runtime wrapper 可直接读取 sim-config source coverage report; + - source compliance report 内嵌 sim-config source coverage; +- `real_simulation_page_smoke.html` 覆盖: + - 页面 API 暴露 `getVirtualHalSimConfigSourceCoverageReport()`; + - diagnostics artifact 中 `virtualHalSimConfigSourceCoverage.complete === true`; + - diagnostics artifact 中包含 LinuxCNC foam sim source evidence; + - API report 中包含 qtdragon on-abort sim source evidence; + - replacement report 内嵌 sim-config source coverage。 + +关键验证: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +real_simulation_programs_node_smoke=ok +browser_real_simulation_page_smoke=ok +``` + +语义边界: + +- 本批没有新增 CNC/G-code/interpreter/planner/kinematics 语义; +- 本批只把 virtual HAL 仿真覆盖与 vendored LinuxCNC `configs/sim` source evidence + 建立机器可读关联; +- `source-manifest.txt` 校验只证明 source asset 存在和覆盖归属,不把 browser virtual HAL + 伪装成 Linux kernel hard-realtime ABI; +- Web 仿真 HAL 的满足性现在同时可由 runtime capability、source compliance、browser + diagnostics artifact 和 sim-config source coverage 四条路径验证。 + +下一步工作内容: + +1. 将 `virtualHalSimConfigSourceCoverage` 接入 project release readiness / batch + acceptance artifact,使 release gate 直接检查 Web virtual HAL 的 LinuxCNC sim-config + 来源覆盖。 +2. 增加 HAL command script fixtures,把典型 `halcmd` 脚本作为 source-derived workflow + gate,覆盖 `setp`、`sets`、`net`、`show`、`getp`、`gets`、`loadrt`、`loadusr`、`addf` + 和 `start/stop`。 +3. 推进 OPFS/session persistence 与 virtual HAL runtime state 整合,把 HAL pin/signal/param、 + motion target、machine status、diagnostics artifact 一并纳入可恢复 Web 仿真会话。 + +### 四、2026-06-17 Release Artifact Virtual HAL Sim Config Source Gate + +继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL +完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 +`virtualHalSimConfigSourceCoverage` 从 SDK/browser report 接入 release readiness 与 batch +acceptance artifact,使 release gate 能直接检查 Web virtual HAL 的 LinuxCNC sim-config +来源覆盖。 + +新增实质能力: + +- `createProjectReleaseReadinessReport()` 新增: + - `virtualHalSimConfigSourceCoverage`; + - `virtualHalSimConfigSourceCoverageReady`; + - missing reason:`virtual-hal-sim-config-source-coverage`; + - summary row:`Virtual HAL sim-config source coverage`; +- `createProjectReleaseReadinessArtifactValidation()` 新增强制校验: + - artifact 必须携带 `linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report`; + - `complete === true`; + - `webSimulationSatisfied === true`; + - `missingTargets.length === 0`; + - `missingCapabilities.length === 0`; + - 必须包含 LinuxCNC foam sim source evidence; + - 必须包含 qtdragon on-abort sim source evidence; +- `createProjectReleaseReadinessArtifactValidationSummaryViewModel()` 新增对应 summary row; +- `createProjectBatchAcceptanceWorkflow()` / `createProjectBatchAcceptanceReport()` 新增可选 + `virtualHalSimConfigSourceCoverage`; +- `createProjectBatchAcceptanceReportValidation()` 新增可选 coverage 校验; +- `write_project_release_readiness_artifact.mjs` 生成 artifact 时读取 + `wasm-port/tools/source-manifest.txt`,并写入 manifest-checked coverage report; +- `write_project_batch_acceptance_artifact.mjs` 生成 artifact 时写入同一 coverage report, + 并新增 accepted capability: + `virtual-hal-sim-config-source-coverage`。 + +新增/扩展验证: + +- SDK surface gate 覆盖: + - release readiness 默认缺少 coverage 时不 ready; + - 传入 coverage 后 release readiness ready; + - release readiness artifact validation 返回 + `virtualHalSimConfigSourceCoverageReady === true`; + - batch acceptance artifact 携带 coverage 后 checklist 为 `4/4 passed`; +- release gate manifest gate 覆盖: + - summary view-model 显示 virtual HAL sim-config source coverage ready; + - artifact URL workflow 读取带 coverage 的 release readiness artifact; +- batch acceptance artifact gate 覆盖: + - JSON artifact 内含 virtual HAL sim-config source coverage; + - source evidence 包含 LinuxCNC foam sim; + - checklist 为 `4/4 passed`; +- release readiness artifact gate 覆盖: + - JSON artifact 内含 virtual HAL sim-config source coverage; + - source evidence 包含 LinuxCNC foam sim 与 qtdragon on-abort sim。 + +关键验证: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +project_release_readiness_artifact_node_smoke=ok +``` + +语义边界: + +- 本批没有新增 G-code/interpreter/planner/kinematics 语义; +- release artifact 只校验 virtual HAL 仿真来源覆盖,不把 browser virtual HAL 声称为 + Linux kernel hard-realtime ABI; +- Web 仿真 HAL 的 LinuxCNC `configs/sim` 来源覆盖现在成为 release readiness 与 batch + acceptance artifact 的一等 gate evidence。 + +下一步工作内容: + +1. 增加 HAL command script fixtures,把典型 `halcmd` 脚本作为 source-derived workflow + gate,覆盖 `setp`、`sets`、`net`、`show`、`getp`、`gets`、`loadrt`、`loadusr`、`addf` + 和 `start/stop`。 +2. 推进 OPFS/session persistence 与 virtual HAL runtime state 整合,把 HAL pin/signal/param、 + motion target、machine status、diagnostics artifact 一并纳入可恢复 Web 仿真会话。 +3. 将 browser diagnostics artifact 与 project release readiness artifact 做 URL workflow + 联动,让 release 页面能直接读取 Web 仿真诊断中的 virtual HAL source coverage。 + +### 五、2026-06-17 Virtual HAL Command Script Fixtures + +继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL +完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把典型 `halcmd` +脚本固化为 source-derived fixture report,使 Web 仿真的 HAL command workflow 可由 SDK +和 browser diagnostics artifact 双路径验证。 + +新增实质能力: + +- 新增 `VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES`: + - `pin-signal-net-show`: + - `newsig`; + - `setp`; + - `sets`; + - `net`; + - `show pin`; + - `show sig`; + - `getp`; + - `gets`; + - `load-thread-start-stop`: + - `loadrt`; + - `loadusr`; + - `addf`; + - `start`; + - `show function`; + - `stop`; +- 新增 `createVirtualHalCommandScriptFixtureReport()`: + - 执行 fixture command script; + - 汇总 covered halcmd actions; + - 检查 missing actions; + - 检查 expected output; + - 输出 `webSimulationSatisfied`; + - 输出 LinuxCNC halcmd source files; +- `createVirtualHalSourceComplianceReport()` 新增内嵌: + - `commandScriptFixtures`; +- `createVirtualHalSimulationReplacementReport()` 新增内嵌: + - `commandScriptFixtures`; +- `createLinuxCncVirtualHalRuntime()` 新增: + - `getCommandScriptFixtureReport()`; +- `createVirtualHalShowRows()` 新增: + - `show function`; + - `show thread`; +- SDK index、SDK README、browser simulation re-export 同步新增该 API; +- Browser simulation API 新增: + - `getVirtualHalCommandScriptFixtureReport()`; +- `exportDiagnosticsArtifact()` 新增: + - `virtualHalCommandScriptFixtures`。 + +新增/扩展验证: + +- `verify_sdk_surface.mjs` 覆盖: + - `VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES` 导出; + - `createVirtualHalCommandScriptFixtureReport()` 导出; + - fixture report `complete === true`; + - fixture report `webSimulationSatisfied === true`; + - `missingActions.length === 0`; + - `missingFixtures.length === 0`; + - source evidence 包含 `linuxcnc/src/hal/utils/halcmd_commands.cc`; + - covered actions 覆盖: + `setp`、`sets`、`net`、`show`、`getp`、`gets`、`loadrt`、`loadusr`、`addf`、 + `start`、`stop`; + - runtime wrapper 可读取 command fixture report; + - replacement report 内嵌 command fixture report; + - source compliance report 内嵌 command fixture report; +- `real_simulation_page_smoke.html` 覆盖: + - 页面 API 暴露 `getVirtualHalCommandScriptFixtureReport()`; + - diagnostics artifact 中 `virtualHalCommandScriptFixtures.complete === true`; + - diagnostics artifact 中 covered actions 包含 `loadusr`; + - API report 包含 `setp`、`stop` 和 LinuxCNC halcmd source evidence; + - replacement/source compliance report 内嵌 command fixture report。 + +关键验证: + +```text +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +browser_real_simulation_page_smoke=ok +``` + +语义边界: + +- 本批没有新增 G-code/interpreter/planner/kinematics 语义; +- 本批只把 Web virtual HAL 的 `halcmd` workflow 与 LinuxCNC halcmd source evidence + 建立可执行 fixture 关联; +- `loadrt`、`loadusr`、`addf`、`start`、`stop` 在 browser virtual HAL 中仍是 Web 仿真 + workflow 的可记录替代层,不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI + 或 native HAL module ABI; +- Web 仿真 HAL 的满足性现在可由 runtime capability、source compliance、sim-config + source coverage、release artifact gate、command script fixture report 和 browser + diagnostics artifact 共同验证。 + +下一步工作内容: + +1. 推进 OPFS/session persistence 与 virtual HAL runtime state 整合,把 HAL pin/signal/param、 + motion target、machine status、diagnostics artifact 一并纳入可恢复 Web 仿真会话。 +2. 将 browser diagnostics artifact 与 project release readiness artifact 做 URL workflow + 联动,让 release 页面能直接读取 Web 仿真诊断中的 virtual HAL source coverage。 +3. 扩展 motion controller 仿真测试矩阵,把多轴 target、servo period、distance-to-go、 + in-position 与 HAL pin feedback 的关系做成 source-derived report/gate。 + +### 六、2026-06-17 OPFS Session Virtual HAL Runtime State Persistence + +继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL +完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 virtual HAL +runtime state 接入 OPFS/session snapshot payload,使 HAL pin/signal/param、motion target、 +machine status、diagnostics/source reports 能随 Web 仿真会话保存和恢复。 + +新增实质能力: + +- `runtime/opfs/snapshot-store.js` 新增: + - `createVirtualHalSessionPayload()`; + - `restoreVirtualHalStateFromSessionSnapshot()`; +- `createMachineSessionSnapshotPayload()` 新增可选: + - `virtualHal`; + - `virtualHalState`; + - `virtualHalOptions`; +- virtual HAL session payload 内容包括: + - `state`:完整 browser virtual HAL state; + - `diagnostics`:simulation replacement report; + - `sourceCompliance`:LinuxCNC source compliance report; + - `simConfigSourceCoverage`:LinuxCNC `configs/sim` source coverage report; + - `commandScriptFixtures`:LinuxCNC halcmd source-derived command fixture report; +- snapshot validation 新增 virtual HAL payload 校验: + - state source 必须为 `browser-virtual-hal`; + - source compliance 必须 complete; + - sim-config source coverage 必须 complete; + - command script fixtures 必须 complete; +- SDK index 与 README 同步导出: + - `createVirtualHalSessionPayload()`; + - `restoreVirtualHalStateFromSessionSnapshot()`。 + +新增/扩展验证: + +- `verify_file_service.mjs` 覆盖: + - 通过 virtual halcmd 建立 signal/net/pin/component/thread state; + - `createVirtualHalSessionPayload()` 写入 HAL signal/net/loadusr state; + - payload 内嵌 source compliance、sim-config source coverage、command script fixtures; + - `createMachineSessionSnapshotPayload()` 可携带 virtual HAL payload; + - `createSessionSnapshot()` 可封装 virtual HAL session payload; + - `restoreVirtualHalStateFromSessionSnapshot()` 可恢复 signal/net/loadusr state; + - `saveSessionSnapshot()` / `loadSessionSnapshot()` 可 OPFS roundtrip virtual HAL state; +- `ini_panel_smoke.html` 覆盖 browser OPFS: + - browser 侧创建 virtual HAL state; + - browser 侧保存 virtual HAL session snapshot; + - browser 侧恢复 HAL net 与 `loadusr halui` state; +- `verify_sdk_surface.mjs` 覆盖: + - SDK index/README 导出新 API; + - virtual HAL session payload 包含 complete source reports; + - session snapshot 可恢复 HAL net。 + +关键验证: + +```text +opfs_file_service_node_smoke=ok +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_release_artifact_url_workflow_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +browser_ini_opfs_smoke=ok +browser_ini_control_page_smoke=ok +browser_ini_launch_smoke=ok +browser_ini_workflow_overview_smoke=ok +browser_ini_shell_integration_workflow_smoke=ok +browser_real_simulation_page_smoke=ok +``` + +语义边界: + +- 本批没有新增 G-code/interpreter/planner/kinematics 语义; +- OPFS/session snapshot 只保存和恢复 Web virtual HAL 仿真状态与来源报告; +- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native + HAL module ABI; +- Web 仿真 HAL 的满足性现在可以随 OPFS/session snapshot 持久化,并在恢复后继续由 + source compliance、sim-config source coverage、command script fixture report 验证。 + +下一步工作内容: + +1. 将 browser diagnostics artifact 与 project release readiness artifact 做 URL workflow + 联动,让 release 页面能直接读取 Web 仿真诊断中的 virtual HAL source coverage。 +2. 扩展 motion controller 仿真测试矩阵,把多轴 target、servo period、distance-to-go、 + in-position 与 HAL pin feedback 的关系做成 source-derived report/gate。 +3. 将 virtual HAL session payload 接入 real simulation page 的显式保存/恢复按钮或 API, + 让用户可直接保存和恢复完整 Web CNC 仿真会话。 + +### 七、2026-06-17 Browser Diagnostics Artifact Release URL Workflow + +继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL +完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 browser +diagnostics artifact 与 project release readiness artifact 做成 URL workflow 联动,使 release +工作流可以直接读取 Web 仿真诊断中的 virtual HAL source coverage、source compliance 和 +halcmd command fixture evidence。 + +新增实质能力: + +- `project-release-readiness.js` 新增: + - `createProjectReleaseBrowserDiagnosticsArtifactValidation()`; + - 对 real browser simulation diagnostics artifact 的 release 级验证; +- browser diagnostics artifact validation 覆盖: + - `apiName === "real-browser-simulation-diagnostics-artifact"`; + - `virtualHal.source === "browser-virtual-hal"`; + - `virtualHalSimulationReplacement.replacesHostRuntimeForSimulation === true`; + - `virtualHalSourceCompliance.complete === true`; + - `virtualHalSimConfigSourceCoverage.complete === true`; + - `virtualHalCommandScriptFixtures.complete === true`; + - LinuxCNC halcmd source evidence 包含 + `linuxcnc/src/hal/utils/halcmd_commands.cc`; + - LinuxCNC sim config source evidence 包含 + `linuxcnc/configs/sim/axis/foam/axis_foam.ini` 与 + `linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc`; +- `loadProjectReleaseReadinessArtifactUrlWorkflow()` 新增可选: + - `diagnosticsUrl`; + - `diagnosticsFetched`; + - `diagnosticsHttpStatus`; + - `diagnosticsFetchError`; + - `diagnosticsValidation`; +- URL workflow 现在在传入 `diagnosticsUrl` 时同时验证: + - project release readiness artifact; + - browser real simulation diagnostics artifact; + - diagnostics artifact 中的 virtual HAL source coverage; +- URL workflow summary/action-plan 新增 browser diagnostics 状态; +- `verify_project_release_gate_manifest.mjs` 同步接受未请求 diagnostics URL 时的 + `diagnostics-workflow: not requested` 合约; +- SDK index 已导出 `createProjectReleaseBrowserDiagnosticsArtifactValidation()`。 + +新增/扩展验证: + +- `verify_project_release_artifact_url_workflow.mjs` 覆盖: + - 构造 browser diagnostics artifact; + - 验证 diagnostics artifact release validation `ready === true`; + - URL workflow 同时 fetch release artifact 与 diagnostics artifact; + - `diagnosticsFetched === true`; + - `diagnosticsHttpStatus === 200`; + - `diagnosticsValidation.ready === true`; + - summary view model 输出 `browser-diagnostics: ready`; +- `verify_sdk_surface.sh` 聚合覆盖: + - SDK surface; + - release gate manifest; + - release artifact URL workflow; + - batch acceptance workflow; + - batch acceptance artifact; +- `verify_real_simulation_browser.sh` 覆盖: + - real simulation page diagnostics artifact 仍可导出完整 virtual HAL source reports; + - 页面 browser API 未因 release URL workflow 联动发生退化。 + +关键验证: + +```text +project_release_artifact_url_workflow_node_smoke=ok +sdk_surface_node_smoke=ok +project_release_gate_manifest_node_smoke=ok +project_batch_acceptance_workflow_node_smoke=ok +project_batch_acceptance_artifact_node_smoke=ok +project_release_readiness_artifact_node_smoke=ok +browser_real_simulation_page_smoke=ok +``` + +语义边界: + +- 本批没有新增 G-code/interpreter/planner/kinematics 语义; +- browser diagnostics artifact 只作为 Web virtual HAL 仿真来源与满足性的 release evidence; +- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native + HAL module ABI; +- Web 仿真 HAL 的满足性现在可由 release artifact URL workflow 联动验证,结论仍限定在 + Web 方式数控系统仿真范围内。 + +下一步工作内容: + +1. 扩展 motion controller 仿真测试矩阵,把多轴 target、servo period、distance-to-go、 + in-position 与 HAL pin feedback 的关系做成 source-derived report/gate。 +2. 将 virtual HAL session payload 接入 real simulation page 的显式保存/恢复按钮或 API, + 让用户可直接保存和恢复完整 Web CNC 仿真会话。 +3. 将 browser diagnostics artifact 的 URL workflow 接入 ini-panel workflow overview 页面, + 让 release readiness 页面能显示 browser diagnostics validation rows。 diff --git a/wasm-port/runtime/opfs/snapshot-store.js b/wasm-port/runtime/opfs/snapshot-store.js index 6e0309e..6820932 100644 --- a/wasm-port/runtime/opfs/snapshot-store.js +++ b/wasm-port/runtime/opfs/snapshot-store.js @@ -7,6 +7,14 @@ import { sessionSnapshotPath, toolTablePath, } from "./path-model.js"; +import { + cloneVirtualHalState, + createVirtualHalCommandScriptFixtureReport, + createVirtualHalSimConfigSourceCoverageReport, + createVirtualHalSimulationReplacementReport, + createVirtualHalSourceComplianceReport, + createVirtualHalState, +} from "../sdk/src/linuxcnc-hal.js"; export const SESSION_SNAPSHOT_FORMAT = "linuxcnc-wasm-session-snapshot"; export const SESSION_SNAPSHOT_VERSION = 1; @@ -45,9 +53,58 @@ function validateMachineSessionSnapshotPayload(payload, machineId) { requiredSnapshotFilePath(payload.files, "parameters", "machine session parameter OPFS path"); requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path"); optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path"); + if (payload.virtualHal !== undefined) { + validateVirtualHalSessionPayload(payload.virtualHal); + } return payload; } +function validateVirtualHalSessionPayload(virtualHal) { + assertPlainObject(virtualHal, "virtual HAL session payload"); + assertPlainObject(virtualHal.state, "virtual HAL session state"); + assertPlainObject(virtualHal.diagnostics, "virtual HAL session diagnostics"); + assertPlainObject(virtualHal.sourceCompliance, "virtual HAL session source compliance"); + assertPlainObject(virtualHal.simConfigSourceCoverage, "virtual HAL session sim-config source coverage"); + assertPlainObject(virtualHal.commandScriptFixtures, "virtual HAL session command script fixtures"); + if (virtualHal.state.source !== "browser-virtual-hal") { + throw new Error(`Unsupported virtual HAL state source: ${virtualHal.state.source}`); + } + if (virtualHal.sourceCompliance.complete !== true) { + throw new Error("virtual HAL source compliance is incomplete."); + } + if (virtualHal.simConfigSourceCoverage.complete !== true) { + throw new Error("virtual HAL sim-config source coverage is incomplete."); + } + if (virtualHal.commandScriptFixtures.complete !== true) { + throw new Error("virtual HAL command script fixtures are incomplete."); + } + return virtualHal; +} + +export function createVirtualHalSessionPayload(halState = createVirtualHalState(), options = {}) { + const state = cloneVirtualHalState(halState); + const diagnostics = options.diagnostics ?? createVirtualHalSimulationReplacementReport(state); + const sourceCompliance = options.sourceCompliance ?? createVirtualHalSourceComplianceReport({ halState: state }); + const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options); + const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport({ ...options, halState: state }); + return validateVirtualHalSessionPayload({ + apiName: "linuxcnc-wasm-virtual-hal-session-payload", + payloadVersion: 1, + source: "browser-virtual-hal", + state, + diagnostics, + sourceCompliance, + simConfigSourceCoverage, + commandScriptFixtures, + }); +} + +export function restoreVirtualHalStateFromSessionSnapshot(snapshot) { + assertPlainObject(snapshot, "session snapshot"); + const virtualHal = validateVirtualHalSessionPayload(snapshot.payload?.virtualHal); + return cloneVirtualHalState(virtualHal.state); +} + export function createMachineSessionSnapshotPayload(machineId, options = {}) { assertPlainObject(options, "machine session snapshot options"); const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ?? @@ -58,6 +115,9 @@ export function createMachineSessionSnapshotPayload(machineId, options = {}) { toolTablePath(machineId, options.toolTableFilename); const gcode = optionalOpfsPath(options.gcodeOpfsPath, "G-code OPFS path") ?? (options.gcodeFilename === undefined ? undefined : gcodeProgramPath(options.gcodeFilename)); + const virtualHal = options.virtualHalState === undefined && options.virtualHal === undefined + ? undefined + : (options.virtualHal ?? createVirtualHalSessionPayload(options.virtualHalState, options.virtualHalOptions ?? {})); return { machineId, @@ -67,6 +127,7 @@ export function createMachineSessionSnapshotPayload(machineId, options = {}) { toolTable, ...(gcode === undefined ? {} : { gcode }), }, + ...(virtualHal === undefined ? {} : { virtualHal }), }; } diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 1cc019f..d3f14de 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -66,8 +66,11 @@ import { createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, + VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, + VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, + VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SOURCE_FILES, @@ -79,6 +82,7 @@ import { createLinuxCncIniSdk, createLinuxCncInterpSdk, createLinuxCncVirtualHalRuntime, + createVirtualHalCommandScriptFixtureReport, createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, @@ -88,8 +92,10 @@ import { createVirtualHalPinInventory, createVirtualHalPinRegistry, createVirtualHalProjectReport, + createVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport, createVirtualHalSimulationRuntimeReport, + createVirtualHalSourceComplianceReport, createVirtualHalState, createVirtualHalSystemCoverageReport, createVirtualHalWasmBridgeSnapshot, @@ -100,6 +106,7 @@ import { stepVirtualHalMotionController, writeVirtualHalPin, createMachineSessionSnapshotPayload, + createVirtualHalSessionPayload, createProjectReleaseGateActionPlan, createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionSummaryViewModel, @@ -157,7 +164,9 @@ import { createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceWorkflow, loadProjectBatchAcceptanceReportUrlWorkflow, + createProjectReleaseBrowserDiagnosticsArtifactValidation, readMachineSessionReadiness, + restoreVirtualHalStateFromSessionSnapshot, renderIniPanelShellWorkflowOverviewEmbeddingMountState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState, @@ -287,10 +296,11 @@ private page state. `readPin()`, `writePin()`, `applyPinUpdates()`, `executeHalCommand()`, `executeHalcmd()`, `stepMotion()`, `stepMotionController()`, `getSimulationRuntimeReport()`, `getSimulationReplacementReport()`, - `getIntegrityReport()`, `getPinInventory()`, `getProjectReport()`, and - `applyToInterpSdk()`. + `getSourceComplianceReport()`, `getIntegrityReport()`, `getPinInventory()`, + `getProjectReport()`, and `applyToInterpSdk()`. - `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`, `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`, + `VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES`, `executeVirtualHalCommand()`, `executeVirtualHalcmd()`, `stepVirtualHalMotion()`, `stepVirtualHalMotionController()`, `createVirtualHalSimulationRuntimeReport()`, and @@ -300,6 +310,13 @@ private page state. `sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs, `addf`, thread start/stop, and servo-period motion stepping for browser simulation and Node dashboards. +- `createVirtualHalSourceComplianceReport()` is the machine-readable gate for + the virtual HAL source rule. It maps replacement targets, runtime + capabilities, and HAL pin families back to LinuxCNC source files such as + `linuxcnc/bin/axis`, `linuxcnc/src/hal/utils/halcmd_commands.cc`, + `linuxcnc/src/emc/usr_intf/halui.cc`, `linuxcnc/src/emc/task/taskclass.cc`, + and `linuxcnc/src/emc/motion/motion.c`, then reports whether the Web + simulation HAL is source-complete. - `createVirtualHalWasmBridgeSnapshot()` returns the pin/value rows that can be applied through the interpreter SDK HAL bridge. It includes AXIS internal pin names such as `jog.x`, full HAL names such as `axisui.jog.x`, task/power diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index b6ed428..2c804a5 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -3,8 +3,11 @@ export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js"; export { VIRTUAL_HAL_AXES, VIRTUAL_HAL_AXISUI_PINS, + VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, + VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, + VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SOURCE_FILES, @@ -15,6 +18,7 @@ export { applyVirtualHalPinUpdates, cloneVirtualHalState, createLinuxCncVirtualHalRuntime, + createVirtualHalCommandScriptFixtureReport, createVirtualHalBridgeActionPlan, createVirtualHalBridgeReadiness, createVirtualHalDroState, @@ -24,8 +28,10 @@ export { createVirtualHalPinInventory, createVirtualHalPinRegistry, createVirtualHalProjectReport, + createVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport, createVirtualHalSimulationRuntimeReport, + createVirtualHalSourceComplianceReport, createVirtualHalState, createVirtualHalSystemCoverageReport, createVirtualHalWasmBridgeSnapshot, @@ -50,6 +56,7 @@ export { createProjectBatchAcceptanceReportValidation, createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceWorkflow, + createProjectReleaseBrowserDiagnosticsArtifactValidation, createProjectReleaseGateActionPlan, createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionSummaryViewModel, @@ -95,8 +102,10 @@ export { export { createMachineSessionSnapshotPayload, createSessionSnapshot, + createVirtualHalSessionPayload, loadMachineSessionSnapshot, loadSessionSnapshot, + restoreVirtualHalStateFromSessionSnapshot, saveMachineSessionSnapshot, saveSessionSnapshot, validateSessionSnapshot, diff --git a/wasm-port/runtime/sdk/src/linuxcnc-hal.js b/wasm-port/runtime/sdk/src/linuxcnc-hal.js index 9b41cbb..3bf8f70 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-hal.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-hal.js @@ -25,9 +25,102 @@ export const VIRTUAL_HAL_SOURCE_FILES = Object.freeze({ motion: "linuxcnc/src/emc/motion/motion.c", motionAxis: "linuxcnc/src/emc/motion/axis.c", motionHoming: "linuxcnc/src/emc/motion/homing.c", + halcmd: "linuxcnc/src/hal/utils/halcmd.c", + halcmdCommands: "linuxcnc/src/hal/utils/halcmd_commands.cc", + halcmdMain: "linuxcnc/src/hal/utils/halcmd_main.c", + halcmdBin: "linuxcnc/bin/halcmd", vendoredMotionHeader: "wasm-port/vendor/linuxcnc/src/emc/motion/motion.h", }); +export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([ + Object.freeze({ + id: "axis-foam", + label: "AXIS foam sim", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/axis/foam/axis_foam.ini", + "linuxcnc/configs/sim/axis/foam/foam.ngc", + ]), + capabilities: Object.freeze(["axis-joint-position-feedback", "hal-pin-signal-param-store"]), + evidence: "Vendored LinuxCNC AXIS foam sim config covers axis motion pins and simple program execution.", + }), + Object.freeze({ + id: "axis-geometry-user-m", + label: "AXIS geometry user-M sim", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/axis/geometry/xyzc.ini", + "linuxcnc/configs/sim/axis/geometry/xyzc.ngc", + "linuxcnc/configs/sim/axis/geometry/M110", + ]), + capabilities: Object.freeze(["userspace-load-command-stubs", "halcmd-simulation-replacement"]), + evidence: "Vendored LinuxCNC geometry sim config declares USER_M_PATH behavior represented as a simulation boundary.", + }), + Object.freeze({ + id: "external-offsets", + label: "External offsets sim", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini", + "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini", + "linuxcnc/configs/sim/axis/external_offsets/M111", + "linuxcnc/configs/sim/axis/external_offsets/dyn_demo.ngc", + "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc", + ]), + capabilities: Object.freeze(["motion-controller-simulation-replacement", "servo-period-motion-step"]), + evidence: "Vendored LinuxCNC external-offset configs exercise motion/HAL pins and deterministic user-M boundary accounting.", + }), + Object.freeze({ + id: "gladevcp-probe", + label: "GladeVCP probe sim", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/axis/gladevcp/gladevcp_panel.ini", + "linuxcnc/configs/sim/axis/gladevcp/probe.ngc", + "linuxcnc/configs/sim/axis/gladevcp/sim.tbl", + ]), + capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "hal-pin-signal-param-store"]), + evidence: "Vendored LinuxCNC probe sim config covers probe input and tool-table staging without browser-owned CNC semantics.", + }), + Object.freeze({ + id: "woodpecker", + label: "Woodpecker sim", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/woodpecker/woodpecker.ini", + "linuxcnc/configs/sim/woodpecker/on_abort.ngc", + "linuxcnc/configs/sim/woodpecker/tool.tbl", + ]), + capabilities: Object.freeze(["halcmd-simulation-replacement", "spindle-coolant-tool-status"]), + evidence: "Vendored LinuxCNC woodpecker sim config supplies deterministic on-abort and tool-table workflows.", + }), + Object.freeze({ + id: "qtdragon-on-abort", + label: "QtDragon on-abort sims", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/qtdragon/qtdragon_xyz/qtdragon_inch.ini", + "linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc", + "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini", + "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", + "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/qtdragon_hd_vertical.ini", + "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc", + "linuxcnc/configs/sim/qtvcp_screens/qtdragon/qtdragon_mpg.ini", + "linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc", + ]), + capabilities: Object.freeze(["realtime-hal-simulation-replacement", "axis-joint-position-feedback"]), + evidence: "Vendored LinuxCNC QtDragon sim configs provide multi-joint and on-abort machine/session coverage for Web simulation.", + }), + Object.freeze({ + id: "vismach-remap-sims", + label: "Vismach remap sims", + sourceFiles: Object.freeze([ + "linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini", + "linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc", + "linuxcnc/configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc", + "linuxcnc/configs/sim/axis/vismach/puma/puma.ini", + "linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc", + "linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc", + ]), + capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "motion-controller-simulation-replacement"]), + evidence: "Vendored LinuxCNC vismach remap configs remain LinuxCNC-owned remap/kinematics assets staged through the WASM boundary.", + }), +]); + export const VIRTUAL_HAL_COVERAGE_STATES = Object.freeze({ simulated: "simulated", bridgeOnly: "bridge-only", @@ -338,6 +431,157 @@ export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([ "motion-controller", ]); +export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([ + Object.freeze({ + id: "pin-signal-net-show", + label: "HAL pin, signal, net, and show workflow", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.halcmdMain, + VIRTUAL_HAL_SOURCE_FILES.halcmdBin, + ]), + commands: Object.freeze([ + "newsig x-pos HAL_FLOAT", + "setp axis.x.pos-cmd 2.5", + "sets x-pos 1.25", + "net x-pos axis.x.pos-cmd joint.0.pos-fb", + "show pin axis.x.*", + "show sig x-pos", + "getp axis.x.pos-cmd", + "gets x-pos", + ]), + requiredActions: Object.freeze(["newsig", "setp", "sets", "net", "show", "getp", "gets"]), + expectedOutput: Object.freeze(["axis.x.pos-cmd", "x-pos"]), + }), + Object.freeze({ + id: "load-thread-start-stop", + label: "HAL load, function, and thread workflow", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.halcmdMain, + VIRTUAL_HAL_SOURCE_FILES.halcmdBin, + ]), + commands: Object.freeze([ + "loadrt trivkins", + "loadusr halui", + "addf motion-controller servo-thread", + "start servo-thread", + "show function motion-controller", + "stop servo-thread", + ]), + requiredActions: Object.freeze(["loadrt", "loadusr", "addf", "start", "show", "stop"]), + expectedOutput: Object.freeze(["motion-controller"]), + }), +]); + +export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({ + "realtime-hal-simulation-replacement": Object.freeze({ + label: "LinuxCNC realtime HAL simulation replacement", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.axisui, + VIRTUAL_HAL_SOURCE_FILES.axisuiScript, + VIRTUAL_HAL_SOURCE_FILES.halui, + VIRTUAL_HAL_SOURCE_FILES.iocontrol, + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.motionHoming, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + evidence: "Virtual HAL pin families are derived from LinuxCNC AXIS, HALUI, iocontrol, and motion source exports.", + }), + "halcmd-simulation-replacement": Object.freeze({ + label: "LinuxCNC halcmd simulation replacement", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.halcmdMain, + VIRTUAL_HAL_SOURCE_FILES.halcmdBin, + ]), + evidence: "Virtual halcmd command verbs mirror LinuxCNC halcmd command surface for simulation workflows.", + }), + "motion-controller-simulation-replacement": Object.freeze({ + label: "LinuxCNC motion controller simulation replacement", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.motionHoming, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + evidence: "Virtual motion controller state and pins are derived from LinuxCNC motion, axis, homing, and motion header exports.", + }), + "hal-pin-signal-param-store": Object.freeze({ + label: "HAL pin/signal/param store", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.axisui, + VIRTUAL_HAL_SOURCE_FILES.halui, + ]), + evidence: "HAL object store behavior is constrained to LinuxCNC HAL pin families and halcmd pin/signal/param verbs.", + }), + "halcmd-setp-sets-net-show-getp-gets": Object.freeze({ + label: "halcmd setp/sets/net/show/getp/gets verbs", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.halcmdMain, + VIRTUAL_HAL_SOURCE_FILES.halcmdBin, + ]), + evidence: "Virtual halcmd supports the LinuxCNC halcmd verbs needed by Web simulation fixtures.", + }), + "userspace-load-command-stubs": Object.freeze({ + label: "loadrt/loadusr/addf/start/stop command stubs", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.halcmd, + VIRTUAL_HAL_SOURCE_FILES.halcmdCommands, + VIRTUAL_HAL_SOURCE_FILES.halcmdMain, + ]), + evidence: "Userspace/realtime load command records follow LinuxCNC halcmd command names while remaining simulation stubs.", + }), + "servo-period-motion-step": Object.freeze({ + label: "Servo-period motion step", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + evidence: "Servo-period stepping exposes LinuxCNC motion pin concepts: target, velocity, distance-to-go, and in-position.", + }), + "axis-joint-position-feedback": Object.freeze({ + label: "Axis/joint position feedback", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionHoming, + ]), + evidence: "Axis and joint feedback rows are derived from LinuxCNC axis/joint/homing HAL pin families.", + }), + "spindle-coolant-tool-status": Object.freeze({ + label: "Spindle, coolant, and tool status", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.halui, + VIRTUAL_HAL_SOURCE_FILES.iocontrol, + ]), + evidence: "Spindle, coolant, and tool state rows are derived from LinuxCNC motion, HALUI, and iocontrol pin families.", + }), + "interpreter-hal-bridge-snapshot": Object.freeze({ + label: "Interpreter HAL bridge snapshot", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.axisui, + VIRTUAL_HAL_SOURCE_FILES.halui, + VIRTUAL_HAL_SOURCE_FILES.iocontrol, + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.motionHoming, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + evidence: "Bridge snapshots only serialize source-derived virtual HAL pins and values into the LinuxCNC-backed interpreter HAL adapter.", + }), +}); + export function createVirtualHalState(overrides = {}) { const base = { apiName: "linuxcnc-wasm-virtual-hal-state", @@ -1777,9 +2021,78 @@ export function executeVirtualHalCommand(halState = createVirtualHalState(), com }; } +export function createVirtualHalCommandScriptFixtureReport(options = {}) { + const fixtures = options.fixtures ?? VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES; + let state = normalizeVirtualHalState(options.halState ?? createVirtualHalState()); + const fixtureRows = fixtures.map((fixture) => { + const commandText = fixture.commands.join("\n"); + const result = executeVirtualHalCommand(state, commandText, options); + state = result.state; + const actions = result.rows.map(({ action }) => action); + const missingActions = fixture.requiredActions.filter((action) => !actions.includes(action)); + const missingOutput = fixture.expectedOutput.filter((token) => !result.output.includes(token)); + const sourceFiles = [...(fixture.sourceFiles ?? [])]; + return { + id: fixture.id, + label: fixture.label, + commandText, + commandCount: result.commandCount, + ok: result.ok === true, + sourceDerived: sourceFiles.length > 0, + sourceFiles, + requiredActions: [...fixture.requiredActions], + actions, + missingActions, + expectedOutput: [...fixture.expectedOutput], + missingOutput, + output: result.output, + rows: result.rows, + ready: result.ok === true && + sourceFiles.length > 0 && + missingActions.length === 0 && + missingOutput.length === 0, + }; + }); + const missingFixtures = fixtureRows + .filter((row) => row.ready !== true) + .map((row) => row.id); + const coveredActions = [...new Set(fixtureRows.flatMap((row) => row.actions))].sort(); + const requiredActions = [...new Set(fixtures.flatMap((fixture) => fixture.requiredActions))].sort(); + const missingActions = requiredActions.filter((action) => !coveredActions.includes(action)); + const complete = fixtureRows.length > 0 && + missingFixtures.length === 0 && + missingActions.length === 0; + return { + apiName: "linuxcnc-wasm-virtual-hal-command-script-fixture-report", + fixtureVersion: 1, + source: "linuxcnc-halcmd-source-derived-virtual-hal", + phase: complete ? "ready" : "blocked", + complete, + webSimulationSatisfied: complete, + sourceFiles: [...new Set(fixtureRows.flatMap((row) => row.sourceFiles))], + requiredActions, + coveredActions, + missingActions, + missingFixtures, + fixtureCount: fixtureRows.length, + commandCount: fixtureRows.reduce((sum, row) => sum + row.commandCount, 0), + finalState: cloneVirtualHalState(state), + rows: fixtureRows, + summaryRows: [ + { id: "fixtures", label: "HAL command fixtures", value: `${fixtureRows.length}` }, + { id: "commands", label: "HAL commands", value: `${fixtureRows.reduce((sum, row) => sum + row.commandCount, 0)}` }, + { id: "actions", label: "Covered halcmd actions", value: missingActions.length === 0 ? "covered" : `missing ${missingActions.length}` }, + { id: "source", label: "LinuxCNC halcmd source", value: complete ? "source-derived" : "incomplete" }, + ], + }; +} + export function createVirtualHalSimulationReplacementReport(halState = createVirtualHalState(), options = {}) { const state = normalizeVirtualHalState(halState); const runtime = createVirtualHalSimulationRuntimeReport(state, options); + const sourceCompliance = createVirtualHalSourceComplianceReport({ halState: state }); + const simConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(options); + const commandScriptFixtures = createVirtualHalCommandScriptFixtureReport({ ...options, halState: state }); const requiredCapabilities = options.requiredCapabilities ?? [ "realtime-hal-simulation-replacement", "halcmd-simulation-replacement", @@ -1823,6 +2136,9 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir requiredCapabilities, missingCapabilities, replacements, + sourceCompliance, + simConfigSourceCoverage, + commandScriptFixtures, runtime, rows: [ { id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" }, @@ -1834,6 +2150,197 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir }; } +function normalizeLinuxCncSourcePath(path) { + const clean = String(path ?? "") + .replace(/^wasm-port\/vendor\/linuxcnc\//, "linuxcnc/") + .replace(/^vendor\/linuxcnc\//, "linuxcnc/") + .replace(/^linuxcnc\//, ""); + const parts = []; + for (const part of clean.split("/")) { + if (!part || part === ".") { + continue; + } + if (part === "..") { + parts.pop(); + continue; + } + parts.push(part); + } + return parts.join("/"); +} + +function sourceManifestSetFromText(manifestText = "") { + return new Set(String(manifestText) + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")) + .map(normalizeLinuxCncSourcePath)); +} + +export function createVirtualHalSimConfigSourceCoverageReport(options = {}) { + const targets = options.targets ?? VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS; + const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries); + const manifestSet = new Set([ + ...sourceManifestSetFromText(options.manifestText ?? ""), + ...(options.manifestEntries ?? []).map(normalizeLinuxCncSourcePath), + ]); + const rows = targets.map((target) => { + const sourceFiles = [...(target.sourceFiles ?? [])]; + const normalizedSourceFiles = sourceFiles.map(normalizeLinuxCncSourcePath); + const missingManifestFiles = manifestProvided + ? sourceFiles.filter((file, index) => !manifestSet.has(normalizedSourceFiles[index])) + : []; + return { + id: target.id, + label: target.label, + source: "linuxcnc/configs/sim", + sourceFiles, + capabilities: [...(target.capabilities ?? [])], + evidence: target.evidence ?? "", + sourceDerived: sourceFiles.length > 0 && missingManifestFiles.length === 0, + manifestChecked: manifestProvided, + missingManifestFiles, + }; + }); + const missingTargets = rows + .filter((row) => !row.sourceDerived) + .map((row) => row.id); + const coveredCapabilities = [...new Set(rows.flatMap((row) => row.capabilities))].sort(); + const requiredCapabilities = options.requiredCapabilities ?? [ + "realtime-hal-simulation-replacement", + "halcmd-simulation-replacement", + "motion-controller-simulation-replacement", + "hal-pin-signal-param-store", + "userspace-load-command-stubs", + "servo-period-motion-step", + "axis-joint-position-feedback", + "spindle-coolant-tool-status", + "interpreter-hal-bridge-snapshot", + ]; + const missingCapabilities = requiredCapabilities.filter((capability) => !coveredCapabilities.includes(capability)); + const complete = rows.length > 0 && missingTargets.length === 0 && missingCapabilities.length === 0; + return { + apiName: "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report", + coverageVersion: 1, + source: "linuxcnc-configs-sim-source-derived-virtual-hal", + phase: complete ? "source-covered" : "incomplete", + complete, + webSimulationSatisfied: complete, + manifestChecked: manifestProvided, + targetCount: rows.length, + sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))], + coveredCapabilities, + requiredCapabilities, + missingCapabilities, + missingTargets, + rows, + summaryRows: [ + { id: "targets", label: "LinuxCNC sim config targets", value: `${rows.length}` }, + { id: "source-files", label: "Source files", value: `${new Set(rows.flatMap((row) => row.sourceFiles)).size}` }, + { id: "capabilities", label: "Covered capabilities", value: missingCapabilities.length === 0 ? "covered" : `missing ${missingCapabilities.length}` }, + { id: "manifest", label: "Manifest check", value: manifestProvided ? (missingTargets.length === 0 ? "passed" : "failed") : "not provided" }, + ], + }; +} + +export function createVirtualHalSourceComplianceReport(options = {}) { + const halState = options.halState ?? createVirtualHalState(); + const snapshot = options.snapshot ?? createVirtualHalWasmBridgeSnapshot(halState); + const systemCoverage = options.systemCoverage ?? createVirtualHalSystemCoverageReport({ + halState, + snapshot, + }); + const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options); + const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport(options); + const allowedSourceFiles = new Set([ + ...Object.values(VIRTUAL_HAL_SOURCE_FILES), + ...VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles), + ...VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.flatMap((fixture) => fixture.sourceFiles), + ...(options.allowedSourceFiles ?? []), + ]); + const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES; + const capabilityRows = requiredCapabilities.map((capability) => { + const entry = VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES[capability]; + const sourceFiles = [...(entry?.sourceFiles ?? [])]; + const missingSource = !entry || sourceFiles.length === 0; + const disallowedSourceFiles = sourceFiles.filter((file) => !allowedSourceFiles.has(file)); + return { + id: capability, + label: entry?.label ?? capability, + capability, + sourceFiles, + evidence: entry?.evidence ?? "", + sourceDerived: !missingSource && disallowedSourceFiles.length === 0, + missingSource, + disallowedSourceFiles, + }; + }); + const targetRows = VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.map((target) => { + const targetCapabilities = target === "linuxcnc-realtime-hal" + ? ["realtime-hal-simulation-replacement", "hal-pin-signal-param-store"] + : target === "halcmd" + ? ["halcmd-simulation-replacement", "halcmd-setp-sets-net-show-getp-gets", "userspace-load-command-stubs"] + : ["motion-controller-simulation-replacement", "servo-period-motion-step", "axis-joint-position-feedback"]; + const rows = capabilityRows.filter((row) => targetCapabilities.includes(row.capability)); + return { + id: target, + target, + sourceDerived: rows.length > 0 && rows.every((row) => row.sourceDerived), + capabilities: targetCapabilities, + sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))], + }; + }); + const missingCapabilitySources = capabilityRows + .filter((row) => !row.sourceDerived) + .map((row) => row.capability); + const missingFamilySources = systemCoverage.missingSourceFamilies ?? []; + const complete = missingCapabilitySources.length === 0 && + missingFamilySources.length === 0 && + simConfigSourceCoverage.complete === true && + commandScriptFixtures.complete === true && + systemCoverage.complete === true && + targetRows.every((row) => row.sourceDerived); + return { + apiName: "linuxcnc-wasm-virtual-hal-source-compliance-report", + complianceVersion: 1, + source: "linuxcnc-source-derived-virtual-hal", + phase: complete ? "source-complete" : "incomplete", + complete, + webSimulationSatisfied: complete, + sourceRule: "Virtual HAL functionality must be derived from LinuxCNC source files, configs, scripts, or LinuxCNC-backed runtime evidence.", + replacementTargets: [...VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS], + allowedSourceFiles: [...allowedSourceFiles], + sourceFiles: [...new Set([ + ...systemCoverage.sourceFiles, + ...capabilityRows.flatMap((row) => row.sourceFiles), + ...simConfigSourceCoverage.sourceFiles, + ...commandScriptFixtures.sourceFiles, + ])], + missingCapabilitySources, + missingFamilySources, + simConfigSourceCoverage, + commandScriptFixtures, + targetRows, + capabilityRows, + familyRows: systemCoverage.families.map((family) => ({ + id: family.id, + label: family.label, + coverage: family.coverage, + sourceFiles: [...family.sourceFiles], + sourceEvidence: family.sourceEvidence, + sourceDerived: family.sourceFiles.length > 0, + })), + rows: [ + { id: "targets", label: "Replacement targets", value: targetRows.every((row) => row.sourceDerived) ? "source-derived" : "incomplete" }, + { id: "capabilities", label: "Runtime capabilities", value: missingCapabilitySources.length === 0 ? "source-derived" : `missing ${missingCapabilitySources.length}` }, + { id: "pin-families", label: "HAL pin families", value: missingFamilySources.length === 0 ? "source-derived" : `missing ${missingFamilySources.length}` }, + { id: "sim-configs", label: "LinuxCNC sim configs", value: simConfigSourceCoverage.complete ? "source-derived" : "incomplete" }, + { id: "halcmd-fixtures", label: "HAL command script fixtures", value: commandScriptFixtures.complete ? "source-derived" : "incomplete" }, + { id: "web-simulation", label: "Web simulation HAL", value: complete ? "satisfied" : "blocked" }, + ], + }; +} + export function createVirtualHalSimulationRuntimeReport(halState = createVirtualHalState(), options = {}) { const state = normalizeVirtualHalState(halState); const snapshot = createVirtualHalWasmBridgeSnapshot(state); @@ -1936,6 +2443,15 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS getSimulationReplacementReport(options = {}) { return createVirtualHalSimulationReplacementReport(state, options); }, + getSourceComplianceReport(options = {}) { + return createVirtualHalSourceComplianceReport({ ...options, halState: state }); + }, + getSimConfigSourceCoverageReport(options = {}) { + return createVirtualHalSimConfigSourceCoverageReport(options); + }, + getCommandScriptFixtureReport(options = {}) { + return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state }); + }, getIntegrityReport(options = {}) { return createVirtualHalIntegrityReport({ ...options, halState: state }); }, @@ -2029,6 +2545,26 @@ function createVirtualHalShowRows(state, kind = "pin", pattern = "") { value: normalizeHalNumericValue(entry.value), })); } + if (kind === "funct" || kind === "function") { + return (state.hal.functions ?? []) + .filter(({ name }) => matches(name)) + .map((entry) => ({ + kind: "function", + name: entry.name, + type: "HAL_FUNCT", + value: entry.thread ?? "", + })); + } + if (kind === "thread") { + return Object.entries(state.hal.threads ?? {}) + .filter(([name]) => matches(name)) + .map(([name, entry]) => ({ + kind: "thread", + name, + type: "HAL_THREAD", + value: entry.running ? "running" : "stopped", + })); + } return createVirtualHalWasmBridgeSnapshot(state).values .filter(({ kind: rowKind, name }) => rowKind === "pin" && matches(name)) .map((row) => ({ diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index fa7d7e3..3644b92 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -88,6 +88,97 @@ const BLOCKED_RUNTIME_FAMILIES = [ const PROJECT_RELEASE_READINESS_ARTIFACT_API = "project-release-readiness-report"; const PROJECT_RELEASE_READINESS_ARTIFACT_VERSION = 1; +function isVirtualHalSimConfigSourceCoverageReady(report) { + const coverage = objectOrEmpty(report); + return coverage.apiName === "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report" && + coverage.complete === true && + coverage.webSimulationSatisfied === true && + arrayOrEmpty(coverage.missingTargets).length === 0 && + arrayOrEmpty(coverage.missingCapabilities).length === 0 && + arrayOrEmpty(coverage.sourceFiles).includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") && + arrayOrEmpty(coverage.sourceFiles).includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc"); +} + +export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifact = {}) { + const artifactObject = objectOrEmpty(artifact); + const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance); + const simConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage); + const commandScriptFixtures = objectOrEmpty(artifactObject.virtualHalCommandScriptFixtures); + const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement); + const virtualHal = objectOrEmpty(artifactObject.virtualHal); + const sourceComplianceReady = sourceCompliance.complete === true && + sourceCompliance.webSimulationSatisfied === true && + arrayOrEmpty(sourceCompliance.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc"); + const simConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(simConfigSourceCoverage); + const commandScriptFixturesReady = commandScriptFixtures.complete === true && + commandScriptFixtures.webSimulationSatisfied === true && + arrayOrEmpty(commandScriptFixtures.coveredActions).includes("loadusr") && + arrayOrEmpty(commandScriptFixtures.coveredActions).includes("stop") && + arrayOrEmpty(commandScriptFixtures.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc"); + const replacementReady = replacement.ready === true && + replacement.replacesHostRuntimeForSimulation === true && + objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true; + const virtualHalReady = virtualHal.source === "browser-virtual-hal"; + const missing = [ + ...(artifactObject.apiName === "real-browser-simulation-diagnostics-artifact" ? [] : ["apiName"]), + ...(virtualHalReady ? [] : ["virtualHal"]), + ...(replacementReady ? [] : ["virtualHalSimulationReplacement"]), + ...(sourceComplianceReady ? [] : ["virtualHalSourceCompliance"]), + ...(simConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), + ...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]), + ]; + return { + apiName: "project-release-browser-diagnostics-artifact-validation", + validationVersion: 1, + phase: missing.length === 0 ? "ready" : "blocked", + ready: missing.length === 0, + missing, + artifactApiName: artifactObject.apiName ?? null, + virtualHalReady, + replacementReady, + sourceComplianceReady, + simConfigSourceCoverageReady, + commandScriptFixturesReady, + sourceFiles: [...new Set([ + ...arrayOrEmpty(sourceCompliance.sourceFiles), + ...arrayOrEmpty(simConfigSourceCoverage.sourceFiles), + ...arrayOrEmpty(commandScriptFixtures.sourceFiles), + ])], + rows: [ + { + id: "artifact", + label: "Browser diagnostics artifact", + value: artifactObject.apiName ?? "missing", + }, + { + id: "virtual-hal", + label: "Virtual HAL state", + value: virtualHalReady ? "ready" : "missing", + }, + { + id: "source-compliance", + label: "Virtual HAL source compliance", + value: sourceComplianceReady ? "ready" : "missing", + }, + { + id: "sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: simConfigSourceCoverageReady ? "ready" : "missing", + }, + { + id: "command-script-fixtures", + label: "Virtual HAL command script fixtures", + value: commandScriptFixturesReady ? "ready" : "missing", + }, + { + id: "missing", + label: "Missing diagnostics evidence", + value: missing.length > 0 ? missing.join(", ") : "none", + }, + ], + }; +} + function outputContains(observedOutputs, expectedOutput) { return observedOutputs.some((output) => String(output).includes(expectedOutput)); } @@ -470,6 +561,7 @@ export function createProjectBatchAcceptanceWorkflow({ capabilities = [], gateResults = {}, observedOutputs = [], + virtualHalSimConfigSourceCoverage = null, } = {}) { const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities }); const gateManifest = createProjectReleaseGateManifest(); @@ -480,10 +572,13 @@ export function createProjectBatchAcceptanceWorkflow({ }); const batchGatePassed = gateResultMatrix.rows .find(({ id }) => id === "project-batch-acceptance")?.passed === true; - const ready = capabilityMatrix.ready && batchGatePassed; + const virtualHalSimConfigSourceCoverageReady = virtualHalSimConfigSourceCoverage === null || + isVirtualHalSimConfigSourceCoverageReady(virtualHalSimConfigSourceCoverage); + const ready = capabilityMatrix.ready && batchGatePassed && virtualHalSimConfigSourceCoverageReady; const missing = [ ...capabilityMatrix.missing, ...(batchGatePassed ? [] : ["project-batch-acceptance"]), + ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]), ]; return { @@ -498,6 +593,8 @@ export function createProjectBatchAcceptanceWorkflow({ capabilityMatrix, capabilities: capabilityMatrix.capabilities, rejectedCapabilities: capabilityMatrix.rejectedCapabilities, + virtualHalSimConfigSourceCoverage, + virtualHalSimConfigSourceCoverageReady, gateResultMatrix, missing, rows: [ @@ -516,6 +613,13 @@ export function createProjectBatchAcceptanceWorkflow({ label: "Batch acceptance gate", value: batchGatePassed ? "passed" : "unknown", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: virtualHalSimConfigSourceCoverage === null + ? "not required" + : (virtualHalSimConfigSourceCoverageReady ? "ready" : "missing"), + }, { id: "missing", label: "Missing acceptance evidence", @@ -649,6 +753,15 @@ export function createProjectBatchAcceptanceChecklist({ detail: workflow?.gateResultMatrix?.rows ?.find?.(({ id }) => id === "project-batch-acceptance")?.status ?? "unknown", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + status: workflow?.virtualHalSimConfigSourceCoverageReady !== false ? "pass" : "blocked", + passed: workflow?.virtualHalSimConfigSourceCoverageReady !== false, + detail: workflow?.virtualHalSimConfigSourceCoverage === null + ? "not required" + : (workflow?.virtualHalSimConfigSourceCoverageReady === true ? "ready" : "missing"), + }, { id: "next-action", label: "Next acceptance action", @@ -680,6 +793,7 @@ export function createProjectBatchAcceptanceReport({ capabilities = [], gateResults = {}, observedOutputs = [], + virtualHalSimConfigSourceCoverage = null, } = {}) { const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities }); const workflow = createProjectBatchAcceptanceWorkflow({ @@ -687,6 +801,7 @@ export function createProjectBatchAcceptanceReport({ capabilities, gateResults, observedOutputs, + virtualHalSimConfigSourceCoverage, }); const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow); const actionPlan = createProjectBatchAcceptanceActionPlan(workflow); @@ -701,6 +816,7 @@ export function createProjectBatchAcceptanceReport({ missing: [...workflow.missing], capabilityMatrix, workflow, + virtualHalSimConfigSourceCoverage, summaryViewModel, actionPlan, checklist, @@ -736,7 +852,12 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) { const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel); const actionPlan = objectOrEmpty(reportObject.actionPlan); const checklist = objectOrEmpty(reportObject.checklist); + const virtualHalSimConfigSourceCoverage = objectOrEmpty(reportObject.virtualHalSimConfigSourceCoverage); const rows = arrayOrEmpty(reportObject.rows); + const virtualHalSimConfigSourceCoverageRequired = reportObject.virtualHalSimConfigSourceCoverage !== null && + reportObject.virtualHalSimConfigSourceCoverage !== undefined; + const virtualHalSimConfigSourceCoverageReady = !virtualHalSimConfigSourceCoverageRequired || + isVirtualHalSimConfigSourceCoverageReady(virtualHalSimConfigSourceCoverage); const missing = [ ...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]), ...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]), @@ -760,7 +881,8 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) { ...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true ? [] : ["checklist"]), - ...(rows.find(({ id, value }) => id === "checklist" && value === "3/3 passed") + ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), + ...(rows.find(({ id, value }) => id === "checklist" && /^([34])\/\1 passed$/.test(value)) ? [] : ["rows.checklist"]), ]; @@ -795,6 +917,13 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) { label: "Checklist", value: checklist.ready === true ? "ready" : "missing", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: virtualHalSimConfigSourceCoverageRequired + ? (virtualHalSimConfigSourceCoverageReady ? "ready" : "missing") + : "not required", + }, { id: "missing", label: "Missing report evidence", @@ -1251,6 +1380,7 @@ export function createProjectReleaseReadinessReport({ gateResults = {}, observedOutputs = [], simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE, + virtualHalSimConfigSourceCoverage = null, blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES, promotedRuntimeFamilies = [], axisScreenshotArtifacts = [], @@ -1283,13 +1413,21 @@ export function createProjectReleaseReadinessReport({ const promotedFamilies = [...promotedRuntimeFamilies]; const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family)); const inventoryReady = inventory.unexpectedFail === 0; + const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady( + virtualHalSimConfigSourceCoverage, + ); const blockedRuntimeReady = promotedBlockedFamilies.length === 0; const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts); const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready; - const ready = releaseGatePassed && inventoryReady && blockedRuntimeReady && axisScreenshotArtifactsReady; + const ready = releaseGatePassed && + inventoryReady && + virtualHalSimConfigSourceCoverageReady && + blockedRuntimeReady && + axisScreenshotArtifactsReady; const missing = [ ...(releaseGatePassed ? [] : ["project-release-gate"]), ...(inventoryReady ? [] : ["sim-config-inventory"]), + ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]), ...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]), ...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]), ]; @@ -1306,6 +1444,8 @@ export function createProjectReleaseReadinessReport({ expectedOutput: "project_release_gate=ok", }, simConfigInventory: inventory, + virtualHalSimConfigSourceCoverage, + virtualHalSimConfigSourceCoverageReady, blockedRuntimeFamilies: blockedFamilies, promotedRuntimeFamilies: promotedFamilies, promotedBlockedFamilies, @@ -1327,6 +1467,11 @@ export function createProjectReleaseReadinessReport({ label: "Sim config inventory", value: `unexpected_fail=${inventory.unexpectedFail}`, }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -1349,6 +1494,7 @@ export function createProjectReleaseReadinessSummaryViewModel( const missing = Array.isArray(report?.missing) ? report.missing : []; const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix); const simConfigInventory = objectOrEmpty(report?.simConfigInventory); + const virtualHalSimConfigSourceCoverageReady = report?.virtualHalSimConfigSourceCoverageReady === true; const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary); const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies); const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies); @@ -1380,6 +1526,11 @@ export function createProjectReleaseReadinessSummaryViewModel( label: "Sim config inventory", value: `unexpected_fail=${simConfigInventory.unexpectedFail ?? "unknown"}`, }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -1457,6 +1608,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel( label: "Gate action plan", value: validation?.gateActionPlanReady === true ? "ready" : "missing", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: validation?.virtualHalSimConfigSourceCoverageReady === true ? "ready" : "missing", + }, { id: "missing", label: "Missing artifact evidence", @@ -1609,14 +1765,19 @@ export function createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ artifactUrl = "", + diagnosticsUrl = "", fetchRef = globalThis.fetch, } = {}) { if (!artifactUrl || typeof fetchRef !== "function") { const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" }); + const diagnosticsValidation = diagnosticsUrl + ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) + : null; const missing = [ ...(artifactUrl ? [] : ["artifact-url"]), ...(typeof fetchRef === "function" ? [] : ["fetch"]), ...jsonWorkflow.missing, + ...(diagnosticsUrl ? ["diagnostics-fetch", ...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`)] : []), ]; return { apiName: "project-release-readiness-artifact-url-workflow", @@ -1624,11 +1785,16 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ phase: "blocked", ready: false, artifactUrl, + diagnosticsUrl, fetched: false, + diagnosticsFetched: false, httpStatus: null, + diagnosticsHttpStatus: null, fetchError: null, + diagnosticsFetchError: null, jsonWorkflow, validation: jsonWorkflow.validation, + diagnosticsValidation, summaryViewModel: jsonWorkflow.summaryViewModel, actionPlan: jsonWorkflow.actionPlan, missing, @@ -1664,14 +1830,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ phase: "blocked", ready: false, artifactUrl, + diagnosticsUrl, fetched: false, + diagnosticsFetched: false, httpStatus, + diagnosticsHttpStatus: null, fetchError, + diagnosticsFetchError: null, jsonWorkflow, validation: jsonWorkflow.validation, + diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null, summaryViewModel: jsonWorkflow.summaryViewModel, actionPlan: jsonWorkflow.actionPlan, - missing: ["artifact-fetch", ...jsonWorkflow.missing], + missing: [ + "artifact-fetch", + ...jsonWorkflow.missing, + ...(diagnosticsUrl ? ["diagnostics-fetch"] : []), + ], rows: [ { id: "fetch", @@ -1694,20 +1869,56 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ const artifactJson = await response.text(); const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson }); + let diagnosticsFetched = false; + let diagnosticsHttpStatus = null; + let diagnosticsFetchError = null; + let diagnosticsValidation = null; + const diagnosticsMissing = []; + if (diagnosticsUrl) { + try { + const diagnosticsResponse = await fetchRef(diagnosticsUrl); + diagnosticsHttpStatus = diagnosticsResponse?.status ?? null; + if (!diagnosticsResponse?.ok) { + diagnosticsFetchError = `diagnostics artifact URL fetch failed${diagnosticsHttpStatus ? `: ${diagnosticsHttpStatus}` : ""}`; + diagnosticsMissing.push("diagnostics-fetch"); + diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({}); + } else { + diagnosticsFetched = true; + const diagnosticsArtifact = JSON.parse(await diagnosticsResponse.text()); + diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(diagnosticsArtifact); + diagnosticsMissing.push(...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`)); + } + } catch (error) { + diagnosticsFetchError = error?.message ?? "diagnostics artifact URL fetch error"; + diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({}); + diagnosticsMissing.push("diagnostics-fetch"); + } + } + const missing = [ + ...jsonWorkflow.missing, + ...diagnosticsMissing, + ]; + const ready = jsonWorkflow.ready === true && + (!diagnosticsUrl || diagnosticsValidation?.ready === true); return { apiName: "project-release-readiness-artifact-url-workflow", workflowVersion: 1, - phase: jsonWorkflow.ready === true ? "ready" : "blocked", - ready: jsonWorkflow.ready === true, + phase: ready ? "ready" : "blocked", + ready, artifactUrl, + diagnosticsUrl, fetched: true, + diagnosticsFetched, httpStatus, + diagnosticsHttpStatus, fetchError: null, + diagnosticsFetchError, jsonWorkflow, validation: jsonWorkflow.validation, + diagnosticsValidation, summaryViewModel: jsonWorkflow.summaryViewModel, actionPlan: jsonWorkflow.actionPlan, - missing: jsonWorkflow.missing, + missing, rows: [ { id: "fetch", @@ -1719,6 +1930,13 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ label: "Artifact JSON workflow", value: jsonWorkflow.ready === true ? "ready" : "blocked", }, + { + id: "diagnostics-workflow", + label: "Browser diagnostics workflow", + value: diagnosticsUrl + ? (diagnosticsValidation?.ready === true ? "ready" : "blocked") + : "not requested", + }, { id: "next-command", label: "Next command", @@ -1734,14 +1952,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ phase: "blocked", ready: false, artifactUrl, + diagnosticsUrl, fetched: false, + diagnosticsFetched: false, httpStatus: null, + diagnosticsHttpStatus: null, fetchError: error?.message ?? "artifact URL fetch error", + diagnosticsFetchError: null, jsonWorkflow, validation: jsonWorkflow.validation, + diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null, summaryViewModel: jsonWorkflow.summaryViewModel, actionPlan: jsonWorkflow.actionPlan, - missing: ["artifact-fetch", ...jsonWorkflow.missing], + missing: [ + "artifact-fetch", + ...jsonWorkflow.missing, + ...(diagnosticsUrl ? ["diagnostics-fetch"] : []), + ], rows: [ { id: "fetch", @@ -1802,6 +2029,13 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel label: "Artifact validation", value: workflow?.validation?.ready === true ? "ready" : "blocked", }, + { + id: "browser-diagnostics", + label: "Browser diagnostics", + value: workflow?.diagnosticsUrl + ? (workflow?.diagnosticsValidation?.ready === true ? "ready" : "blocked") + : "not requested", + }, { id: "next-command", label: "Next command", @@ -1850,6 +2084,15 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowActionPlan( expectedOutput: null, }] : []), + ...(missing.includes("diagnostics-fetch") + ? [{ + id: "verify-diagnostics-url-fetch", + label: "Verify browser diagnostics artifact URL fetch", + kind: "fetch", + command: null, + expectedOutput: null, + }] + : []), ]; const commandActions = validationCommands.map((command) => ({ id: command.id ?? command.gateId ?? `command-${command.step}`, @@ -1902,6 +2145,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { const artifactObject = objectOrEmpty(artifact); const releaseGate = objectOrEmpty(artifactObject.releaseGate); const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory); + const virtualHalSimConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage); const gateManifest = objectOrEmpty(artifactObject.gateManifest); const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest); const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel); @@ -1947,6 +2191,9 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { && gateActionPlan.pendingCount === 0 && gateActionPlan.nextCommand === null && actionPlanCommands.length === 0; + const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady( + virtualHalSimConfigSourceCoverage, + ) && artifactObject.virtualHalSimConfigSourceCoverageReady === true; const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts); const axisScreenshotSummaryReady = !axisScreenshotArtifactSummary.apiName || axisScreenshotArtifacts.length === 0 @@ -1984,6 +2231,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { ...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]), ...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]), ...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]), + ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), ...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",") ? [] : ["blockedRuntimeFamilies"]), @@ -2017,6 +2265,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { gateExecutionSummaryReady: executionSummaryReady, gateResultMatrixReady: matrixReady, gateActionPlanReady: actionPlanReady, + virtualHalSimConfigSourceCoverageReady, axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady, axisScreenshotArtifactCount: axisScreenshotArtifacts.length, blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies), @@ -2056,6 +2305,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { label: "Gate action plan", value: actionPlanReady ? "ready" : "missing", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing", + }, { id: "axis-screenshot-artifacts", label: "AXIS screenshot artifacts", diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html index f9f59eb..42b087f 100644 --- a/wasm-port/runtime/ui/simulation/index.html +++ b/wasm-port/runtime/ui/simulation/index.html @@ -1797,13 +1797,16 @@ M2 applyVirtualHalAction as applySharedVirtualHalAction, applyVirtualHalPinUpdates as applySharedVirtualHalPinUpdates, cloneVirtualHalState as cloneSharedVirtualHalState, + createVirtualHalCommandScriptFixtureReport as createSharedVirtualHalCommandScriptFixtureReport, createVirtualHalDroState as createSharedVirtualHalDroState, createVirtualHalIntegrityReport as createSharedVirtualHalIntegrityReport, createVirtualHalLimitsHomeState as createSharedVirtualHalLimitsHomeState, createVirtualHalMachineStatusState as createSharedVirtualHalMachineStatusState, createVirtualHalPinRegistry as createSharedVirtualHalPinRegistry, + createVirtualHalSimConfigSourceCoverageReport as createSharedVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport as createSharedVirtualHalSimulationReplacementReport, createVirtualHalSimulationRuntimeReport as createSharedVirtualHalSimulationRuntimeReport, + createVirtualHalSourceComplianceReport as createSharedVirtualHalSourceComplianceReport, createVirtualHalState, executeVirtualHalcmd as executeSharedVirtualHalcmd, readVirtualHalPin as readSharedVirtualHalPin, @@ -2481,6 +2484,18 @@ M2 return createSharedVirtualHalSimulationReplacementReport(virtualHalState); } + function createVirtualHalSourceComplianceReport() { + return createSharedVirtualHalSourceComplianceReport({ halState: virtualHalState }); + } + + function createVirtualHalSimConfigSourceCoverageReport() { + return createSharedVirtualHalSimConfigSourceCoverageReport(); + } + + function createVirtualHalCommandScriptFixtureReport() { + return createSharedVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }); + } + function renderMachineStatusDom(machineStatus) { const statusMap = { "spindle-state": machineStatus.spindle.state, @@ -2567,6 +2582,9 @@ M2 diagnostics: renderDiagnostics(), virtualHal: cloneVirtualHalState(), virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(), + virtualHalSourceCompliance: createVirtualHalSourceComplianceReport(), + virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(), + virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(), statusHistory: getStatusHistory(), mdiHistory: getMdiHistory(), recentPrograms: getRecentPrograms(), @@ -3869,6 +3887,9 @@ M2 stepVirtualHalMotionController: (options = {}) => stepVirtualHalMotionController(options), getVirtualHalSimulationRuntimeReport: () => createVirtualHalSimulationRuntimeReport(), getVirtualHalSimulationReplacementReport: () => createVirtualHalSimulationReplacementReport(), + getVirtualHalSourceComplianceReport: () => createVirtualHalSourceComplianceReport(), + getVirtualHalSimConfigSourceCoverageReport: () => createVirtualHalSimConfigSourceCoverageReport(), + getVirtualHalCommandScriptFixtureReport: () => createVirtualHalCommandScriptFixtureReport(), getVirtualRealtimeHalRuntimeReport: () => createVirtualHalSimulationReplacementReport(), getVirtualHalDroState: () => createVirtualHalDroState(), getVirtualHalLimitsHomeState: () => createVirtualHalLimitsHomeState(), diff --git a/wasm-port/runtime/ui/simulation/simulation-app.js b/wasm-port/runtime/ui/simulation/simulation-app.js index ae20dd2..065f677 100644 --- a/wasm-port/runtime/ui/simulation/simulation-app.js +++ b/wasm-port/runtime/ui/simulation/simulation-app.js @@ -24,13 +24,16 @@ export { applyVirtualHalPinUpdates, cloneVirtualHalState, createLinuxCncVirtualHalRuntime, + createVirtualHalCommandScriptFixtureReport, createVirtualHalDroState, createVirtualHalIntegrityReport, createVirtualHalLimitsHomeState, createVirtualHalMachineStatusState, createVirtualHalPinRegistry, + createVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport, createVirtualHalSimulationRuntimeReport, + createVirtualHalSourceComplianceReport, createVirtualHalState, createVirtualHalWasmBridgeSnapshot, executeVirtualHalCommand, diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index 3b5684b..4629f6d 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -18,11 +18,17 @@ } from "../../runtime/opfs/path-model.js"; import { createMachineSessionSnapshotPayload, + createVirtualHalSessionPayload, loadMachineSessionSnapshot, loadSessionSnapshot, + restoreVirtualHalStateFromSessionSnapshot, saveMachineSessionSnapshot, saveSessionSnapshot, } from "../../runtime/opfs/snapshot-store.js"; + import { + createVirtualHalState, + executeVirtualHalCommand, + } from "../../runtime/sdk/src/linuxcnc-hal.js"; import { gcodeFilenameFromProgramPath, loadGcodeProgram, @@ -166,6 +172,38 @@ TOOL_TABLE = browser-tool.tbl assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload"); assertEqual(loadedSnapshot.payload.files.gcode, snapshotPayload.files.gcode, "snapshot G-code payload"); assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata"); + const virtualHalState = executeVirtualHalCommand( + createVirtualHalState(), + "newsig x-pos HAL_FLOAT\nsetp axis.x.pos-cmd 4.5\nsets x-pos 3.25\nnet x-pos axis.x.pos-cmd joint.0.pos-fb\nloadrt trivkins\nloadusr halui\naddf motion-controller servo-thread\nstart servo-thread", + ).state; + const virtualHalSnapshotPayload = createMachineSessionSnapshotPayload("browser-smoke", { + gcodeFilename: "browser-smoke.ngc", + virtualHal: createVirtualHalSessionPayload(virtualHalState), + }); + assertEqual( + virtualHalSnapshotPayload.virtualHal.state.hal.signals["x-pos"].value, + 3.25, + "browser virtual HAL signal snapshot", + ); + const savedVirtualHalSnapshot = await saveSessionSnapshot( + "browser-virtual-hal-session", + virtualHalSnapshotPayload, + { + createdAt: "2026-06-08T00:00:00.000Z", + metadata: { source: "browser-virtual-hal-smoke" }, + }, + ); + const restoredVirtualHalState = restoreVirtualHalStateFromSessionSnapshot(savedVirtualHalSnapshot); + assertEqual( + restoredVirtualHalState.hal.nets["x-pos"].includes("joint.0.pos-fb"), + true, + "browser virtual HAL restored net", + ); + assertEqual( + restoredVirtualHalState.hal.loadedComponents.some(({ component }) => component === "halui"), + true, + "browser virtual HAL restored loadusr", + ); const customMachineSnapshotPayload = createMachineSessionSnapshotPayload("browser-smoke", { parameterFilename: "browser-linuxcnc.var", toolTableFilename: "browser-tool.tbl", diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index 3131a5c..61392f9 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -241,6 +241,9 @@ !api?.stepVirtualHalMotionController || !api?.getVirtualHalSimulationRuntimeReport || !api?.getVirtualHalSimulationReplacementReport || + !api?.getVirtualHalSourceComplianceReport || + !api?.getVirtualHalSimConfigSourceCoverageReport || + !api?.getVirtualHalCommandScriptFixtureReport || !api?.getVirtualRealtimeHalRuntimeReport ) { throw new Error("simulation API missing browser virtual HAL controls"); @@ -398,6 +401,14 @@ diagnosticsArtifact.statusHistory.length === 0 || diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" || diagnosticsArtifact.virtualHalSimulationReplacement?.ready !== true || + diagnosticsArtifact.virtualHalSimulationReplacement?.sourceCompliance?.webSimulationSatisfied !== true || + diagnosticsArtifact.virtualHalSourceCompliance?.complete !== true || + diagnosticsArtifact.virtualHalSourceCompliance?.webSimulationSatisfied !== true || + !diagnosticsArtifact.virtualHalSourceCompliance?.sourceFiles?.includes("linuxcnc/src/hal/utils/halcmd_commands.cc") || + diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.complete !== true || + !diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") || + diagnosticsArtifact.virtualHalCommandScriptFixtures?.complete !== true || + !diagnosticsArtifact.virtualHalCommandScriptFixtures?.coveredActions?.includes("loadusr") || diagnosticsArtifact.limitsHome.source !== "browser-virtual-hal" || diagnosticsArtifact.preview.renderer !== "threejs" || diagnosticsArtifact.toolTable.state !== "not-loaded" @@ -712,6 +723,16 @@ ) { throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`); } + const virtualHalCommandScriptFixtures = api.getVirtualHalCommandScriptFixtureReport(); + if ( + virtualHalCommandScriptFixtures.apiName !== "linuxcnc-wasm-virtual-hal-command-script-fixture-report" || + virtualHalCommandScriptFixtures.complete !== true || + !virtualHalCommandScriptFixtures.coveredActions.includes("setp") || + !virtualHalCommandScriptFixtures.coveredActions.includes("stop") || + !virtualHalCommandScriptFixtures.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc") + ) { + throw new Error(`AXIS-style virtual HAL command fixtures did not pass: ${JSON.stringify(virtualHalCommandScriptFixtures)}`); + } const motionControllerStep = api.stepVirtualHalMotionController({ target: { x: 2.25 }, maxVelocity: 10, @@ -732,6 +753,15 @@ virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true || virtualHalReplacement.replacements.halcmd.ready !== true || virtualHalReplacement.replacements["motion-controller"].ready !== true || + virtualHalReplacement.sourceCompliance?.complete !== true || + virtualHalReplacement.sourceCompliance?.webSimulationSatisfied !== true || + virtualHalReplacement.simConfigSourceCoverage?.complete !== true || + virtualHalReplacement.commandScriptFixtures?.complete !== true || + api.getVirtualHalSourceComplianceReport().complete !== true || + api.getVirtualHalSourceComplianceReport().commandScriptFixtures?.complete !== true || + !api.getVirtualHalSourceComplianceReport().sourceFiles.includes("linuxcnc/src/emc/motion/motion.c") || + api.getVirtualHalSimConfigSourceCoverageReport().complete !== true || + !api.getVirtualHalSimConfigSourceCoverageReport().sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc") || api.getVirtualRealtimeHalRuntimeReport().ready !== true || api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true ) { diff --git a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs index 99ca203..426410f 100644 --- a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs @@ -27,6 +27,7 @@ assert.equal(validation.gateExecutionManifestReady, true); assert.equal(validation.gateExecutionSummaryReady, true); assert.equal(validation.gateResultMatrixReady, true); assert.equal(validation.gateActionPlanReady, true); +assert.equal(validation.virtualHalSimConfigSourceCoverageReady, true); assert.equal(validation.axisScreenshotArtifactSummaryReady, true); assert.ok(validation.axisScreenshotArtifactCount === 0 || validation.axisScreenshotArtifactCount === 4); assert.deepEqual(validation.expectedGateIds, [ @@ -52,6 +53,12 @@ assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-mat assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(artifact.gateActionPlan.pendingCount, 0); assert.equal(artifact.gateActionPlan.nextCommand, null); +assert.equal(artifact.virtualHalSimConfigSourceCoverage.apiName, "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report"); +assert.equal(artifact.virtualHalSimConfigSourceCoverage.complete, true); +assert.equal(artifact.virtualHalSimConfigSourceCoverage.webSimulationSatisfied, true); +assert.equal(artifact.virtualHalSimConfigSourceCoverageReady, true); +assert.equal(artifact.virtualHalSimConfigSourceCoverage.sourceFiles.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini"), true); +assert.equal(artifact.virtualHalSimConfigSourceCoverage.sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc"), true); assert.equal(artifact.axisScreenshotArtifactSummary.apiName, "project-release-axis-screenshot-artifact-summary"); if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) { assert.equal(artifact.axisScreenshotArtifactSummary.artifactCount, 4); diff --git a/wasm-port/tests/host/write_project_release_readiness_artifact.mjs b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs index c25860e..ba73572 100644 --- a/wasm-port/tests/host/write_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs @@ -2,11 +2,15 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { createProjectReleaseReadinessReport } from "../../runtime/sdk/src/index.js"; +import { + createProjectReleaseReadinessReport, + createVirtualHalSimConfigSourceCoverageReport, +} from "../../runtime/sdk/src/index.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dirname, "../.."); const outputPath = process.argv[2] ?? resolve(root, "build/project-release-readiness.json"); +const sourceManifestText = readFileSync(resolve(root, "tools/source-manifest.txt"), "utf8"); const axisScreenshotArtifactDir = process.env.AXIS_SCREENSHOT_ARTIFACT_DIR ? resolve(process.env.AXIS_SCREENSHOT_ARTIFACT_DIR) : null; @@ -60,6 +64,9 @@ const report = createProjectReleaseReadinessReport({ "host_wasm_opfs_browser_smokes=ok", "project_release_gate=ok", ], + virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport({ + manifestText: sourceManifestText, + }), axisScreenshotArtifacts: loadAxisScreenshotArtifacts(axisScreenshotArtifactDir), }); diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index 610a8ee..f1e8251 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -18,12 +18,18 @@ import { import { createMachineSessionSnapshotPayload, createSessionSnapshot, + createVirtualHalSessionPayload, loadMachineSessionSnapshot, loadSessionSnapshot, + restoreVirtualHalStateFromSessionSnapshot, saveMachineSessionSnapshot, saveSessionSnapshot, validateSessionSnapshot, } from "../../../runtime/opfs/snapshot-store.js"; +import { + createVirtualHalState, + executeVirtualHalCommand, +} from "../../../runtime/sdk/src/linuxcnc-hal.js"; import { gcodeFilenameFromProgramPath, loadGcodeProgram, @@ -247,6 +253,25 @@ assert.deepEqual(snapshotPayload, { gcode: gcodeProgramPath("fixture.ngc"), }, }); +let virtualHalState = createVirtualHalState(); +virtualHalState = executeVirtualHalCommand( + virtualHalState, + "newsig x-pos HAL_FLOAT\nsetp axis.x.pos-cmd 3.5\nsets x-pos 2.25\nnet x-pos axis.x.pos-cmd joint.0.pos-fb\nloadrt trivkins\nloadusr halui\naddf motion-controller servo-thread\nstart servo-thread", +).state; +const virtualHalSessionPayload = createVirtualHalSessionPayload(virtualHalState); +assert.equal(virtualHalSessionPayload.apiName, "linuxcnc-wasm-virtual-hal-session-payload"); +assert.equal(virtualHalSessionPayload.state.source, "browser-virtual-hal"); +assert.equal(virtualHalSessionPayload.state.hal.signals["x-pos"].value, 2.25); +assert.equal(virtualHalSessionPayload.state.hal.nets["x-pos"].includes("axis.x.pos-cmd"), true); +assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true); +assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true); +assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true); +const virtualHalSnapshotPayload = createMachineSessionSnapshotPayload("virtual-hal-machine", { + gcodeFilename: "virtual-hal.ngc", + virtualHal: virtualHalSessionPayload, +}); +assert.equal(virtualHalSnapshotPayload.virtualHal.state.hal.signals["x-pos"].value, 2.25); +assert.equal(virtualHalSnapshotPayload.virtualHal.commandScriptFixtures.coveredActions.includes("loadusr"), true); assert.deepEqual( createMachineSessionSnapshotPayload("custom-machine", { parameterFilename: "custom.var", @@ -280,6 +305,13 @@ assert.deepEqual(snapshot, { payload: snapshotPayload, }); assert.equal(validateSessionSnapshot(snapshot, "session-1"), snapshot); +const virtualHalSessionSnapshot = createSessionSnapshot("virtual-hal-session", virtualHalSnapshotPayload, { + createdAt: "2026-06-08T00:00:00.000Z", + metadata: { source: "node-virtual-hal-session" }, +}); +const restoredVirtualHalState = restoreVirtualHalStateFromSessionSnapshot(virtualHalSessionSnapshot); +assert.equal(restoredVirtualHalState.hal.signals["x-pos"].value, 2.25); +assert.equal(restoredVirtualHalState.hal.loadedComponents.some(({ component }) => component === "halui"), true); assert.throws( () => validateSessionSnapshot({ ...snapshot, format: "other-format" }, "session-1"), /Unsupported session snapshot format/, @@ -321,6 +353,20 @@ assertSessionSnapshotStoragePath( sessionSnapshotPath("session-1"), ); assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot); +await saveSessionSnapshot("virtual-hal-session", virtualHalSnapshotPayload, { + storage, + createdAt: "2026-06-08T00:00:00.000Z", + metadata: { source: "node-virtual-hal-session" }, +}); +const loadedVirtualHalSessionSnapshot = await loadSessionSnapshot("virtual-hal-session", { storage }); +assert.equal( + loadedVirtualHalSessionSnapshot.payload.virtualHal.state.hal.signals["x-pos"].value, + 2.25, +); +assert.equal( + restoreVirtualHalStateFromSessionSnapshot(loadedVirtualHalSessionSnapshot).hal.nets["x-pos"].includes("joint.0.pos-fb"), + true, +); const customSnapshot = await saveSessionSnapshot("session-1", snapshotPayload, { storage, filename: "custom-snapshot.json", diff --git a/wasm-port/tests/sdk/node/verify_project_batch_acceptance_artifact.mjs b/wasm-port/tests/sdk/node/verify_project_batch_acceptance_artifact.mjs index 9b1e517..624bd56 100644 --- a/wasm-port/tests/sdk/node/verify_project_batch_acceptance_artifact.mjs +++ b/wasm-port/tests/sdk/node/verify_project_batch_acceptance_artifact.mjs @@ -34,12 +34,17 @@ assert.equal(jsonWorkflow.report.apiName, "project-batch-acceptance-report"); assert.equal(jsonWorkflow.report.reportVersion, 1); assert.equal(jsonWorkflow.report.ready, true); assert.equal(jsonWorkflow.report.capabilityMatrix.ready, true); -assert.deepEqual(jsonWorkflow.report.capabilityMatrix.capabilityTypes, ["workflow", "gate"]); +assert.deepEqual(jsonWorkflow.report.capabilityMatrix.capabilityTypes, ["workflow", "api", "gate"]); assert.equal(jsonWorkflow.report.workflow.ready, true); +assert.equal(jsonWorkflow.report.workflow.virtualHalSimConfigSourceCoverageReady, true); +assert.equal(jsonWorkflow.report.virtualHalSimConfigSourceCoverage.apiName, "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report"); +assert.equal(jsonWorkflow.report.virtualHalSimConfigSourceCoverage.complete, true); +assert.equal(jsonWorkflow.report.virtualHalSimConfigSourceCoverage.webSimulationSatisfied, true); +assert.equal(jsonWorkflow.report.virtualHalSimConfigSourceCoverage.sourceFiles.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini"), true); assert.equal(jsonWorkflow.report.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete"); assert.equal(jsonWorkflow.report.actionPlan.nextCommand, null); -assert.equal(jsonWorkflow.report.checklist.passedCount, 3); -assert.equal(jsonWorkflow.report.rows.find(({ id }) => id === "checklist")?.value, "3/3 passed"); +assert.equal(jsonWorkflow.report.checklist.passedCount, 4); +assert.equal(jsonWorkflow.report.rows.find(({ id }) => id === "checklist")?.value, "4/4 passed"); assert.equal(validationSummary.apiName, "project-batch-acceptance-report-validation-summary-view-model"); assert.equal(validationSummary.statusLine, "Ready: Batch acceptance report evidence complete"); assert.equal(validationSummary.rows.find(({ id }) => id === "validation")?.value, "ready"); diff --git a/wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.mjs b/wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.mjs index 12279e8..1335fa2 100644 --- a/wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.mjs +++ b/wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.mjs @@ -121,6 +121,7 @@ assert.deepEqual( ["batch", "batch-acceptance-workflow"], ["capabilities", "api, gate"], ["project-batch-acceptance", "passed"], + ["virtual-hal-sim-config-source-coverage", "not required"], ["missing", "none"], ], ); @@ -148,13 +149,14 @@ assert.equal(readyChecklist.checklistVersion, 1); assert.equal(readyChecklist.phase, "ready"); assert.equal(readyChecklist.ready, true); assert.equal(readyChecklist.batchId, "batch-acceptance-workflow"); -assert.equal(readyChecklist.passedCount, 3); +assert.equal(readyChecklist.passedCount, 4); assert.equal(readyChecklist.blockedCount, 0); assert.deepEqual( readyChecklist.rows.map(({ id, value }) => [id, value]), [ ["accepted-capability", "pass: api, gate"], ["batch-acceptance-gate", "pass: passed"], + ["virtual-hal-sim-config-source-coverage", "pass: not required"], ["next-action", "pass: none"], ], ); @@ -174,13 +176,13 @@ assert.equal(readyReport.capabilityMatrix.apiName, "project-batch-acceptance-cap assert.equal(readyReport.workflow.apiName, "project-batch-acceptance-workflow"); assert.equal(readyReport.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete"); assert.equal(readyReport.actionPlan.nextCommand, null); -assert.equal(readyReport.checklist.passedCount, 3); +assert.equal(readyReport.checklist.passedCount, 4); assert.deepEqual( readyReport.rows.map(({ id, value }) => [id, value]), [ ["summary", "Ready: Batch acceptance evidence complete"], ["capabilities", "api, gate"], - ["checklist", "3/3 passed"], + ["checklist", "4/4 passed"], ["next-command", "none"], ], ); @@ -205,6 +207,7 @@ assert.deepEqual( ["capability-matrix", "ready"], ["workflow", "ready"], ["checklist", "ready"], + ["virtual-hal-sim-config-source-coverage", "not required"], ["missing", "none"], ], ); @@ -348,13 +351,14 @@ const blockedChecklist = createProjectBatchAcceptanceChecklist({ }); assert.equal(blockedChecklist.phase, "blocked"); assert.equal(blockedChecklist.ready, false); -assert.equal(blockedChecklist.passedCount, 0); +assert.equal(blockedChecklist.passedCount, 1); assert.equal(blockedChecklist.blockedCount, 3); assert.deepEqual( blockedChecklist.items.map(({ id, status }) => [id, status]), [ ["accepted-capability", "blocked"], ["batch-acceptance-gate", "blocked"], + ["virtual-hal-sim-config-source-coverage", "pass"], ["next-action", "blocked"], ], ); diff --git a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs index 40944d5..515d688 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs @@ -1,23 +1,50 @@ import assert from "node:assert/strict"; import { + createProjectReleaseBrowserDiagnosticsArtifactValidation, createProjectReleaseGateManifest, createProjectReleaseReadinessArtifactUrlWorkflowActionPlan, createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel, createProjectReleaseReadinessReport, + createVirtualHalSimConfigSourceCoverageReport, + createVirtualHalCommandScriptFixtureReport, + createVirtualHalSimulationReplacementReport, + createVirtualHalSourceComplianceReport, + createVirtualHalState, loadProjectReleaseReadinessArtifactUrlWorkflow, } from "../../../runtime/sdk/src/index.js"; const manifest = createProjectReleaseGateManifest(); +const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(); +const virtualHalState = createVirtualHalState(); +const browserDiagnosticsArtifact = { + apiName: "real-browser-simulation-diagnostics-artifact", + artifactVersion: 1, + virtualHal: virtualHalState, + virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(virtualHalState), + virtualHalSourceCompliance: createVirtualHalSourceComplianceReport({ halState: virtualHalState }), + virtualHalSimConfigSourceCoverage, + virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }), +}; +assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDiagnosticsArtifact).ready, true); const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage, })); let fetchedUrl = null; const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ artifactUrl: "/project-release-readiness.json", + diagnosticsUrl: "/real-browser-simulation-diagnostics.json", fetchRef: async (url) => { fetchedUrl = url; + if (url === "/real-browser-simulation-diagnostics.json") { + return { + ok: true, + status: 200, + text: async () => JSON.stringify(browserDiagnosticsArtifact), + }; + } return { ok: true, status: 200, @@ -28,7 +55,7 @@ const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ const readySummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow); const readyActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(readyWorkflow); -assert.equal(fetchedUrl, "/project-release-readiness.json"); +assert.equal(fetchedUrl, "/real-browser-simulation-diagnostics.json"); assert.equal(readyWorkflow.apiName, "project-release-readiness-artifact-url-workflow"); assert.equal(readyWorkflow.workflowVersion, 1); assert.equal(readyWorkflow.ready, true); @@ -37,6 +64,9 @@ assert.equal(readyWorkflow.httpStatus, 200); assert.equal(readyWorkflow.fetchError, null); assert.equal(readyWorkflow.jsonWorkflow.ready, true); assert.equal(readyWorkflow.validation.ready, true); +assert.equal(readyWorkflow.diagnosticsValidation.ready, true); +assert.equal(readyWorkflow.diagnosticsFetched, true); +assert.equal(readyWorkflow.diagnosticsHttpStatus, 200); assert.equal(readyWorkflow.actionPlan.ready, true); assert.equal(readySummary.apiName, "project-release-readiness-artifact-url-workflow-summary-view-model"); assert.equal(readySummary.statusLine, "Ready: Artifact URL workflow complete"); @@ -47,6 +77,7 @@ assert.deepEqual( ["fetch", "ready (200)"], ["json-workflow", "ready"], ["artifact-validation", "ready"], + ["browser-diagnostics", "ready"], ["next-command", "none"], ["missing", "none"], ], diff --git a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs index 7d7ea21..3c823b6 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { + createVirtualHalSimConfigSourceCoverageReport, createProjectReleaseGateActionPlan, createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionSummaryViewModel, @@ -17,6 +18,7 @@ import { } from "../../../runtime/sdk/src/index.js"; const manifest = createProjectReleaseGateManifest(); +const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(); assert.equal(manifest.apiName, "project-release-gate-manifest"); assert.equal(manifest.manifestVersion, 1); @@ -201,6 +203,7 @@ assert.match(partialActionPlan.shellScript, /wasm-port\/tests\/host\/verify_proj const report = createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage, }); assert.deepEqual(report.gateManifest, manifest); assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); @@ -243,6 +246,11 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { label: "Sim config inventory", value: "unexpected_fail=0", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: "ready", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -262,11 +270,12 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { }); assert.equal( createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine, - "Waiting: project-release-gate", + "Waiting: project-release-gate, virtual-hal-sim-config-source-coverage", ); assert.equal( createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage, promotedRuntimeFamilies: ["L4-TOOL-DB"], })).rows.find(({ id }) => id === "blocked-runtime-families")?.value, "promoted: L4-TOOL-DB", @@ -308,6 +317,7 @@ assert.deepEqual( ["gate-execution-summary", "ready"], ["gate-result-matrix", "ready"], ["gate-action-plan", "ready"], + ["virtual-hal-sim-config-source-coverage", "ready"], ["missing", "none"], ], ); @@ -376,6 +386,7 @@ assert.deepEqual( [ ["fetch", "ready"], ["json-workflow", "ready"], + ["diagnostics-workflow", "not requested"], ["next-command", "none"], ], ); @@ -393,6 +404,7 @@ assert.deepEqual( ["fetch", "ready (200)"], ["json-workflow", "ready"], ["artifact-validation", "ready"], + ["browser-diagnostics", "not requested"], ["next-command", "none"], ["missing", "none"], ], diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 5df8ce3..9416503 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -32,8 +32,11 @@ import { createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, + VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, + VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, + VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SOURCE_FILES, @@ -48,6 +51,7 @@ import { createLinuxCncVirtualHalRuntime, createVirtualHalIntegrityReport, createMachineSessionSnapshotPayload, + createVirtualHalSessionPayload, createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, @@ -63,6 +67,7 @@ import { createProjectBatchAcceptanceReportValidation, createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceWorkflow, + createProjectReleaseBrowserDiagnosticsArtifactValidation, createProjectReleaseGateActionPlan, createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionSummaryViewModel, @@ -78,11 +83,14 @@ import { createProjectReleaseReadinessSummaryViewModel, createVirtualHalBridgeActionPlan, createVirtualHalBridgeReadiness, + createVirtualHalCommandScriptFixtureReport, createVirtualHalPinInventory, createVirtualHalPinRegistry, createVirtualHalProjectReport, + createVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport, createVirtualHalState, + createVirtualHalSourceComplianceReport, createVirtualHalSystemCoverageReport, createVirtualHalWasmBridgeSnapshot, executeVirtualHalCommand, @@ -146,6 +154,7 @@ import { renderIniPanelShellWorkflowOverviewEmbeddingMountState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState, restoreMachineParametersFromOpfs, + restoreVirtualHalStateFromSessionSnapshot, saveMachineSessionSnapshot, saveMachineTextFiles, saveSessionSnapshot, @@ -160,6 +169,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dirname, "../../.."); const sdkIndexText = readFileSync(resolve(root, "runtime/sdk/src/index.js"), "utf8"); const sdkReadmeText = readFileSync(resolve(root, "runtime/sdk/README.md"), "utf8"); +const sourceManifestText = readFileSync(resolve(root, "tools/source-manifest.txt"), "utf8"); const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8"); const requiredExports = [ @@ -189,6 +199,7 @@ const requiredExports = [ ["createProjectBatchAcceptanceReportValidation", createProjectBatchAcceptanceReportValidation], ["createProjectBatchAcceptanceSummaryViewModel", createProjectBatchAcceptanceSummaryViewModel], ["createProjectBatchAcceptanceWorkflow", createProjectBatchAcceptanceWorkflow], + ["createProjectReleaseBrowserDiagnosticsArtifactValidation", createProjectReleaseBrowserDiagnosticsArtifactValidation], ["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan], ["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest], ["createProjectReleaseGateExecutionSummaryViewModel", createProjectReleaseGateExecutionSummaryViewModel], @@ -220,12 +231,15 @@ const requiredExports = [ ["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime], ["createVirtualHalBridgeActionPlan", createVirtualHalBridgeActionPlan], ["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness], + ["createVirtualHalCommandScriptFixtureReport", createVirtualHalCommandScriptFixtureReport], ["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport], ["createVirtualHalPinInventory", createVirtualHalPinInventory], ["createVirtualHalPinRegistry", createVirtualHalPinRegistry], ["createVirtualHalProjectReport", createVirtualHalProjectReport], + ["createVirtualHalSimConfigSourceCoverageReport", createVirtualHalSimConfigSourceCoverageReport], ["createVirtualHalSimulationReplacementReport", createVirtualHalSimulationReplacementReport], ["createVirtualHalSimulationRuntimeReport", createVirtualHalSimulationRuntimeReport], + ["createVirtualHalSourceComplianceReport", createVirtualHalSourceComplianceReport], ["createVirtualHalState", createVirtualHalState], ["createVirtualHalSystemCoverageReport", createVirtualHalSystemCoverageReport], ["createVirtualHalWasmBridgeSnapshot", createVirtualHalWasmBridgeSnapshot], @@ -359,10 +373,12 @@ const requiredExports = [ ["defaultMachinePaths", defaultMachinePaths], ["normalizeOpfsPath", normalizeOpfsPath], ["createMachineSessionSnapshotPayload", createMachineSessionSnapshotPayload], + ["createVirtualHalSessionPayload", createVirtualHalSessionPayload], ["createSessionSnapshot", createSessionSnapshot], ["saveSessionSnapshot", saveSessionSnapshot], ["saveMachineSessionSnapshot", saveMachineSessionSnapshot], ["loadMachineSessionSnapshot", loadMachineSessionSnapshot], + ["restoreVirtualHalStateFromSessionSnapshot", restoreVirtualHalStateFromSessionSnapshot], ["validateSessionSnapshot", validateSessionSnapshot], ["machineFilePaths", machineFilePaths], ["saveMachineTextFiles", saveMachineTextFiles], @@ -468,14 +484,29 @@ assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("motion-contro assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("linuxcnc-realtime-hal"), true); assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("halcmd"), true); assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("motion-controller"), true); +assert.equal(VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.some(({ id }) => id === "pin-signal-net-show"), true); +assert.equal(VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.some(({ id }) => id === "load-thread-start-stop"), true); +assert.equal(VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.some(({ id }) => id === "axis-foam"), true); +assert.equal(VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.some(({ id }) => id === "qtdragon-on-abort"), true); +assert.equal(VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS + .find(({ id }) => id === "external-offsets") + .sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/M111"), true); +assert.equal(VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES["halcmd-simulation-replacement"].sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true); +assert.equal(VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES["motion-controller-simulation-replacement"].sourceFiles.includes("linuxcnc/src/emc/motion/motion.c"), true); assert.equal(VIRTUAL_HAL_COVERAGE_STATES.runtimeBoundary, "runtime-boundary"); assert.equal(VIRTUAL_HAL_SOURCE_FILES.halui, "linuxcnc/src/emc/usr_intf/halui.cc"); assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "iocontrol"), true); assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "motion-core"), true); assert.match(sdkIndexText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/); +assert.match(sdkIndexText, /\bVIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES\b/); +assert.match(sdkReadmeText, /\bVIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/); +assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS\b/); +assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS\b/); +assert.match(sdkIndexText, /\bVIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES\b/); +assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\b/); @@ -486,6 +517,8 @@ assert.match(sdkIndexText, /\bexecuteVirtualHalCommand\b/); assert.match(sdkReadmeText, /\bexecuteVirtualHalCommand\b/); assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/); assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/); +assert.match(sdkIndexText, /\bcreateVirtualHalCommandScriptFixtureReport\b/); +assert.match(sdkReadmeText, /\bcreateVirtualHalCommandScriptFixtureReport\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/); assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/); @@ -494,6 +527,10 @@ assert.match(sdkIndexText, /\bcreateVirtualHalSimulationRuntimeReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationRuntimeReport\b/); assert.match(sdkIndexText, /\bcreateVirtualHalSimulationReplacementReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationReplacementReport\b/); +assert.match(sdkIndexText, /\bcreateVirtualHalSimConfigSourceCoverageReport\b/); +assert.match(sdkReadmeText, /\bcreateVirtualHalSimConfigSourceCoverageReport\b/); +assert.match(sdkIndexText, /\bcreateVirtualHalSourceComplianceReport\b/); +assert.match(sdkReadmeText, /\bcreateVirtualHalSourceComplianceReport\b/); assert.match(sdkIndexText, /\bcreateVirtualHalSystemCoverageReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalSystemCoverageReport\b/); assert.match(sdkIndexText, /\bcreateVirtualHalPinRegistry\b/); @@ -596,6 +633,17 @@ const directHalcmdAliasResult = executeVirtualHalcmd(virtualHalRuntime.getState( assert.equal(directHalcmdAliasResult.ok, true); assert.equal(directHalcmdAliasResult.apiName, "linuxcnc-wasm-virtual-halcmd-result"); assert.match(directHalcmdAliasResult.output, /halui\.machine\.is-on/); +const commandScriptFixtureReport = createVirtualHalCommandScriptFixtureReport({ halState: virtualHalRuntime.getState() }); +assert.equal(commandScriptFixtureReport.apiName, "linuxcnc-wasm-virtual-hal-command-script-fixture-report"); +assert.equal(commandScriptFixtureReport.complete, true); +assert.equal(commandScriptFixtureReport.webSimulationSatisfied, true); +assert.equal(commandScriptFixtureReport.missingActions.length, 0); +assert.equal(commandScriptFixtureReport.missingFixtures.length, 0); +assert.equal(commandScriptFixtureReport.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true); +for (const action of ["setp", "sets", "net", "show", "getp", "gets", "loadrt", "loadusr", "addf", "start", "stop"]) { + assert.equal(commandScriptFixtureReport.coveredActions.includes(action), true); +} +assert.equal(virtualHalRuntime.getCommandScriptFixtureReport().complete, true); const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), { target: { x: 3 }, maxVelocity: 10, @@ -636,7 +684,73 @@ assert.equal(replacementReport.replacements["linuxcnc-realtime-hal"].ready, true assert.equal(replacementReport.replacements.halcmd.ready, true); assert.equal(replacementReport.replacements["motion-controller"].ready, true); assert.equal(replacementReport.hardRealtime, false); +assert.equal(replacementReport.sourceCompliance.complete, true); +assert.equal(replacementReport.sourceCompliance.webSimulationSatisfied, true); +assert.equal(replacementReport.simConfigSourceCoverage.complete, true); +assert.equal(replacementReport.commandScriptFixtures.complete, true); assert.equal(virtualHalRuntime.getSimulationReplacementReport().ready, true); +const simConfigSourceCoverageReport = createVirtualHalSimConfigSourceCoverageReport({ manifestText: sourceManifestText }); +assert.equal(simConfigSourceCoverageReport.apiName, "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report"); +assert.equal(simConfigSourceCoverageReport.complete, true); +assert.equal(simConfigSourceCoverageReport.webSimulationSatisfied, true); +assert.equal(simConfigSourceCoverageReport.manifestChecked, true); +assert.equal(simConfigSourceCoverageReport.missingTargets.length, 0); +assert.equal(simConfigSourceCoverageReport.missingCapabilities.length, 0); +assert.equal(simConfigSourceCoverageReport.sourceFiles.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini"), true); +assert.equal(simConfigSourceCoverageReport.sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc"), true); +assert.equal(simConfigSourceCoverageReport.rows.every(({ sourceDerived }) => sourceDerived), true); +assert.equal(virtualHalRuntime.getSimConfigSourceCoverageReport({ manifestText: sourceManifestText }).complete, true); +const sourceComplianceReport = createVirtualHalSourceComplianceReport({ halState: virtualHalRuntime.getState() }); +assert.equal(sourceComplianceReport.apiName, "linuxcnc-wasm-virtual-hal-source-compliance-report"); +assert.equal(sourceComplianceReport.complete, true); +assert.equal(sourceComplianceReport.webSimulationSatisfied, true); +assert.equal(sourceComplianceReport.missingCapabilitySources.length, 0); +assert.equal(sourceComplianceReport.missingFamilySources.length, 0); +assert.equal(sourceComplianceReport.targetRows.every(({ sourceDerived }) => sourceDerived), true); +assert.equal(sourceComplianceReport.capabilityRows.every(({ sourceDerived }) => sourceDerived), true); +assert.equal(sourceComplianceReport.familyRows.every(({ sourceDerived }) => sourceDerived), true); +assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/bin/axis"), true); +assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true); +assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/src/emc/motion/motion.c"), true); +assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini"), true); +assert.equal(sourceComplianceReport.simConfigSourceCoverage.complete, true); +assert.equal(sourceComplianceReport.commandScriptFixtures.complete, true); +assert.equal(virtualHalRuntime.getSourceComplianceReport().complete, true); +const virtualHalSessionPayload = createVirtualHalSessionPayload(virtualHalRuntime.getState(), { + simConfigSourceCoverage: simConfigSourceCoverageReport, + commandScriptFixtures: commandScriptFixtureReport, +}); +assert.equal(virtualHalSessionPayload.apiName, "linuxcnc-wasm-virtual-hal-session-payload"); +assert.equal(virtualHalSessionPayload.state.hal.signals["x-pos"].value, 1.25); +assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true); +assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true); +assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true); +const virtualHalSessionSnapshot = createSessionSnapshot( + "sdk-virtual-hal-session", + createMachineSessionSnapshotPayload("sdk-mill", { + gcodeFilename: "demo.ngc", + virtualHal: virtualHalSessionPayload, + }), + { createdAt: "2026-06-17T00:00:00.000Z" }, +); +assert.equal( + restoreVirtualHalStateFromSessionSnapshot(virtualHalSessionSnapshot).hal.nets["x-pos"].includes("axis.x.pos-cmd"), + true, +); +const browserDiagnosticsArtifactValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({ + apiName: "real-browser-simulation-diagnostics-artifact", + virtualHal: virtualHalRuntime.getState(), + virtualHalSimulationReplacement: replacementReport, + virtualHalSourceCompliance: sourceComplianceReport, + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, + virtualHalCommandScriptFixtures: commandScriptFixtureReport, +}); +assert.equal(browserDiagnosticsArtifactValidation.apiName, "project-release-browser-diagnostics-artifact-validation"); +assert.equal(browserDiagnosticsArtifactValidation.ready, true); +assert.equal(browserDiagnosticsArtifactValidation.sourceComplianceReady, true); +assert.equal(browserDiagnosticsArtifactValidation.simConfigSourceCoverageReady, true); +assert.equal(browserDiagnosticsArtifactValidation.commandScriptFixturesReady, true); +assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true); const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }); assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true); assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0); @@ -705,14 +819,17 @@ assert.equal( batchId: "sdk-surface-batch", capabilities: batchAcceptanceWorkflow.capabilities, observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, }).rows.find(({ id }) => id === "checklist")?.value, - "3/3 passed", + "4/4 passed", ); const batchAcceptanceReport = createProjectBatchAcceptanceReport({ batchId: "sdk-surface-batch", capabilities: batchAcceptanceWorkflow.capabilities, observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, }); +assert.equal(batchAcceptanceReport.virtualHalSimConfigSourceCoverage.complete, true); assert.equal(createProjectBatchAcceptanceReportValidation(batchAcceptanceReport).ready, true); assert.equal( createProjectBatchAcceptanceReportValidationSummaryViewModel( @@ -766,7 +883,7 @@ assert.equal( ); assert.equal(releaseReadinessWaiting.apiName, "project-release-readiness-report"); assert.equal(releaseReadinessWaiting.ready, false); -assert.deepEqual(releaseReadinessWaiting.missing, ["project-release-gate"]); +assert.deepEqual(releaseReadinessWaiting.missing, ["project-release-gate", "virtual-hal-sim-config-source-coverage"]); assert.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0); assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []); assert.equal(releaseReadinessWaiting.gateActionPlan.apiName, "project-release-gate-action-plan"); @@ -778,9 +895,11 @@ assert.equal( ); const releaseReadinessReady = createProjectReleaseReadinessReport({ observedOutputs: ["project_release_gate=ok"], + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, }); assert.equal(releaseReadinessReady.ready, true); assert.equal(releaseReadinessReady.releaseGate.passed, true); +assert.equal(releaseReadinessReady.virtualHalSimConfigSourceCoverageReady, true); assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest()); assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount); assert.equal(createProjectReleaseGateActionPlan({ @@ -796,6 +915,7 @@ assert.equal( ); const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, axisScreenshotArtifacts: ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"].map((viewportName) => ({ apiName: "real-browser-simulation-axis-screenshot-artifact", artifactVersion: 1, @@ -829,6 +949,7 @@ assert.equal( ); const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.stringify(createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, }))); assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report"); const releaseReadinessArtifactValidation = createProjectReleaseReadinessArtifactValidation(releaseReadinessArtifact); @@ -848,6 +969,7 @@ assert.deepEqual(releaseReadinessArtifactValidation, { gateExecutionSummaryReady: true, gateResultMatrixReady: true, gateActionPlanReady: true, + virtualHalSimConfigSourceCoverageReady: true, axisScreenshotArtifactSummaryReady: true, axisScreenshotArtifactCount: 0, blockedRuntimeFamilies: [ @@ -891,6 +1013,11 @@ assert.deepEqual(releaseReadinessArtifactValidation, { label: "Gate action plan", value: "ready", }, + { + id: "virtual-hal-sim-config-source-coverage", + label: "Virtual HAL sim-config source coverage", + value: "ready", + }, { id: "axis-screenshot-artifacts", label: "AXIS screenshot artifacts", @@ -987,6 +1114,7 @@ assert.deepEqual( "simConfigInventory.passed", "simConfigInventory.skipped", "simConfigInventory.unexpectedFail", + "virtualHalSimConfigSourceCoverage", "blockedRuntimeFamilies", "gateManifest", "gateExecutionManifest", diff --git a/wasm-port/tests/sdk/node/write_project_batch_acceptance_artifact.mjs b/wasm-port/tests/sdk/node/write_project_batch_acceptance_artifact.mjs index 92c99e1..e2e4a46 100644 --- a/wasm-port/tests/sdk/node/write_project_batch_acceptance_artifact.mjs +++ b/wasm-port/tests/sdk/node/write_project_batch_acceptance_artifact.mjs @@ -1,12 +1,16 @@ -import { mkdirSync, renameSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { createProjectBatchAcceptanceReport } from "../../../runtime/sdk/src/index.js"; +import { + createProjectBatchAcceptanceReport, + createVirtualHalSimConfigSourceCoverageReport, +} from "../../../runtime/sdk/src/index.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dirname, "../../.."); const outputPath = process.argv[2] ?? resolve(root, "build/project-batch-acceptance.json"); +const sourceManifestText = readFileSync(resolve(root, "tools/source-manifest.txt"), "utf8"); const report = createProjectBatchAcceptanceReport({ batchId: "project-batch-acceptance-artifact", @@ -18,6 +22,13 @@ const report = createProjectBatchAcceptanceReport({ evidence: "project_batch_acceptance_artifact_node_smoke=ok", command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh", }, + { + id: "virtual-hal-sim-config-source-coverage", + type: "api", + label: "Virtual HAL sim-config source coverage", + evidence: "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report", + command: "wasm-port/tests/sdk/node/verify_sdk_surface.sh", + }, { id: "project-batch-acceptance-workflow-gate", type: "gate", @@ -27,6 +38,9 @@ const report = createProjectBatchAcceptanceReport({ }, ], observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], + virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport({ + manifestText: sourceManifestText, + }), }); mkdirSync(dirname(outputPath), { recursive: true }); diff --git a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs index 98022d5..f0d27e1 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs @@ -4,6 +4,9 @@ import { createProjectReleaseGateManifest, createProjectReleaseReadinessReport, } from "../../../runtime/sdk/src/project-release-readiness.js"; +import { + createVirtualHalSimConfigSourceCoverageReport, +} from "../../../runtime/sdk/src/linuxcnc-hal.js"; import { INI_PANEL_ENTRIES, @@ -1350,6 +1353,7 @@ assert.equal(workflowDomMountResult.renderResult.dataset.handoffScope, "workflow assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), shellViewModel.shellWorkflowOverviewContract); const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), + virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(), })); const releaseArtifactValidation = validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson( releaseReadinessArtifactJson, @@ -1655,7 +1659,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead rowsNode: releaseArtifactRowsNode, }); assert.equal(releaseArtifactRenderResult.rendered, true); -assert.equal(releaseArtifactRenderResult.rowCount, 9); +assert.equal(releaseArtifactRenderResult.rowCount, 10); assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact"); assert.equal( releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), @@ -1673,8 +1677,12 @@ assert.equal( releaseArtifactRenderResult.rowIds.includes("axis-screenshot-artifacts"), true, ); +assert.equal( + releaseArtifactRenderResult.rowIds.includes("virtual-hal-sim-config-source-coverage"), + true, +); assert.equal(releaseArtifactMountResult.ready, true); -assert.equal(releaseArtifactMountResult.renderResult.rowCount, 9); +assert.equal(releaseArtifactMountResult.renderResult.rowCount, 10); assert.deepEqual( validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing, ["artifact-json"], @@ -1715,7 +1723,7 @@ assert.equal( ); assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons"); assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true); -assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 9); +assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 10); const releaseArtifactUrlWorkflowSummary = createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel( releaseArtifactUrlWorkflow,