虚拟HAL完全满足Web数控仿真

结论:虚拟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。
This commit is contained in:
2026-06-17 22:03:26 +08:00
parent a4d449ad48
commit 3a6972437f
21 changed files with 2017 additions and 240 deletions

View File

@@ -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 语义扩展。

759
text17.txt Normal file
View File

@@ -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。

View File

@@ -7,6 +7,14 @@ import {
sessionSnapshotPath, sessionSnapshotPath,
toolTablePath, toolTablePath,
} from "./path-model.js"; } 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_FORMAT = "linuxcnc-wasm-session-snapshot";
export const SESSION_SNAPSHOT_VERSION = 1; 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, "parameters", "machine session parameter OPFS path");
requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path"); requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path");
optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path"); optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path");
if (payload.virtualHal !== undefined) {
validateVirtualHalSessionPayload(payload.virtualHal);
}
return payload; 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 = {}) { export function createMachineSessionSnapshotPayload(machineId, options = {}) {
assertPlainObject(options, "machine session snapshot options"); assertPlainObject(options, "machine session snapshot options");
const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ?? const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ??
@@ -58,6 +115,9 @@ export function createMachineSessionSnapshotPayload(machineId, options = {}) {
toolTablePath(machineId, options.toolTableFilename); toolTablePath(machineId, options.toolTableFilename);
const gcode = optionalOpfsPath(options.gcodeOpfsPath, "G-code OPFS path") ?? const gcode = optionalOpfsPath(options.gcodeOpfsPath, "G-code OPFS path") ??
(options.gcodeFilename === undefined ? undefined : gcodeProgramPath(options.gcodeFilename)); (options.gcodeFilename === undefined ? undefined : gcodeProgramPath(options.gcodeFilename));
const virtualHal = options.virtualHalState === undefined && options.virtualHal === undefined
? undefined
: (options.virtualHal ?? createVirtualHalSessionPayload(options.virtualHalState, options.virtualHalOptions ?? {}));
return { return {
machineId, machineId,
@@ -67,6 +127,7 @@ export function createMachineSessionSnapshotPayload(machineId, options = {}) {
toolTable, toolTable,
...(gcode === undefined ? {} : { gcode }), ...(gcode === undefined ? {} : { gcode }),
}, },
...(virtualHal === undefined ? {} : { virtualHal }),
}; };
} }

View File

@@ -66,8 +66,11 @@ import {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES, VIRTUAL_HAL_SOURCE_FILES,
@@ -79,6 +82,7 @@ import {
createLinuxCncIniSdk, createLinuxCncIniSdk,
createLinuxCncInterpSdk, createLinuxCncInterpSdk,
createLinuxCncVirtualHalRuntime, createLinuxCncVirtualHalRuntime,
createVirtualHalCommandScriptFixtureReport,
createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState, createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary, createMachineSessionPersistenceSummary,
@@ -88,8 +92,10 @@ import {
createVirtualHalPinInventory, createVirtualHalPinInventory,
createVirtualHalPinRegistry, createVirtualHalPinRegistry,
createVirtualHalProjectReport, createVirtualHalProjectReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport, createVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport, createVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState, createVirtualHalState,
createVirtualHalSystemCoverageReport, createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot, createVirtualHalWasmBridgeSnapshot,
@@ -100,6 +106,7 @@ import {
stepVirtualHalMotionController, stepVirtualHalMotionController,
writeVirtualHalPin, writeVirtualHalPin,
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createVirtualHalSessionPayload,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel, createProjectReleaseGateExecutionSummaryViewModel,
@@ -157,7 +164,9 @@ import {
createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow, createProjectBatchAcceptanceWorkflow,
loadProjectBatchAcceptanceReportUrlWorkflow, loadProjectBatchAcceptanceReportUrlWorkflow,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
readMachineSessionReadiness, readMachineSessionReadiness,
restoreVirtualHalStateFromSessionSnapshot,
renderIniPanelShellWorkflowOverviewEmbeddingMountState, renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
@@ -287,10 +296,11 @@ private page state.
`readPin()`, `writePin()`, `applyPinUpdates()`, `executeHalCommand()`, `readPin()`, `writePin()`, `applyPinUpdates()`, `executeHalCommand()`,
`executeHalcmd()`, `stepMotion()`, `stepMotionController()`, `executeHalcmd()`, `stepMotion()`, `stepMotionController()`,
`getSimulationRuntimeReport()`, `getSimulationReplacementReport()`, `getSimulationRuntimeReport()`, `getSimulationReplacementReport()`,
`getIntegrityReport()`, `getPinInventory()`, `getProjectReport()`, and `getSourceComplianceReport()`, `getIntegrityReport()`, `getPinInventory()`,
`applyToInterpSdk()`. `getProjectReport()`, and `applyToInterpSdk()`.
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`, - `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`,
`VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`, `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`,
`VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES`,
`executeVirtualHalCommand()`, `executeVirtualHalcmd()`, `executeVirtualHalCommand()`, `executeVirtualHalcmd()`,
`stepVirtualHalMotion()`, `stepVirtualHalMotionController()`, `stepVirtualHalMotion()`, `stepVirtualHalMotionController()`,
`createVirtualHalSimulationRuntimeReport()`, and `createVirtualHalSimulationRuntimeReport()`, and
@@ -300,6 +310,13 @@ private page state.
`sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs, `sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs,
`addf`, thread start/stop, and servo-period motion stepping for browser `addf`, thread start/stop, and servo-period motion stepping for browser
simulation and Node dashboards. 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 - `createVirtualHalWasmBridgeSnapshot()` returns the pin/value rows that can be
applied through the interpreter SDK HAL bridge. It includes AXIS internal pin 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 names such as `jog.x`, full HAL names such as `axisui.jog.x`, task/power

View File

@@ -3,8 +3,11 @@ export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export { export {
VIRTUAL_HAL_AXES, VIRTUAL_HAL_AXES,
VIRTUAL_HAL_AXISUI_PINS, VIRTUAL_HAL_AXISUI_PINS,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES, VIRTUAL_HAL_SOURCE_FILES,
@@ -15,6 +18,7 @@ export {
applyVirtualHalPinUpdates, applyVirtualHalPinUpdates,
cloneVirtualHalState, cloneVirtualHalState,
createLinuxCncVirtualHalRuntime, createLinuxCncVirtualHalRuntime,
createVirtualHalCommandScriptFixtureReport,
createVirtualHalBridgeActionPlan, createVirtualHalBridgeActionPlan,
createVirtualHalBridgeReadiness, createVirtualHalBridgeReadiness,
createVirtualHalDroState, createVirtualHalDroState,
@@ -24,8 +28,10 @@ export {
createVirtualHalPinInventory, createVirtualHalPinInventory,
createVirtualHalPinRegistry, createVirtualHalPinRegistry,
createVirtualHalProjectReport, createVirtualHalProjectReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport, createVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport, createVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState, createVirtualHalState,
createVirtualHalSystemCoverageReport, createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot, createVirtualHalWasmBridgeSnapshot,
@@ -50,6 +56,7 @@ export {
createProjectBatchAcceptanceReportValidation, createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow, createProjectBatchAcceptanceWorkflow,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel, createProjectReleaseGateExecutionSummaryViewModel,
@@ -95,8 +102,10 @@ export {
export { export {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createSessionSnapshot, createSessionSnapshot,
createVirtualHalSessionPayload,
loadMachineSessionSnapshot, loadMachineSessionSnapshot,
loadSessionSnapshot, loadSessionSnapshot,
restoreVirtualHalStateFromSessionSnapshot,
saveMachineSessionSnapshot, saveMachineSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
validateSessionSnapshot, validateSessionSnapshot,

View File

@@ -25,9 +25,102 @@ export const VIRTUAL_HAL_SOURCE_FILES = Object.freeze({
motion: "linuxcnc/src/emc/motion/motion.c", motion: "linuxcnc/src/emc/motion/motion.c",
motionAxis: "linuxcnc/src/emc/motion/axis.c", motionAxis: "linuxcnc/src/emc/motion/axis.c",
motionHoming: "linuxcnc/src/emc/motion/homing.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", 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({ export const VIRTUAL_HAL_COVERAGE_STATES = Object.freeze({
simulated: "simulated", simulated: "simulated",
bridgeOnly: "bridge-only", bridgeOnly: "bridge-only",
@@ -338,6 +431,157 @@ export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([
"motion-controller", "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 = {}) { export function createVirtualHalState(overrides = {}) {
const base = { const base = {
apiName: "linuxcnc-wasm-virtual-hal-state", 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 = {}) { export function createVirtualHalSimulationReplacementReport(halState = createVirtualHalState(), options = {}) {
const state = normalizeVirtualHalState(halState); const state = normalizeVirtualHalState(halState);
const runtime = createVirtualHalSimulationRuntimeReport(state, options); const runtime = createVirtualHalSimulationRuntimeReport(state, options);
const sourceCompliance = createVirtualHalSourceComplianceReport({ halState: state });
const simConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(options);
const commandScriptFixtures = createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
const requiredCapabilities = options.requiredCapabilities ?? [ const requiredCapabilities = options.requiredCapabilities ?? [
"realtime-hal-simulation-replacement", "realtime-hal-simulation-replacement",
"halcmd-simulation-replacement", "halcmd-simulation-replacement",
@@ -1823,6 +2136,9 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
requiredCapabilities, requiredCapabilities,
missingCapabilities, missingCapabilities,
replacements, replacements,
sourceCompliance,
simConfigSourceCoverage,
commandScriptFixtures,
runtime, runtime,
rows: [ rows: [
{ id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" }, { 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 = {}) { export function createVirtualHalSimulationRuntimeReport(halState = createVirtualHalState(), options = {}) {
const state = normalizeVirtualHalState(halState); const state = normalizeVirtualHalState(halState);
const snapshot = createVirtualHalWasmBridgeSnapshot(state); const snapshot = createVirtualHalWasmBridgeSnapshot(state);
@@ -1936,6 +2443,15 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS
getSimulationReplacementReport(options = {}) { getSimulationReplacementReport(options = {}) {
return createVirtualHalSimulationReplacementReport(state, 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 = {}) { getIntegrityReport(options = {}) {
return createVirtualHalIntegrityReport({ ...options, halState: state }); return createVirtualHalIntegrityReport({ ...options, halState: state });
}, },
@@ -2029,6 +2545,26 @@ function createVirtualHalShowRows(state, kind = "pin", pattern = "") {
value: normalizeHalNumericValue(entry.value), 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 return createVirtualHalWasmBridgeSnapshot(state).values
.filter(({ kind: rowKind, name }) => rowKind === "pin" && matches(name)) .filter(({ kind: rowKind, name }) => rowKind === "pin" && matches(name))
.map((row) => ({ .map((row) => ({

View File

@@ -88,6 +88,97 @@ const BLOCKED_RUNTIME_FAMILIES = [
const PROJECT_RELEASE_READINESS_ARTIFACT_API = "project-release-readiness-report"; const PROJECT_RELEASE_READINESS_ARTIFACT_API = "project-release-readiness-report";
const PROJECT_RELEASE_READINESS_ARTIFACT_VERSION = 1; 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) { function outputContains(observedOutputs, expectedOutput) {
return observedOutputs.some((output) => String(output).includes(expectedOutput)); return observedOutputs.some((output) => String(output).includes(expectedOutput));
} }
@@ -470,6 +561,7 @@ export function createProjectBatchAcceptanceWorkflow({
capabilities = [], capabilities = [],
gateResults = {}, gateResults = {},
observedOutputs = [], observedOutputs = [],
virtualHalSimConfigSourceCoverage = null,
} = {}) { } = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities }); const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const gateManifest = createProjectReleaseGateManifest(); const gateManifest = createProjectReleaseGateManifest();
@@ -480,10 +572,13 @@ export function createProjectBatchAcceptanceWorkflow({
}); });
const batchGatePassed = gateResultMatrix.rows const batchGatePassed = gateResultMatrix.rows
.find(({ id }) => id === "project-batch-acceptance")?.passed === true; .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 = [ const missing = [
...capabilityMatrix.missing, ...capabilityMatrix.missing,
...(batchGatePassed ? [] : ["project-batch-acceptance"]), ...(batchGatePassed ? [] : ["project-batch-acceptance"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]),
]; ];
return { return {
@@ -498,6 +593,8 @@ export function createProjectBatchAcceptanceWorkflow({
capabilityMatrix, capabilityMatrix,
capabilities: capabilityMatrix.capabilities, capabilities: capabilityMatrix.capabilities,
rejectedCapabilities: capabilityMatrix.rejectedCapabilities, rejectedCapabilities: capabilityMatrix.rejectedCapabilities,
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigSourceCoverageReady,
gateResultMatrix, gateResultMatrix,
missing, missing,
rows: [ rows: [
@@ -516,6 +613,13 @@ export function createProjectBatchAcceptanceWorkflow({
label: "Batch acceptance gate", label: "Batch acceptance gate",
value: batchGatePassed ? "passed" : "unknown", 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", id: "missing",
label: "Missing acceptance evidence", label: "Missing acceptance evidence",
@@ -649,6 +753,15 @@ export function createProjectBatchAcceptanceChecklist({
detail: workflow?.gateResultMatrix?.rows detail: workflow?.gateResultMatrix?.rows
?.find?.(({ id }) => id === "project-batch-acceptance")?.status ?? "unknown", ?.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", id: "next-action",
label: "Next acceptance action", label: "Next acceptance action",
@@ -680,6 +793,7 @@ export function createProjectBatchAcceptanceReport({
capabilities = [], capabilities = [],
gateResults = {}, gateResults = {},
observedOutputs = [], observedOutputs = [],
virtualHalSimConfigSourceCoverage = null,
} = {}) { } = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities }); const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const workflow = createProjectBatchAcceptanceWorkflow({ const workflow = createProjectBatchAcceptanceWorkflow({
@@ -687,6 +801,7 @@ export function createProjectBatchAcceptanceReport({
capabilities, capabilities,
gateResults, gateResults,
observedOutputs, observedOutputs,
virtualHalSimConfigSourceCoverage,
}); });
const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow); const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow);
const actionPlan = createProjectBatchAcceptanceActionPlan(workflow); const actionPlan = createProjectBatchAcceptanceActionPlan(workflow);
@@ -701,6 +816,7 @@ export function createProjectBatchAcceptanceReport({
missing: [...workflow.missing], missing: [...workflow.missing],
capabilityMatrix, capabilityMatrix,
workflow, workflow,
virtualHalSimConfigSourceCoverage,
summaryViewModel, summaryViewModel,
actionPlan, actionPlan,
checklist, checklist,
@@ -736,7 +852,12 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel); const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel);
const actionPlan = objectOrEmpty(reportObject.actionPlan); const actionPlan = objectOrEmpty(reportObject.actionPlan);
const checklist = objectOrEmpty(reportObject.checklist); const checklist = objectOrEmpty(reportObject.checklist);
const virtualHalSimConfigSourceCoverage = objectOrEmpty(reportObject.virtualHalSimConfigSourceCoverage);
const rows = arrayOrEmpty(reportObject.rows); const rows = arrayOrEmpty(reportObject.rows);
const virtualHalSimConfigSourceCoverageRequired = reportObject.virtualHalSimConfigSourceCoverage !== null &&
reportObject.virtualHalSimConfigSourceCoverage !== undefined;
const virtualHalSimConfigSourceCoverageReady = !virtualHalSimConfigSourceCoverageRequired ||
isVirtualHalSimConfigSourceCoverageReady(virtualHalSimConfigSourceCoverage);
const missing = [ const missing = [
...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]), ...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]),
...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]), ...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]),
@@ -760,7 +881,8 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true ...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true
? [] ? []
: ["checklist"]), : ["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"]), : ["rows.checklist"]),
]; ];
@@ -795,6 +917,13 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
label: "Checklist", label: "Checklist",
value: checklist.ready === true ? "ready" : "missing", 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", id: "missing",
label: "Missing report evidence", label: "Missing report evidence",
@@ -1251,6 +1380,7 @@ export function createProjectReleaseReadinessReport({
gateResults = {}, gateResults = {},
observedOutputs = [], observedOutputs = [],
simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE, simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
virtualHalSimConfigSourceCoverage = null,
blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES, blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES,
promotedRuntimeFamilies = [], promotedRuntimeFamilies = [],
axisScreenshotArtifacts = [], axisScreenshotArtifacts = [],
@@ -1283,13 +1413,21 @@ export function createProjectReleaseReadinessReport({
const promotedFamilies = [...promotedRuntimeFamilies]; const promotedFamilies = [...promotedRuntimeFamilies];
const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family)); const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family));
const inventoryReady = inventory.unexpectedFail === 0; const inventoryReady = inventory.unexpectedFail === 0;
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
);
const blockedRuntimeReady = promotedBlockedFamilies.length === 0; const blockedRuntimeReady = promotedBlockedFamilies.length === 0;
const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts); const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts);
const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready; const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready;
const ready = releaseGatePassed && inventoryReady && blockedRuntimeReady && axisScreenshotArtifactsReady; const ready = releaseGatePassed &&
inventoryReady &&
virtualHalSimConfigSourceCoverageReady &&
blockedRuntimeReady &&
axisScreenshotArtifactsReady;
const missing = [ const missing = [
...(releaseGatePassed ? [] : ["project-release-gate"]), ...(releaseGatePassed ? [] : ["project-release-gate"]),
...(inventoryReady ? [] : ["sim-config-inventory"]), ...(inventoryReady ? [] : ["sim-config-inventory"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]),
...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]), ...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]),
...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]), ...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]),
]; ];
@@ -1306,6 +1444,8 @@ export function createProjectReleaseReadinessReport({
expectedOutput: "project_release_gate=ok", expectedOutput: "project_release_gate=ok",
}, },
simConfigInventory: inventory, simConfigInventory: inventory,
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigSourceCoverageReady,
blockedRuntimeFamilies: blockedFamilies, blockedRuntimeFamilies: blockedFamilies,
promotedRuntimeFamilies: promotedFamilies, promotedRuntimeFamilies: promotedFamilies,
promotedBlockedFamilies, promotedBlockedFamilies,
@@ -1327,6 +1467,11 @@ export function createProjectReleaseReadinessReport({
label: "Sim config inventory", label: "Sim config inventory",
value: `unexpected_fail=${inventory.unexpectedFail}`, 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", id: "blocked-runtime-families",
label: "Blocked runtime families", label: "Blocked runtime families",
@@ -1349,6 +1494,7 @@ export function createProjectReleaseReadinessSummaryViewModel(
const missing = Array.isArray(report?.missing) ? report.missing : []; const missing = Array.isArray(report?.missing) ? report.missing : [];
const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix); const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix);
const simConfigInventory = objectOrEmpty(report?.simConfigInventory); const simConfigInventory = objectOrEmpty(report?.simConfigInventory);
const virtualHalSimConfigSourceCoverageReady = report?.virtualHalSimConfigSourceCoverageReady === true;
const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary); const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary);
const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies); const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies);
const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies); const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies);
@@ -1380,6 +1526,11 @@ export function createProjectReleaseReadinessSummaryViewModel(
label: "Sim config inventory", label: "Sim config inventory",
value: `unexpected_fail=${simConfigInventory.unexpectedFail ?? "unknown"}`, 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", id: "blocked-runtime-families",
label: "Blocked runtime families", label: "Blocked runtime families",
@@ -1457,6 +1608,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
label: "Gate action plan", label: "Gate action plan",
value: validation?.gateActionPlanReady === true ? "ready" : "missing", 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", id: "missing",
label: "Missing artifact evidence", label: "Missing artifact evidence",
@@ -1609,14 +1765,19 @@ export function createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson
export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl = "", artifactUrl = "",
diagnosticsUrl = "",
fetchRef = globalThis.fetch, fetchRef = globalThis.fetch,
} = {}) { } = {}) {
if (!artifactUrl || typeof fetchRef !== "function") { if (!artifactUrl || typeof fetchRef !== "function") {
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" }); const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" });
const diagnosticsValidation = diagnosticsUrl
? createProjectReleaseBrowserDiagnosticsArtifactValidation({})
: null;
const missing = [ const missing = [
...(artifactUrl ? [] : ["artifact-url"]), ...(artifactUrl ? [] : ["artifact-url"]),
...(typeof fetchRef === "function" ? [] : ["fetch"]), ...(typeof fetchRef === "function" ? [] : ["fetch"]),
...jsonWorkflow.missing, ...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch", ...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`)] : []),
]; ];
return { return {
apiName: "project-release-readiness-artifact-url-workflow", apiName: "project-release-readiness-artifact-url-workflow",
@@ -1624,11 +1785,16 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked", phase: "blocked",
ready: false, ready: false,
artifactUrl, artifactUrl,
diagnosticsUrl,
fetched: false, fetched: false,
diagnosticsFetched: false,
httpStatus: null, httpStatus: null,
diagnosticsHttpStatus: null,
fetchError: null, fetchError: null,
diagnosticsFetchError: null,
jsonWorkflow, jsonWorkflow,
validation: jsonWorkflow.validation, validation: jsonWorkflow.validation,
diagnosticsValidation,
summaryViewModel: jsonWorkflow.summaryViewModel, summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan, actionPlan: jsonWorkflow.actionPlan,
missing, missing,
@@ -1664,14 +1830,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked", phase: "blocked",
ready: false, ready: false,
artifactUrl, artifactUrl,
diagnosticsUrl,
fetched: false, fetched: false,
diagnosticsFetched: false,
httpStatus, httpStatus,
diagnosticsHttpStatus: null,
fetchError, fetchError,
diagnosticsFetchError: null,
jsonWorkflow, jsonWorkflow,
validation: jsonWorkflow.validation, validation: jsonWorkflow.validation,
diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null,
summaryViewModel: jsonWorkflow.summaryViewModel, summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan, actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing], missing: [
"artifact-fetch",
...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch"] : []),
],
rows: [ rows: [
{ {
id: "fetch", id: "fetch",
@@ -1694,20 +1869,56 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
const artifactJson = await response.text(); const artifactJson = await response.text();
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson }); 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 { return {
apiName: "project-release-readiness-artifact-url-workflow", apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1, workflowVersion: 1,
phase: jsonWorkflow.ready === true ? "ready" : "blocked", phase: ready ? "ready" : "blocked",
ready: jsonWorkflow.ready === true, ready,
artifactUrl, artifactUrl,
diagnosticsUrl,
fetched: true, fetched: true,
diagnosticsFetched,
httpStatus, httpStatus,
diagnosticsHttpStatus,
fetchError: null, fetchError: null,
diagnosticsFetchError,
jsonWorkflow, jsonWorkflow,
validation: jsonWorkflow.validation, validation: jsonWorkflow.validation,
diagnosticsValidation,
summaryViewModel: jsonWorkflow.summaryViewModel, summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan, actionPlan: jsonWorkflow.actionPlan,
missing: jsonWorkflow.missing, missing,
rows: [ rows: [
{ {
id: "fetch", id: "fetch",
@@ -1719,6 +1930,13 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
label: "Artifact JSON workflow", label: "Artifact JSON workflow",
value: jsonWorkflow.ready === true ? "ready" : "blocked", 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", id: "next-command",
label: "Next command", label: "Next command",
@@ -1734,14 +1952,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked", phase: "blocked",
ready: false, ready: false,
artifactUrl, artifactUrl,
diagnosticsUrl,
fetched: false, fetched: false,
diagnosticsFetched: false,
httpStatus: null, httpStatus: null,
diagnosticsHttpStatus: null,
fetchError: error?.message ?? "artifact URL fetch error", fetchError: error?.message ?? "artifact URL fetch error",
diagnosticsFetchError: null,
jsonWorkflow, jsonWorkflow,
validation: jsonWorkflow.validation, validation: jsonWorkflow.validation,
diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null,
summaryViewModel: jsonWorkflow.summaryViewModel, summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan, actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing], missing: [
"artifact-fetch",
...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch"] : []),
],
rows: [ rows: [
{ {
id: "fetch", id: "fetch",
@@ -1802,6 +2029,13 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
label: "Artifact validation", label: "Artifact validation",
value: workflow?.validation?.ready === true ? "ready" : "blocked", 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", id: "next-command",
label: "Next command", label: "Next command",
@@ -1850,6 +2084,15 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
expectedOutput: null, 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) => ({ const commandActions = validationCommands.map((command) => ({
id: command.id ?? command.gateId ?? `command-${command.step}`, id: command.id ?? command.gateId ?? `command-${command.step}`,
@@ -1902,6 +2145,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact); const artifactObject = objectOrEmpty(artifact);
const releaseGate = objectOrEmpty(artifactObject.releaseGate); const releaseGate = objectOrEmpty(artifactObject.releaseGate);
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory); const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const virtualHalSimConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage);
const gateManifest = objectOrEmpty(artifactObject.gateManifest); const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest); const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel); const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel);
@@ -1947,6 +2191,9 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
&& gateActionPlan.pendingCount === 0 && gateActionPlan.pendingCount === 0
&& gateActionPlan.nextCommand === null && gateActionPlan.nextCommand === null
&& actionPlanCommands.length === 0; && actionPlanCommands.length === 0;
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
) && artifactObject.virtualHalSimConfigSourceCoverageReady === true;
const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts); const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts);
const axisScreenshotSummaryReady = !axisScreenshotArtifactSummary.apiName const axisScreenshotSummaryReady = !axisScreenshotArtifactSummary.apiName
|| axisScreenshotArtifacts.length === 0 || axisScreenshotArtifacts.length === 0
@@ -1984,6 +2231,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]), ...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]),
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]), ...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]),
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]), ...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",") ...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",")
? [] ? []
: ["blockedRuntimeFamilies"]), : ["blockedRuntimeFamilies"]),
@@ -2017,6 +2265,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
gateExecutionSummaryReady: executionSummaryReady, gateExecutionSummaryReady: executionSummaryReady,
gateResultMatrixReady: matrixReady, gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady, gateActionPlanReady: actionPlanReady,
virtualHalSimConfigSourceCoverageReady,
axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady, axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady,
axisScreenshotArtifactCount: axisScreenshotArtifacts.length, axisScreenshotArtifactCount: axisScreenshotArtifacts.length,
blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies), blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
@@ -2056,6 +2305,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
label: "Gate action plan", label: "Gate action plan",
value: actionPlanReady ? "ready" : "missing", 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", id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts", label: "AXIS screenshot artifacts",

View File

@@ -1797,13 +1797,16 @@ M2
applyVirtualHalAction as applySharedVirtualHalAction, applyVirtualHalAction as applySharedVirtualHalAction,
applyVirtualHalPinUpdates as applySharedVirtualHalPinUpdates, applyVirtualHalPinUpdates as applySharedVirtualHalPinUpdates,
cloneVirtualHalState as cloneSharedVirtualHalState, cloneVirtualHalState as cloneSharedVirtualHalState,
createVirtualHalCommandScriptFixtureReport as createSharedVirtualHalCommandScriptFixtureReport,
createVirtualHalDroState as createSharedVirtualHalDroState, createVirtualHalDroState as createSharedVirtualHalDroState,
createVirtualHalIntegrityReport as createSharedVirtualHalIntegrityReport, createVirtualHalIntegrityReport as createSharedVirtualHalIntegrityReport,
createVirtualHalLimitsHomeState as createSharedVirtualHalLimitsHomeState, createVirtualHalLimitsHomeState as createSharedVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState as createSharedVirtualHalMachineStatusState, createVirtualHalMachineStatusState as createSharedVirtualHalMachineStatusState,
createVirtualHalPinRegistry as createSharedVirtualHalPinRegistry, createVirtualHalPinRegistry as createSharedVirtualHalPinRegistry,
createVirtualHalSimConfigSourceCoverageReport as createSharedVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport as createSharedVirtualHalSimulationReplacementReport, createVirtualHalSimulationReplacementReport as createSharedVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport as createSharedVirtualHalSimulationRuntimeReport, createVirtualHalSimulationRuntimeReport as createSharedVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport as createSharedVirtualHalSourceComplianceReport,
createVirtualHalState, createVirtualHalState,
executeVirtualHalcmd as executeSharedVirtualHalcmd, executeVirtualHalcmd as executeSharedVirtualHalcmd,
readVirtualHalPin as readSharedVirtualHalPin, readVirtualHalPin as readSharedVirtualHalPin,
@@ -2481,6 +2484,18 @@ M2
return createSharedVirtualHalSimulationReplacementReport(virtualHalState); return createSharedVirtualHalSimulationReplacementReport(virtualHalState);
} }
function createVirtualHalSourceComplianceReport() {
return createSharedVirtualHalSourceComplianceReport({ halState: virtualHalState });
}
function createVirtualHalSimConfigSourceCoverageReport() {
return createSharedVirtualHalSimConfigSourceCoverageReport();
}
function createVirtualHalCommandScriptFixtureReport() {
return createSharedVirtualHalCommandScriptFixtureReport({ halState: virtualHalState });
}
function renderMachineStatusDom(machineStatus) { function renderMachineStatusDom(machineStatus) {
const statusMap = { const statusMap = {
"spindle-state": machineStatus.spindle.state, "spindle-state": machineStatus.spindle.state,
@@ -2567,6 +2582,9 @@ M2
diagnostics: renderDiagnostics(), diagnostics: renderDiagnostics(),
virtualHal: cloneVirtualHalState(), virtualHal: cloneVirtualHalState(),
virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(), virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(),
virtualHalSourceCompliance: createVirtualHalSourceComplianceReport(),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(),
statusHistory: getStatusHistory(), statusHistory: getStatusHistory(),
mdiHistory: getMdiHistory(), mdiHistory: getMdiHistory(),
recentPrograms: getRecentPrograms(), recentPrograms: getRecentPrograms(),
@@ -3869,6 +3887,9 @@ M2
stepVirtualHalMotionController: (options = {}) => stepVirtualHalMotionController(options), stepVirtualHalMotionController: (options = {}) => stepVirtualHalMotionController(options),
getVirtualHalSimulationRuntimeReport: () => createVirtualHalSimulationRuntimeReport(), getVirtualHalSimulationRuntimeReport: () => createVirtualHalSimulationRuntimeReport(),
getVirtualHalSimulationReplacementReport: () => createVirtualHalSimulationReplacementReport(), getVirtualHalSimulationReplacementReport: () => createVirtualHalSimulationReplacementReport(),
getVirtualHalSourceComplianceReport: () => createVirtualHalSourceComplianceReport(),
getVirtualHalSimConfigSourceCoverageReport: () => createVirtualHalSimConfigSourceCoverageReport(),
getVirtualHalCommandScriptFixtureReport: () => createVirtualHalCommandScriptFixtureReport(),
getVirtualRealtimeHalRuntimeReport: () => createVirtualHalSimulationReplacementReport(), getVirtualRealtimeHalRuntimeReport: () => createVirtualHalSimulationReplacementReport(),
getVirtualHalDroState: () => createVirtualHalDroState(), getVirtualHalDroState: () => createVirtualHalDroState(),
getVirtualHalLimitsHomeState: () => createVirtualHalLimitsHomeState(), getVirtualHalLimitsHomeState: () => createVirtualHalLimitsHomeState(),

View File

@@ -24,13 +24,16 @@ export {
applyVirtualHalPinUpdates, applyVirtualHalPinUpdates,
cloneVirtualHalState, cloneVirtualHalState,
createLinuxCncVirtualHalRuntime, createLinuxCncVirtualHalRuntime,
createVirtualHalCommandScriptFixtureReport,
createVirtualHalDroState, createVirtualHalDroState,
createVirtualHalIntegrityReport, createVirtualHalIntegrityReport,
createVirtualHalLimitsHomeState, createVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState, createVirtualHalMachineStatusState,
createVirtualHalPinRegistry, createVirtualHalPinRegistry,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport, createVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport, createVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState, createVirtualHalState,
createVirtualHalWasmBridgeSnapshot, createVirtualHalWasmBridgeSnapshot,
executeVirtualHalCommand, executeVirtualHalCommand,

View File

@@ -18,11 +18,17 @@
} from "../../runtime/opfs/path-model.js"; } from "../../runtime/opfs/path-model.js";
import { import {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createVirtualHalSessionPayload,
loadMachineSessionSnapshot, loadMachineSessionSnapshot,
loadSessionSnapshot, loadSessionSnapshot,
restoreVirtualHalStateFromSessionSnapshot,
saveMachineSessionSnapshot, saveMachineSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
} from "../../runtime/opfs/snapshot-store.js"; } from "../../runtime/opfs/snapshot-store.js";
import {
createVirtualHalState,
executeVirtualHalCommand,
} from "../../runtime/sdk/src/linuxcnc-hal.js";
import { import {
gcodeFilenameFromProgramPath, gcodeFilenameFromProgramPath,
loadGcodeProgram, loadGcodeProgram,
@@ -166,6 +172,38 @@ TOOL_TABLE = browser-tool.tbl
assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload"); assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload");
assertEqual(loadedSnapshot.payload.files.gcode, snapshotPayload.files.gcode, "snapshot G-code payload"); assertEqual(loadedSnapshot.payload.files.gcode, snapshotPayload.files.gcode, "snapshot G-code payload");
assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata"); 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", { const customMachineSnapshotPayload = createMachineSessionSnapshotPayload("browser-smoke", {
parameterFilename: "browser-linuxcnc.var", parameterFilename: "browser-linuxcnc.var",
toolTableFilename: "browser-tool.tbl", toolTableFilename: "browser-tool.tbl",

View File

@@ -241,6 +241,9 @@
!api?.stepVirtualHalMotionController || !api?.stepVirtualHalMotionController ||
!api?.getVirtualHalSimulationRuntimeReport || !api?.getVirtualHalSimulationRuntimeReport ||
!api?.getVirtualHalSimulationReplacementReport || !api?.getVirtualHalSimulationReplacementReport ||
!api?.getVirtualHalSourceComplianceReport ||
!api?.getVirtualHalSimConfigSourceCoverageReport ||
!api?.getVirtualHalCommandScriptFixtureReport ||
!api?.getVirtualRealtimeHalRuntimeReport !api?.getVirtualRealtimeHalRuntimeReport
) { ) {
throw new Error("simulation API missing browser virtual HAL controls"); throw new Error("simulation API missing browser virtual HAL controls");
@@ -398,6 +401,14 @@
diagnosticsArtifact.statusHistory.length === 0 || diagnosticsArtifact.statusHistory.length === 0 ||
diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" || diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" ||
diagnosticsArtifact.virtualHalSimulationReplacement?.ready !== true || 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.limitsHome.source !== "browser-virtual-hal" ||
diagnosticsArtifact.preview.renderer !== "threejs" || diagnosticsArtifact.preview.renderer !== "threejs" ||
diagnosticsArtifact.toolTable.state !== "not-loaded" diagnosticsArtifact.toolTable.state !== "not-loaded"
@@ -712,6 +723,16 @@
) { ) {
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`); 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({ const motionControllerStep = api.stepVirtualHalMotionController({
target: { x: 2.25 }, target: { x: 2.25 },
maxVelocity: 10, maxVelocity: 10,
@@ -732,6 +753,15 @@
virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true || virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true ||
virtualHalReplacement.replacements.halcmd.ready !== true || virtualHalReplacement.replacements.halcmd.ready !== true ||
virtualHalReplacement.replacements["motion-controller"].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.getVirtualRealtimeHalRuntimeReport().ready !== true ||
api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true
) { ) {

View File

@@ -27,6 +27,7 @@ assert.equal(validation.gateExecutionManifestReady, true);
assert.equal(validation.gateExecutionSummaryReady, true); assert.equal(validation.gateExecutionSummaryReady, true);
assert.equal(validation.gateResultMatrixReady, true); assert.equal(validation.gateResultMatrixReady, true);
assert.equal(validation.gateActionPlanReady, true); assert.equal(validation.gateActionPlanReady, true);
assert.equal(validation.virtualHalSimConfigSourceCoverageReady, true);
assert.equal(validation.axisScreenshotArtifactSummaryReady, true); assert.equal(validation.axisScreenshotArtifactSummaryReady, true);
assert.ok(validation.axisScreenshotArtifactCount === 0 || validation.axisScreenshotArtifactCount === 4); assert.ok(validation.axisScreenshotArtifactCount === 0 || validation.axisScreenshotArtifactCount === 4);
assert.deepEqual(validation.expectedGateIds, [ 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.apiName, "project-release-gate-action-plan");
assert.equal(artifact.gateActionPlan.pendingCount, 0); assert.equal(artifact.gateActionPlan.pendingCount, 0);
assert.equal(artifact.gateActionPlan.nextCommand, null); 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"); assert.equal(artifact.axisScreenshotArtifactSummary.apiName, "project-release-axis-screenshot-artifact-summary");
if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) { if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) {
assert.equal(artifact.axisScreenshotArtifactSummary.artifactCount, 4); assert.equal(artifact.axisScreenshotArtifactSummary.artifactCount, 4);

View File

@@ -2,11 +2,15 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile
import { dirname, resolve } from "node:path"; import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url"; 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 __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../.."); const root = resolve(__dirname, "../..");
const outputPath = process.argv[2] ?? resolve(root, "build/project-release-readiness.json"); 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 const axisScreenshotArtifactDir = process.env.AXIS_SCREENSHOT_ARTIFACT_DIR
? resolve(process.env.AXIS_SCREENSHOT_ARTIFACT_DIR) ? resolve(process.env.AXIS_SCREENSHOT_ARTIFACT_DIR)
: null; : null;
@@ -60,6 +64,9 @@ const report = createProjectReleaseReadinessReport({
"host_wasm_opfs_browser_smokes=ok", "host_wasm_opfs_browser_smokes=ok",
"project_release_gate=ok", "project_release_gate=ok",
], ],
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport({
manifestText: sourceManifestText,
}),
axisScreenshotArtifacts: loadAxisScreenshotArtifacts(axisScreenshotArtifactDir), axisScreenshotArtifacts: loadAxisScreenshotArtifacts(axisScreenshotArtifactDir),
}); });

View File

@@ -18,12 +18,18 @@ import {
import { import {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createSessionSnapshot, createSessionSnapshot,
createVirtualHalSessionPayload,
loadMachineSessionSnapshot, loadMachineSessionSnapshot,
loadSessionSnapshot, loadSessionSnapshot,
restoreVirtualHalStateFromSessionSnapshot,
saveMachineSessionSnapshot, saveMachineSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
validateSessionSnapshot, validateSessionSnapshot,
} from "../../../runtime/opfs/snapshot-store.js"; } from "../../../runtime/opfs/snapshot-store.js";
import {
createVirtualHalState,
executeVirtualHalCommand,
} from "../../../runtime/sdk/src/linuxcnc-hal.js";
import { import {
gcodeFilenameFromProgramPath, gcodeFilenameFromProgramPath,
loadGcodeProgram, loadGcodeProgram,
@@ -247,6 +253,25 @@ assert.deepEqual(snapshotPayload, {
gcode: gcodeProgramPath("fixture.ngc"), 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( assert.deepEqual(
createMachineSessionSnapshotPayload("custom-machine", { createMachineSessionSnapshotPayload("custom-machine", {
parameterFilename: "custom.var", parameterFilename: "custom.var",
@@ -280,6 +305,13 @@ assert.deepEqual(snapshot, {
payload: snapshotPayload, payload: snapshotPayload,
}); });
assert.equal(validateSessionSnapshot(snapshot, "session-1"), snapshot); 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( assert.throws(
() => validateSessionSnapshot({ ...snapshot, format: "other-format" }, "session-1"), () => validateSessionSnapshot({ ...snapshot, format: "other-format" }, "session-1"),
/Unsupported session snapshot format/, /Unsupported session snapshot format/,
@@ -321,6 +353,20 @@ assertSessionSnapshotStoragePath(
sessionSnapshotPath("session-1"), sessionSnapshotPath("session-1"),
); );
assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot); 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, { const customSnapshot = await saveSessionSnapshot("session-1", snapshotPayload, {
storage, storage,
filename: "custom-snapshot.json", filename: "custom-snapshot.json",

View File

@@ -34,12 +34,17 @@ assert.equal(jsonWorkflow.report.apiName, "project-batch-acceptance-report");
assert.equal(jsonWorkflow.report.reportVersion, 1); assert.equal(jsonWorkflow.report.reportVersion, 1);
assert.equal(jsonWorkflow.report.ready, true); assert.equal(jsonWorkflow.report.ready, true);
assert.equal(jsonWorkflow.report.capabilityMatrix.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.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.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete");
assert.equal(jsonWorkflow.report.actionPlan.nextCommand, null); assert.equal(jsonWorkflow.report.actionPlan.nextCommand, null);
assert.equal(jsonWorkflow.report.checklist.passedCount, 3); assert.equal(jsonWorkflow.report.checklist.passedCount, 4);
assert.equal(jsonWorkflow.report.rows.find(({ id }) => id === "checklist")?.value, "3/3 passed"); 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.apiName, "project-batch-acceptance-report-validation-summary-view-model");
assert.equal(validationSummary.statusLine, "Ready: Batch acceptance report evidence complete"); assert.equal(validationSummary.statusLine, "Ready: Batch acceptance report evidence complete");
assert.equal(validationSummary.rows.find(({ id }) => id === "validation")?.value, "ready"); assert.equal(validationSummary.rows.find(({ id }) => id === "validation")?.value, "ready");

View File

@@ -121,6 +121,7 @@ assert.deepEqual(
["batch", "batch-acceptance-workflow"], ["batch", "batch-acceptance-workflow"],
["capabilities", "api, gate"], ["capabilities", "api, gate"],
["project-batch-acceptance", "passed"], ["project-batch-acceptance", "passed"],
["virtual-hal-sim-config-source-coverage", "not required"],
["missing", "none"], ["missing", "none"],
], ],
); );
@@ -148,13 +149,14 @@ assert.equal(readyChecklist.checklistVersion, 1);
assert.equal(readyChecklist.phase, "ready"); assert.equal(readyChecklist.phase, "ready");
assert.equal(readyChecklist.ready, true); assert.equal(readyChecklist.ready, true);
assert.equal(readyChecklist.batchId, "batch-acceptance-workflow"); assert.equal(readyChecklist.batchId, "batch-acceptance-workflow");
assert.equal(readyChecklist.passedCount, 3); assert.equal(readyChecklist.passedCount, 4);
assert.equal(readyChecklist.blockedCount, 0); assert.equal(readyChecklist.blockedCount, 0);
assert.deepEqual( assert.deepEqual(
readyChecklist.rows.map(({ id, value }) => [id, value]), readyChecklist.rows.map(({ id, value }) => [id, value]),
[ [
["accepted-capability", "pass: api, gate"], ["accepted-capability", "pass: api, gate"],
["batch-acceptance-gate", "pass: passed"], ["batch-acceptance-gate", "pass: passed"],
["virtual-hal-sim-config-source-coverage", "pass: not required"],
["next-action", "pass: none"], ["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.workflow.apiName, "project-batch-acceptance-workflow");
assert.equal(readyReport.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete"); assert.equal(readyReport.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete");
assert.equal(readyReport.actionPlan.nextCommand, null); assert.equal(readyReport.actionPlan.nextCommand, null);
assert.equal(readyReport.checklist.passedCount, 3); assert.equal(readyReport.checklist.passedCount, 4);
assert.deepEqual( assert.deepEqual(
readyReport.rows.map(({ id, value }) => [id, value]), readyReport.rows.map(({ id, value }) => [id, value]),
[ [
["summary", "Ready: Batch acceptance evidence complete"], ["summary", "Ready: Batch acceptance evidence complete"],
["capabilities", "api, gate"], ["capabilities", "api, gate"],
["checklist", "3/3 passed"], ["checklist", "4/4 passed"],
["next-command", "none"], ["next-command", "none"],
], ],
); );
@@ -205,6 +207,7 @@ assert.deepEqual(
["capability-matrix", "ready"], ["capability-matrix", "ready"],
["workflow", "ready"], ["workflow", "ready"],
["checklist", "ready"], ["checklist", "ready"],
["virtual-hal-sim-config-source-coverage", "not required"],
["missing", "none"], ["missing", "none"],
], ],
); );
@@ -348,13 +351,14 @@ const blockedChecklist = createProjectBatchAcceptanceChecklist({
}); });
assert.equal(blockedChecklist.phase, "blocked"); assert.equal(blockedChecklist.phase, "blocked");
assert.equal(blockedChecklist.ready, false); assert.equal(blockedChecklist.ready, false);
assert.equal(blockedChecklist.passedCount, 0); assert.equal(blockedChecklist.passedCount, 1);
assert.equal(blockedChecklist.blockedCount, 3); assert.equal(blockedChecklist.blockedCount, 3);
assert.deepEqual( assert.deepEqual(
blockedChecklist.items.map(({ id, status }) => [id, status]), blockedChecklist.items.map(({ id, status }) => [id, status]),
[ [
["accepted-capability", "blocked"], ["accepted-capability", "blocked"],
["batch-acceptance-gate", "blocked"], ["batch-acceptance-gate", "blocked"],
["virtual-hal-sim-config-source-coverage", "pass"],
["next-action", "blocked"], ["next-action", "blocked"],
], ],
); );

View File

@@ -1,23 +1,50 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { import {
createProjectReleaseBrowserDiagnosticsArtifactValidation,
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan, createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel, createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessReport, createProjectReleaseReadinessReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalCommandScriptFixtureReport,
createVirtualHalSimulationReplacementReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState,
loadProjectReleaseReadinessArtifactUrlWorkflow, loadProjectReleaseReadinessArtifactUrlWorkflow,
} from "../../../runtime/sdk/src/index.js"; } from "../../../runtime/sdk/src/index.js";
const manifest = createProjectReleaseGateManifest(); 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({ const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
})); }));
let fetchedUrl = null; let fetchedUrl = null;
const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json", artifactUrl: "/project-release-readiness.json",
diagnosticsUrl: "/real-browser-simulation-diagnostics.json",
fetchRef: async (url) => { fetchRef: async (url) => {
fetchedUrl = url; fetchedUrl = url;
if (url === "/real-browser-simulation-diagnostics.json") {
return {
ok: true,
status: 200,
text: async () => JSON.stringify(browserDiagnosticsArtifact),
};
}
return { return {
ok: true, ok: true,
status: 200, status: 200,
@@ -28,7 +55,7 @@ const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
const readySummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow); const readySummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow);
const readyActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(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.apiName, "project-release-readiness-artifact-url-workflow");
assert.equal(readyWorkflow.workflowVersion, 1); assert.equal(readyWorkflow.workflowVersion, 1);
assert.equal(readyWorkflow.ready, true); assert.equal(readyWorkflow.ready, true);
@@ -37,6 +64,9 @@ assert.equal(readyWorkflow.httpStatus, 200);
assert.equal(readyWorkflow.fetchError, null); assert.equal(readyWorkflow.fetchError, null);
assert.equal(readyWorkflow.jsonWorkflow.ready, true); assert.equal(readyWorkflow.jsonWorkflow.ready, true);
assert.equal(readyWorkflow.validation.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(readyWorkflow.actionPlan.ready, true);
assert.equal(readySummary.apiName, "project-release-readiness-artifact-url-workflow-summary-view-model"); assert.equal(readySummary.apiName, "project-release-readiness-artifact-url-workflow-summary-view-model");
assert.equal(readySummary.statusLine, "Ready: Artifact URL workflow complete"); assert.equal(readySummary.statusLine, "Ready: Artifact URL workflow complete");
@@ -47,6 +77,7 @@ assert.deepEqual(
["fetch", "ready (200)"], ["fetch", "ready (200)"],
["json-workflow", "ready"], ["json-workflow", "ready"],
["artifact-validation", "ready"], ["artifact-validation", "ready"],
["browser-diagnostics", "ready"],
["next-command", "none"], ["next-command", "none"],
["missing", "none"], ["missing", "none"],
], ],

View File

@@ -1,6 +1,7 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { import {
createVirtualHalSimConfigSourceCoverageReport,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel, createProjectReleaseGateExecutionSummaryViewModel,
@@ -17,6 +18,7 @@ import {
} from "../../../runtime/sdk/src/index.js"; } from "../../../runtime/sdk/src/index.js";
const manifest = createProjectReleaseGateManifest(); const manifest = createProjectReleaseGateManifest();
const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport();
assert.equal(manifest.apiName, "project-release-gate-manifest"); assert.equal(manifest.apiName, "project-release-gate-manifest");
assert.equal(manifest.manifestVersion, 1); assert.equal(manifest.manifestVersion, 1);
@@ -201,6 +203,7 @@ assert.match(partialActionPlan.shellScript, /wasm-port\/tests\/host\/verify_proj
const report = createProjectReleaseReadinessReport({ const report = createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
}); });
assert.deepEqual(report.gateManifest, manifest); assert.deepEqual(report.gateManifest, manifest);
assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
@@ -243,6 +246,11 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
label: "Sim config inventory", label: "Sim config inventory",
value: "unexpected_fail=0", value: "unexpected_fail=0",
}, },
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: "ready",
},
{ {
id: "blocked-runtime-families", id: "blocked-runtime-families",
label: "Blocked runtime families", label: "Blocked runtime families",
@@ -262,11 +270,12 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
}); });
assert.equal( assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine, createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine,
"Waiting: project-release-gate", "Waiting: project-release-gate, virtual-hal-sim-config-source-coverage",
); );
assert.equal( assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({ createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
promotedRuntimeFamilies: ["L4-TOOL-DB"], promotedRuntimeFamilies: ["L4-TOOL-DB"],
})).rows.find(({ id }) => id === "blocked-runtime-families")?.value, })).rows.find(({ id }) => id === "blocked-runtime-families")?.value,
"promoted: L4-TOOL-DB", "promoted: L4-TOOL-DB",
@@ -308,6 +317,7 @@ assert.deepEqual(
["gate-execution-summary", "ready"], ["gate-execution-summary", "ready"],
["gate-result-matrix", "ready"], ["gate-result-matrix", "ready"],
["gate-action-plan", "ready"], ["gate-action-plan", "ready"],
["virtual-hal-sim-config-source-coverage", "ready"],
["missing", "none"], ["missing", "none"],
], ],
); );
@@ -376,6 +386,7 @@ assert.deepEqual(
[ [
["fetch", "ready"], ["fetch", "ready"],
["json-workflow", "ready"], ["json-workflow", "ready"],
["diagnostics-workflow", "not requested"],
["next-command", "none"], ["next-command", "none"],
], ],
); );
@@ -393,6 +404,7 @@ assert.deepEqual(
["fetch", "ready (200)"], ["fetch", "ready (200)"],
["json-workflow", "ready"], ["json-workflow", "ready"],
["artifact-validation", "ready"], ["artifact-validation", "ready"],
["browser-diagnostics", "not requested"],
["next-command", "none"], ["next-command", "none"],
["missing", "none"], ["missing", "none"],
], ],

View File

@@ -32,8 +32,11 @@ import {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES, VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES, VIRTUAL_HAL_SOURCE_FILES,
@@ -48,6 +51,7 @@ import {
createLinuxCncVirtualHalRuntime, createLinuxCncVirtualHalRuntime,
createVirtualHalIntegrityReport, createVirtualHalIntegrityReport,
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createVirtualHalSessionPayload,
createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState, createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary, createMachineSessionPersistenceSummary,
@@ -63,6 +67,7 @@ import {
createProjectBatchAcceptanceReportValidation, createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel, createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow, createProjectBatchAcceptanceWorkflow,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel, createProjectReleaseGateExecutionSummaryViewModel,
@@ -78,11 +83,14 @@ import {
createProjectReleaseReadinessSummaryViewModel, createProjectReleaseReadinessSummaryViewModel,
createVirtualHalBridgeActionPlan, createVirtualHalBridgeActionPlan,
createVirtualHalBridgeReadiness, createVirtualHalBridgeReadiness,
createVirtualHalCommandScriptFixtureReport,
createVirtualHalPinInventory, createVirtualHalPinInventory,
createVirtualHalPinRegistry, createVirtualHalPinRegistry,
createVirtualHalProjectReport, createVirtualHalProjectReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport, createVirtualHalSimulationReplacementReport,
createVirtualHalState, createVirtualHalState,
createVirtualHalSourceComplianceReport,
createVirtualHalSystemCoverageReport, createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot, createVirtualHalWasmBridgeSnapshot,
executeVirtualHalCommand, executeVirtualHalCommand,
@@ -146,6 +154,7 @@ import {
renderIniPanelShellWorkflowOverviewEmbeddingMountState, renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState, renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
restoreMachineParametersFromOpfs, restoreMachineParametersFromOpfs,
restoreVirtualHalStateFromSessionSnapshot,
saveMachineSessionSnapshot, saveMachineSessionSnapshot,
saveMachineTextFiles, saveMachineTextFiles,
saveSessionSnapshot, saveSessionSnapshot,
@@ -160,6 +169,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../.."); const root = resolve(__dirname, "../../..");
const sdkIndexText = readFileSync(resolve(root, "runtime/sdk/src/index.js"), "utf8"); const sdkIndexText = readFileSync(resolve(root, "runtime/sdk/src/index.js"), "utf8");
const sdkReadmeText = readFileSync(resolve(root, "runtime/sdk/README.md"), "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 trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
const requiredExports = [ const requiredExports = [
@@ -189,6 +199,7 @@ const requiredExports = [
["createProjectBatchAcceptanceReportValidation", createProjectBatchAcceptanceReportValidation], ["createProjectBatchAcceptanceReportValidation", createProjectBatchAcceptanceReportValidation],
["createProjectBatchAcceptanceSummaryViewModel", createProjectBatchAcceptanceSummaryViewModel], ["createProjectBatchAcceptanceSummaryViewModel", createProjectBatchAcceptanceSummaryViewModel],
["createProjectBatchAcceptanceWorkflow", createProjectBatchAcceptanceWorkflow], ["createProjectBatchAcceptanceWorkflow", createProjectBatchAcceptanceWorkflow],
["createProjectReleaseBrowserDiagnosticsArtifactValidation", createProjectReleaseBrowserDiagnosticsArtifactValidation],
["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan], ["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan],
["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest], ["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest],
["createProjectReleaseGateExecutionSummaryViewModel", createProjectReleaseGateExecutionSummaryViewModel], ["createProjectReleaseGateExecutionSummaryViewModel", createProjectReleaseGateExecutionSummaryViewModel],
@@ -220,12 +231,15 @@ const requiredExports = [
["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime], ["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime],
["createVirtualHalBridgeActionPlan", createVirtualHalBridgeActionPlan], ["createVirtualHalBridgeActionPlan", createVirtualHalBridgeActionPlan],
["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness], ["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness],
["createVirtualHalCommandScriptFixtureReport", createVirtualHalCommandScriptFixtureReport],
["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport], ["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport],
["createVirtualHalPinInventory", createVirtualHalPinInventory], ["createVirtualHalPinInventory", createVirtualHalPinInventory],
["createVirtualHalPinRegistry", createVirtualHalPinRegistry], ["createVirtualHalPinRegistry", createVirtualHalPinRegistry],
["createVirtualHalProjectReport", createVirtualHalProjectReport], ["createVirtualHalProjectReport", createVirtualHalProjectReport],
["createVirtualHalSimConfigSourceCoverageReport", createVirtualHalSimConfigSourceCoverageReport],
["createVirtualHalSimulationReplacementReport", createVirtualHalSimulationReplacementReport], ["createVirtualHalSimulationReplacementReport", createVirtualHalSimulationReplacementReport],
["createVirtualHalSimulationRuntimeReport", createVirtualHalSimulationRuntimeReport], ["createVirtualHalSimulationRuntimeReport", createVirtualHalSimulationRuntimeReport],
["createVirtualHalSourceComplianceReport", createVirtualHalSourceComplianceReport],
["createVirtualHalState", createVirtualHalState], ["createVirtualHalState", createVirtualHalState],
["createVirtualHalSystemCoverageReport", createVirtualHalSystemCoverageReport], ["createVirtualHalSystemCoverageReport", createVirtualHalSystemCoverageReport],
["createVirtualHalWasmBridgeSnapshot", createVirtualHalWasmBridgeSnapshot], ["createVirtualHalWasmBridgeSnapshot", createVirtualHalWasmBridgeSnapshot],
@@ -359,10 +373,12 @@ const requiredExports = [
["defaultMachinePaths", defaultMachinePaths], ["defaultMachinePaths", defaultMachinePaths],
["normalizeOpfsPath", normalizeOpfsPath], ["normalizeOpfsPath", normalizeOpfsPath],
["createMachineSessionSnapshotPayload", createMachineSessionSnapshotPayload], ["createMachineSessionSnapshotPayload", createMachineSessionSnapshotPayload],
["createVirtualHalSessionPayload", createVirtualHalSessionPayload],
["createSessionSnapshot", createSessionSnapshot], ["createSessionSnapshot", createSessionSnapshot],
["saveSessionSnapshot", saveSessionSnapshot], ["saveSessionSnapshot", saveSessionSnapshot],
["saveMachineSessionSnapshot", saveMachineSessionSnapshot], ["saveMachineSessionSnapshot", saveMachineSessionSnapshot],
["loadMachineSessionSnapshot", loadMachineSessionSnapshot], ["loadMachineSessionSnapshot", loadMachineSessionSnapshot],
["restoreVirtualHalStateFromSessionSnapshot", restoreVirtualHalStateFromSessionSnapshot],
["validateSessionSnapshot", validateSessionSnapshot], ["validateSessionSnapshot", validateSessionSnapshot],
["machineFilePaths", machineFilePaths], ["machineFilePaths", machineFilePaths],
["saveMachineTextFiles", saveMachineTextFiles], ["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("linuxcnc-realtime-hal"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("halcmd"), 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_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_COVERAGE_STATES.runtimeBoundary, "runtime-boundary");
assert.equal(VIRTUAL_HAL_SOURCE_FILES.halui, "linuxcnc/src/emc/usr_intf/halui.cc"); 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 === "iocontrol"), true);
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "motion-core"), 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(sdkIndexText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/);
assert.match(sdkReadmeText, /\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(sdkIndexText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/);
assert.match(sdkReadmeText, /\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(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\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(sdkReadmeText, /\bexecuteVirtualHalCommand\b/);
assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/); assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/); assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalCommandScriptFixtureReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalCommandScriptFixtureReport\b/);
assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/);
assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/); assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/);
assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/);
@@ -494,6 +527,10 @@ assert.match(sdkIndexText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationRuntimeReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSimulationReplacementReport\b/); assert.match(sdkIndexText, /\bcreateVirtualHalSimulationReplacementReport\b/);
assert.match(sdkReadmeText, /\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(sdkIndexText, /\bcreateVirtualHalSystemCoverageReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSystemCoverageReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalSystemCoverageReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalPinRegistry\b/); assert.match(sdkIndexText, /\bcreateVirtualHalPinRegistry\b/);
@@ -596,6 +633,17 @@ const directHalcmdAliasResult = executeVirtualHalcmd(virtualHalRuntime.getState(
assert.equal(directHalcmdAliasResult.ok, true); assert.equal(directHalcmdAliasResult.ok, true);
assert.equal(directHalcmdAliasResult.apiName, "linuxcnc-wasm-virtual-halcmd-result"); assert.equal(directHalcmdAliasResult.apiName, "linuxcnc-wasm-virtual-halcmd-result");
assert.match(directHalcmdAliasResult.output, /halui\.machine\.is-on/); 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(), { const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), {
target: { x: 3 }, target: { x: 3 },
maxVelocity: 10, 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.halcmd.ready, true);
assert.equal(replacementReport.replacements["motion-controller"].ready, true); assert.equal(replacementReport.replacements["motion-controller"].ready, true);
assert.equal(replacementReport.hardRealtime, false); 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); 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 }); const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS });
assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true); assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true);
assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0); assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
@@ -705,14 +819,17 @@ assert.equal(
batchId: "sdk-surface-batch", batchId: "sdk-surface-batch",
capabilities: batchAcceptanceWorkflow.capabilities, capabilities: batchAcceptanceWorkflow.capabilities,
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
}).rows.find(({ id }) => id === "checklist")?.value, }).rows.find(({ id }) => id === "checklist")?.value,
"3/3 passed", "4/4 passed",
); );
const batchAcceptanceReport = createProjectBatchAcceptanceReport({ const batchAcceptanceReport = createProjectBatchAcceptanceReport({
batchId: "sdk-surface-batch", batchId: "sdk-surface-batch",
capabilities: batchAcceptanceWorkflow.capabilities, capabilities: batchAcceptanceWorkflow.capabilities,
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
}); });
assert.equal(batchAcceptanceReport.virtualHalSimConfigSourceCoverage.complete, true);
assert.equal(createProjectBatchAcceptanceReportValidation(batchAcceptanceReport).ready, true); assert.equal(createProjectBatchAcceptanceReportValidation(batchAcceptanceReport).ready, true);
assert.equal( assert.equal(
createProjectBatchAcceptanceReportValidationSummaryViewModel( createProjectBatchAcceptanceReportValidationSummaryViewModel(
@@ -766,7 +883,7 @@ assert.equal(
); );
assert.equal(releaseReadinessWaiting.apiName, "project-release-readiness-report"); assert.equal(releaseReadinessWaiting.apiName, "project-release-readiness-report");
assert.equal(releaseReadinessWaiting.ready, false); 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.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0);
assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []); assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []);
assert.equal(releaseReadinessWaiting.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(releaseReadinessWaiting.gateActionPlan.apiName, "project-release-gate-action-plan");
@@ -778,9 +895,11 @@ assert.equal(
); );
const releaseReadinessReady = createProjectReleaseReadinessReport({ const releaseReadinessReady = createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"], observedOutputs: ["project_release_gate=ok"],
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
}); });
assert.equal(releaseReadinessReady.ready, true); assert.equal(releaseReadinessReady.ready, true);
assert.equal(releaseReadinessReady.releaseGate.passed, true); assert.equal(releaseReadinessReady.releaseGate.passed, true);
assert.equal(releaseReadinessReady.virtualHalSimConfigSourceCoverageReady, true);
assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest()); assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest());
assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount); assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount);
assert.equal(createProjectReleaseGateActionPlan({ assert.equal(createProjectReleaseGateActionPlan({
@@ -796,6 +915,7 @@ assert.equal(
); );
const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({ const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
axisScreenshotArtifacts: ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"].map((viewportName) => ({ axisScreenshotArtifacts: ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"].map((viewportName) => ({
apiName: "real-browser-simulation-axis-screenshot-artifact", apiName: "real-browser-simulation-axis-screenshot-artifact",
artifactVersion: 1, artifactVersion: 1,
@@ -829,6 +949,7 @@ assert.equal(
); );
const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.stringify(createProjectReleaseReadinessReport({ const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
}))); })));
assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report"); assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report");
const releaseReadinessArtifactValidation = createProjectReleaseReadinessArtifactValidation(releaseReadinessArtifact); const releaseReadinessArtifactValidation = createProjectReleaseReadinessArtifactValidation(releaseReadinessArtifact);
@@ -848,6 +969,7 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
gateExecutionSummaryReady: true, gateExecutionSummaryReady: true,
gateResultMatrixReady: true, gateResultMatrixReady: true,
gateActionPlanReady: true, gateActionPlanReady: true,
virtualHalSimConfigSourceCoverageReady: true,
axisScreenshotArtifactSummaryReady: true, axisScreenshotArtifactSummaryReady: true,
axisScreenshotArtifactCount: 0, axisScreenshotArtifactCount: 0,
blockedRuntimeFamilies: [ blockedRuntimeFamilies: [
@@ -891,6 +1013,11 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
label: "Gate action plan", label: "Gate action plan",
value: "ready", value: "ready",
}, },
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: "ready",
},
{ {
id: "axis-screenshot-artifacts", id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts", label: "AXIS screenshot artifacts",
@@ -987,6 +1114,7 @@ assert.deepEqual(
"simConfigInventory.passed", "simConfigInventory.passed",
"simConfigInventory.skipped", "simConfigInventory.skipped",
"simConfigInventory.unexpectedFail", "simConfigInventory.unexpectedFail",
"virtualHalSimConfigSourceCoverage",
"blockedRuntimeFamilies", "blockedRuntimeFamilies",
"gateManifest", "gateManifest",
"gateExecutionManifest", "gateExecutionManifest",

View File

@@ -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 { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url"; 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 __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../.."); const root = resolve(__dirname, "../../..");
const outputPath = process.argv[2] ?? resolve(root, "build/project-batch-acceptance.json"); 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({ const report = createProjectBatchAcceptanceReport({
batchId: "project-batch-acceptance-artifact", batchId: "project-batch-acceptance-artifact",
@@ -18,6 +22,13 @@ const report = createProjectBatchAcceptanceReport({
evidence: "project_batch_acceptance_artifact_node_smoke=ok", evidence: "project_batch_acceptance_artifact_node_smoke=ok",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh", 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", id: "project-batch-acceptance-workflow-gate",
type: "gate", type: "gate",
@@ -27,6 +38,9 @@ const report = createProjectBatchAcceptanceReport({
}, },
], ],
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"], observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport({
manifestText: sourceManifestText,
}),
}); });
mkdirSync(dirname(outputPath), { recursive: true }); mkdirSync(dirname(outputPath), { recursive: true });

View File

@@ -4,6 +4,9 @@ import {
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseReadinessReport, createProjectReleaseReadinessReport,
} from "../../../runtime/sdk/src/project-release-readiness.js"; } from "../../../runtime/sdk/src/project-release-readiness.js";
import {
createVirtualHalSimConfigSourceCoverageReport,
} from "../../../runtime/sdk/src/linuxcnc-hal.js";
import { import {
INI_PANEL_ENTRIES, INI_PANEL_ENTRIES,
@@ -1350,6 +1353,7 @@ assert.equal(workflowDomMountResult.renderResult.dataset.handoffScope, "workflow
assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), shellViewModel.shellWorkflowOverviewContract); assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), shellViewModel.shellWorkflowOverviewContract);
const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
})); }));
const releaseArtifactValidation = validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson( const releaseArtifactValidation = validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson(
releaseReadinessArtifactJson, releaseReadinessArtifactJson,
@@ -1655,7 +1659,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
rowsNode: releaseArtifactRowsNode, rowsNode: releaseArtifactRowsNode,
}); });
assert.equal(releaseArtifactRenderResult.rendered, true); 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.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
assert.equal( assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
@@ -1673,8 +1677,12 @@ assert.equal(
releaseArtifactRenderResult.rowIds.includes("axis-screenshot-artifacts"), releaseArtifactRenderResult.rowIds.includes("axis-screenshot-artifacts"),
true, true,
); );
assert.equal(
releaseArtifactRenderResult.rowIds.includes("virtual-hal-sim-config-source-coverage"),
true,
);
assert.equal(releaseArtifactMountResult.ready, true); assert.equal(releaseArtifactMountResult.ready, true);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 9); assert.equal(releaseArtifactMountResult.renderResult.rowCount, 10);
assert.deepEqual( assert.deepEqual(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing, validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
["artifact-json"], ["artifact-json"],
@@ -1715,7 +1723,7 @@ assert.equal(
); );
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons"); assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true); assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 9); assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 10);
const releaseArtifactUrlWorkflowSummary = const releaseArtifactUrlWorkflowSummary =
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel( createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseArtifactUrlWorkflow, releaseArtifactUrlWorkflow,