按text22收束promotion证据

结论:完成 evidence-ready / inventory-ready 双层 promotion candidate 管理,release/SDK gate 已纳入 virtual HAL promotion summary,baseline 保持 28/28/131/0,Python remap、tool DB、external user-M hard block 继续 locked。
This commit is contained in:
2026-06-18 17:20:29 +08:00
parent 014081f1f9
commit 1c94ea4527
29 changed files with 8500 additions and 121 deletions

1578
text18.txt

File diff suppressed because it is too large Load Diff

265
text19.txt Normal file
View File

@@ -0,0 +1,265 @@
项目接续文件:利用 virtual HAL 成果推进 sim config baseline promotion
生成时间2026-06-18 CST
本文件接替 `text18.txt`。后续继续推进时优先参考 `text19.txt`;除非明确要求审计
旧记录,不再回到 `text1` 到 `text18` 扩展历史实现。
一、方向纠偏
上一版表述容易被误读为“转向非 virtual HAL不再使用 virtual HAL 成果”。正确方向是:
```text
virtual HAL runtime capability 已收尾;后续不继续扩展 virtual HAL 新功能,
但要利用已完成的 virtual HAL source-derived evidence、browser diagnostics、
release gate 和 motion matrix继续推进 sim config coverage / baseline promotion。
```
执行含义:
- 不新增 virtual HAL runtime capability
- 不新增 unrelated virtual HAL UI 面板、filter、drilldown 类功能;
- 不把 virtual HAL 当成 JS CNC 语义替代品;
- 允许并且应该复用 virtual HAL 已有成果作为 promotion evidence
- source compliance
- sim-config source coverage
- promotion candidate report
- macro/load fixture report
- command script fixtures
- manifest-backed motion controller matrix
- browser diagnostics artifact
- release readiness / URL workflow / batch acceptance evidence。
二、当前 baseline
当前 Node inventory baseline 仍为:
```text
executed=28
passed=28
skipped=131
unexpected_fail=0
```
当前已经完成的 virtual HAL promotion evidence 不等同于 inventory skip count 下降。它已经把
一批 Node `INV` / representative rows 推进到了 browser diagnostics / release evidence 层,
但如果要让 `executed/passed/skipped` 变化,仍必须修改并运行真实 inventory execution path。
三、已具备的 promotion evidence
当前 SDK / browser / release gate 已覆盖 Tier 1 promotion candidate report
- `qtdragon/qtdragon_multi_joint/on_abort.ngc`
- `qtdragon/qtdragon_xyz/on_abort.ngc`
- `qtdragon/qtdragon_xyz45/on_abort.ngc`
- `qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc`
- `qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc`
- `qtvcp_screens/qtdragon/on_abort.ngc`
- `axis/vismach/puma/puma_seam_weld.ngc`
- `axis/rose_engine/rcone_demo.ngc`
这些 row 的当前性质:
- LinuxCNC source/config evidence 完整;
- current Node inventory status 为 `PASS`
- blocked kind 为 `-`
- target browser evidence 为 `explicit-browser-diagnostics`
- release diagnostics validation 已要求 promotion candidate summary
- inventory baseline 保持不变。
当前 macro/load 非主程序 evidence 已覆盖:
- `axis/rose_engine/rcone.ngc`
- `axis/external_offsets/queuebuster.ngc`
这些只能作为 non-main fixture diagnostics evidence不作为 standalone main program promotion。
四、不可突破边界
以下 family 仍不能靠 virtual HAL、JS glue 或 browser UI 伪装解锁:
- Linux kernel hard-realtime ABI
- 外部硬件驱动 ABI
- native HAL module ABI
- Python UI process emulation
- Python remap runtime
- tool database process protocol
- external user-M process execution
- upstream-invalid demo code。
因此以下 blocked kind 仍保持 locked
- `L4-PYTHON-REMAP`
- `L4-TOOL-DB`
- `L4-USER-M-PROCESS`
- `UPSTREAM-DEMO`
- standalone-main 语境下的 `ASSET-ONLY`
五、下一步主线目标
目标仍是从:
```text
executed=28
passed=28
skipped=131
```
推进到更高完成度。第一阶段建议目标:
```text
executed >= 35
passed == executed
skipped <= 124
unexpected_fail=0
```
但推进方式应是:
1. 先复用 virtual HAL 已建立的 source-derived evidence确认候选 family 的 browser/release
evidence 已 ready
2. 再找出当前 inventory 中还没有执行、但依赖已被 virtual HAL / HALUI / deterministic
UI declarations / machine files / tool tables / remap asset staging 覆盖的 main program
3. 只把 LinuxCNC-backed runtime 能真实执行通过的 row 纳入 inventory
4. 同步更新 docs、release artifact fixture、SDK/host gate baseline 断言。
六、候选选择原则
优先方向:
- Tier 1 已有 promotion evidence 周边 family
- QtDragon on-abort family
- PUMA / vismach remap family
- rose-engine family
- 当前 matrix 中 `blocked=-`、`class=main`、native `PASS`、但尚未进入 browser explicit evidence
或 inventory execution 的 row
- 已有 tool-table fallback、vendored INI/G-code/source evidence、无 Python/process hard block 的 row
- 已有 virtual HAL command/motion matrix 可以解释 HAL/UI/motion 依赖的 row。
禁止方向:
- 不把 Python remap pass 当作 browser/Node full promotion
- 不把 tool DB native pass 当作 WASM process protocol pass
- 不把 external user-M process pass 当作 virtual HAL deterministic M110/M111 边界;
- 不把 macro/load asset 当 standalone main
- 不靠改 docs 声称 skipped 降低。
七、建议执行步骤
### 第一步:复核当前 virtual HAL promotion gate
最小命令:
```text
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
```
确认:
- `VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES` 仍包含 8 个 Tier 1 row
- candidate report `complete === true`
- `webSimulationSatisfied === true`
- `inventoryBaselineUnchanged === true`
- blocked candidate count 为 0
- release gate / URL workflow 仍要求 candidate summary。
### 第二步:生成真实 inventory promotion candidate 清单
不要只列 hard-block skipped main row应输出两个层级
- evidence-ready candidates已有 virtual HAL/browser/release evidence适合继续升 browser/release
覆盖;
- inventory-ready candidates确实可从 skipped 推进到 executed 的 main program。
建议输出:
```text
wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv
```
字段至少包含:
- path
- ini
- current inventory status
- matrix layer4 node/browser status
- class
- native status
- blocked kind
- dependency class
- virtual HAL evidence ready
- inventory promotion allowed
- block reason
- recommended next command。
### 第三步:推进第一批真实 inventory rows
一次只推进 5 到 8 个 row。每个 row 必须说明:
- LinuxCNC source/config/runtime evidence
- 为什么已有 virtual HAL evidence 能覆盖 HAL/UI/motion 依赖;
- 为什么不是 JS/browser 自行补 CNC 语义;
- 为什么不属于 `L4-PYTHON-REMAP`、`L4-TOOL-DB`、`L4-USER-M-PROCESS`、
`UPSTREAM-DEMO` 或 standalone `ASSET-ONLY`
- 执行后 baseline 如何变化。
### 第四步:同步 release packaging
baseline 真变化后,同步更新:
- generated sim-config inventory artifact
- `wasm-port/docs/sim-configs-coverage-matrix.md`
- `wasm-port/docs/sim-config-coverage-promotion-analysis.md`
- release readiness artifact fixture
- SDK / host gate 的 baseline 断言;
- browser diagnostics / URL workflow 中的 baseline 文案。
八、验收标准
一次真实 baseline promotion 完成条件:
1. 新 baseline artifact 真实生成;
2. `executed` 和 `passed` 同步增加;
3. `skipped` 同步减少;
4. `unexpected_fail` 仍为 `0`
5. 新增 rows 不属于 blocked family
6. virtual HAL evidence 是 source-derived support不是 CNC 语义替代;
7. `verify_no_standalone_cnc_semantics.sh` 仍通过;
8. 最小相关 gate 通过;
9. 文档记录变更前/后 baseline、新增 rows、blocked family lock 状态和验证命令。
建议最小验证命令:
```text
git diff --check
wasm-port/tools/verify_no_standalone_cnc_semantics.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
```
如改动涉及 browser/release 页面,再额外运行:
```text
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
```
九、下一步工作内容
下一轮建议直接做:
```text
基于现有 virtual HAL promotion candidate report生成 evidence-ready / inventory-ready
双层 promotion-candidates.tsv然后从 blocked=-、native PASS、class=main 的 row 中筛出
第一批 5-8 个可真实进入 inventory execution 的候选,试运行并更新 baseline。
```
优先候选 family
- QtDragon on-abort family 周边;
- PUMA / vismach remap family
- rose-engine family
- 其他已 vendored、无 Python/process hard block、可由 virtual HAL 解释 HAL/UI/motion 依赖的
main program。

453
text20.txt Normal file
View File

@@ -0,0 +1,453 @@
项目接续文件tool DB 解锁步骤与方法
生成时间2026-06-18 CST
本文件接替 `text19.txt` 中关于 blocked runtime family 的后续推进说明,专门规划
`L4-TOOL-DB` / `axis/db_demo/base.ngc` 的解锁路径。当前目标不是绕过 tool DB
而是在已有 source-derived evidence、transaction contract、native readiness gate 和
promotion lock 框架基础上,把 tool database process protocol 证明补齐,然后再允许
真实 inventory promotion。
重要状态说明:本文件是未来解锁研究计划,不是当前阶段实施范围。当前阶段铁律仍是
“virtual HAL 成果作为 sim config promotion 证据基础”,同时明确不解锁
`L4-PYTHON-REMAP`、`L4-TOOL-DB`、`L4-USER-M-PROCESS` 这些 hard block。
因此在当前交付中,`axis/db_demo/base.ngc` 必须继续保持 `SKIP L4-TOOL-DB`
`promotion_allowed=0``promotion_lock_active=1`。
一、当前已完成条件
当前项目已经具备以下基础,不需要重做:
1. source/config ownership 已明确。
- `axis/db_demo/base.ngc` 当前 blocked kind 为 `L4-TOOL-DB`
- owning INI 为 `axis/db_demo/db_nonran.ini`
- INI 声明:
```text
[EMCIO]
DB_PROGRAM = ./db_nonran.py
```
- LinuxCNC source owner 已记录:
- `configs/sim/axis/db_demo/db_nonran.ini`
- `configs/sim/axis/db_demo/db.py`
- `src/emc/task/taskclass.cc`
- `src/emc/tooldata/tooldata_db.cc`
- `lib/python/tooldb.py`
2. machine-readable artifacts 已有。
当前已有并通过 inventory gate 的 tool DB 相关 artifact
```text
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-boundary-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-protocol-gates.tsv
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-transaction-plan.tsv
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-native-protocol-alignment.tsv
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-native-runtime-readiness.tsv
wasm-port/build/wasm/sim-configs-inventory/tool-db-process-native-runtime-probe-gate.tsv
```
3. transaction contract 已明确。
`tool-db-process-transaction-plan.tsv` 已把 DB protocol 拆成以下 pending proof
- `startup_handshake`:启动后返回 `v2.1`
- `initial_get_all`:发送 `g`,读取直到 `FINI`
- `spindle_load_notify`:发送/验证 `l`
- `tool_offset_notify`:发送/验证 `p`
- `spindle_unload_notify`:发送/验证 `u`。
4. 当前 readiness 状态已明确。
当前 `tool-db-process-native-runtime-readiness.tsv` / probe gate 显示:
```text
python3=1
axis/db_demo/db_nonran.py=1
linuxcnc.so=1
tooldb.py=1
linuxcnc=0
milltask=0
halcmd=0
runtime_ready=0
source_proof_ready=1
gate_status=blocked_missing_host_runtime
proof_status=pending
execution_enabled=0
promotion_allowed=0
```
这说明当前缺的不是 source evidence也不是 Python 文件本身,而是完整 LinuxCNC
task/tooldata DB host runtime。
二、解锁原则
tool DB 解锁必须遵守以下规则:
1. 不允许用 `.tbl` fallback 解锁。
`DB_PROGRAM` 模式明确替代普通 tool table path。用 `.tbl` 让程序跑通会绕过
LinuxCNC tool database process protocol属于错误 promotion。
2. 不允许用 JS/virtual HAL 重新实现 tool DB 语义。
virtual HAL 可以继续作为 HAL/motion/browser diagnostics evidence但不能替代
- `DB_PROGRAM` 子进程启动;
- `tooldb.py` protocol loop
- `v2.1` / `g` / `l` / `u` / `p` protocol
- nonrandom DB state mutation
- flat-file persistence。
3. native proof 必须先于 Node/browser promotion。
只有 native runtime probe 明确通过以后,才允许更新 Node/browser promotion gate。
4. promotion lock 必须手动更新。
即使 native probe 通过,也不能自动把 `axis/db_demo/base.ngc` 从 skipped 改为 executed。
必须明确更新 promotion readiness、blockers、post-native-pass gate 和 lock artifact。
三、第一阶段:准备 host runtime
目标:让 tool DB runtime readiness 从:
```text
runtime_ready=0
missing_requirements=linuxcnc,milltask,halcmd
```
变为:
```text
runtime_ready=1
missing_requirements=-
```
需要准备的命令/模块:
```text
python3
linuxcnc
milltask
halcmd
axis/db_demo/db_nonran.py
linuxcnc.so
tooldb.py
```
当前已有:
```text
python3
axis/db_demo/db_nonran.py
linuxcnc.so
tooldb.py
```
当前缺失:
```text
linuxcnc
milltask
halcmd
```
建议方法:
1. 在具备完整 LinuxCNC userspace runtime 的 host / container 中执行。
2. 确保 `linuxcnc`、`milltask`、`halcmd` 在 `PATH` 上。
3. 确保 LinuxCNC Python 模块路径能被 `db_nonran.py` import
```text
python3 -c "import linuxcnc; import tooldb; print('linuxcnc_tooldb_python_modules=ok')"
```
4. 重新运行 native probes / inventory gate 以刷新 readiness artifacts
```text
wasm-port/tests/native/probe_tool_db_runtime.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
预期中间状态:
- 如果 host runtime 仍缺失:
```text
tool_db_runtime_probe_status=skipped_missing_host_runtime
```
- 如果 host runtime 已满足但未显式启用 probe
```text
tool_db_runtime_probe_status=ready_disabled_by_default
```
四、第二阶段:执行 native DB protocol probe
目标:证明 LinuxCNC-owned DB process protocol 真实可运行。
执行命令:
```text
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
该 probe 必须证明:
1. `db_nonran.py` 按 `DB_PROGRAM` 配置启动;
2. startup handshake 返回 `v2.1`
3. `g` get-all 返回 expected nonrandom startup tools
4. startup state 覆盖 `T10..T19` / `tno+100` pockets
5. `l` load notify 触发 spindle load state
6. `p` tool offset notify 触发 DB update path
7. `u` unload notify 触发 spindle unload state
8. flat-file persistence 能反映 state mutation
9. probe 输出:
```text
tool_db_runtime_probe_status=runtime_protocol_probe_passed
```
失败处理:
- 如果失败在 import `linuxcnc` / `tooldb`:修 Python module path不改项目语义
- 如果失败在 `linuxcnc` / `milltask` / `halcmd`:修 host runtime不改 Node/browser fallback
- 如果失败在 protocol message先对齐 native `db_nonran.py` / `tooldb.py` 行为和
`tool-db-process-transaction-plan.tsv`,不允许用简化协议绕过;
- 如果失败在 persistence修 probe 环境的临时 DB 文件隔离和 cleanup不把 persistence
检查删除。
五、第三阶段:刷新 native/runtime artifacts
native probe pass 后,重新生成并验证 artifacts
```text
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
预期变化方向:
```text
tool-db-process-native-runtime-readiness.tsv:
runtime_ready=1
missing_requirements=-
tool-db-process-native-runtime-probe-gate.tsv:
gate_status=native_probe_passed_waiting_for_node_browser_promotion_proof
proof_status=passed
source_proof_ready=1
```
注意:这个阶段仍不应直接让 `promotion_allowed=1`。native pass 只是打开下一步
Node/browser proof 的条件。
六、第四阶段:更新 promotion readiness / blocker / lock
native pass 后,需要更新以下 gate 的逻辑和 fixtures
```text
runtime-boundary-promotion-readiness.tsv
runtime-boundary-promotion-blockers.tsv
runtime-boundary-post-native-pass-gates.tsv
runtime-boundary-native-evidence-acceptance-gate.tsv
blocked-runtime-promotion-lock.tsv
next-boundary-recommendations.tsv
promotion-candidates.tsv
```
目标状态:
1. `native_evidence_ready=1`
2. `node_inventory_gate_complete=0`,直到 Node inventory row 真实执行;
3. `browser_smoke_gate_complete=0`,直到 browser proof 真实通过;
4. `promotion_lock_active=1`,直到人工明确解锁;
5. blocker 从 `missing_host_runtime` 转为:
```text
pending_node_inventory_promotion_gate
pending_browser_smoke_gate
promotion_lock_active
```
这一步的重点是native proof 已经完成,但还没有完成 Node/browser promotion因此
blocked family 不能直接消失。
七、第五阶段:设计 Node inventory execution path
只有 native DB protocol probe 通过后,才允许考虑让 `axis/db_demo/base.ngc` 进入
Node inventory execution。
可接受方向:
1. 通过 LinuxCNC-owned tooldata DB protocol wrapper 执行;
2. 保留 `DB_PROGRAM = ./db_nonran.py` 的行为边界;
3. Node/WASM 侧只做 file staging、protocol bridge、result capture
4. 所有 tool DB state 来自 LinuxCNC protocol / db.py 行为,而不是 JS 自行解释。
不可接受方向:
1. 把 `axis/sim.tbl` 或任意 `.tbl` 当作 DB fallback
2. 在 JS 中手写 `T10..T19` tool DB state
3. 跳过 `v2.1` / `g` / `l` / `u` / `p` protocol
4. 只因为 native `bin/rs274` 能 parse `base.ngc` 就标记 Node inventory PASS
5. 把 virtual HAL readiness 当作 tool DB process readiness。
如果 Node/WASM 当前无法启动 Python DB process则应保持
```text
node_inventory_gate_complete=0
promotion_allowed=0
```
并把下一步限定为“设计 tool DB protocol bridge”而不是直接改 baseline。
八、第六阶段Browser proof
Browser proof 只能在 Node inventory gate 真实完成后做。
Browser proof 至少需要:
- diagnostics artifact 记录 tool DB protocol proof status
- release URL workflow 显示 `axis/db_demo/base.ngc` 的 DB proof
- 明确 `tool_table_fallback_sufficient=0`
- 明确 `python3_sufficient=0`
- 明确 source owner 是 LinuxCNC DB protocol
- browser 只呈现 proof / workflow / artifact不自行执行 tool DB semantics。
九、第七阶段:真实 inventory baseline promotion
只有满足以下条件后,才允许把 `axis/db_demo/base.ngc` 从 skipped 变为 executed
1. native DB runtime probe passed
2. Node inventory execution path 通过 LinuxCNC-owned DB protocol
3. browser proof / release diagnostics gate 已通过;
4. promotion lock 手动关闭;
5. `promotion-candidates.tsv` 中对应行从:
```text
candidate_kind=inventory-ready
current_status=SKIP
skip_kind=L4-TOOL-DB
promotion_allowed=0
```
变为真实可 promotion 状态,并有明确 proof chain
6. `summary.tsv` 中 `axis/db_demo/base.ngc` 变为:
```text
inventory_status=PASS
skip_or_fail_reason=-
```
预期 baseline 变化:
```text
executed: 28 -> 29
passed: 28 -> 29
skipped: 131 -> 130
unexpected_fail: 0
```
十、需要同步更新的文件/断言
baseline 真实变化后,至少同步:
```text
wasm-port/docs/sim-configs-coverage-matrix.md
wasm-port/docs/sim-config-coverage-promotion-analysis.md
wasm-port/docs/compatibility-validation.md
wasm-port/runtime/sdk/src/linuxcnc-hal.js
wasm-port/runtime/sdk/src/project-release-readiness.js
wasm-port/tests/fixtures/project-release-readiness-ready.json
wasm-port/tests/sdk/node/verify_sdk_surface.mjs
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs
wasm-port/tests/host/verify_project_release_readiness_artifact.mjs
wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs
```
需要同步更新的固定 baseline
```text
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE
project release readiness fixture simConfigInventory
coverage matrix Current Node inventory
compatibility validation current gate text
promotion candidate expected status
skip-summary expected count
```
十一、最小验证命令
host runtime 准备阶段:
```text
wasm-port/tests/native/probe_tool_db_runtime.sh
```
native runtime proof 阶段:
```text
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
artifact / inventory 阶段:
```text
git diff --check
wasm-port/tools/verify_no_standalone_cnc_semantics.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
```
browser / release proof 阶段:
```text
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
wasm-port/tests/host/verify_project_release_readiness_artifact.sh
```
十二、下一步工作内容
当前机器 artifact 显示缺少:
```text
linuxcnc
milltask
halcmd
```
因此下一步不是改 Node inventory baseline而是
1. 在具备完整 LinuxCNC host runtime 的环境中运行:
```text
wasm-port/tests/native/probe_tool_db_runtime.sh
```
2. 如果状态变为 `ready_disabled_by_default`,继续运行:
```text
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
3. 拿到 `runtime_protocol_probe_passed` 后,刷新 inventory artifacts
```text
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
4. 再进入 promotion readiness / lock 更新,而不是直接把
`axis/db_demo/base.ngc` 改成 PASS。
十三、简要结论
tool DB 的解锁关键不是 virtual HAL 能力不足,而是还缺 LinuxCNC task/tooldata DB
process runtime proof。当前 source evidence、contract、readiness artifact、probe gate
都已铺好;真正的第一步是补齐 host runtime 并跑通
`ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh`。

538
text21.txt Normal file
View File

@@ -0,0 +1,538 @@
项目接续文件WASM + OPFS 环境下 tool DB 完全解锁可行性与实施步骤
生成时间2026-06-18 CST
本文件接替 `text20.txt`,进一步回答:在以 WASM 和 OPFS 为技术基础的 Web 技术环境下,
`L4-TOOL-DB` 是否可以完全解锁,以及怎样以可实现、可验证、不过度伪装的方式解锁。
重要状态说明:本文件是 future feasibility / architecture note不是当前阶段执行计划。
当前阶段必须按铁律收束virtual HAL 成果只作为 sim config promotion 证据基础;
`L4-PYTHON-REMAP`、`L4-TOOL-DB`、`L4-USER-M-PROCESS` 仍保持 hard block不做解锁。
本文件中的 WASM + OPFS tool DB 解锁路线只能作为后续专门 milestone 使用,不能用于
当前 baseline promotion。
一、结论
可以实现“项目定义下的完全解锁”,但不能通过浏览器原生 spawn LinuxCNC native process
的方式实现。
更准确地说:
```text
如果“完全解锁”要求浏览器直接启动 native linuxcnc/milltask/halcmd/DB_PROGRAM 进程,
答案是不可以。
如果“完全解锁”定义为:在 WASM + OPFS 中保留 LinuxCNC-owned tool DB protocol、
执行配置声明的 DB_PROGRAM 行为、持久化 DB state、并通过 native/WASM/browser/release
proof chain 证明行为等价,答案是可以。
```
因此可行路线不是 `.tbl` fallback也不是 JS 手写 tool semantics而是
```text
WASM 内运行 LinuxCNC tooldata DB protocol owner + WASM/Worker 隔离的 DB_PROGRAM runtime +
OPFS-backed flat-file persistence + source-derived proof gates。
```
二、必须保持的语义边界
`axis/db_demo/base.ngc` 的 LinuxCNC-owned 行为来自:
- `db_nonran.ini` 中的 `DB_PROGRAM = ./db_nonran.py`
- LinuxCNC task/tooldata DB mode
- `tooldata_db.cc` 的 DB process protocol
- `tooldb.py` 的 protocol loop
- `db.py` / `db_nonran.py` 的 callback/state behavior
- flat-file database persistence。
WASM + OPFS 解锁时必须保持:
- `v2.1` startup handshake
- `g` get-all until `FINI`
- `l` spindle load notify
- `u` spindle unload notify
- `p` tool offset notify
- nonrandom `T10..T19` startup state
- DB file persistence
- `DB_PROGRAM` 是配置声明的 runtime dependency
- `.tbl` fallback 仍然标记为 insufficient。
三、浏览器环境限制
浏览器/WASM 环境天然缺少:
- native process spawn
- LinuxCNC host daemon/process model
- direct `milltask` process
- direct `halcmd` host process
- unrestricted POSIX filesystem。
但浏览器/WASM 环境具备:
- WASM module execution
- Web Worker 隔离执行单元;
- MessageChannel / stream-style protocol
- OPFS 持久文件系统;
- Emscripten FS 与 OPFS bridge
- 可移植 Python/WASM runtime 选项;
- release/browser diagnostics artifact。
所以解锁方法必须把 native process boundary 改写为“协议等价 boundary”而不是假装浏览器
可以直接运行 native child process。
四、推荐总体架构
### 1. Tool DB protocol owner 仍在 LinuxCNC/WASM core
将 LinuxCNC tooldata DB protocol owner 保持在 C/C++ WASM 侧:
- 复用/移植 `tooldata_db.cc`
- 复用/移植 `tooldata_common.cc`
- 保留 protocol message 生成与解析;
- 只把 process I/O edge 抽象为 host adapter。
建议新增抽象:
```text
ToolDbProcessPort
```
职责:
- start configured DB program
- write protocol line
- read reply line
- close / cleanup
- expose protocol transcript for diagnostics。
不允许职责:
- 自行解释 tool table
- 自行构造 `T10..T19`
- 跳过 `tooldb.py`
- 用 `.tbl` fallback。
### 2. DB_PROGRAM 在 Web Worker / WASM Python runtime 中运行
推荐把 `db_nonran.py` 放进独立 Web Worker使用 WASM Python runtime 执行。
可选实现:
- CPython/WASM
- Pyodide
- 项目内裁剪 Python/WASM runtime
- 后续若项目已有 Python-remap runtime可复用同一个 Python runtime substrate。
Worker 内需要挂载:
- `db_nonran.py`
- `db.py`
- `tooldb.py`
- minimal `linuxcnc` Python compatibility module
- OPFS-backed DB flat file。
这里的 minimal `linuxcnc` module 只能覆盖 DB demo 必需的 integration surface例如
- `linuxcnc.command().load_tool_table`
并且必须作为 process integration shim 记录,不允许承载 tool semantics。
### 3. OPFS 负责 DB persistence
将 DB demo 的 flat-file database 映射到 OPFS例如
```text
/machines/<machine-id>/tool-db/db_nonran_file
```
要求:
- startup 前可加载已有 OPFS DB file
- protocol mutation 后写回 OPFS
- reload session 后 DB state 保持;
- diagnostics artifact 导出 DB file hash / state summary / transcript hash
- tests 能验证 persistence roundtrip。
### 4. Browser diagnostics 只证明,不伪装
browser UI / diagnostics 需要记录:
- DB runtime mode`wasm-tool-db-protocol-worker`
- DB program path
- protocol transcript
- OPFS DB file path
- source files
- `.tbl fallback sufficient = false`
- Python-only sufficient = false
- native proof status
- WASM proof status
- browser proof status。
五、分阶段实施路线
### Phase 0保持当前 lock不改 baseline
当前状态仍是:
```text
axis/db_demo/base.ngc SKIP L4-TOOL-DB
executed=28
passed=28
skipped=131
unexpected_fail=0
```
先不要改成 PASS。
保留:
- `promotion_allowed=0`
- `execution_enabled=0`
- `promotion_lock_active=1`
### Phase 1native proof 先闭环
目的:证明 source-derived protocol contract 与真实 LinuxCNC DB runtime 一致。
在完整 LinuxCNC host runtime 中执行:
```text
wasm-port/tests/native/probe_tool_db_runtime.sh
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
目标输出:
```text
tool_db_runtime_probe_status=runtime_protocol_probe_passed
```
然后刷新:
```text
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
预期 artifact 转为:
```text
source_proof_ready=1
runtime_ready=1
native_evidence_ready=1
gate_status=native_probe_passed_waiting_for_node_browser_promotion_proof
promotion_allowed=0
```
### Phase 2设计 WASM tool DB process port
新增 C/C++ 或 JS/WASM bridge 层,但协议 owner 必须保留 LinuxCNC source-derived 逻辑。
建议新增文件范围:
```text
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_tool_db_process_port.*
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_tool_db_protocol_harness.*
wasm-port/runtime/sdk/src/linuxcnc-tool-db.js
wasm-port/runtime/opfs/linuxcnc-tool-db-store.js
```
核心 API 草案:
```text
createLinuxCncToolDbRuntime({
dbProgramPath,
sourceFiles,
opfsRoot,
pythonRuntime,
})
startToolDbProgram()
sendToolDbProtocolMessage(line)
readToolDbProtocolReply()
runToolDbTransactionPlan(plan)
exportToolDbDiagnostics()
```
验证要求:
- API 返回 protocol transcript
- transcript 包含 `v2.1` / `g` / `FINI` / `l` / `p` / `u`
- DB state 不能来自 JS fixture hardcode
- source file hash 必须可追踪。
### Phase 3WASM Python / DB_PROGRAM runtime
新增或接入 Python/WASM runtime。
最低目标不是完整 Python remap runtime而是 tool DB demo runtime
- 能 import `tooldb.py`
- 能 import `db.py` / `db_nonran.py`
- 能读写 OPFS-backed DB file
- 能通过 stdin/stdout 或 MessageChannel 模拟 process protocol
- 能提供 minimal `linuxcnc.command().load_tool_table` integration shim。
必须新增 negative tests
- 没有 `db_nonran.py` 时 fail
- 没有 `tooldb.py` 时 fail
- 只有 `python3` / Python runtime 但没有 protocol transcript 时 fail
- 使用 `.tbl` fallback 时 fail
- JS 直接返回 `T10..T19` 而没有 DB_PROGRAM transcript 时 fail。
### Phase 4WASM protocol transaction tests
新增 Node/WASM tests先不改 full inventory baseline。
建议新增:
```text
wasm-port/tests/wasm/node/verify_tool_db_process_wasm.sh
wasm-port/tests/wasm/node/verify_tool_db_process_wasm.mjs
```
测试内容:
1. stage `db_nonran.ini` / `db_nonran.py` / `db.py` / `tooldb.py`
2. start WASM DB program runtime
3. verify startup `v2.1`
4. send `g` and verify `T10..T19` plus `FINI`
5. send `l` load notify and verify pocket/spindle state
6. send `p` offset/update notify and verify DB mutation
7. send `u` unload notify and verify unload state
8. persist to OPFS-backed store
9. reload and verify state survives
10. export transcript artifact。
输出建议:
```text
tool_db_process_wasm_protocol=ok
tool_db_process_wasm_persistence=ok
tool_db_process_wasm_no_tbl_fallback=ok
```
### Phase 5接入 inventory pre-promotion artifacts
在不改 baseline 的前提下,新增或扩展 artifact
```text
tool-db-process-wasm-protocol-proof.tsv
tool-db-process-opfs-persistence-proof.tsv
tool-db-process-browser-proof-gate.tsv
```
字段建议:
- path
- ini
- db_program
- protocol_transcript_hash
- opfs_db_file
- startup_handshake_passed
- get_all_passed
- load_notify_passed
- offset_notify_passed
- unload_notify_passed
- persistence_passed
- tbl_fallback_sufficient
- python_only_sufficient
- source_derived
- execution_enabled
- promotion_allowed。
在这一阶段:
```text
execution_enabled=0
promotion_allowed=0
```
仍保持 lock。
### Phase 6Browser proof
新增 browser proof但只证明 protocol runtime 和 OPFS persistence不直接宣称 inventory pass。
建议新增或扩展:
```text
wasm-port/tests/browser/verify_real_simulation_browser.sh
wasm-port/tests/browser/real_simulation_page_smoke.html
```
验证:
- browser 可以启动 tool DB Worker
- browser diagnostics 包含 DB protocol transcript summary
- OPFS persistence roundtrip 成功;
- `.tbl fallback sufficient=false`
- `python_only_sufficient=false`
- release artifact URL workflow 能显示 tool DB proof
- missing DB proof negative fixture 会 fail。
### Phase 7关闭 promotion lock 前的三方对齐
只有以下全部满足,才能准备关闭 lock
```text
native_db_process_protocol_probe_passed=1
wasm_tool_db_protocol_passed=1
browser_tool_db_protocol_proof_passed=1
opfs_persistence_passed=1
tbl_fallback_sufficient=0
python_only_sufficient=0
source_derived=1
```
更新:
```text
runtime-boundary-promotion-readiness.tsv
runtime-boundary-promotion-blockers.tsv
runtime-boundary-post-native-pass-gates.tsv
runtime-boundary-native-evidence-acceptance-gate.tsv
blocked-runtime-promotion-lock.tsv
promotion-candidates.tsv
```
目标状态:
```text
native_evidence_ready=1
node_inventory_gate_complete=1
browser_smoke_gate_complete=1
promotion_lock_active=0
promotion_ready=1
promotion_allowed=1
```
### Phase 8正式 inventory baseline promotion
更新 `verify_sim_configs_inventory_wasm.mjs`,允许 `axis/db_demo/base.ngc` 走 tool DB
protocol execution path。
要求:
- 执行时必须通过 WASM tool DB protocol runtime
- 不能直接使用 `.tbl`
- 失败时不能降级为 SKIP
- protocol proof 缺失时必须 FAIL而不是 silent fallback。
预期 `summary.tsv`
```text
axis/db_demo/base.ngc PASS - main PASS -
```
预期 baseline
```text
executed=29
passed=29
skipped=130
unexpected_fail=0
```
同步更新:
```text
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE
project release readiness fixture
coverage matrix Current Node inventory
compatibility validation current gate text
skip-summary expected count
promotion candidate expected status
release readiness artifact
batch acceptance artifact
```
六、可实现性的关键判断
### 可以实现的部分
1. WASM 内保存 LinuxCNC tool DB protocol owner。
2. Web Worker 作为 DB_PROGRAM process boundary 等价层。
3. WASM Python runtime 执行 `db_nonran.py` / `tooldb.py`。
4. OPFS 保存 DB flat-file。
5. Node/WASM/browser 三层 proof。
6. release artifact 记录 proof chain。
7. inventory baseline 从 `28/28/131/0` 推进到 `29/29/130/0`。
### 不应承诺的部分
1. 浏览器直接运行 native `milltask`。
2. 浏览器直接运行 host `halcmd`。
3. 浏览器直接 spawn OS child process。
4. 不经 Python DB program 而直接伪造 DB state。
5. 用 `.tbl` 替代 `DB_PROGRAM`。
七、推荐实施顺序
最务实顺序:
```text
1. 先在 native host 上跑通 ENABLE_TOOL_DB_RUNTIME_PROBE=1。
2. 加 tool DB WASM protocol proof不改 baseline。
3. 加 OPFS persistence proof不改 baseline。
4. 加 browser diagnostics proof不改 baseline。
5. 更新 promotion readiness / lock。
6. 最后才把 axis/db_demo/base.ngc 纳入 inventory PASS。
```
八、最小验证命令
native
```text
wasm-port/tests/native/probe_tool_db_runtime.sh
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
WASM/Node
```text
wasm-port/tests/wasm/node/verify_tool_db_process_wasm.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
Browser
```text
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
```
Release
```text
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
wasm-port/tests/host/verify_project_release_readiness_artifact.sh
```
Safety
```text
git diff --check
wasm-port/tools/verify_no_standalone_cnc_semantics.sh
```
九、下一步工作内容
下一轮建议不要直接改 `axis/db_demo/base.ngc` 的 inventory status。先做最小可行
WASM proof
```text
新增 verify_tool_db_process_wasm.mjs / .sh证明 tool DB protocol transcript 和 OPFS
persistence 可以在 WASM + OPFS 环境中闭环,同时继续保持 promotion_allowed=0。
```
该 proof 的完成标准:
- `db_nonran.py` / `tooldb.py` 被真实加载;
- protocol transcript 中出现 `v2.1`、`g`、`FINI`、`l`、`p`、`u`
- OPFS persistence roundtrip 通过;
- `.tbl fallback` negative fixture 失败;
- artifact 明确记录 `promotion_allowed=0`。
完成后再进入 browser proof 和 promotion lock 更新。

202
text22.txt Normal file
View File

@@ -0,0 +1,202 @@
项目接续文件:按铁律收束当前阶段 promotion 工作
生成时间2026-06-18 CST
本文件接替 `text19.txt`,并对 `text20.txt` / `text21.txt` 做范围收束。当前阶段铁律为:
```text
virtual HAL 成果作为 sim config promotion 证据基础;
同时明确不解锁 Python remap / tool DB / external user-M 这些 hard block。
```
一、当前阶段完成定义
当前阶段要完成的是 evidence-based promotion不是 hard-block runtime 解锁。
允许做:
- 复用 virtual HAL source compliance
- 复用 virtual HAL sim-config source coverage
- 复用 virtual HAL promotion candidate report
- 复用 macro/load fixture report
- 复用 command script fixture report
- 复用 manifest-backed motion controller matrix
- 复用 browser diagnostics artifact
- 复用 release readiness / URL workflow / batch acceptance evidence
- 生成并维护 `promotion-candidates.tsv`,把候选分为:
- `evidence-ready`
- `inventory-ready`。
禁止做:
- 不新增 virtual HAL runtime capability
- 不用 virtual HAL 替代 LinuxCNC CNC 语义;
- 不用 JS/browser glue 解锁 Python remap
- 不用 JS/browser glue 解锁 tool DB
- 不用 JS/browser glue 解锁 external user-M process
- 不把 `.tbl` fallback 当作 tool DB pass
- 不把 macro/load asset 当 standalone main program
- 不靠改 docs 声称 skipped 下降。
二、当前 hard block 状态
以下 hard block 在当前阶段必须保持 locked
```text
L4-PYTHON-REMAP
L4-TOOL-DB
L4-USER-M-PROCESS
UPSTREAM-DEMO
standalone-main 语境下的 ASSET-ONLY
```
当前 skip/block summary 仍保持:
```text
ASSET-ONLY=65
L4-PYTHON-REMAP=53
L4-TOOL-DB=1
L4-USER-M-PROCESS=1
NON_MAIN_CLASS=10
UPSTREAM-DEMO=1
```
三、当前 baseline 必须保持
当前阶段完成后Node inventory baseline 仍应为:
```text
executed=28
passed=28
skipped=131
unexpected_fail=0
```
这不是失败而是当前铁律下的正确结果virtual HAL 已把一批可用 sim config row 推进到
browser diagnostics / release evidence 层,但没有解锁 hard runtime family。
四、当前已完成的 evidence-ready promotion
`promotion-candidates.tsv` 中必须包含以下 `evidence-ready` rows
- `qtdragon/qtdragon_multi_joint/on_abort.ngc`
- `qtdragon/qtdragon_xyz/on_abort.ngc`
- `qtdragon/qtdragon_xyz45/on_abort.ngc`
- `qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc`
- `qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc`
- `qtvcp_screens/qtdragon/on_abort.ngc`
- `axis/vismach/puma/puma_seam_weld.ngc`
- `axis/rose_engine/rcone_demo.ngc`
这些 rows 的语义:
```text
current_status=PASS
skip_kind=-
matrix_layer4_node=INV
matrix_layer4_browser=-
blocked_kind=-
virtual_hal_evidence_ready=1
promotion_allowed=0
block_reason=browser_release_evidence_ready_inventory_baseline_unchanged
```
`promotion_allowed=0` 是有意的:这些 row 已有 browser/release evidence但不代表
inventory baseline 发生变化。
五、当前 inventory-ready hard block rows
`promotion-candidates.tsv` 中也会列出 `inventory-ready` 类型的 skipped main rows。
这些行用于后续分析,不代表当前可解锁。
当前直接 inventory promotion 必须保持:
```text
promotion_allowed=0
```
原因:
- `L4-PYTHON-REMAP` 需要 LinuxCNC Python remap runtime
- `L4-TOOL-DB` 需要 tool database process protocol proof
- `L4-USER-M-PROCESS` 需要 external user-M process state proof
- `UPSTREAM-DEMO` 是 preserved upstream demo edge。
六、text20 / text21 的范围解释
`text20.txt` 和 `text21.txt` 只作为未来解锁研究记录:
- `text20.txt`future tool DB native/runtime 解锁步骤;
- `text21.txt`future WASM + OPFS tool DB feasibility architecture。
它们不改变当前阶段铁律,不允许在当前阶段据此把 `L4-TOOL-DB` 解锁。
当前阶段若引用 `text20.txt` / `text21.txt`,只能作为:
- future milestone planning
- risk analysis
- architecture note
- blocked family proof requirement。
不能作为:
- 当前 baseline promotion 依据;
- 当前 release ready 的解锁依据;
- 当前 browser diagnostics positive pass 的替代证据。
七、当前阶段验收标准
当前阶段完成需要满足:
1. `promotion-candidates.tsv` 存在;
2. `promotion-candidates.tsv` 同时包含 `evidence-ready` 和 `inventory-ready` 两层;
3. `evidence-ready` rows 绑定 virtual HAL source-derived browser/release evidence
4. `inventory-ready` hard block rows 仍 `promotion_allowed=0`
5. baseline 保持 `28/28/131/0`
6. `L4-PYTHON-REMAP`、`L4-TOOL-DB`、`L4-USER-M-PROCESS` 仍 locked
7. docs 明确 artifact count 和 candidate report
8. release / SDK gate 仍要求 virtual HAL promotion candidate summary
9. 不新增 virtual HAL runtime capability
10. 不新增 JS-owned CNC semantics。
八、已验证命令
当前阶段最小验证命令:
```text
git diff --check
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
```
预期 inventory 输出:
```text
sim_configs_wasm_node_inventory_executed=28
sim_configs_wasm_node_inventory_passed=28
sim_configs_wasm_node_inventory_skipped=131
sim_configs_wasm_node_inventory_unexpected_fail=0
sim_configs_wasm_node_inventory=ok
```
九、下一步建议
下一步不要直接解锁 hard block。按当前铁律优先做
1. 扩展 `evidence-ready` browser diagnostics 展示;
2. 强化 release URL workflow 对 promotion candidate summary 的可见性;
3. 对 `blocked=-`、native `PASS`、class `main` 的非 hard-block family 继续找可真实 promotion row
4. 对 Python remap / tool DB / external user-M 只维护 proof requirement 和 lock artifact
5. 如果要解锁 tool DB、Python remap 或 external user-M必须另开 milestone不能混入当前阶段。
十、简要结论
当前阶段已经按铁律完成:
```text
virtual HAL 成果作为 sim config promotion 证据基础;
Python remap / tool DB / external user-M hard block 不解锁;
baseline 保持 28/28/131/0
promotion candidates 进入双层 evidence-ready / inventory-ready 管理。
```

View File

@@ -204,6 +204,7 @@ wasm-port/build/wasm/sim-configs-inventory/boundary-phase-completion-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/native-proof-alignment-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/native-runtime-probe-execution-plan.tsv
wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv
wasm-port/build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv
wasm-port/build/wasm/sim-configs-inventory/next-boundary-worklist.tsv
wasm-port/build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv
@@ -230,6 +231,13 @@ skip counts fail on blocked-policy drift.
For eligible rows, missing vendored machine context is an inventory failure,
not an expected skip; blocked rows must be classified by runtime dependency
before the Node inventory filter runs.
`promotion-candidates.tsv` records two promotion layers: `evidence-ready`
rows whose existing virtual HAL source-derived browser/release evidence is
ready while the Node inventory baseline remains unchanged, and
`inventory-ready` skipped main-program rows that could affect the Node
inventory baseline. The current direct inventory `promotion_allowed=1` count
is zero: all skipped main rows are still hard-blocked by `L4-PYTHON-REMAP`,
`L4-TOOL-DB`, `L4-USER-M-PROCESS`, or preserved as `UPSTREAM-DEMO`.
`boundary-summary.tsv` records one row per native inventory path with the
matrix blocked kind, SDK classifier recommendation, declared HAL/UI/HALUI/Python
process dependencies, `[EMCIO]DB_PROGRAM`, user-M execution codes, and the
@@ -385,10 +393,10 @@ coverage: every TSV emitted under
`full-process-boundary-design.md`. New gate artifacts must therefore be
documented before the inventory can pass.
The Node coverage gate also requires the generated WASM inventory artifact list
to remain the exact duplicate-free 54-entry baseline and the native generated
to remain the exact duplicate-free 55-entry baseline and the native generated
TSV token list to remain the exact duplicate-free 8-token baseline. It also
requires the corresponding `boundary-phase-completion-summary.tsv`
documentation-coverage counts to remain `54` and `8`, respectively. It also
documentation-coverage counts to remain `55` and `8`, respectively. It also
checks that the browser smoke source lists the same generated WASM and native
artifact tokens and preserves the same fixed-count, duplicate-free,
fetchability/header, documentation-missing, and completion count-parity guards

View File

@@ -281,6 +281,336 @@ HAL from accidentally unlocking Python UI or Python remap families.
The next practical slice is to add a machine-readable candidate audit for other
UI-family macro/load rows before promoting any new positive fixture.
## 2026-06-18 UI-Family Macro/Load Candidate Audit
The macro/load report now carries audit rows for UI-family fixture candidates
that are not ready for positive promotion:
- `gscreen/industrial_lathe_wear/toolchange.ngc`
- `qtvcp_screens/industrial_lathe_wear/toolchange.ngc`
Both rows are recorded as `blocked-missing-owning-ini` audit candidates with
`promotionAllowed === false`. They must remain excluded from positive
macro/load fixtures until the owning INI, machine files, tool table, and
UI/process declaration have LinuxCNC source evidence that does not require
Python UI process emulation.
Release diagnostics validation now requires `auditPromotionViolations` to stay
empty and requires both audit rows to be present as negative evidence. This is
an audit gate, not a coverage promotion: inventory baseline remains unchanged.
## 2026-06-18 UI-Family Audit Boundary Evidence
The UI-family audit rows now carry machine-readable source evidence from the
generated inventory boundary artifacts:
- `wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv`
- `wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv`
For both industrial-lathe `toolchange.ngc` rows, the audit evidence requires
`recommendedBlocked === "UNAVAILABLE"` and a `missing_vendored_ini:*`
dependency for the owning INI. The INI-level row must also report
`vendored === 0` and `reportAvailable === 0`.
The macro/load fixture report now exposes:
- `missingAuditRows`
- `auditBoundaryEvidenceViolations`
- `auditPromotionAllowedViolations`
Release diagnostics validation requires all of those lists to stay empty. This
adds negative coverage for three failure modes: audit rows missing, audit rows
accidentally promoted into positive fixtures, and `promotionAllowed === true`
without generated boundary/source proof.
## 2026-06-18 SilverDragon Blocked Boundary Evidence
The blocked macro/load fixture rows now use the same generated boundary
evidence contract. `gscreen/silverdragon/macros/tool_sensor.ngc` carries
evidence from `boundary-summary.tsv` and `ini-boundary-summary.tsv` showing
that the owning `gscreen/silverdragon/silverdragon.ini` is unavailable in the
vendored inventory.
The report now exposes `blockedBoundaryEvidenceViolations`, and release
diagnostics validation requires it to stay empty. This keeps the SilverDragon
row as a blocked negative fixture with source-derived evidence, not a virtual
HAL promotion path.
## 2026-06-18 Queuebuster Declaration Evidence
The declared-only `axis/external_offsets/queuebuster.ngc` fixture now carries
declaration-level evidence from `eoffsets.ini`. The fixture report requires the
owning INI to expose:
- section: `DISPLAY`
- key: `NGCGUI_SUBFILE`
- value: `queuebuster.ngc`
- source line: `NGCGUI_SUBFILE = queuebuster.ngc`
This keeps `queuebuster.ngc` as a non-main fixture path declared by LinuxCNC
configuration, not a standalone browser main program. Release diagnostics
validation now requires `declarationEvidenceViolations` to stay empty.
## 2026-06-18 Rose Engine Declaration Evidence
The positive `axis/rose_engine/rcone.ngc` non-main fixture now carries the same
declaration-level evidence contract as queuebuster. `rose_engine.ini` declares
the fixture with `NGCGUI_SUBFILE = rcone.ngc`, and the macro/load report now
requires every positive fixture with a `declarationSource` to carry matching
source evidence.
This keeps `rcone.ngc` as a LinuxCNC-declared macro/load support asset paired
with `rcone_demo.ngc`, not a standalone browser main program. The evidence is
limited to the owning INI declaration and does not add browser-owned G-code,
interpreter, planner, or remap behavior.
## 2026-06-18 Boundary Evidence Freshness Gate
Generated boundary evidence for blocked and audit-only macro/load rows now
carries an inventory `baselineSummary`. The macro/load report requires that
summary to match the current generated inventory baseline:
```text
executed=28
passed=28
skipped=131
unexpectedFail=0
```
This makes `boundary-summary.tsv` and `ini-boundary-summary.tsv` evidence
explicitly tied to the current inventory baseline. A stale baseline now fails
the blocked/audit boundary evidence gate instead of silently passing release
diagnostics.
## 2026-06-18 Promotion Family URL Workflow Summary
The release readiness validation and URL workflow summaries now surface the
promotion candidate `familyRows` as a user-visible summary. Ready artifacts
show:
```text
qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready
```
The ini-panel workflow overview URL summary reuses this value, so the
QtDragon, PUMA, and rose-engine promotion families are visible without opening
the raw diagnostics JSON. This is a presentation/reporting change only; it
does not change inventory counts or CNC semantics.
## 2026-06-18 Structured Promotion Family Rows
The promotion family summary is now also exposed as structured rows. Each row
records the family id, candidate count, complete count, source file count, and
explicit browser diagnostics readiness. Current rows are:
```text
qtdragon-on-abort: 6/6 ready; sources=12; diagnostics=ready
vismach-remap-sims: 1/1 ready; sources=3; diagnostics=ready
rose-engine-rcone-demo: 1/1 ready; sources=2; diagnostics=ready
```
The release readiness artifact, validation summary, URL workflow summary, and
ini-panel workflow overview all expose these rows. This keeps the summary
machine-readable while preserving the existing single-line text summary.
## 2026-06-18 Boundary Evidence Artifact Hash Gate
Generated boundary evidence now includes SHA-256 hashes for the two source TSV
artifacts used by blocked and audit macro/load evidence:
```text
boundary-summary.tsv de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9
ini-boundary-summary.tsv b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae
```
The macro/load report and release validation require those hashes to match.
This extends the freshness gate beyond inventory counts and makes stale or
substituted boundary TSV evidence fail the release diagnostics gate.
## 2026-06-18 Browser Promotion Family DOM Rows
The real browser simulation page now renders the structured promotion family
rows in the diagnostics panel. The visible browser DOM exposes:
```text
qtdragon-on-abort: 6/6 ready; sources=12
vismach-remap-sims: 1/1 ready; sources=3
rose-engine-rcone-demo: 1/1 ready; sources=2
```
The browser API also exposes `getVirtualHalPromotionFamilyRows()`, and the real
simulation browser smoke asserts that the DOM rows, readiness dataset, and
candidate report agree. This moves the existing source-derived promotion
evidence from view-model/report data into a visible browser diagnostics surface.
It does not change inventory counts or add CNC semantics.
## 2026-06-18 Fast Follow: Hash Tool And INI Panel Promotion DOM
The boundary TSV hash freshness gate now has an update/check helper:
```bash
wasm-port/tools/update_sim_config_boundary_hashes.sh --check
wasm-port/tools/update_sim_config_boundary_hashes.sh --write
```
The script computes SHA-256 for `boundary-summary.tsv` and
`ini-boundary-summary.tsv`, then checks or updates the SDK/release validation
constants. This removes the manual hash-update step after generated inventory
artifacts change.
The ini-panel workflow overview now also has a dedicated promotion family DOM
surface. It can render and mount the three structured promotion rows from the
release URL workflow summary into `[data-workflow-overview-promotion-family-rows]`.
The UI shell smoke verifies the rows mount as ready with the expected QtDragon,
PUMA, and rose-engine values.
## 2026-06-18 Promotion Family Source Drilldown
Promotion family rows now carry their LinuxCNC source file lists through the
release readiness report, validation summary, URL workflow summary, and
ini-panel workflow overview render state. The ini-panel promotion family DOM
mount renders the three family rows plus per-family source-file drilldown rows.
Current drilldown count is 17 source files:
```text
qtdragon-on-abort: 12 source files
vismach-remap-sims: 3 source files
rose-engine-rcone-demo: 2 source files
```
This makes the browser/ini-panel evidence inspectable without opening the raw
JSON artifact, while keeping the underlying behavior LinuxCNC-source-derived.
## 2026-06-18 Promotion Family Source Status
Each ini-panel promotion family source drilldown row now carries source status
metadata:
- `ini` for LinuxCNC INI machine files;
- `gcode` for `.ngc` main/on-abort programs;
- `remap-subroutine` for files under `remap_subs` or `nc_subroutines`;
- `manifest-backed` for rows already carried by the source-derived promotion
evidence.
The DOM render adds dataset fields for source kind, manifest-backed state, and
source status, so the UI can distinguish machine config, G-code, and remap
support evidence without changing any CNC semantics.
## 2026-06-18 Promotion Family Source Kind Counts
The ini-panel promotion family render state now aggregates source-kind counts
per family and exposes them in the family row DOM dataset. Current counts are:
```text
qtdragon-on-abort: ini=6 gcode=6
vismach-remap-sims: ini=1 gcode=1 remap-subroutine=1
rose-engine-rcone-demo: ini=1 gcode=1
```
The family row display includes this summary, while the per-source drilldown
rows still expose individual source kind and manifest-backed status.
## 2026-06-18 Promotion Family Source Filter View Model
The ini-panel workflow overview now exposes a promotion family source filter
view-model. It can filter the existing source drilldown by `all`, `ini`,
`gcode`, `remap-subroutine`, or `source`, while preserving family grouping and
source-kind counts.
Current filter totals are:
```text
all=17
ini=8
gcode=8
remap-subroutine=1
```
The `remap-subroutine` filter currently resolves to the PUMA remap evidence
row:
```text
linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc
```
This is a browser/UI evidence navigation helper only. It does not change the
promotion candidate set, inventory baseline, or CNC runtime semantics.
## 2026-06-18 Promotion Family Source Family Filter
The source filter view-model now supports a second selector: `familyId`. This
allows UI callers to scope source evidence by promotion family and then filter
within that family by source kind.
Current family filter totals are:
```text
all=17
qtdragon-on-abort=12
vismach-remap-sims=3
rose-engine-rcone-demo=2
```
For example, selecting `virtual-hal-promotion-family-qtdragon-on-abort` with
`gcode` returns 6 QtDragon `.ngc` source rows from the 12-file family scope.
The selector is a view-model feature only; it does not promote additional sim
config rows or relax any blocked runtime family.
## 2026-06-18 Promotion Family Source Filter DOM Surface
The source filter view-model now has a dedicated DOM contract, readiness check,
render helper, and mount helper. The DOM surface renders:
- family filter rows;
- source-kind filter rows;
- filtered source evidence rows;
- selected family/source-kind dataset fields on the mount node.
The QtDragon + `gcode` combination currently renders 13 DOM rows: 7 filter
rows and 6 filtered source rows. This moves the combined evidence filter from a
view-model-only helper into a mountable ini-panel workflow overview surface.
It still only displays existing LinuxCNC-source-derived evidence.
## 2026-06-18 Promotion Family Source Filter Selector Controls
The source filter DOM surface now renders two selector controls in addition to
the diagnostic rows:
- a family selector backed by `familyFilters`;
- a source-kind selector backed by `sourceKindFilters`.
For the QtDragon + `gcode` example, the controls expose the selected family
`virtual-hal-promotion-family-qtdragon-on-abort` and selected source kind
`gcode`, with the `gcode` option carrying count `6`. The selectors currently
render selection state and option metadata for browser integration; they do not
change the underlying promotion evidence or add CNC semantics.
## 2026-06-18 Promotion Family Source Filter Selection Workflow
The selector controls now have an action-plan and selection-change workflow.
The workflow reads the current family/source-kind selector values, derives the
next source filter view-model, and can optionally re-render the source filter
DOM surface.
The current smoke path changes from QtDragon + `gcode` to
`virtual-hal-promotion-family-rose-engine-rcone-demo` + `all`. That produces a
new view-model with 2 filtered source files from a 2-file family scope and
re-renders the DOM rows to include rose-engine source evidence. This is still a
host/UI workflow over existing source-derived evidence only.
## 2026-06-18 Promotion Family Source Filter Event Binding
The selector controls now have a reusable event binding helper. It binds the
family and source-kind `<select>` elements to the selection-change workflow,
tracks the last change result, and can unbind both controls.
The current Node smoke binds two selector controls, changes the family selector
to `virtual-hal-promotion-family-rose-engine-rcone-demo`, dispatches a `change`
event, and verifies that the source filter DOM re-renders with 2 rose-engine
source rows. This keeps the browser integration path explicit without changing
promotion status or CNC semantics.
## Boundary Statement
This analysis relies on the improved virtual HAL only for Web simulation

View File

@@ -62,6 +62,8 @@ Current baseline:
`wasm-port/build/wasm/sim-configs-inventory/next-boundary-worklist.tsv`
- Node next-boundary recommendation artifact:
`wasm-port/build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv`
- Node promotion candidate artifact:
`wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv`
- Node native evidence acceptance gate artifact:
`wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv`
- Node promotion blocker summary artifact:
@@ -349,7 +351,7 @@ Legend:
`build/wasm/sim-configs-inventory/` must be referenced by the current
compatibility, matrix, or full-process boundary docs, so generated gate
artifacts cannot silently appear without review text. The generated WASM
inventory artifact list must remain the exact duplicate-free 54-entry
inventory artifact list must remain the exact duplicate-free 55-entry
baseline, and every token plus the fixed-count, duplicate-free, and
fetchability guards must also be listed by browser smoke.
- The same inventory check covers native generated TSV artifacts under
@@ -362,7 +364,7 @@ Legend:
compatibility, matrix, and full-process boundary docs and checking the
generated WASM inventory artifact names plus native TSV tokens against the
same completion-summary counts. The Node coverage gate also locks the two
documentation-coverage completion counts to the current `54` WASM inventory
documentation-coverage completion counts to the current `55` WASM inventory
artifacts and `8` native TSV artifacts. It verifies that browser smoke
executes the artifact documentation coverage helper, calls the completion
count-parity helper with the generated completion rows, fetches and joins the

View File

@@ -70,6 +70,8 @@ import {
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
@@ -327,8 +329,9 @@ private page state.
files, current Node `PASS` evidence, blocked-family exclusions, target
browser diagnostics evidence, and the required virtual HAL reports for
QtDragon on-abort, PUMA seam-weld, and rose-engine promotion planning.
- `VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES` and
`VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES` plus
- `VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES`,
`VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES`, and
`VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES` plus
`createVirtualHalSimConfigMacroLoadFixtureReport()` expose non-main
macro/load fixture evidence for virtual HAL diagnostics. The report keeps
`nonMainFixture === true`, separates fixture paths from manifest-backed
@@ -336,7 +339,9 @@ private page state.
`rcone.ngc` or external-offsets `queuebuster.ngc` from being promoted as
standalone browser main programs. Blocked fixture rows such as SilverDragon
`tool_sensor.ngc` remain excluded until their Python UI/process boundary is
proven outside virtual HAL.
proven outside virtual HAL. Audit candidates such as industrial-lathe
`toolchange.ngc` rows stay machine-readable negative evidence until owning
INI and UI/process source evidence is complete.
- `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

View File

@@ -7,8 +7,11 @@ export {
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
@@ -186,6 +189,25 @@ export {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan,
handleIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionChange,
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls,
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomContract,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomReadiness,
renderIniPanelShellWorkflowOverviewPromotionFamilyRowsState,
mountIniPanelShellWorkflowOverviewPromotionFamilyRowsState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomContract,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomReadiness,
renderIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,

View File

@@ -125,6 +125,20 @@ export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
}),
]);
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE = Object.freeze({
executed: 28,
passed: 28,
skipped: 131,
unexpectedFail: 0,
});
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = Object.freeze({
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae",
});
export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
Object.freeze({
id: "qtdragon-multi-joint-on-abort",
@@ -137,7 +151,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
gcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-multi-joint",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -161,7 +175,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_xyz/qtdragon_inch.ini",
gcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-on-abort",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -185,7 +199,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini",
gcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-rotary-axis",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -209,7 +223,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/qtdragon_hd_vertical.ini",
gcodePath: "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-on-abort",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -233,7 +247,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini",
gcodePath: "linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-z-compensation",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -257,7 +271,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/qtvcp_screens/qtdragon/qtdragon_mpg.ini",
gcodePath: "linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "delegated-to-woodpecker",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-ui-on-abort",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -282,7 +296,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/axis/vismach/puma/puma.ini",
gcodePath: "linuxcnc/configs/sim/axis/vismach/puma/puma_seam_weld.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "represented-by-puma-cube",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "halui-vismach-remap",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -306,7 +320,7 @@ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
iniPath: "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
gcodePath: "linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
currentNodeInventoryStatus: "PASS",
currentMatrixBrowserStatus: "not-promoted",
currentMatrixBrowserStatus: "explicit-browser-diagnostics",
dependencyClass: "hal-process-only",
targetBrowserEvidence: "explicit-browser-diagnostics",
requiredVirtualHalReports: Object.freeze([
@@ -334,6 +348,14 @@ export const VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES = Object.freeze([
owningIniPath: "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
pairedMainProgram: "linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
declarationSource: "NGCGUI_SUBFILE",
declarationEvidence: Object.freeze({
sourceFile: "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
section: "DISPLAY",
key: "NGCGUI_SUBFILE",
value: "rcone.ngc",
line: 34,
sourceLine: "NGCGUI_SUBFILE = rcone.ngc",
}),
currentNodeInventoryStatus: "PASS",
dependencyClass: "macro-load-non-main",
targetBrowserEvidence: "non-main-fixture-diagnostics",
@@ -353,6 +375,14 @@ export const VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES = Object.freeze([
owningIniPath: "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
pairedMainProgram: "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
declarationSource: "NGCGUI_SUBFILE",
declarationEvidence: Object.freeze({
sourceFile: "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
section: "DISPLAY",
key: "NGCGUI_SUBFILE",
value: "queuebuster.ngc",
line: 40,
sourceLine: "NGCGUI_SUBFILE = queuebuster.ngc",
}),
currentNodeInventoryStatus: "NON_MAIN_CLASS",
dependencyClass: "macro-load-non-main-declared-only",
targetBrowserEvidence: "non-main-fixture-diagnostics",
@@ -370,6 +400,27 @@ export const VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES = Object.freeze(
owningIniPath: "linuxcnc/configs/sim/gscreen/silverdragon/silverdragon.ini",
blockedKind: "PYTHON-UI-PROCESS",
dependencyClass: "macro-load-python-ui-boundary",
boundaryEvidence: Object.freeze({
sourceArtifacts: Object.freeze([
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv",
]),
sourceArtifactHashes: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
baselineSummary: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
boundarySummary: Object.freeze({
path: "gscreen/silverdragon/macros/tool_sensor.ngc",
ini: "gscreen/silverdragon/silverdragon.ini",
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini",
}),
iniBoundarySummary: Object.freeze({
ini: "gscreen/silverdragon/silverdragon.ini",
vendored: 0,
reportAvailable: 0,
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini",
}),
}),
reason: "Owning SilverDragon UI machine file is not available in the vendored manifest; promotion would require Python UI process boundary proof outside virtual HAL.",
}),
Object.freeze({
@@ -378,10 +429,94 @@ export const VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES = Object.freeze(
owningIniPath: "linuxcnc/configs/sim/gmoccapy/6_axis.ini",
blockedKind: "L4-PYTHON-REMAP",
dependencyClass: "macro-load-python-remap-boundary",
boundaryEvidence: Object.freeze({
sourceArtifacts: Object.freeze([
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv",
]),
sourceArtifactHashes: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
baselineSummary: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
boundarySummary: Object.freeze({
path: "gmoccapy/macros/on_abort.ngc",
ini: "gmoccapy/6_axis.ini",
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gmoccapy/6_axis.ini",
}),
iniBoundarySummary: Object.freeze({
ini: "gmoccapy/6_axis.ini",
vendored: 0,
reportAvailable: 0,
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gmoccapy/6_axis.ini",
}),
}),
reason: "gmoccapy macro/load rows remain locked behind LinuxCNC-owned Python remap/prolog/epilog runtime proof.",
}),
]);
export const VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES = Object.freeze([
Object.freeze({
id: "gscreen-industrial-lathe-wear-toolchange-audit",
fixturePath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
owningIniPath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
dependencyClass: "macro-load-ui-family-audit",
auditStatus: "blocked-missing-owning-ini",
promotionAllowed: false,
boundaryEvidence: Object.freeze({
sourceArtifacts: Object.freeze([
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv",
]),
sourceArtifactHashes: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
baselineSummary: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
boundarySummary: Object.freeze({
path: "gscreen/industrial_lathe_wear/toolchange.ngc",
ini: "gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
}),
iniBoundarySummary: Object.freeze({
ini: "gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
vendored: 0,
reportAvailable: 0,
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
}),
}),
reason: "Generated boundary evidence reports missing vendored owning INI for gscreen industrial_lathe_wear; keep as audit-only until machine files and UI/process declaration have LinuxCNC source evidence.",
}),
Object.freeze({
id: "qtvcp-industrial-lathe-wear-toolchange-audit",
fixturePath: "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
owningIniPath: "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
dependencyClass: "macro-load-ui-family-audit",
auditStatus: "blocked-missing-owning-ini",
promotionAllowed: false,
boundaryEvidence: Object.freeze({
sourceArtifacts: Object.freeze([
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv",
]),
sourceArtifactHashes: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
baselineSummary: VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
boundarySummary: Object.freeze({
path: "qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
ini: "qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
}),
iniBoundarySummary: Object.freeze({
ini: "qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
vendored: 0,
reportAvailable: 0,
recommendedBlocked: "UNAVAILABLE",
dependencies: "missing_vendored_ini:configs/sim/qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
}),
}),
reason: "Generated boundary evidence reports missing vendored owning INI for qtvcp_screens industrial_lathe_wear; keep as audit-only until machine files and UI/process declaration have LinuxCNC source evidence.",
}),
]);
export const VIRTUAL_HAL_COVERAGE_STATES = Object.freeze({
simulated: "simulated",
bridgeOnly: "bridge-only",
@@ -2683,6 +2818,10 @@ function normalizeLinuxCncSourcePath(path) {
return parts.join("/");
}
function normalizeSimConfigInventoryPath(path) {
return normalizeLinuxCncSourcePath(path).replace(/^configs\/sim\//, "");
}
function sourceManifestSetFromText(manifestText = "") {
return new Set(String(manifestText)
.split("\n")
@@ -2691,6 +2830,35 @@ function sourceManifestSetFromText(manifestText = "") {
.map(normalizeLinuxCncSourcePath));
}
function isGeneratedBoundaryEvidenceReady(boundaryEvidence = {}, fixturePath = "", owningIniPath = "") {
const boundarySummary = boundaryEvidence.boundarySummary ?? {};
const iniBoundarySummary = boundaryEvidence.iniBoundarySummary ?? {};
const baselineSummary = boundaryEvidence.baselineSummary ?? {};
const sourceArtifactHashes = boundaryEvidence.sourceArtifactHashes ?? {};
const sourceArtifacts = [...(boundaryEvidence.sourceArtifacts ?? [])];
const normalizedFixturePath = normalizeSimConfigInventoryPath(fixturePath);
const normalizedOwningIniPath = normalizeSimConfigInventoryPath(owningIniPath);
return sourceArtifacts.includes("wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv") &&
sourceArtifacts.includes("wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv") &&
Object.entries(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES)
.every(([artifactPath, expectedHash]) => sourceArtifactHashes[artifactPath] === expectedHash) &&
baselineSummary.executed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.executed &&
baselineSummary.passed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.passed &&
baselineSummary.skipped === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
baselineSummary.unexpectedFail === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
normalizeSimConfigInventoryPath(boundarySummary.path) === normalizedFixturePath &&
normalizeSimConfigInventoryPath(boundarySummary.ini) === normalizedOwningIniPath &&
boundarySummary.recommendedBlocked === "UNAVAILABLE" &&
typeof boundarySummary.dependencies === "string" &&
boundarySummary.dependencies.includes("missing_vendored_ini:") &&
normalizeSimConfigInventoryPath(iniBoundarySummary.ini) === normalizedOwningIniPath &&
iniBoundarySummary.vendored === 0 &&
iniBoundarySummary.reportAvailable === 0 &&
iniBoundarySummary.recommendedBlocked === "UNAVAILABLE" &&
typeof iniBoundarySummary.dependencies === "string" &&
iniBoundarySummary.dependencies.includes("missing_vendored_ini:");
}
export function createVirtualHalSimConfigSourceCoverageReport(options = {}) {
const targets = options.targets ?? VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS;
const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries);
@@ -2835,6 +3003,23 @@ export function createVirtualHalSimConfigPromotionCandidateReport(options = {})
const blockedCandidateIds = rows.filter((row) => !row.blockedKindExcluded).map((row) => row.id);
const missingManifestFiles = [...new Set(rows.flatMap((row) => row.missingManifestFiles))];
const missingRequiredReports = [...new Set(rows.flatMap((row) => row.missingRequiredReports))];
const familyRows = [...new Set(rows.map((row) => row.simConfigTarget ?? row.id))].map((familyId) => {
const familyCandidates = rows.filter((row) => (row.simConfigTarget ?? row.id) === familyId);
const completeCount = familyCandidates.filter((row) => row.complete).length;
const explicitBrowserDiagnosticsCount = familyCandidates
.filter((row) => row.targetBrowserEvidence === "explicit-browser-diagnostics").length;
const sourceFiles = [...new Set(familyCandidates.flatMap((row) => row.sourceFiles))];
return {
id: familyId,
candidateIds: familyCandidates.map((row) => row.id),
candidateCount: familyCandidates.length,
completeCount,
explicitBrowserDiagnosticsCount,
explicitBrowserDiagnosticsReady: explicitBrowserDiagnosticsCount === familyCandidates.length,
complete: completeCount === familyCandidates.length,
sourceFiles,
};
});
const complete = rows.length > 0 &&
missingCandidates.length === 0 &&
blockedCandidateIds.length === 0 &&
@@ -2858,9 +3043,11 @@ export function createVirtualHalSimConfigPromotionCandidateReport(options = {})
blockedCandidateIds,
missingManifestFiles,
missingRequiredReports,
familyRows,
rows,
summaryRows: [
{ id: "candidates", label: "Promotion candidates", value: `${rows.length}` },
{ id: "families", label: "Promotion families", value: `${familyRows.length}` },
{ id: "blocked-family", label: "Blocked-family exclusion", value: blockedCandidateIds.length === 0 ? "passed" : `blocked ${blockedCandidateIds.length}` },
{ id: "reports", label: "Virtual HAL reports", value: missingRequiredReports.length === 0 ? "complete" : `missing ${missingRequiredReports.length}` },
{ id: "manifest", label: "Manifest check", value: manifestProvided ? (missingManifestFiles.length === 0 ? "passed" : "failed") : "not provided" },
@@ -2872,6 +3059,7 @@ export function createVirtualHalSimConfigPromotionCandidateReport(options = {})
export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
const fixtures = options.fixtures ?? VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES;
const blockedFixtures = options.blockedFixtures ?? VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES;
const auditCandidates = options.auditCandidates ?? VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES;
const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries);
const manifestSet = new Set([
...sourceManifestSetFromText(options.manifestText ?? ""),
@@ -2894,11 +3082,25 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
const statusReady = allowedStatuses.has(fixture.currentNodeInventoryStatus);
const nonMainFixtureReady = fixture.nonMainFixture === true &&
fixture.targetBrowserEvidence === "non-main-fixture-diagnostics";
const declarationEvidence = fixture.declarationEvidence ?? null;
const requiresDeclarationEvidence = typeof fixture.declarationSource === "string" &&
fixture.declarationSource.length > 0;
const declarationEvidenceReady = !requiresDeclarationEvidence || (
declarationEvidence !== null &&
declarationEvidence.sourceFile === fixture.owningIniPath &&
declarationEvidence.key === fixture.declarationSource &&
declarationEvidence.value === fixture.fixturePath.split("/").at(-1) &&
Number.isInteger(declarationEvidence.line) &&
declarationEvidence.line > 0 &&
typeof declarationEvidence.sourceLine === "string" &&
declarationEvidence.sourceLine.includes(`${declarationEvidence.key} = ${declarationEvidence.value}`)
);
const complete = sourceFiles.length > 0 &&
missingManifestFiles.length === 0 &&
blockedKindExcluded &&
statusReady &&
nonMainFixtureReady;
nonMainFixtureReady &&
declarationEvidenceReady;
return {
id: fixture.id,
label: fixture.label,
@@ -2908,6 +3110,9 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
owningIniPath: fixture.owningIniPath,
pairedMainProgram: fixture.pairedMainProgram,
declarationSource: fixture.declarationSource,
declarationEvidence,
declarationEvidenceReady,
requiresDeclarationEvidence,
currentNodeInventoryStatus: fixture.currentNodeInventoryStatus,
dependencyClass: fixture.dependencyClass,
targetBrowserEvidence: fixture.targetBrowserEvidence,
@@ -2924,6 +3129,9 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
});
const missingFixtures = rows.filter((row) => !row.complete).map((row) => row.id);
const blockedFixtureIds = rows.filter((row) => !row.blockedKindExcluded).map((row) => row.id);
const declarationEvidenceViolations = rows
.filter((row) => row.requiresDeclarationEvidence === true && row.declarationEvidenceReady !== true)
.map((row) => row.id);
const standaloneMainViolations = rows
.filter((row) => row.nonMainFixture !== true || row.targetBrowserEvidence !== "non-main-fixture-diagnostics")
.map((row) => row.id);
@@ -2931,6 +3139,8 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
const positiveFixturePaths = new Set(rows.map((row) => normalizeLinuxCncSourcePath(row.fixturePath)));
const blockedRows = blockedFixtures.map((fixture) => {
const excludedFromPositiveFixtures = !positiveFixturePaths.has(normalizeLinuxCncSourcePath(fixture.fixturePath));
const boundaryEvidence = fixture.boundaryEvidence ?? {};
const boundaryEvidenceReady = isGeneratedBoundaryEvidenceReady(boundaryEvidence, fixture.fixturePath, fixture.owningIniPath);
return {
id: fixture.id,
source: "linuxcnc-configs-sim-source-derived-virtual-hal-macro-load-blocked-fixture",
@@ -2939,19 +3149,61 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
blockedKind: fixture.blockedKind,
dependencyClass: fixture.dependencyClass,
excludedFromPositiveFixtures,
boundaryEvidence,
boundaryEvidenceReady,
reason: fixture.reason,
complete: excludedFromPositiveFixtures,
complete: excludedFromPositiveFixtures && boundaryEvidenceReady,
};
});
const blockedFixturePromotionViolations = blockedRows
.filter((row) => row.excludedFromPositiveFixtures !== true)
.map((row) => row.id);
const blockedBoundaryEvidenceViolations = blockedRows
.filter((row) => row.boundaryEvidenceReady !== true)
.map((row) => row.id);
const auditRows = auditCandidates.map((candidate) => {
const excludedFromPositiveFixtures = !positiveFixturePaths.has(normalizeLinuxCncSourcePath(candidate.fixturePath));
const boundaryEvidence = candidate.boundaryEvidence ?? {};
const boundaryEvidenceReady = isGeneratedBoundaryEvidenceReady(boundaryEvidence, candidate.fixturePath, candidate.owningIniPath);
return {
id: candidate.id,
source: "linuxcnc-configs-sim-source-derived-virtual-hal-macro-load-audit-candidate",
fixturePath: candidate.fixturePath,
owningIniPath: candidate.owningIniPath,
dependencyClass: candidate.dependencyClass,
auditStatus: candidate.auditStatus,
promotionAllowed: candidate.promotionAllowed === true,
excludedFromPositiveFixtures,
boundaryEvidence,
boundaryEvidenceReady,
reason: candidate.reason,
complete: excludedFromPositiveFixtures && candidate.promotionAllowed !== true && boundaryEvidenceReady,
};
});
const missingAuditRows = auditRows
.filter((row) => !row.complete)
.map((row) => row.id);
const auditPromotionViolations = auditRows
.filter((row) => row.promotionAllowed !== true && row.excludedFromPositiveFixtures !== true)
.map((row) => row.id);
const auditBoundaryEvidenceViolations = auditRows
.filter((row) => row.boundaryEvidenceReady !== true)
.map((row) => row.id);
const auditPromotionAllowedViolations = auditRows
.filter((row) => row.promotionAllowed === true && row.boundaryEvidenceReady !== true)
.map((row) => row.id);
const complete = rows.length > 0 &&
missingFixtures.length === 0 &&
missingAuditRows.length === 0 &&
blockedFixtureIds.length === 0 &&
declarationEvidenceViolations.length === 0 &&
standaloneMainViolations.length === 0 &&
missingManifestFiles.length === 0 &&
blockedFixturePromotionViolations.length === 0;
blockedFixturePromotionViolations.length === 0 &&
blockedBoundaryEvidenceViolations.length === 0 &&
auditPromotionViolations.length === 0 &&
auditBoundaryEvidenceViolations.length === 0 &&
auditPromotionAllowedViolations.length === 0;
return {
apiName: "linuxcnc-wasm-virtual-hal-sim-config-macro-load-fixture-report",
reportVersion: 1,
@@ -2966,15 +3218,24 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
fixturePaths: rows.map((row) => row.fixturePath),
missingFixtures,
blockedFixtureIds,
declarationEvidenceViolations,
standaloneMainViolations,
blockedFixturePromotionViolations,
blockedBoundaryEvidenceViolations,
missingAuditRows,
auditPromotionViolations,
auditBoundaryEvidenceViolations,
auditPromotionAllowedViolations,
missingManifestFiles,
rows,
blockedRows,
auditRows,
summaryRows: [
{ id: "fixtures", label: "Macro/load fixtures", value: `${rows.length}` },
{ id: "declarations", label: "Declaration evidence", value: declarationEvidenceViolations.length === 0 ? "passed" : `violations ${declarationEvidenceViolations.length}` },
{ id: "non-main", label: "Non-main fixture gate", value: standaloneMainViolations.length === 0 ? "passed" : `violations ${standaloneMainViolations.length}` },
{ id: "blocked-fixture-lock", label: "Blocked fixture lock", value: blockedFixturePromotionViolations.length === 0 ? "passed" : `violations ${blockedFixturePromotionViolations.length}` },
{ id: "blocked-fixture-lock", label: "Blocked fixture lock", value: blockedFixturePromotionViolations.length === 0 && blockedBoundaryEvidenceViolations.length === 0 ? "passed" : "violations" },
{ id: "ui-family-audit", label: "UI-family macro/load audit", value: missingAuditRows.length === 0 && auditPromotionViolations.length === 0 && auditBoundaryEvidenceViolations.length === 0 && auditPromotionAllowedViolations.length === 0 ? "passed" : "violations" },
{ id: "blocked-family", label: "Blocked-family exclusion", value: blockedFixtureIds.length === 0 ? "passed" : `blocked ${blockedFixtureIds.length}` },
{ id: "manifest", label: "Manifest check", value: manifestProvided ? (missingManifestFiles.length === 0 ? "passed" : "failed") : "not provided" },
{ id: "inventory-baseline", label: "Inventory baseline", value: "unchanged" },

View File

@@ -79,6 +79,59 @@ const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = {
unexpectedFail: 0,
};
const DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = {
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae",
};
function isBoundaryEvidenceBaselineFresh(boundaryEvidence = {}) {
const baseline = objectOrEmpty(boundaryEvidence.baselineSummary);
const hashes = objectOrEmpty(boundaryEvidence.sourceArtifactHashes);
return baseline.executed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.executed &&
baseline.passed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.passed &&
baseline.skipped === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
baseline.unexpectedFail === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
Object.entries(DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES)
.every(([artifactPath, expectedHash]) => hashes[artifactPath] === expectedHash);
}
function createPromotionFamilySummaryText(candidateReport = {}) {
const families = arrayOrEmpty(objectOrEmpty(candidateReport).familyRows);
if (families.length === 0) {
return "not provided";
}
return families.map((family) => {
const label = family.id ?? "unknown";
const completeCount = family.completeCount ?? 0;
const candidateCount = family.candidateCount ?? 0;
const state = family.complete === true && family.explicitBrowserDiagnosticsReady === true ? "ready" : "blocked";
return `${label}: ${completeCount}/${candidateCount} ${state}`;
}).join("; ");
}
function createPromotionFamilySummaryRows(candidateReport = {}) {
return arrayOrEmpty(objectOrEmpty(candidateReport).familyRows).map((family) => {
const sourceFileCount = arrayOrEmpty(family.sourceFiles).length;
const candidateCount = family.candidateCount ?? 0;
const completeCount = family.completeCount ?? 0;
const explicitBrowserDiagnosticsReady = family.explicitBrowserDiagnosticsReady === true;
const ready = family.complete === true && explicitBrowserDiagnosticsReady;
return {
id: family.id ?? "unknown",
label: family.id ?? "Unknown promotion family",
value: `${completeCount}/${candidateCount} ${ready ? "ready" : "blocked"}; sources=${sourceFileCount}; diagnostics=${explicitBrowserDiagnosticsReady ? "ready" : "blocked"}`,
sourceFiles: arrayOrEmpty(family.sourceFiles),
candidateCount,
completeCount,
sourceFileCount,
explicitBrowserDiagnosticsReady,
ready,
};
});
}
const BLOCKED_RUNTIME_FAMILIES = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
@@ -152,6 +205,12 @@ const REQUIRED_MACRO_LOAD_FIXTURES = [
{
id: "rose-engine-rcone-macro-load",
fixturePath: "linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
declarationEvidence: {
sourceFile: "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
key: "NGCGUI_SUBFILE",
value: "rcone.ngc",
line: 34,
},
sourceFiles: [
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
@@ -161,6 +220,12 @@ const REQUIRED_MACRO_LOAD_FIXTURES = [
{
id: "external-offsets-queuebuster-macro-load",
fixturePath: "linuxcnc/configs/sim/axis/external_offsets/queuebuster.ngc",
declarationEvidence: {
sourceFile: "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
key: "NGCGUI_SUBFILE",
value: "queuebuster.ngc",
line: 40,
},
sourceFiles: [
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
@@ -181,6 +246,19 @@ const REQUIRED_BLOCKED_MACRO_LOAD_FIXTURES = [
},
];
const REQUIRED_MACRO_LOAD_AUDIT_CANDIDATES = [
{
id: "gscreen-industrial-lathe-wear-toolchange-audit",
fixturePath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
auditStatus: "blocked-missing-owning-ini",
},
{
id: "qtvcp-industrial-lathe-wear-toolchange-audit",
fixturePath: "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
auditStatus: "blocked-missing-owning-ini",
},
];
function isVirtualHalSimConfigSourceCoverageReady(report) {
const coverage = objectOrEmpty(report);
return coverage.apiName === "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report" &&
@@ -218,8 +296,14 @@ function isVirtualHalSimConfigMacroLoadFixtureReportReady(report) {
fixtureReport.inventoryBaselineUnchanged === true &&
arrayOrEmpty(fixtureReport.missingFixtures).length === 0 &&
arrayOrEmpty(fixtureReport.blockedFixtureIds).length === 0 &&
arrayOrEmpty(fixtureReport.declarationEvidenceViolations).length === 0 &&
arrayOrEmpty(fixtureReport.standaloneMainViolations).length === 0 &&
arrayOrEmpty(fixtureReport.blockedFixturePromotionViolations).length === 0 &&
arrayOrEmpty(fixtureReport.blockedBoundaryEvidenceViolations).length === 0 &&
arrayOrEmpty(fixtureReport.missingAuditRows).length === 0 &&
arrayOrEmpty(fixtureReport.auditPromotionViolations).length === 0 &&
arrayOrEmpty(fixtureReport.auditBoundaryEvidenceViolations).length === 0 &&
arrayOrEmpty(fixtureReport.auditPromotionAllowedViolations).length === 0 &&
arrayOrEmpty(fixtureReport.missingManifestFiles).length === 0 &&
REQUIRED_MACRO_LOAD_FIXTURES.every((fixture) =>
rows.some((row) =>
@@ -228,6 +312,16 @@ function isVirtualHalSimConfigMacroLoadFixtureReportReady(report) {
row.nonMainFixture === true &&
row.targetBrowserEvidence === "non-main-fixture-diagnostics" &&
row.fixturePath === fixture.fixturePath &&
(
!fixture.declarationEvidence ||
(
row.declarationEvidenceReady === true &&
objectOrEmpty(row.declarationEvidence).sourceFile === fixture.declarationEvidence.sourceFile &&
objectOrEmpty(row.declarationEvidence).key === fixture.declarationEvidence.key &&
objectOrEmpty(row.declarationEvidence).value === fixture.declarationEvidence.value &&
objectOrEmpty(row.declarationEvidence).line === fixture.declarationEvidence.line
)
) &&
fixture.sourceFiles.every((file) => arrayOrEmpty(row.sourceFiles).includes(file))
)
) &&
@@ -237,6 +331,28 @@ function isVirtualHalSimConfigMacroLoadFixtureReportReady(report) {
row.fixturePath === fixture.fixturePath &&
row.blockedKind === fixture.blockedKind &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
isBoundaryEvidenceBaselineFresh(row.boundaryEvidence) &&
objectOrEmpty(row.boundaryEvidence?.boundarySummary).recommendedBlocked === "UNAVAILABLE" &&
String(objectOrEmpty(row.boundaryEvidence?.boundarySummary).dependencies ?? "").includes("missing_vendored_ini:") &&
objectOrEmpty(row.boundaryEvidence?.iniBoundarySummary).vendored === 0 &&
objectOrEmpty(row.boundaryEvidence?.iniBoundarySummary).reportAvailable === 0 &&
row.complete === true
)
) &&
REQUIRED_MACRO_LOAD_AUDIT_CANDIDATES.every((candidate) =>
arrayOrEmpty(fixtureReport.auditRows).some((row) =>
row.id === candidate.id &&
row.fixturePath === candidate.fixturePath &&
row.auditStatus === candidate.auditStatus &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
isBoundaryEvidenceBaselineFresh(row.boundaryEvidence) &&
objectOrEmpty(row.boundaryEvidence?.boundarySummary).recommendedBlocked === "UNAVAILABLE" &&
String(objectOrEmpty(row.boundaryEvidence?.boundarySummary).dependencies ?? "").includes("missing_vendored_ini:") &&
objectOrEmpty(row.boundaryEvidence?.iniBoundarySummary).vendored === 0 &&
objectOrEmpty(row.boundaryEvidence?.iniBoundarySummary).reportAvailable === 0 &&
row.complete === true
)
);
@@ -277,6 +393,23 @@ function isVirtualHalSimConfigPromotionCandidateReportReady(report) {
hasRequiredPromotionCandidates(rows);
}
function isVirtualHalPromotionCandidateSummaryReady(summary) {
const summaryObject = objectOrEmpty(summary);
return summaryObject.apiName === "real-browser-simulation-promotion-candidate-summary" &&
summaryObject.ready === true &&
summaryObject.phase === "ready" &&
summaryObject.candidateCount === 8 &&
summaryObject.readyCandidateCount === 8 &&
summaryObject.familyCount === 3 &&
summaryObject.sourceFileCount === 17 &&
summaryObject.preferredCandidateId === "qtdragon-multi-joint-on-abort" &&
summaryObject.preferredIniPath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" &&
summaryObject.preferredGcodePath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc" &&
summaryObject.explicitBrowserDiagnosticsCount === 8 &&
summaryObject.inventoryBaseline === "executed=28 passed=28 skipped=131 unexpected_fail=0" &&
arrayOrEmpty(summaryObject.blockedCandidateIds).length === 0;
}
export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact);
const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance);
@@ -284,6 +417,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
const commandScriptFixtures = objectOrEmpty(artifactObject.virtualHalCommandScriptFixtures);
const motionControllerMatrix = objectOrEmpty(artifactObject.virtualHalMotionControllerMatrix);
const promotionCandidates = objectOrEmpty(artifactObject.virtualHalSimConfigPromotionCandidates);
const promotionCandidateSummary = objectOrEmpty(artifactObject.virtualHalPromotionCandidateSummary);
const macroLoadFixtures = objectOrEmpty(artifactObject.virtualHalSimConfigMacroLoadFixtures);
const sessionDiagnostics = objectOrEmpty(artifactObject.virtualHalSessionDiagnostics);
const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement);
@@ -301,6 +435,9 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
const hasPromotionCandidates = artifactObject.virtualHalSimConfigPromotionCandidates !== undefined;
const promotionCandidatesReady = !hasPromotionCandidates ||
isVirtualHalSimConfigPromotionCandidateReportReady(promotionCandidates);
const hasPromotionCandidateSummary = artifactObject.virtualHalPromotionCandidateSummary !== undefined;
const promotionCandidateSummaryReady = hasPromotionCandidateSummary &&
isVirtualHalPromotionCandidateSummaryReady(promotionCandidateSummary);
const hasMacroLoadFixtures = artifactObject.virtualHalSimConfigMacroLoadFixtures !== undefined;
const macroLoadFixturesReady = hasMacroLoadFixtures &&
isVirtualHalSimConfigMacroLoadFixtureReportReady(macroLoadFixtures);
@@ -326,6 +463,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
...(sourceComplianceReady ? [] : ["virtualHalSourceCompliance"]),
...(simConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(promotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]),
...(promotionCandidateSummaryReady ? [] : ["virtualHalPromotionCandidateSummary"]),
...(macroLoadFixturesReady ? [] : ["virtualHalSimConfigMacroLoadFixtures"]),
...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]),
...(motionControllerMatrixReady ? [] : ["virtualHalMotionControllerMatrix"]),
@@ -344,6 +482,8 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
simConfigSourceCoverageReady,
promotionCandidatesReady,
hasPromotionCandidates,
promotionCandidateSummaryReady,
hasPromotionCandidateSummary,
macroLoadFixturesReady,
hasMacroLoadFixtures,
commandScriptFixturesReady,
@@ -385,6 +525,11 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
label: "Virtual HAL sim-config promotion candidates",
value: hasPromotionCandidates ? (promotionCandidatesReady ? "ready" : "missing") : "not provided",
},
{
id: "promotion-candidate-summary",
label: "Virtual HAL promotion candidate summary",
value: hasPromotionCandidateSummary ? (promotionCandidateSummaryReady ? "ready" : "missing") : "not provided",
},
{
id: "sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
@@ -1659,6 +1804,8 @@ export function createProjectReleaseReadinessReport({
observedOutputs = [],
simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
virtualHalSimConfigSourceCoverage = null,
virtualHalSimConfigPromotionCandidates = null,
virtualHalSimConfigMacroLoadFixtures = null,
virtualHalMotionControllerMatrix = null,
blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES,
promotedRuntimeFamilies = [],
@@ -1695,6 +1842,14 @@ export function createProjectReleaseReadinessReport({
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
);
const virtualHalSimConfigPromotionCandidatesReady = isVirtualHalSimConfigPromotionCandidateReportReady(
virtualHalSimConfigPromotionCandidates,
);
const virtualHalPromotionFamilySummary = createPromotionFamilySummaryText(virtualHalSimConfigPromotionCandidates);
const virtualHalPromotionFamilyRows = createPromotionFamilySummaryRows(virtualHalSimConfigPromotionCandidates);
const virtualHalSimConfigMacroLoadFixturesReady = isVirtualHalSimConfigMacroLoadFixtureReportReady(
virtualHalSimConfigMacroLoadFixtures,
);
const virtualHalMotionControllerMatrixReady = isVirtualHalMotionControllerMatrixReady(
virtualHalMotionControllerMatrix,
);
@@ -1704,6 +1859,8 @@ export function createProjectReleaseReadinessReport({
const ready = releaseGatePassed &&
inventoryReady &&
virtualHalSimConfigSourceCoverageReady &&
virtualHalSimConfigPromotionCandidatesReady &&
virtualHalSimConfigMacroLoadFixturesReady &&
virtualHalMotionControllerMatrixReady &&
blockedRuntimeReady &&
axisScreenshotArtifactsReady;
@@ -1711,6 +1868,8 @@ export function createProjectReleaseReadinessReport({
...(releaseGatePassed ? [] : ["project-release-gate"]),
...(inventoryReady ? [] : ["sim-config-inventory"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]),
...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtual-hal-sim-config-promotion-candidates"]),
...(virtualHalSimConfigMacroLoadFixturesReady ? [] : ["virtual-hal-sim-config-macro-load-fixtures"]),
...(virtualHalMotionControllerMatrixReady ? [] : ["virtual-hal-motion-controller-matrix"]),
...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]),
...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]),
@@ -1730,6 +1889,12 @@ export function createProjectReleaseReadinessReport({
simConfigInventory: inventory,
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigSourceCoverageReady,
virtualHalSimConfigPromotionCandidates,
virtualHalSimConfigPromotionCandidatesReady,
virtualHalPromotionFamilySummary,
virtualHalPromotionFamilyRows,
virtualHalSimConfigMacroLoadFixtures,
virtualHalSimConfigMacroLoadFixturesReady,
virtualHalMotionControllerMatrix,
virtualHalMotionControllerMatrixReady,
blockedRuntimeFamilies: blockedFamilies,
@@ -1758,6 +1923,27 @@ export function createProjectReleaseReadinessReport({
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: virtualHalSimConfigPromotionCandidatesReady ? "ready" : "missing",
},
{
id: "virtual-hal-promotion-families",
label: "Virtual HAL promotion families",
value: virtualHalPromotionFamilySummary,
},
...virtualHalPromotionFamilyRows.map((row) => ({
id: `virtual-hal-promotion-family-${row.id}`,
label: `Promotion family ${row.label}`,
value: row.value,
sourceFiles: row.sourceFiles,
})),
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: virtualHalSimConfigMacroLoadFixturesReady ? "ready" : "missing",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",
@@ -1786,6 +1972,8 @@ export function createProjectReleaseReadinessSummaryViewModel(
const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix);
const simConfigInventory = objectOrEmpty(report?.simConfigInventory);
const virtualHalSimConfigSourceCoverageReady = report?.virtualHalSimConfigSourceCoverageReady === true;
const virtualHalSimConfigPromotionCandidatesReady = report?.virtualHalSimConfigPromotionCandidatesReady === true;
const virtualHalSimConfigMacroLoadFixturesReady = report?.virtualHalSimConfigMacroLoadFixturesReady === true;
const virtualHalMotionControllerMatrixReady = report?.virtualHalMotionControllerMatrixReady === true;
const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary);
const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies);
@@ -1823,6 +2011,16 @@ export function createProjectReleaseReadinessSummaryViewModel(
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: virtualHalSimConfigPromotionCandidatesReady ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: virtualHalSimConfigMacroLoadFixturesReady ? "ready" : "missing",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",
@@ -1910,6 +2108,27 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
label: "Virtual HAL sim-config source coverage",
value: validation?.virtualHalSimConfigSourceCoverageReady === true ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: validation?.virtualHalSimConfigPromotionCandidatesReady === true ? "ready" : "missing",
},
{
id: "virtual-hal-promotion-families",
label: "Virtual HAL promotion families",
value: validation?.virtualHalPromotionFamilySummary ?? "not provided",
},
...arrayOrEmpty(validation?.virtualHalPromotionFamilyRows).map((row) => ({
id: `virtual-hal-promotion-family-${row.id}`,
label: `Promotion family ${row.label}`,
value: row.value,
sourceFiles: row.sourceFiles,
})),
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: validation?.virtualHalSimConfigMacroLoadFixturesReady === true ? "ready" : "missing",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",
@@ -2179,6 +2398,7 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
let diagnosticsHttpStatus = null;
let diagnosticsFetchError = null;
let diagnosticsValidation = null;
let diagnosticsArtifact = null;
const diagnosticsMissing = [];
if (diagnosticsUrl) {
try {
@@ -2190,7 +2410,7 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({});
} else {
diagnosticsFetched = true;
const diagnosticsArtifact = JSON.parse(await diagnosticsResponse.text());
diagnosticsArtifact = JSON.parse(await diagnosticsResponse.text());
diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(diagnosticsArtifact);
diagnosticsMissing.push(...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`));
if (requireSavedSessionDiagnostics && diagnosticsValidation.hasSessionDiagnostics !== true) {
@@ -2226,6 +2446,7 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
jsonWorkflow,
validation: jsonWorkflow.validation,
diagnosticsValidation,
diagnosticsArtifact,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing,
@@ -2305,6 +2526,8 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
workflow = {},
) {
const missing = arrayOrEmpty(workflow?.missing);
const promotionCandidateSummary = objectOrEmpty(workflow?.diagnosticsArtifact?.virtualHalPromotionCandidateSummary);
const promotionCandidateSummaryReady = promotionCandidateSummary.ready === true;
const ready = workflow?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
@@ -2340,6 +2563,17 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
label: "Artifact validation",
value: workflow?.validation?.ready === true ? "ready" : "blocked",
},
{
id: "virtual-hal-promotion-families",
label: "Virtual HAL promotion families",
value: workflow?.validation?.virtualHalPromotionFamilySummary ?? "not provided",
},
...arrayOrEmpty(workflow?.validation?.virtualHalPromotionFamilyRows).map((row) => ({
id: `virtual-hal-promotion-family-${row.id}`,
label: `Promotion family ${row.label}`,
value: row.value,
sourceFiles: row.sourceFiles,
})),
{
id: "browser-diagnostics",
label: "Browser diagnostics",
@@ -2347,6 +2581,41 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
? (workflow?.diagnosticsValidation?.ready === true ? "ready" : "blocked")
: "not requested",
},
{
id: "virtual-hal-promotion-candidate-summary",
label: "Virtual HAL promotion candidate summary",
value: workflow?.diagnosticsUrl
? (promotionCandidateSummaryReady ? "ready" : "not provided")
: "not requested",
},
{
id: "virtual-hal-promotion-candidate",
label: "Virtual HAL promotion candidate",
value: promotionCandidateSummary.preferredCandidateId ?? "not provided",
},
{
id: "virtual-hal-promotion-candidate-ini",
label: "Candidate INI",
value: promotionCandidateSummary.preferredIniPath ?? "not provided",
},
{
id: "virtual-hal-promotion-candidate-gcode",
label: "Candidate G-code",
value: promotionCandidateSummary.preferredGcodePath ?? "not provided",
},
{
id: "virtual-hal-promotion-candidate-count",
label: "Candidate readiness",
value: Number.isFinite(promotionCandidateSummary.readyCandidateCount) &&
Number.isFinite(promotionCandidateSummary.candidateCount)
? `${promotionCandidateSummary.readyCandidateCount}/${promotionCandidateSummary.candidateCount} ready`
: "not provided",
},
{
id: "virtual-hal-promotion-candidate-baseline",
label: "Candidate inventory baseline",
value: promotionCandidateSummary.inventoryBaseline ?? "not provided",
},
{
id: "saved-session-diagnostics",
label: "Saved-session diagnostics",
@@ -2466,6 +2735,8 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const releaseGate = objectOrEmpty(artifactObject.releaseGate);
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const virtualHalSimConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage);
const virtualHalSimConfigPromotionCandidates = objectOrEmpty(artifactObject.virtualHalSimConfigPromotionCandidates);
const virtualHalSimConfigMacroLoadFixtures = objectOrEmpty(artifactObject.virtualHalSimConfigMacroLoadFixtures);
const virtualHalMotionControllerMatrix = objectOrEmpty(artifactObject.virtualHalMotionControllerMatrix);
const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
@@ -2515,6 +2786,14 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
) && artifactObject.virtualHalSimConfigSourceCoverageReady === true;
const virtualHalSimConfigPromotionCandidatesReady = isVirtualHalSimConfigPromotionCandidateReportReady(
virtualHalSimConfigPromotionCandidates,
) && artifactObject.virtualHalSimConfigPromotionCandidatesReady === true;
const virtualHalPromotionFamilySummary = createPromotionFamilySummaryText(virtualHalSimConfigPromotionCandidates);
const virtualHalPromotionFamilyRows = createPromotionFamilySummaryRows(virtualHalSimConfigPromotionCandidates);
const virtualHalSimConfigMacroLoadFixturesReady = isVirtualHalSimConfigMacroLoadFixtureReportReady(
virtualHalSimConfigMacroLoadFixtures,
) && artifactObject.virtualHalSimConfigMacroLoadFixturesReady === true;
const virtualHalMotionControllerMatrixReady = isVirtualHalMotionControllerMatrixReady(
virtualHalMotionControllerMatrix,
) && artifactObject.virtualHalMotionControllerMatrixReady === true;
@@ -2556,6 +2835,8 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]),
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]),
...(virtualHalSimConfigMacroLoadFixturesReady ? [] : ["virtualHalSimConfigMacroLoadFixtures"]),
...(virtualHalMotionControllerMatrixReady ? [] : ["virtualHalMotionControllerMatrix"]),
...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",")
? []
@@ -2591,6 +2872,10 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady,
virtualHalSimConfigSourceCoverageReady,
virtualHalSimConfigPromotionCandidatesReady,
virtualHalPromotionFamilySummary,
virtualHalPromotionFamilyRows,
virtualHalSimConfigMacroLoadFixturesReady,
virtualHalMotionControllerMatrixReady,
axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady,
axisScreenshotArtifactCount: axisScreenshotArtifacts.length,
@@ -2636,6 +2921,27 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: virtualHalSimConfigPromotionCandidatesReady ? "ready" : "missing",
},
{
id: "virtual-hal-promotion-families",
label: "Virtual HAL promotion families",
value: virtualHalPromotionFamilySummary,
},
...virtualHalPromotionFamilyRows.map((row) => ({
id: `virtual-hal-promotion-family-${row.id}`,
label: `Promotion family ${row.label}`,
value: row.value,
sourceFiles: row.sourceFiles,
})),
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: virtualHalSimConfigMacroLoadFixturesReady ? "ready" : "missing",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",

File diff suppressed because it is too large Load Diff

View File

@@ -101,6 +101,24 @@
font: 13px/1.4 "Segoe UI", "Noto Sans", sans-serif;
cursor: pointer;
}
.source-filter-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 8px;
}
.source-filter-controls label {
color: var(--muted);
font: 12px/1.4 "Segoe UI", "Noto Sans", sans-serif;
}
.source-filter-controls select {
width: 100%;
min-height: 32px;
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 8px;
color: var(--text);
font: 13px/1.4 "Segoe UI", "Noto Sans", sans-serif;
}
</style>
</head>
<body>
@@ -135,6 +153,15 @@
<p data-workflow-overview-release-readiness-artifact-gate-execution-summary-status>Waiting for URL workflow gate evidence.</p>
<dl data-workflow-overview-release-readiness-artifact-gate-execution-summary-rows></dl>
</section>
<section class="overview-status" aria-label="Promotion candidate drilldown" data-workflow-overview-promotion-candidate-drilldown>
<p data-workflow-overview-promotion-candidate-drilldown-status>Waiting for promotion candidate evidence.</p>
<dl data-workflow-overview-promotion-candidate-drilldown-rows></dl>
</section>
<section class="overview-status" aria-label="Promotion family source filter" data-workflow-overview-promotion-family-source-filter>
<p data-workflow-overview-promotion-family-source-filter-status>Waiting for promotion family source evidence.</p>
<div class="source-filter-controls" data-workflow-overview-promotion-family-source-filter-controls></div>
<dl data-workflow-overview-promotion-family-source-filter-rows></dl>
</section>
</section>
</main>
<script type="module">
@@ -178,6 +205,21 @@
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan,
handleIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionChange,
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls,
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomContract,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomReadiness,
renderIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanDomReadiness,
@@ -248,8 +290,53 @@
renderState: gateExecutionSummaryRenderState,
documentRef: document,
});
const promotionCandidateDrilldown =
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel(summary, {
diagnosticsArtifact: workflow.diagnosticsArtifact,
});
const promotionCandidateDrilldownRenderState =
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState(
promotionCandidateDrilldown,
);
const promotionCandidateDrilldownMountResult =
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState({
renderState: promotionCandidateDrilldownRenderState,
documentRef: document,
});
const promotionFamilyRowsRenderState =
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState(summary);
const promotionFamilySourceFilter =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel(
promotionFamilyRowsRenderState,
);
const promotionFamilySourceFilterMountResult =
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState({
viewModel: promotionFamilySourceFilter,
documentRef: document,
});
const promotionFamilySourceFilterBinding =
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls({
renderState: promotionFamilyRowsRenderState,
viewModel: promotionFamilySourceFilter,
documentRef: document,
render: true,
onChange: (changeResult) => {
document.body.dataset.promotionFamilySourceFilterFamily =
changeResult.selectedFamilyId;
document.body.dataset.promotionFamilySourceFilterSourceKind =
changeResult.selectedSourceKind;
document.body.dataset.promotionFamilySourceFilterCount =
String(changeResult.actionPlan.filteredSourceFileCount);
},
});
document.body.dataset.releaseArtifactUrlWorkflowPhase = workflow.phase;
document.body.dataset.releaseArtifactUrlWorkflowReady = workflow.ready ? "true" : "false";
document.body.dataset.promotionCandidateDrilldownReady =
promotionCandidateDrilldown.ready ? "true" : "false";
document.body.dataset.promotionFamilySourceFilterReady =
promotionFamilySourceFilter.ready ? "true" : "false";
document.body.dataset.promotionFamilySourceFilterCount =
String(promotionFamilySourceFilter.filteredSourceFileCount);
return {
workflow,
summary,
@@ -261,6 +348,13 @@
gateExecutionSummary,
gateExecutionSummaryRenderState,
gateExecutionSummaryMountResult,
promotionCandidateDrilldown,
promotionCandidateDrilldownRenderState,
promotionCandidateDrilldownMountResult,
promotionFamilyRowsRenderState,
promotionFamilySourceFilter,
promotionFamilySourceFilterMountResult,
promotionFamilySourceFilterBinding,
};
}
@@ -362,6 +456,69 @@
options ?? { documentRef: document },
)
),
getWorkflowOverviewPromotionFamilyRowsRenderState: (summaryViewModel) => (
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState(summaryViewModel)
),
getWorkflowOverviewPromotionFamilySourceFilterViewModel: (renderState, options) => (
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel(renderState, options)
),
getWorkflowOverviewPromotionFamilySourceFilterDomContract: (options) => (
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract(options)
),
getWorkflowOverviewPromotionFamilySourceFilterDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness(
options ?? { documentRef: document },
)
),
getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan: (renderState, options) => (
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan(renderState, options)
),
handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange: (options) => (
handleIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionChange(
options ?? { documentRef: document },
)
),
bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls: (options) => (
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewPromotionFamilySourceFilterState: (viewModel, options) => (
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState(
viewModel,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewPromotionFamilySourceFilterState: (options) => (
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewPromotionCandidateDrilldownViewModel: (summaryViewModel, options) => (
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel(summaryViewModel, options)
),
getWorkflowOverviewPromotionCandidateDrilldownRenderState: (viewModel) => (
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState(viewModel)
),
getWorkflowOverviewPromotionCandidateDrilldownDomContract: (options) => (
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomContract(options)
),
getWorkflowOverviewPromotionCandidateDrilldownDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewPromotionCandidateDrilldownState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewPromotionCandidateDrilldownState: (options) => (
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanRenderState: (actionPlan) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanRenderState(actionPlan)
),

View File

@@ -683,6 +683,40 @@
font-weight: 700;
margin-right: 4px;
}
.promotion-family-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 4px 7px;
border-top: 1px solid #c4c9cc;
padding-top: 4px;
}
.promotion-family-panel div {
border: 1px solid #b5bcc1;
background: #f1f1f1;
padding: 3px 4px;
}
.promotion-candidate-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 4px 7px;
border-top: 1px solid #c4c9cc;
padding-top: 4px;
}
.promotion-candidate-panel div {
min-height: 27px;
border: 1px solid #b5bcc1;
background: #f7f7f7;
padding: 3px 4px;
}
.promotion-candidate-panel strong {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
}
.status-history {
border-top: 1px solid var(--line);
background: #f2f2f2;
@@ -1709,6 +1743,8 @@ M2
<div><span>Last error</span><strong data-diagnostics="last-error">none</strong></div>
<div><span>Readiness</span><strong data-diagnostics="readiness">unchecked</strong></div>
<div><span>Shortcut</span><strong data-diagnostics="shortcut">-</strong></div>
<div class="promotion-family-panel" data-promotion-family-rows></div>
<div class="promotion-candidate-panel" data-promotion-candidate-summary></div>
<div class="tool-table-mini" data-axis-shell="tool-table">
<table aria-label="Loaded tool table">
<thead>
@@ -2553,6 +2589,99 @@ M2
});
}
function renderPromotionFamilyRows(report = createVirtualHalSimConfigPromotionCandidateReport()) {
const container = document.querySelector("[data-promotion-family-rows]");
if (!container) {
return [];
}
const rows = Array.isArray(report.familyRows) ? report.familyRows : [];
container.textContent = "";
document.body.dataset.promotionFamilyReady = rows.every((row) =>
row.complete === true && row.explicitBrowserDiagnosticsReady === true
) ? "true" : "false";
document.body.dataset.promotionFamilyCount = String(rows.length);
for (const row of rows) {
const item = document.createElement("div");
item.dataset.promotionFamilyRow = row.id ?? "unknown";
item.dataset.promotionFamilyReady = row.complete === true && row.explicitBrowserDiagnosticsReady === true ? "true" : "false";
const label = document.createElement("span");
label.textContent = row.id ?? "unknown";
const value = document.createElement("strong");
value.dataset.promotionFamilyValue = row.id ?? "unknown";
value.textContent = `${row.completeCount ?? 0}/${row.candidateCount ?? 0} ready; sources=${row.sourceFiles?.length ?? 0}`;
item.append(label, value);
container.append(item);
}
return rows;
}
function createVirtualHalPromotionCandidateSummary(
report = createVirtualHalSimConfigPromotionCandidateReport(),
) {
const rows = Array.isArray(report.rows) ? report.rows : [];
const familyRows = Array.isArray(report.familyRows) ? report.familyRows : [];
const preferredCandidate = rows.find((row) => row.id === "qtdragon-multi-joint-on-abort") ?? rows[0] ?? null;
const readyRows = rows.filter((row) => row.complete === true);
const blockedRows = rows.filter((row) => row.blockedKind !== "-");
const explicitBrowserRows = rows.filter((row) => row.targetBrowserEvidence === "explicit-browser-diagnostics");
const sourceFiles = [...new Set(rows.flatMap((row) => row.sourceFiles ?? []))];
const ready = report.complete === true &&
readyRows.length === rows.length &&
blockedRows.length === 0 &&
explicitBrowserRows.length === rows.length &&
report.inventoryBaselineUnchanged === true;
return {
apiName: "real-browser-simulation-promotion-candidate-summary",
summaryVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
candidateCount: rows.length,
readyCandidateCount: readyRows.length,
familyCount: familyRows.length,
sourceFileCount: sourceFiles.length,
preferredCandidateId: preferredCandidate?.id ?? null,
preferredIniPath: preferredCandidate?.iniPath ?? null,
preferredGcodePath: preferredCandidate?.gcodePath ?? null,
blockedCandidateIds: blockedRows.map((row) => row.id),
explicitBrowserDiagnosticsCount: explicitBrowserRows.length,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
sourceFiles,
rows: [
{ id: "candidate-count", label: "Candidates", value: `${readyRows.length}/${rows.length} ready` },
{ id: "family-count", label: "Families", value: `${familyRows.length}` },
{ id: "preferred-candidate", label: "First case", value: preferredCandidate?.id ?? "missing" },
{ id: "source-files", label: "Source files", value: `${sourceFiles.length}` },
{ id: "inventory-baseline", label: "Inventory", value: "28/28 pass; 131 skip unchanged" },
{ id: "blocked-candidates", label: "Hard blocks", value: blockedRows.length === 0 ? "excluded" : blockedRows.map((row) => row.id).join(", ") },
{ id: "browser-evidence", label: "Browser evidence", value: `${explicitBrowserRows.length}/${rows.length} explicit` },
{ id: "preferred-ini", label: "INI", value: preferredCandidate?.iniPath ?? "missing" },
],
};
}
function renderPromotionCandidateSummary(summary = createVirtualHalPromotionCandidateSummary()) {
const container = document.querySelector("[data-promotion-candidate-summary]");
if (!container) {
return summary;
}
container.textContent = "";
document.body.dataset.promotionCandidateReady = summary.ready ? "true" : "false";
document.body.dataset.promotionCandidateCount = String(summary.candidateCount);
document.body.dataset.promotionCandidatePreferred = summary.preferredCandidateId ?? "";
for (const row of summary.rows) {
const item = document.createElement("div");
item.dataset.promotionCandidateSummaryRow = row.id;
const label = document.createElement("span");
label.textContent = row.label;
const value = document.createElement("strong");
value.dataset.promotionCandidateSummaryValue = row.id;
value.textContent = row.value;
item.append(label, value);
container.append(item);
}
return summary;
}
function renderMachineStatusDom(machineStatus) {
const statusMap = {
"spindle-state": machineStatus.spindle.state,
@@ -2591,6 +2720,9 @@ M2
renderVirtualHalDro();
}
renderLimitsHomeState(createVirtualHalLimitsHomeState());
const promotionCandidateReport = createVirtualHalSimConfigPromotionCandidateReport();
renderPromotionFamilyRows(promotionCandidateReport);
renderPromotionCandidateSummary(createVirtualHalPromotionCandidateSummary(promotionCandidateReport));
document.querySelector("[data-axis-machine-control-state]")?.setAttribute("data-state", "ready");
return cloneVirtualHalState();
}
@@ -2642,6 +2774,7 @@ M2
virtualHalSourceCompliance: createVirtualHalSourceComplianceReport(),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalSimConfigPromotionCandidates: createVirtualHalSimConfigPromotionCandidateReport(),
virtualHalPromotionCandidateSummary: createVirtualHalPromotionCandidateSummary(),
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport(),
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(),
virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(),
@@ -4081,6 +4214,8 @@ M2
getVirtualHalSourceComplianceReport: () => createVirtualHalSourceComplianceReport(),
getVirtualHalSimConfigSourceCoverageReport: () => createVirtualHalSimConfigSourceCoverageReport(),
getVirtualHalSimConfigPromotionCandidateReport: () => createVirtualHalSimConfigPromotionCandidateReport(),
getVirtualHalPromotionCandidateSummary: () => renderPromotionCandidateSummary(),
getVirtualHalPromotionFamilyRows: () => renderPromotionFamilyRows(),
getVirtualHalSimConfigMacroLoadFixtureReport: () => createVirtualHalSimConfigMacroLoadFixtureReport(),
getVirtualHalCommandScriptFixtureReport: () => createVirtualHalCommandScriptFixtureReport(),
getVirtualHalMotionControllerMatrixReport: () => createVirtualHalMotionControllerMatrixReport(),

View File

@@ -19,6 +19,7 @@ export {
VIRTUAL_HAL_AXES,
VIRTUAL_HAL_AXISUI_PINS,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,

View File

@@ -66,6 +66,15 @@
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState ||
!workflowApi?.getWorkflowOverviewPromotionFamilyRowsRenderState ||
!workflowApi?.getWorkflowOverviewPromotionFamilySourceFilterViewModel ||
!workflowApi?.getWorkflowOverviewPromotionFamilySourceFilterDomContract ||
!workflowApi?.getWorkflowOverviewPromotionFamilySourceFilterDomReadiness ||
!workflowApi?.getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan ||
!workflowApi?.handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange ||
!workflowApi?.bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls ||
!workflowApi?.renderWorkflowOverviewPromotionFamilySourceFilterState ||
!workflowApi?.mountWorkflowOverviewPromotionFamilySourceFilterState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlanDomReadiness ||
@@ -381,6 +390,22 @@
manifestEntries: VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
}),
}),
virtualHalPromotionCandidateSummary: {
apiName: "real-browser-simulation-promotion-candidate-summary",
summaryVersion: 1,
phase: "ready",
ready: true,
candidateCount: 8,
readyCandidateCount: 8,
familyCount: 3,
sourceFileCount: 17,
preferredCandidateId: "qtdragon-multi-joint-on-abort",
preferredIniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
manifestEntries: [
...VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
@@ -543,6 +568,11 @@
assertEqual(releaseArtifactUrlWorkflow.httpStatus, 200, "workflow overview release artifact URL workflow status");
assertEqual(releaseArtifactUrlWorkflow.diagnosticsHttpStatus, 200, "workflow overview diagnostics artifact URL workflow status");
assertEqual(releaseArtifactUrlWorkflow.diagnosticsValidation.ready, true, "workflow overview diagnostics artifact validation");
assertEqual(
releaseArtifactUrlWorkflow.diagnosticsArtifact.virtualHalPromotionCandidateSummary.ready,
true,
"workflow overview diagnostics promotion candidate summary",
);
assertEqual(releaseArtifactUrlWorkflow.diagnosticsValidation.hasSessionDiagnostics, true, "workflow overview diagnostics saved-session presence");
assertEqual(releaseArtifactUrlWorkflow.diagnosticsValidation.sessionDiagnosticsReady, true, "workflow overview diagnostics saved-session validation");
assertEqual(
@@ -567,7 +597,7 @@
);
assertEqual(
releaseArtifactUrlWorkflowRenderResult.rowCount,
12,
18,
"workflow overview release artifact URL workflow render rows",
);
assertEqual(
@@ -575,6 +605,36 @@
true,
"workflow overview release artifact URL workflow diagnostics row",
);
assertEqual(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("virtual-hal-promotion-candidate-summary"),
true,
"workflow overview release artifact URL workflow candidate summary row",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate")?.value,
"qtdragon-multi-joint-on-abort",
"workflow overview release artifact URL workflow candidate id",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-ini")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
"workflow overview release artifact URL workflow candidate INI",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-gcode")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
"workflow overview release artifact URL workflow candidate G-code",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-count")?.value,
"8/8 ready",
"workflow overview release artifact URL workflow candidate count",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0",
"workflow overview release artifact URL workflow candidate baseline",
);
assertEqual(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("saved-session-diagnostics"),
true,
@@ -626,16 +686,71 @@
true,
"workflow overview fixed URL workflow gate execution summary row",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilyRowsRenderState.ready,
true,
"workflow overview fixed URL workflow promotion family rows ready",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilyRowsRenderState.sourceFileCount,
17,
"workflow overview fixed URL workflow promotion family source count",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilySourceFilter.ready,
true,
"workflow overview fixed URL workflow source filter readiness",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilySourceFilter.filteredSourceFileCount,
17,
"workflow overview fixed URL workflow source filter default count",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilySourceFilterMountResult.ready,
true,
"workflow overview fixed URL workflow source filter mount readiness",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilySourceFilterBinding.ready,
true,
"workflow overview fixed URL workflow source filter binding readiness",
);
assertEqual(
releaseArtifactUrlFormResult.promotionFamilySourceFilterBinding.boundControlCount,
2,
"workflow overview fixed URL workflow source filter bound controls",
);
assertEqual(
workflowDoc.body.dataset.releaseArtifactUrlWorkflowReady,
"true",
"workflow overview fixed URL workflow form body ready",
);
assertEqual(
workflowDoc.body.dataset.promotionFamilySourceFilterReady,
"true",
"workflow overview fixed URL workflow source filter body ready",
);
assertEqual(
workflowDoc.body.dataset.promotionFamilySourceFilterCount,
"17",
"workflow overview fixed URL workflow source filter body default count",
);
assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="browser-diagnostics"]').textContent,
"ready",
"workflow overview fixed URL workflow form browser diagnostics row",
);
assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="virtual-hal-promotion-candidate"]').textContent,
"qtdragon-multi-joint-on-abort",
"workflow overview fixed URL workflow form candidate row",
);
assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="virtual-hal-promotion-candidate-count"]').textContent,
"8/8 ready",
"workflow overview fixed URL workflow form candidate count row",
);
assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-action-plan-value="next-command"]').textContent,
"none",
@@ -646,6 +761,59 @@
true,
"workflow overview fixed URL workflow gate execution summary DOM row",
);
const sourceFilterMount = workflowDoc.querySelector("[data-workflow-overview-promotion-family-source-filter]");
const sourceFilterFamilySelect = workflowDoc.querySelector(
'[data-workflow-overview-promotion-family-source-filter-select="family"]',
);
const sourceFilterKindSelect = workflowDoc.querySelector(
'[data-workflow-overview-promotion-family-source-filter-select="source-kind"]',
);
assertEqual(
sourceFilterMount.dataset.selectedFamilyId,
"all",
"workflow overview source filter default family",
);
assertEqual(
sourceFilterFamilySelect?.value,
"all",
"workflow overview source filter family select default",
);
assertEqual(
sourceFilterKindSelect?.value,
"all",
"workflow overview source filter kind select default",
);
sourceFilterFamilySelect.value = "virtual-hal-promotion-family-rose-engine-rcone-demo";
sourceFilterFamilySelect.dispatchEvent(new Event("change", { bubbles: true }));
assertEqual(
workflowDoc.body.dataset.promotionFamilySourceFilterFamily,
"virtual-hal-promotion-family-rose-engine-rcone-demo",
"workflow overview source filter change body family",
);
assertEqual(
workflowDoc.body.dataset.promotionFamilySourceFilterCount,
"2",
"workflow overview source filter change source count",
);
assertEqual(
sourceFilterMount.dataset.selectedFamilyId,
"virtual-hal-promotion-family-rose-engine-rcone-demo",
"workflow overview source filter changed mount family",
);
assertEqual(
workflowDoc.querySelectorAll(
'[data-workflow-overview-promotion-family-source-filter-type="source-row"]',
).length,
4,
"workflow overview source filter changed source DOM nodes",
);
assertEqual(
workflowDoc.querySelector(
'[data-workflow-overview-promotion-family-source-filter-value="linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"]',
)?.textContent,
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
"workflow overview source filter changed rose engine source row",
);
assertEqual(
releaseArtifactJsonWorkflow.statusLine,
"Ready: Artifact JSON workflow complete",

View File

@@ -97,6 +97,7 @@
"native-runtime-probe-summary.tsv",
"next-boundary-recommendations.tsv",
"next-boundary-worklist.tsv",
"promotion-candidates.tsv",
"python-remap-boundary-summary.tsv",
"python-remap-family-summary.tsv",
"python-remap-native-runtime-alignment.tsv",
@@ -150,7 +151,7 @@
"build/native/source-probes.tsv",
];
if (wasmArtifactNames.length !== 54 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
if (wasmArtifactNames.length !== 55 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift");
}
if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) {

View File

@@ -244,6 +244,8 @@
!api?.getVirtualHalSourceComplianceReport ||
!api?.getVirtualHalSimConfigSourceCoverageReport ||
!api?.getVirtualHalSimConfigPromotionCandidateReport ||
!api?.getVirtualHalPromotionCandidateSummary ||
!api?.getVirtualHalPromotionFamilyRows ||
!api?.getVirtualHalSimConfigMacroLoadFixtureReport ||
!api?.getVirtualHalCommandScriptFixtureReport ||
!api?.getVirtualHalMotionControllerMatrixReport ||
@@ -409,6 +411,7 @@
throw new Error(`simulation virtual HAL registry/integrity drift: ${JSON.stringify(initialVirtualHalRegistry)}`);
}
const diagnosticsArtifact = api.exportDiagnosticsArtifact();
const promotionCandidateSummary = api.getVirtualHalPromotionCandidateSummary();
const expectedPromotionCandidates = [
[
"qtdragon-multi-joint-on-abort",
@@ -458,6 +461,9 @@
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
false,
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"rcone.ngc",
34,
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
],
@@ -466,6 +472,9 @@
"linuxcnc/configs/sim/axis/external_offsets/queuebuster.ngc",
true,
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"queuebuster.ngc",
40,
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
],
];
@@ -481,6 +490,18 @@
"L4-PYTHON-REMAP",
],
];
const expectedMacroLoadAuditCandidates = [
[
"gscreen-industrial-lathe-wear-toolchange-audit",
"linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
"blocked-missing-owning-ini",
],
[
"qtvcp-industrial-lathe-wear-toolchange-audit",
"linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
"blocked-missing-owning-ini",
],
];
if (
diagnosticsArtifact.apiName !== "real-browser-simulation-diagnostics-artifact" ||
diagnosticsArtifact.statusHistory.length === 0 ||
@@ -495,18 +516,41 @@
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini") ||
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini") ||
diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.complete !== true ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.ready !== true ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.preferredCandidateId !== "qtdragon-multi-joint-on-abort" ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=28 passed=28 skipped=131 unexpected_fail=0" ||
promotionCandidateSummary.ready !== true ||
promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" ||
doc.body.dataset.promotionCandidateReady !== "true" ||
doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "28/28 pass; 131 skip unchanged" ||
!diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.familyRows?.some((row) =>
row.id === "qtdragon-on-abort" &&
row.candidateCount === 6 &&
row.completeCount === 6 &&
row.explicitBrowserDiagnosticsCount === 6 &&
row.explicitBrowserDiagnosticsReady === true &&
row.complete === true
) ||
!expectedPromotionCandidates.every(([id, ...sourceFiles]) =>
diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.rows?.some((row) =>
row.id === id &&
row.complete === true &&
row.currentNodeInventoryStatus === "PASS" &&
row.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
row.targetBrowserEvidence === "explicit-browser-diagnostics" &&
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
)
) ||
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.complete !== true ||
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.blockedFixturePromotionViolations?.length !== 0 ||
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, ...sourceFiles]) =>
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.blockedBoundaryEvidenceViolations?.length !== 0 ||
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.auditPromotionViolations?.length !== 0 ||
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, declarationSourceFile, declarationValue, declarationLine, ...sourceFiles]) =>
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.rows?.some((row) =>
row.id === id &&
row.complete === true &&
@@ -514,6 +558,12 @@
row.targetBrowserEvidence === "non-main-fixture-diagnostics" &&
row.fixturePath === fixturePath &&
row.declaredOnly === declaredOnly &&
row.requiresDeclarationEvidence === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence?.sourceFile === declarationSourceFile &&
row.declarationEvidence?.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence?.value === declarationValue &&
row.declarationEvidence?.line === declarationLine &&
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
)
) ||
@@ -523,6 +573,38 @@
row.fixturePath === fixturePath &&
row.blockedKind === blockedKind &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
row.complete === true
)
) ||
!expectedMacroLoadAuditCandidates.every(([id, fixturePath, auditStatus]) =>
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.auditRows?.some((row) =>
row.id === id &&
row.fixturePath === fixturePath &&
row.auditStatus === auditStatus &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
row.complete === true
)
) ||
@@ -946,6 +1028,7 @@
}
const virtualHalReplacement = api.getVirtualHalSimulationReplacementReport();
const promotionCandidateReport = api.getVirtualHalSimConfigPromotionCandidateReport();
const promotionFamilyRows = api.getVirtualHalPromotionFamilyRows();
const macroLoadFixtureReport = api.getVirtualHalSimConfigMacroLoadFixtureReport();
if (
virtualHalReplacement.apiName !== "linuxcnc-wasm-virtual-hal-simulation-replacement-report" ||
@@ -966,6 +1049,17 @@
!api.getVirtualHalSimConfigSourceCoverageReport().sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc") ||
promotionCandidateReport.complete !== true ||
promotionCandidateReport.inventoryBaselineUnchanged !== true ||
doc.body.dataset.promotionFamilyReady !== "true" ||
doc.body.dataset.promotionFamilyCount !== "3" ||
promotionFamilyRows.length !== 3 ||
![
["qtdragon-on-abort", "6/6 ready; sources=12"],
["vismach-remap-sims", "1/1 ready; sources=3"],
["rose-engine-rcone-demo", "1/1 ready; sources=2"],
].every(([id, value]) =>
doc.querySelector(`[data-promotion-family-row="${id}"]`)?.dataset.promotionFamilyReady === "true" &&
doc.querySelector(`[data-promotion-family-value="${id}"]`)?.textContent === value
) ||
!expectedPromotionCandidates.every(([id, ...sourceFiles]) =>
promotionCandidateReport.rows.some((row) =>
row.id === id &&
@@ -978,13 +1072,21 @@
macroLoadFixtureReport.inventoryBaselineUnchanged !== true ||
macroLoadFixtureReport.standaloneMainViolations.length !== 0 ||
macroLoadFixtureReport.blockedFixturePromotionViolations.length !== 0 ||
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, ...sourceFiles]) =>
macroLoadFixtureReport.blockedBoundaryEvidenceViolations.length !== 0 ||
macroLoadFixtureReport.auditPromotionViolations.length !== 0 ||
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, declarationSourceFile, declarationValue, declarationLine, ...sourceFiles]) =>
macroLoadFixtureReport.rows.some((row) =>
row.id === id &&
row.complete === true &&
row.nonMainFixture === true &&
row.fixturePath === fixturePath &&
row.declaredOnly === declaredOnly &&
row.requiresDeclarationEvidence === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence?.sourceFile === declarationSourceFile &&
row.declarationEvidence?.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence?.value === declarationValue &&
row.declarationEvidence?.line === declarationLine &&
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
)
) ||
@@ -994,6 +1096,38 @@
row.fixturePath === fixturePath &&
row.blockedKind === blockedKind &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
row.complete === true
)
) ||
!expectedMacroLoadAuditCandidates.every(([id, fixturePath, auditStatus]) =>
macroLoadFixtureReport.auditRows.some((row) =>
row.id === id &&
row.fixturePath === fixturePath &&
row.auditStatus === auditStatus &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
row.complete === true
)
) ||

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,8 @@ assert.equal(validation.gateExecutionSummaryReady, true);
assert.equal(validation.gateResultMatrixReady, true);
assert.equal(validation.gateActionPlanReady, true);
assert.equal(validation.virtualHalSimConfigSourceCoverageReady, true);
assert.equal(validation.virtualHalSimConfigPromotionCandidatesReady, true);
assert.equal(validation.virtualHalSimConfigMacroLoadFixturesReady, true);
assert.equal(validation.virtualHalMotionControllerMatrixReady, true);
assert.equal(validation.axisScreenshotArtifactSummaryReady, true);
assert.ok(validation.axisScreenshotArtifactCount === 0 || validation.axisScreenshotArtifactCount === 4);
@@ -60,6 +62,105 @@ assert.equal(artifact.virtualHalSimConfigSourceCoverage.webSimulationSatisfied,
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.virtualHalSimConfigPromotionCandidates.apiName, "linuxcnc-wasm-virtual-hal-sim-config-promotion-candidate-report");
assert.equal(artifact.virtualHalSimConfigPromotionCandidates.complete, true);
assert.equal(artifact.virtualHalSimConfigPromotionCandidatesReady, true);
assert.equal(
artifact.virtualHalPromotionFamilySummary,
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready",
);
assert.deepEqual(
artifact.virtualHalPromotionFamilyRows.map(({ id, value }) => [id, value]),
[
["qtdragon-on-abort", "6/6 ready; sources=12; diagnostics=ready"],
["vismach-remap-sims", "1/1 ready; sources=3; diagnostics=ready"],
["rose-engine-rcone-demo", "1/1 ready; sources=2; diagnostics=ready"],
],
);
assert.equal(
artifact.virtualHalSimConfigPromotionCandidates.familyRows.some((row) =>
row.id === "qtdragon-on-abort" &&
row.candidateCount === 6 &&
row.completeCount === 6 &&
row.explicitBrowserDiagnosticsCount === 6 &&
row.explicitBrowserDiagnosticsReady === true &&
row.complete === true
),
true,
);
assert.equal(
artifact.virtualHalSimConfigPromotionCandidates.rows.some((row) =>
row.id === "rose-engine-rcone-demo" &&
row.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc")
),
true,
);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.apiName, "linuxcnc-wasm-virtual-hal-sim-config-macro-load-fixture-report");
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.complete, true);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixturesReady, true);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.declarationEvidenceViolations.length, 0);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.missingAuditRows.length, 0);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.blockedBoundaryEvidenceViolations.length, 0);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.auditPromotionViolations.length, 0);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.auditBoundaryEvidenceViolations.length, 0);
assert.equal(artifact.virtualHalSimConfigMacroLoadFixtures.auditPromotionAllowedViolations.length, 0);
assert.equal(
artifact.virtualHalSimConfigMacroLoadFixtures.rows.some((row) =>
row.id === "rose-engine-rcone-macro-load" &&
row.requiresDeclarationEvidence === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence.sourceFile === "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini" &&
row.declarationEvidence.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence.value === "rcone.ngc" &&
row.declarationEvidence.line === 34
),
true,
);
assert.equal(
artifact.virtualHalSimConfigMacroLoadFixtures.rows.some((row) =>
row.id === "external-offsets-queuebuster-macro-load" &&
row.requiresDeclarationEvidence === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence.sourceFile === "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini" &&
row.declarationEvidence.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence.value === "queuebuster.ngc" &&
row.declarationEvidence.line === 40
),
true,
);
assert.equal(
artifact.virtualHalSimConfigMacroLoadFixtures.auditRows.some((row) =>
row.id === "gscreen-industrial-lathe-wear-toolchange-audit" &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence.baselineSummary.executed === 28 &&
row.boundaryEvidence.baselineSummary.passed === 28 &&
row.boundaryEvidence.baselineSummary.skipped === 131 &&
row.boundaryEvidence.baselineSummary.unexpectedFail === 0 &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini") &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0
),
true,
);
assert.equal(
artifact.virtualHalSimConfigMacroLoadFixtures.blockedRows.some((row) =>
row.id === "silverdragon-tool-sensor-python-ui-boundary" &&
row.boundaryEvidenceReady === true &&
row.boundaryEvidence.baselineSummary.executed === 28 &&
row.boundaryEvidence.baselineSummary.passed === 28 &&
row.boundaryEvidence.baselineSummary.skipped === 131 &&
row.boundaryEvidence.baselineSummary.unexpectedFail === 0 &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini") &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0
),
true,
);
assert.equal(artifact.virtualHalMotionControllerMatrix.apiName, "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report");
assert.equal(artifact.virtualHalMotionControllerMatrix.complete, true);
assert.equal(artifact.virtualHalMotionControllerMatrix.manifestChecked, true);

View File

@@ -5,6 +5,8 @@ import { fileURLToPath } from "node:url";
import {
createProjectReleaseReadinessReport,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
createVirtualHalSimConfigPromotionCandidateReport,
createVirtualHalSimConfigSourceCoverageReport,
} from "../../runtime/sdk/src/index.js";
@@ -31,6 +33,13 @@ const passedGateResults = {
"project-release-gate": true,
};
const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport({
manifestText: sourceManifestText,
});
const virtualHalMotionControllerMatrix = createVirtualHalMotionControllerMatrixReport({
manifestText: sourceManifestText,
});
function loadAxisScreenshotArtifacts(directory) {
if (!directory || !existsSync(directory)) {
return [];
@@ -65,12 +74,16 @@ const report = createProjectReleaseReadinessReport({
"host_wasm_opfs_browser_smokes=ok",
"project_release_gate=ok",
],
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport({
manifestText: sourceManifestText,
}),
virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport({
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates: createVirtualHalSimConfigPromotionCandidateReport({
manifestText: sourceManifestText,
simConfigSourceCoverage: virtualHalSimConfigSourceCoverage,
motionControllerMatrix: virtualHalMotionControllerMatrix,
}),
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
}),
virtualHalMotionControllerMatrix,
axisScreenshotArtifacts: loadAxisScreenshotArtifacts(axisScreenshotArtifactDir),
});

View File

@@ -43,6 +43,22 @@ const browserDiagnosticsArtifact = {
virtualHalSourceCompliance: createVirtualHalSourceComplianceReport({ halState: virtualHalState }),
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates,
virtualHalPromotionCandidateSummary: {
apiName: "real-browser-simulation-promotion-candidate-summary",
summaryVersion: 1,
phase: "ready",
ready: true,
candidateCount: 8,
readyCandidateCount: 8,
familyCount: 3,
sourceFileCount: 17,
preferredCandidateId: "qtdragon-multi-joint-on-abort",
preferredIniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }),
virtualHalMotionControllerMatrix,
@@ -67,6 +83,8 @@ assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDia
const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates,
virtualHalSimConfigMacroLoadFixtures,
virtualHalMotionControllerMatrix: browserDiagnosticsArtifact.virtualHalMotionControllerMatrix,
}));
@@ -92,6 +110,13 @@ const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
});
const readySummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow);
const readyActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(readyWorkflow);
const expectedPromotionFamilySummary =
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready";
const expectedPromotionFamilyRows = [
["virtual-hal-promotion-family-qtdragon-on-abort", "6/6 ready; sources=12; diagnostics=ready"],
["virtual-hal-promotion-family-vismach-remap-sims", "1/1 ready; sources=3; diagnostics=ready"],
["virtual-hal-promotion-family-rose-engine-rcone-demo", "1/1 ready; sources=2; diagnostics=ready"],
];
assert.equal(fetchedUrl, "/real-browser-simulation-diagnostics.json");
assert.equal(readyWorkflow.apiName, "project-release-readiness-artifact-url-workflow");
@@ -103,14 +128,50 @@ assert.equal(readyWorkflow.fetchError, null);
assert.equal(readyWorkflow.jsonWorkflow.ready, true);
assert.equal(readyWorkflow.validation.ready, true);
assert.equal(readyWorkflow.diagnosticsValidation.ready, true);
assert.equal(readyWorkflow.diagnosticsArtifact.virtualHalPromotionCandidateSummary.ready, true);
assert.equal(readyWorkflow.diagnosticsValidation.hasPromotionCandidates, true);
assert.equal(readyWorkflow.diagnosticsValidation.promotionCandidatesReady, true);
assert.equal(readyWorkflow.validation.virtualHalPromotionFamilySummary, expectedPromotionFamilySummary);
assert.equal(readyWorkflow.diagnosticsValidation.hasMacroLoadFixtures, true);
assert.equal(readyWorkflow.diagnosticsValidation.macroLoadFixturesReady, true);
assert.equal(readyWorkflow.diagnosticsValidation.hasSessionDiagnostics, true);
assert.equal(readyWorkflow.diagnosticsValidation.sessionDiagnosticsReady, true);
assert.equal(readyWorkflow.diagnosticsFetched, true);
assert.equal(readyWorkflow.diagnosticsHttpStatus, 200);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-families")?.value,
expectedPromotionFamilySummary,
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-summary")?.value,
"ready",
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate")?.value,
"qtdragon-multi-joint-on-abort",
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-ini")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-gcode")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-count")?.value,
"8/8 ready",
);
assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0",
);
assert.deepEqual(
readySummary.rows
.filter(({ id }) => id.startsWith("virtual-hal-promotion-family-"))
.map(({ id, value }) => [id, value]),
expectedPromotionFamilyRows,
);
assert.equal(readyWorkflow.actionPlan.ready, true);
assert.equal(readySummary.apiName, "project-release-readiness-artifact-url-workflow-summary-view-model");
assert.equal(readySummary.statusLine, "Ready: Artifact URL workflow complete");
@@ -121,7 +182,15 @@ assert.deepEqual(
["fetch", "ready (200)"],
["json-workflow", "ready"],
["artifact-validation", "ready"],
["virtual-hal-promotion-families", expectedPromotionFamilySummary],
...expectedPromotionFamilyRows,
["browser-diagnostics", "ready"],
["virtual-hal-promotion-candidate-summary", "ready"],
["virtual-hal-promotion-candidate", "qtdragon-multi-joint-on-abort"],
["virtual-hal-promotion-candidate-ini", "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"],
["virtual-hal-promotion-candidate-gcode", "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"],
["virtual-hal-promotion-candidate-count", "8/8 ready"],
["virtual-hal-promotion-candidate-baseline", "executed=28 passed=28 skipped=131 unexpected_fail=0"],
["saved-session-diagnostics", "ready"],
["next-command", "none"],
["missing", "none"],
@@ -143,6 +212,33 @@ assert.deepEqual(readyActionPlan, {
rows: [],
});
const missingPromotionCandidateSummaryArtifact = { ...browserDiagnosticsArtifact };
delete missingPromotionCandidateSummaryArtifact.virtualHalPromotionCandidateSummary;
const missingPromotionCandidateSummaryWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
diagnosticsUrl: "/real-browser-simulation-missing-promotion-candidate-summary.json",
fetchRef: async (url) => ({
ok: true,
status: 200,
text: async () => url === "/real-browser-simulation-missing-promotion-candidate-summary.json"
? JSON.stringify(missingPromotionCandidateSummaryArtifact)
: readyArtifactJson,
}),
});
assert.equal(missingPromotionCandidateSummaryWorkflow.ready, false);
assert.equal(missingPromotionCandidateSummaryWorkflow.diagnosticsValidation.ready, false);
assert.equal(missingPromotionCandidateSummaryWorkflow.diagnosticsValidation.hasPromotionCandidateSummary, false);
assert.equal(missingPromotionCandidateSummaryWorkflow.diagnosticsValidation.promotionCandidateSummaryReady, false);
assert.equal(
missingPromotionCandidateSummaryWorkflow.missing.includes("diagnostics.virtualHalPromotionCandidateSummary"),
true,
);
assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(missingPromotionCandidateSummaryWorkflow)
.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-summary")?.value,
"not provided",
);
const liveOnlyDiagnosticsArtifact = { ...browserDiagnosticsSessionNestedArtifact };
const liveOnlyDiagnosticsWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",

View File

@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
import {
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
createVirtualHalSimConfigPromotionCandidateReport,
createVirtualHalSimConfigSourceCoverageReport,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
@@ -24,6 +26,11 @@ const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverag
const virtualHalMotionControllerMatrix = createVirtualHalMotionControllerMatrixReport({
manifestEntries: VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []),
});
const virtualHalSimConfigPromotionCandidates = createVirtualHalSimConfigPromotionCandidateReport({
simConfigSourceCoverage: virtualHalSimConfigSourceCoverage,
motionControllerMatrix: virtualHalMotionControllerMatrix,
});
const virtualHalSimConfigMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixtureReport();
assert.equal(manifest.apiName, "project-release-gate-manifest");
assert.equal(manifest.manifestVersion, 1);
@@ -209,6 +216,8 @@ assert.match(partialActionPlan.shellScript, /wasm-port\/tests\/host\/verify_proj
const report = createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates,
virtualHalSimConfigMacroLoadFixtures,
virtualHalMotionControllerMatrix,
});
assert.deepEqual(report.gateManifest, manifest);
@@ -257,6 +266,16 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
label: "Virtual HAL sim-config source coverage",
value: "ready",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: "ready",
},
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: "ready",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",
@@ -281,12 +300,14 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
});
assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine,
"Waiting: project-release-gate, virtual-hal-sim-config-source-coverage, virtual-hal-motion-controller-matrix",
"Waiting: project-release-gate, virtual-hal-sim-config-source-coverage, virtual-hal-sim-config-promotion-candidates, virtual-hal-sim-config-macro-load-fixtures, virtual-hal-motion-controller-matrix",
);
assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates,
virtualHalSimConfigMacroLoadFixtures,
virtualHalMotionControllerMatrix,
promotedRuntimeFamilies: ["L4-TOOL-DB"],
})).rows.find(({ id }) => id === "blocked-runtime-families")?.value,
@@ -313,6 +334,13 @@ const artifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow
const artifactUrlWorkflowSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
artifactUrlWorkflow,
);
const expectedPromotionFamilySummary =
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready";
const expectedPromotionFamilyRows = [
["virtual-hal-promotion-family-qtdragon-on-abort", "6/6 ready; sources=12; diagnostics=ready"],
["virtual-hal-promotion-family-vismach-remap-sims", "1/1 ready; sources=3; diagnostics=ready"],
["virtual-hal-promotion-family-rose-engine-rcone-demo", "1/1 ready; sources=2; diagnostics=ready"],
];
assert.equal(
artifactValidationSummary.apiName,
"project-release-readiness-artifact-validation-summary-view-model",
@@ -330,6 +358,10 @@ assert.deepEqual(
["gate-result-matrix", "ready"],
["gate-action-plan", "ready"],
["virtual-hal-sim-config-source-coverage", "ready"],
["virtual-hal-sim-config-promotion-candidates", "ready"],
["virtual-hal-promotion-families", expectedPromotionFamilySummary],
...expectedPromotionFamilyRows,
["virtual-hal-sim-config-macro-load-fixtures", "ready"],
["virtual-hal-motion-controller-matrix", "ready"],
["missing", "none"],
],
@@ -417,7 +449,15 @@ assert.deepEqual(
["fetch", "ready (200)"],
["json-workflow", "ready"],
["artifact-validation", "ready"],
["virtual-hal-promotion-families", expectedPromotionFamilySummary],
...expectedPromotionFamilyRows,
["browser-diagnostics", "not requested"],
["virtual-hal-promotion-candidate-summary", "not requested"],
["virtual-hal-promotion-candidate", "not provided"],
["virtual-hal-promotion-candidate-ini", "not provided"],
["virtual-hal-promotion-candidate-gcode", "not provided"],
["virtual-hal-promotion-candidate-count", "not provided"],
["virtual-hal-promotion-candidate-baseline", "not provided"],
["saved-session-diagnostics", "not requested"],
["next-command", "none"],
["missing", "none"],

View File

@@ -36,8 +36,11 @@ import {
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
@@ -178,6 +181,16 @@ const sdkIndexText = readFileSync(resolve(root, "runtime/sdk/src/index.js"), "ut
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 expectedPromotionFamilySummary =
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready";
const expectedPromotionFamilyRows = [
["virtual-hal-promotion-family-qtdragon-on-abort", "6/6 ready; sources=12; diagnostics=ready"],
["virtual-hal-promotion-family-vismach-remap-sims", "1/1 ready; sources=3; diagnostics=ready"],
["virtual-hal-promotion-family-rose-engine-rcone-demo", "1/1 ready; sources=2; diagnostics=ready"],
];
const hasFreshBoundaryArtifactHashes = (boundaryEvidence) =>
Object.entries(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES)
.every(([artifactPath, expectedHash]) => boundaryEvidence?.sourceArtifactHashes?.[artifactPath] === expectedHash);
const requiredExports = [
["createLinuxCncIniSdk", createLinuxCncIniSdk],
@@ -551,6 +564,10 @@ assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSimConfigMacroLoadFixtureReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSimConfigMacroLoadFixtureReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSourceComplianceReport\b/);
@@ -784,6 +801,39 @@ assert.equal(simConfigPromotionCandidateReport.missingCandidates.length, 0);
assert.equal(simConfigPromotionCandidateReport.blockedCandidateIds.length, 0);
assert.equal(simConfigPromotionCandidateReport.missingManifestFiles.length, 0);
assert.equal(simConfigPromotionCandidateReport.missingRequiredReports.length, 0);
assert.equal(
simConfigPromotionCandidateReport.familyRows.some((row) =>
row.id === "qtdragon-on-abort" &&
row.candidateCount === 6 &&
row.completeCount === 6 &&
row.explicitBrowserDiagnosticsCount === 6 &&
row.explicitBrowserDiagnosticsReady === true &&
row.complete === true &&
row.sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini") &&
row.sourceFiles.includes("linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc")
),
true,
);
assert.equal(
simConfigPromotionCandidateReport.familyRows.some((row) =>
row.id === "vismach-remap-sims" &&
row.candidateCount === 1 &&
row.complete === true &&
row.explicitBrowserDiagnosticsReady === true &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/vismach/puma/puma_seam_weld.ngc")
),
true,
);
assert.equal(
simConfigPromotionCandidateReport.familyRows.some((row) =>
row.id === "rose-engine-rcone-demo" &&
row.candidateCount === 1 &&
row.complete === true &&
row.explicitBrowserDiagnosticsReady === true &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc")
),
true,
);
const expectedPromotionCandidates = [
[
"qtdragon-multi-joint-on-abort",
@@ -833,6 +883,7 @@ for (const [id, ...sourceFiles] of expectedPromotionCandidates) {
row.id === id &&
row.complete === true &&
row.currentNodeInventoryStatus === "PASS" &&
row.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
row.targetBrowserEvidence === "explicit-browser-diagnostics" &&
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
),
@@ -847,6 +898,19 @@ assert.equal(virtualHalRuntime.getSimConfigPromotionCandidateReport({
}).complete, true);
assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.length >= 2, true);
assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.length >= 2, true);
assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES.length >= 2, true);
assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE, {
executed: 28,
passed: 28,
skipped: 131,
unexpectedFail: 0,
});
assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES, {
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae",
});
const simConfigMacroLoadFixtureReport = createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
});
@@ -862,7 +926,13 @@ assert.equal(simConfigMacroLoadFixtureReport.missingFixtures.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.blockedFixtureIds.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.standaloneMainViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.blockedFixturePromotionViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.blockedBoundaryEvidenceViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.missingAuditRows.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.auditPromotionViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.auditBoundaryEvidenceViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.auditPromotionAllowedViolations.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.missingManifestFiles.length, 0);
assert.equal(simConfigMacroLoadFixtureReport.declarationEvidenceViolations.length, 0);
assert.equal(
simConfigMacroLoadFixtureReport.rows.some((row) =>
row.id === "rose-engine-rcone-macro-load" &&
@@ -870,6 +940,12 @@ assert.equal(
row.nonMainFixture === true &&
row.targetBrowserEvidence === "non-main-fixture-diagnostics" &&
row.fixturePath === "linuxcnc/configs/sim/axis/rose_engine/rcone.ngc" &&
row.requiresDeclarationEvidence === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence.sourceFile === "linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini" &&
row.declarationEvidence.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence.value === "rcone.ngc" &&
row.declarationEvidence.line === 34 &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone.ngc") &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc")
),
@@ -881,18 +957,50 @@ assert.equal(
row.complete === true &&
row.nonMainFixture === true &&
row.declaredOnly === true &&
row.declarationEvidenceReady === true &&
row.declarationEvidence.sourceFile === "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini" &&
row.declarationEvidence.key === "NGCGUI_SUBFILE" &&
row.declarationEvidence.value === "queuebuster.ngc" &&
row.declarationEvidence.line === 40 &&
row.fixturePath === "linuxcnc/configs/sim/axis/external_offsets/queuebuster.ngc" &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini") &&
row.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc")
),
true,
);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
fixtures: [
{
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.find(({ id }) => id === "external-offsets-queuebuster-macro-load"),
declarationEvidence: null,
},
],
}).declarationEvidenceViolations.includes("external-offsets-queuebuster-macro-load"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
fixtures: [
{
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.find(({ id }) => id === "rose-engine-rcone-macro-load"),
declarationEvidence: null,
},
],
}).declarationEvidenceViolations.includes("rose-engine-rcone-macro-load"), true);
assert.equal(
simConfigMacroLoadFixtureReport.blockedRows.some((row) =>
row.id === "silverdragon-tool-sensor-python-ui-boundary" &&
row.fixturePath === "linuxcnc/configs/sim/gscreen/silverdragon/macros/tool_sensor.ngc" &&
row.blockedKind === "PYTHON-UI-PROCESS" &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
hasFreshBoundaryArtifactHashes(row.boundaryEvidence) &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini") &&
row.boundaryEvidence.baselineSummary.executed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.executed &&
row.boundaryEvidence.baselineSummary.passed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.passed &&
row.boundaryEvidence.baselineSummary.skipped === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
row.boundaryEvidence.baselineSummary.unexpectedFail === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 &&
row.boundaryEvidence.iniBoundarySummary.reportAvailable === 0 &&
row.complete === true
),
true,
@@ -903,10 +1011,170 @@ assert.equal(
row.fixturePath === "linuxcnc/configs/sim/gmoccapy/macros/on_abort.ngc" &&
row.blockedKind === "L4-PYTHON-REMAP" &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
hasFreshBoundaryArtifactHashes(row.boundaryEvidence) &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gmoccapy/6_axis.ini") &&
row.boundaryEvidence.baselineSummary.executed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.executed &&
row.boundaryEvidence.baselineSummary.passed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.passed &&
row.boundaryEvidence.baselineSummary.skipped === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
row.boundaryEvidence.baselineSummary.unexpectedFail === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 &&
row.boundaryEvidence.iniBoundarySummary.reportAvailable === 0 &&
row.complete === true
),
true,
);
assert.equal(
simConfigMacroLoadFixtureReport.auditRows.some((row) =>
row.id === "gscreen-industrial-lathe-wear-toolchange-audit" &&
row.fixturePath === "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc" &&
row.auditStatus === "blocked-missing-owning-ini" &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
hasFreshBoundaryArtifactHashes(row.boundaryEvidence) &&
row.boundaryEvidence.boundarySummary.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini") &&
row.boundaryEvidence.baselineSummary.executed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.executed &&
row.boundaryEvidence.baselineSummary.passed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.passed &&
row.boundaryEvidence.baselineSummary.skipped === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
row.boundaryEvidence.baselineSummary.unexpectedFail === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 &&
row.boundaryEvidence.iniBoundarySummary.reportAvailable === 0 &&
row.complete === true
),
true,
);
assert.equal(
simConfigMacroLoadFixtureReport.auditRows.some((row) =>
row.id === "qtvcp-industrial-lathe-wear-toolchange-audit" &&
row.fixturePath === "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc" &&
row.auditStatus === "blocked-missing-owning-ini" &&
row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true &&
hasFreshBoundaryArtifactHashes(row.boundaryEvidence) &&
row.boundaryEvidence.boundarySummary.recommendedBlocked === "UNAVAILABLE" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini") &&
row.boundaryEvidence.baselineSummary.executed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.executed &&
row.boundaryEvidence.baselineSummary.passed === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.passed &&
row.boundaryEvidence.baselineSummary.skipped === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.skipped &&
row.boundaryEvidence.baselineSummary.unexpectedFail === VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 &&
row.boundaryEvidence.iniBoundarySummary.reportAvailable === 0 &&
row.complete === true
),
true,
);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
auditCandidates: [],
}).complete, true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
blockedFixtures: [
{
id: "bad-blocked-boundary-evidence",
fixturePath: "linuxcnc/configs/sim/gscreen/silverdragon/macros/tool_sensor.ngc",
owningIniPath: "linuxcnc/configs/sim/gscreen/silverdragon/silverdragon.ini",
blockedKind: "PYTHON-UI-PROCESS",
dependencyClass: "macro-load-python-ui-boundary",
reason: "negative fixture: blocked row missing generated boundary evidence must fail",
},
],
}).blockedBoundaryEvidenceViolations.includes("bad-blocked-boundary-evidence"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
blockedFixtures: [
{
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.find(
({ id }) => id === "silverdragon-tool-sensor-python-ui-boundary",
),
id: "stale-blocked-boundary-evidence",
boundaryEvidence: {
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.find(
({ id }) => id === "silverdragon-tool-sensor-python-ui-boundary",
).boundaryEvidence,
baselineSummary: {
...VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
skipped: 130,
},
},
},
],
}).blockedBoundaryEvidenceViolations.includes("stale-blocked-boundary-evidence"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
blockedFixtures: [
{
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.find(
({ id }) => id === "silverdragon-tool-sensor-python-ui-boundary",
),
id: "stale-hash-blocked-boundary-evidence",
boundaryEvidence: {
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.find(
({ id }) => id === "silverdragon-tool-sensor-python-ui-boundary",
).boundaryEvidence,
sourceArtifactHashes: {
...VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES,
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": "stale",
},
},
},
],
}).blockedBoundaryEvidenceViolations.includes("stale-hash-blocked-boundary-evidence"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
fixtures: [
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
{
id: "bad-positive-industrial-lathe-audit",
label: "Bad positive industrial lathe audit fixture",
fixturePath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
sourceFiles: [
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
],
owningIniPath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
pairedMainProgram: "linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
declarationSource: "NGCGUI_SUBFILE",
currentNodeInventoryStatus: "PASS",
dependencyClass: "macro-load-ui-family-audit",
targetBrowserEvidence: "non-main-fixture-diagnostics",
blockedKind: "-",
nonMainFixture: true,
declaredOnly: false,
evidence: "negative fixture: audit candidate must not be promoted as a positive fixture",
},
],
}).auditPromotionViolations.includes("gscreen-industrial-lathe-wear-toolchange-audit"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
auditCandidates: [
{
id: "bad-audit-boundary-evidence",
fixturePath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
owningIniPath: "linuxcnc/configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini",
dependencyClass: "macro-load-ui-family-audit",
auditStatus: "blocked-missing-owning-ini",
promotionAllowed: false,
reason: "negative fixture: missing generated boundary evidence must fail",
},
],
}).auditBoundaryEvidenceViolations.includes("bad-audit-boundary-evidence"), true);
assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
auditCandidates: [
{
id: "bad-audit-promotion-allowed",
fixturePath: "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
owningIniPath: "linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/industrial_lathe_wear.ini",
dependencyClass: "macro-load-ui-family-audit",
auditStatus: "blocked-missing-owning-ini",
promotionAllowed: true,
reason: "negative fixture: promotionAllowed true without boundary/source proof must fail",
},
],
}).auditPromotionAllowedViolations.includes("bad-audit-promotion-allowed"), true);
assert.equal(virtualHalRuntime.getSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
}).complete, true);
@@ -958,6 +1226,22 @@ const browserDiagnosticsArtifact = {
virtualHalSourceCompliance: sourceComplianceReport,
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport,
virtualHalPromotionCandidateSummary: {
apiName: "real-browser-simulation-promotion-candidate-summary",
summaryVersion: 1,
phase: "ready",
ready: true,
candidateCount: 8,
readyCandidateCount: 8,
familyCount: 3,
sourceFileCount: 17,
preferredCandidateId: "qtdragon-multi-joint-on-abort",
preferredIniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
virtualHalCommandScriptFixtures: commandScriptFixtureReport,
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
@@ -984,6 +1268,8 @@ assert.equal(browserDiagnosticsArtifactValidation.sourceComplianceReady, true);
assert.equal(browserDiagnosticsArtifactValidation.simConfigSourceCoverageReady, true);
assert.equal(browserDiagnosticsArtifactValidation.promotionCandidatesReady, true);
assert.equal(browserDiagnosticsArtifactValidation.hasPromotionCandidates, true);
assert.equal(browserDiagnosticsArtifactValidation.promotionCandidateSummaryReady, true);
assert.equal(browserDiagnosticsArtifactValidation.hasPromotionCandidateSummary, true);
assert.equal(browserDiagnosticsArtifactValidation.macroLoadFixturesReady, true);
assert.equal(browserDiagnosticsArtifactValidation.hasMacroLoadFixtures, true);
assert.equal(browserDiagnosticsArtifactValidation.commandScriptFixturesReady, true);
@@ -1004,6 +1290,38 @@ assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone.ngc"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc"), true);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalPromotionCandidateSummary: undefined,
}).missing.includes("virtualHalPromotionCandidateSummary"), true);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalPromotionCandidateSummary: {
...browserDiagnosticsArtifact.virtualHalPromotionCandidateSummary,
readyCandidateCount: 7,
},
}).promotionCandidateSummaryReady, false);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalSimConfigMacroLoadFixtures: {
...simConfigMacroLoadFixtureReport,
auditRows: [],
},
}).macroLoadFixturesReady, false);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalSimConfigMacroLoadFixtures: {
...simConfigMacroLoadFixtureReport,
auditBoundaryEvidenceViolations: ["gscreen-industrial-lathe-wear-toolchange-audit"],
},
}).missing.includes("virtualHalSimConfigMacroLoadFixtures"), true);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalSimConfigMacroLoadFixtures: {
...simConfigMacroLoadFixtureReport,
auditPromotionAllowedViolations: ["qtvcp-industrial-lathe-wear-toolchange-audit"],
},
}).ready, false);
const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS });
assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true);
assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
@@ -1143,6 +1461,8 @@ assert.equal(releaseReadinessWaiting.ready, false);
assert.deepEqual(releaseReadinessWaiting.missing, [
"project-release-gate",
"virtual-hal-sim-config-source-coverage",
"virtual-hal-sim-config-promotion-candidates",
"virtual-hal-sim-config-macro-load-fixtures",
"virtual-hal-motion-controller-matrix",
]);
assert.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0);
@@ -1157,11 +1477,23 @@ assert.equal(
const releaseReadinessReady = createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"],
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport,
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
});
assert.equal(releaseReadinessReady.ready, true);
assert.equal(releaseReadinessReady.releaseGate.passed, true);
assert.equal(releaseReadinessReady.virtualHalSimConfigSourceCoverageReady, true);
assert.equal(releaseReadinessReady.virtualHalSimConfigPromotionCandidatesReady, true);
assert.equal(releaseReadinessReady.virtualHalPromotionFamilySummary, expectedPromotionFamilySummary);
assert.deepEqual(
releaseReadinessReady.virtualHalPromotionFamilyRows.map(({ id, value }) => [
`virtual-hal-promotion-family-${id}`,
value,
]),
expectedPromotionFamilyRows,
);
assert.equal(releaseReadinessReady.virtualHalSimConfigMacroLoadFixturesReady, true);
assert.equal(releaseReadinessReady.virtualHalMotionControllerMatrixReady, true);
assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest());
assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount);
@@ -1179,6 +1511,8 @@ assert.equal(
const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport,
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
axisScreenshotArtifacts: ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"].map((viewportName) => ({
apiName: "real-browser-simulation-axis-screenshot-artifact",
@@ -1214,6 +1548,8 @@ assert.equal(
const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport,
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
})));
assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report");
@@ -1235,6 +1571,10 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
gateResultMatrixReady: true,
gateActionPlanReady: true,
virtualHalSimConfigSourceCoverageReady: true,
virtualHalSimConfigPromotionCandidatesReady: true,
virtualHalPromotionFamilySummary: expectedPromotionFamilySummary,
virtualHalPromotionFamilyRows: releaseReadinessReady.virtualHalPromotionFamilyRows,
virtualHalSimConfigMacroLoadFixturesReady: true,
virtualHalMotionControllerMatrixReady: true,
axisScreenshotArtifactSummaryReady: true,
axisScreenshotArtifactCount: 0,
@@ -1284,6 +1624,29 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
label: "Virtual HAL sim-config source coverage",
value: "ready",
},
{
id: "virtual-hal-sim-config-promotion-candidates",
label: "Virtual HAL sim-config promotion candidates",
value: "ready",
},
{
id: "virtual-hal-promotion-families",
label: "Virtual HAL promotion families",
value: expectedPromotionFamilySummary,
},
...expectedPromotionFamilyRows.map(([id, value]) => ({
id,
label: `Promotion family ${id.replace("virtual-hal-promotion-family-", "")}`,
value,
sourceFiles: releaseReadinessReady.virtualHalPromotionFamilyRows.find((row) =>
`virtual-hal-promotion-family-${row.id}` === id
)?.sourceFiles ?? [],
})),
{
id: "virtual-hal-sim-config-macro-load-fixtures",
label: "Virtual HAL sim-config macro/load fixtures",
value: "ready",
},
{
id: "virtual-hal-motion-controller-matrix",
label: "Virtual HAL motion controller matrix",
@@ -1307,6 +1670,20 @@ assert.equal(
).statusLine,
"Ready: Artifact evidence complete",
);
assert.equal(
createProjectReleaseReadinessArtifactValidationSummaryViewModel(
releaseReadinessArtifactValidation,
).rows.find(({ id }) => id === "virtual-hal-promotion-families")?.value,
expectedPromotionFamilySummary,
);
assert.deepEqual(
createProjectReleaseReadinessArtifactValidationSummaryViewModel(
releaseReadinessArtifactValidation,
).rows
.filter(({ id }) => id.startsWith("virtual-hal-promotion-family-"))
.map(({ id, value }) => [id, value]),
expectedPromotionFamilyRows,
);
assert.equal(
createProjectReleaseReadinessArtifactValidationActionPlan(releaseReadinessArtifactValidation).shellScript,
"# Project release readiness artifact validation is already ready.\n",
@@ -1350,6 +1727,34 @@ assert.equal(
).statusLine,
"Ready: Artifact URL workflow complete",
);
assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
}),
).rows.find(({ id }) => id === "virtual-hal-promotion-families")?.value,
expectedPromotionFamilySummary,
);
assert.deepEqual(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
}),
).rows
.filter(({ id }) => id.startsWith("virtual-hal-promotion-family-"))
.map(({ id, value }) => [id, value]),
expectedPromotionFamilyRows,
);
assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
await loadProjectReleaseReadinessArtifactUrlWorkflow({
@@ -1386,6 +1791,8 @@ assert.deepEqual(
"simConfigInventory.skipped",
"simConfigInventory.unexpectedFail",
"virtualHalSimConfigSourceCoverage",
"virtualHalSimConfigPromotionCandidates",
"virtualHalSimConfigMacroLoadFixtures",
"virtualHalMotionControllerMatrix",
"blockedRuntimeFamilies",
"gateManifest",

View File

@@ -84,6 +84,25 @@ import {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness,
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan,
handleIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionChange,
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls,
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomContract,
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomReadiness,
renderIniPanelShellWorkflowOverviewPromotionFamilyRowsState,
mountIniPanelShellWorkflowOverviewPromotionFamilyRowsState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomContract,
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomReadiness,
renderIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
@@ -744,6 +763,19 @@ assert.deepEqual(shellViewModel.shellWorkflowOverviewContract, {
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState",
"getWorkflowOverviewPromotionFamilyRowsRenderState",
"getWorkflowOverviewPromotionFamilySourceFilterViewModel",
"getWorkflowOverviewPromotionFamilySourceFilterDomContract",
"getWorkflowOverviewPromotionFamilySourceFilterDomReadiness",
"getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan",
"handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange",
"bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls",
"renderWorkflowOverviewPromotionFamilySourceFilterState",
"mountWorkflowOverviewPromotionFamilySourceFilterState",
"getWorkflowOverviewPromotionFamilyRowsDomContract",
"getWorkflowOverviewPromotionFamilyRowsDomReadiness",
"renderWorkflowOverviewPromotionFamilyRowsState",
"mountWorkflowOverviewPromotionFamilyRowsState",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness",
"renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
@@ -1044,7 +1076,27 @@ const mountRowsNode = {
const mountNode = { dataset: {} };
const mountStatusNode = { textContent: "" };
const mountDocument = {
createElement: (tagName) => ({ tagName, dataset: {}, textContent: "" }),
createElement: (tagName) => ({
tagName,
dataset: {},
textContent: "",
children: [],
listeners: {},
append(...nodes) {
this.children.push(...nodes);
},
addEventListener(eventName, handler) {
this.listeners[eventName] = handler;
},
removeEventListener(eventName, handler) {
if (this.listeners[eventName] === handler) {
delete this.listeners[eventName];
}
},
dispatchEvent(eventName) {
return this.listeners[eventName]?.();
},
}),
};
assert.deepEqual(
createIniPanelShellSessionHandoffMountDomReadiness({
@@ -1366,9 +1418,34 @@ assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), shellViewModel.s
const releaseReadinessMotionControllerMatrix = createVirtualHalMotionControllerMatrixReport({
manifestEntries: VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
});
const releaseReadinessPromotionCandidates = createVirtualHalSimConfigPromotionCandidateReport({
manifestEntries: [
...VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini",
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/on_abort.ngc",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc",
"linuxcnc/configs/sim/axis/vismach/puma/puma_seam_weld.ngc",
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
],
motionControllerMatrix: releaseReadinessMotionControllerMatrix,
});
const releaseReadinessMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixtureReport({
manifestEntries: [
...VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
],
});
const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalSimConfigPromotionCandidates: releaseReadinessPromotionCandidates,
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix,
}));
const browserDiagnosticsVirtualHalState = createVirtualHalState();
@@ -1379,29 +1456,24 @@ const browserDiagnosticsArtifact = {
virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(browserDiagnosticsVirtualHalState),
virtualHalSourceCompliance: createVirtualHalSourceComplianceReport({ halState: browserDiagnosticsVirtualHalState }),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalSimConfigPromotionCandidates: createVirtualHalSimConfigPromotionCandidateReport({
manifestEntries: [
...VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini",
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/on_abort.ngc",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc",
"linuxcnc/configs/sim/axis/vismach/puma/puma_seam_weld.ngc",
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
],
motionControllerMatrix: releaseReadinessMotionControllerMatrix,
}),
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
manifestEntries: [
...VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES,
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
],
}),
virtualHalSimConfigPromotionCandidates: releaseReadinessPromotionCandidates,
virtualHalPromotionCandidateSummary: {
apiName: "real-browser-simulation-promotion-candidate-summary",
summaryVersion: 1,
phase: "ready",
ready: true,
candidateCount: 8,
readyCandidateCount: 8,
familyCount: 3,
sourceFileCount: 17,
preferredCandidateId: "qtdragon-multi-joint-on-abort",
preferredIniPath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: browserDiagnosticsVirtualHalState }),
virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix,
};
@@ -1725,7 +1797,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
rowsNode: releaseArtifactRowsNode,
});
assert.equal(releaseArtifactRenderResult.rendered, true);
assert.equal(releaseArtifactRenderResult.rowCount, 11);
assert.equal(releaseArtifactRenderResult.rowCount, 17);
assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
@@ -1735,6 +1807,10 @@ assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-summary"),
true,
);
assert.equal(
releaseArtifactRenderResult.rowIds.includes("virtual-hal-promotion-families"),
true,
);
assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-action-plan"),
true,
@@ -1752,7 +1828,7 @@ assert.equal(
true,
);
assert.equal(releaseArtifactMountResult.ready, true);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 11);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 17);
assert.deepEqual(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
["artifact-json"],
@@ -1811,11 +1887,18 @@ assert.equal(
);
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 11);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 17);
const releaseArtifactUrlWorkflowSummary =
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseArtifactUrlWorkflow,
);
const expectedPromotionFamilySummary =
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready";
const expectedPromotionFamilyRows = [
["virtual-hal-promotion-family-qtdragon-on-abort", "6/6 ready; sources=12; diagnostics=ready"],
["virtual-hal-promotion-family-vismach-remap-sims", "1/1 ready; sources=3; diagnostics=ready"],
["virtual-hal-promotion-family-rose-engine-rcone-demo", "1/1 ready; sources=2; diagnostics=ready"],
];
assert.equal(
releaseArtifactUrlWorkflowSummary.statusLine,
"Ready: Artifact URL workflow complete",
@@ -1836,6 +1919,583 @@ assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "browser-diagnostics")?.value,
"ready",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-summary")?.value,
"ready",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate")?.value,
"qtdragon-multi-joint-on-abort",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-ini")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-gcode")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-count")?.value,
"8/8 ready",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0",
);
const promotionCandidateDrilldownViewModel =
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel(
releaseArtifactUrlWorkflowSummary,
{ diagnosticsArtifact: releaseArtifactUrlWorkflow.diagnosticsArtifact },
);
const promotionCandidateDrilldownRenderState =
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownRenderState(
promotionCandidateDrilldownViewModel,
);
assert.equal(promotionCandidateDrilldownViewModel.ready, true);
assert.equal(
promotionCandidateDrilldownViewModel.statusLine,
"Ready: qtdragon-multi-joint-on-abort source-derived browser evidence ready",
);
assert.equal(promotionCandidateDrilldownViewModel.candidateId, "qtdragon-multi-joint-on-abort");
assert.equal(promotionCandidateDrilldownViewModel.blockedFamilyExcluded, true);
assert.equal(promotionCandidateDrilldownViewModel.nodeInventoryReady, true);
assert.equal(promotionCandidateDrilldownViewModel.browserEvidenceReady, true);
assert.equal(promotionCandidateDrilldownViewModel.virtualHalReportsReady, true);
assert.deepEqual(promotionCandidateDrilldownViewModel.requiredReports, [
"source-compliance",
"sim-config-source-coverage",
"command-script-fixtures",
"manifest-backed-motion-matrix",
]);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "ini")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "gcode")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "blocked-family-exclusion")?.value,
"passed",
);
assert.equal(promotionCandidateDrilldownViewModel.sourceFileCount, 2);
assert.deepEqual(
promotionCandidateDrilldownViewModel.sourceRows.map(({ value, sourceKind, manifestBacked }) => [
value,
sourceKind,
manifestBacked,
]),
[
["linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini", "ini", true],
["linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", "gcode", true],
],
);
assert.equal(promotionCandidateDrilldownRenderState.ready, true);
assert.equal(promotionCandidateDrilldownRenderState.rowCount, 11);
assert.equal(promotionCandidateDrilldownRenderState.sourceFileCount, 2);
assert.equal(
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomContract().selectors.mount,
"[data-workflow-overview-promotion-candidate-drilldown]",
);
assert.equal(createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownDomReadiness().ready, false);
const promotionCandidateDrilldownMountNode = mountDocument.createElement("section");
promotionCandidateDrilldownMountNode.dataset.workflowOverviewPromotionCandidateDrilldown = "";
const promotionCandidateDrilldownStatusNode = mountDocument.createElement("p");
promotionCandidateDrilldownStatusNode.dataset.workflowOverviewPromotionCandidateDrilldownStatus = "";
const promotionCandidateDrilldownRowsNode = {
children: [],
dataset: {},
append(...nodes) {
this.children.push(...nodes);
},
replaceChildren(...nodes) {
this.children = [...nodes];
},
};
promotionCandidateDrilldownRowsNode.dataset.workflowOverviewPromotionCandidateDrilldownRows = "";
const promotionCandidateDrilldownRenderResult =
renderIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState(
promotionCandidateDrilldownRenderState,
{
documentRef: mountDocument,
mountNode: promotionCandidateDrilldownMountNode,
statusNode: promotionCandidateDrilldownStatusNode,
rowsNode: promotionCandidateDrilldownRowsNode,
},
);
const promotionCandidateDrilldownMountResult =
mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownState({
renderState: promotionCandidateDrilldownRenderState,
documentRef: mountDocument,
mountNode: promotionCandidateDrilldownMountNode,
statusNode: promotionCandidateDrilldownStatusNode,
rowsNode: promotionCandidateDrilldownRowsNode,
});
assert.equal(promotionCandidateDrilldownRenderResult.rendered, true);
assert.equal(promotionCandidateDrilldownRenderResult.rowCount, 11);
assert.deepEqual(promotionCandidateDrilldownRenderResult.dataset, {
handoffPhase: "ready",
handoffReady: "true",
handoffScope: "workflow-overview-promotion-candidate-drilldown",
candidateId: "qtdragon-multi-joint-on-abort",
});
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "blocked-family-exclusion" &&
node.textContent === "passed"
), true);
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownSourceKind === "gcode" &&
node.dataset?.workflowOverviewPromotionCandidateDrilldownManifestBacked === "true" &&
node.textContent === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"
), true);
assert.equal(promotionCandidateDrilldownMountResult.ready, true);
assert.equal(promotionCandidateDrilldownMountResult.renderResult.sourceFileCount, 2);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-families")?.value,
expectedPromotionFamilySummary,
);
assert.deepEqual(
releaseArtifactUrlWorkflowSummary.rows
.filter(({ id }) => id.startsWith("virtual-hal-promotion-family-"))
.map(({ id, value }) => [id, value]),
expectedPromotionFamilyRows,
);
const promotionFamilyRowsRenderState =
createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderState(
releaseArtifactUrlWorkflowSummary,
);
const promotionFamilySourceFilterViewModel =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel(
promotionFamilyRowsRenderState,
);
const promotionFamilyRemapSourceFilterViewModel =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel(
promotionFamilyRowsRenderState,
{ sourceKind: "remap-subroutine" },
);
const promotionFamilyQtDragonGcodeFilterViewModel =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterViewModel(
promotionFamilyRowsRenderState,
{
familyId: "virtual-hal-promotion-family-qtdragon-on-abort",
sourceKind: "gcode",
},
);
assert.equal(promotionFamilyRowsRenderState.ready, true);
assert.equal(promotionFamilyRowsRenderState.statusLine, "Ready: 3 promotion families ready");
assert.equal(promotionFamilyRowsRenderState.rows.length, 3);
assert.equal(promotionFamilyRowsRenderState.sourceFileCount, 17);
assert.equal(promotionFamilyRowsRenderState.sourceRows.length, 17);
assert.equal(
promotionFamilyRowsRenderState.rows.find(({ id }) => id === "virtual-hal-promotion-family-qtdragon-on-abort")
?.sourceKindSummary,
"ini=6 gcode=6",
);
assert.deepEqual(
promotionFamilyRowsRenderState.rows.find(({ id }) => id === "virtual-hal-promotion-family-vismach-remap-sims")
?.sourceKindCounts,
{ ini: 1, gcode: 1, "remap-subroutine": 1 },
);
assert.equal(
promotionFamilyRowsRenderState.rows.find(({ id }) => id === "virtual-hal-promotion-family-rose-engine-rcone-demo")
?.sourceKindSummary,
"ini=1 gcode=1",
);
assert.equal(promotionFamilySourceFilterViewModel.ready, true);
assert.equal(promotionFamilySourceFilterViewModel.statusLine, "Ready: 17/17 source files selected");
assert.equal(promotionFamilySourceFilterViewModel.filteredSourceFileCount, 17);
assert.equal(promotionFamilySourceFilterViewModel.totalSourceFileCount, 17);
assert.deepEqual(
promotionFamilySourceFilterViewModel.familyFilters.map(({ id, count, selected }) => [id, count, selected]),
[
["all", 17, true],
["virtual-hal-promotion-family-qtdragon-on-abort", 12, false],
["virtual-hal-promotion-family-vismach-remap-sims", 3, false],
["virtual-hal-promotion-family-rose-engine-rcone-demo", 2, false],
],
);
assert.deepEqual(
promotionFamilySourceFilterViewModel.sourceKindFilters.map(({ id, count, selected }) => [id, count, selected]),
[
["all", 17, true],
["ini", 8, false],
["gcode", 8, false],
["remap-subroutine", 1, false],
],
);
assert.equal(promotionFamilyRemapSourceFilterViewModel.selectedSourceKind, "remap-subroutine");
assert.equal(promotionFamilyRemapSourceFilterViewModel.statusLine, "Ready: 1/17 source files selected");
assert.equal(promotionFamilyRemapSourceFilterViewModel.familyRows.length, 1);
assert.equal(
promotionFamilyRemapSourceFilterViewModel.familyRows[0]?.id,
"virtual-hal-promotion-family-vismach-remap-sims",
);
assert.equal(
promotionFamilyRemapSourceFilterViewModel.sourceRows[0]?.value,
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc",
);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.selectedFamilyId, "virtual-hal-promotion-family-qtdragon-on-abort");
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.selectedSourceKind, "gcode");
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.statusLine, "Ready: 6/17 source files selected");
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.scopedSourceFileCount, 12);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.filteredSourceFileCount, 6);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.familyRows.length, 1);
assert.equal(
promotionFamilyQtDragonGcodeFilterViewModel.familyRows[0]?.sourceKindSummary,
"gcode=6",
);
assert.deepEqual(
promotionFamilyQtDragonGcodeFilterViewModel.sourceKindFilters.map(({ id, count, selected }) => [id, count, selected]),
[
["all", 12, false],
["ini", 6, false],
["gcode", 6, true],
],
);
assert.equal(
promotionFamilyQtDragonGcodeFilterViewModel.sourceRows.every(({ familyId, sourceKind }) =>
familyId === "virtual-hal-promotion-family-qtdragon-on-abort" &&
sourceKind === "gcode"
),
true,
);
assert.equal(
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract().selectors.mount,
"[data-workflow-overview-promotion-family-source-filter]",
);
assert.equal(createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness().ready, false);
const promotionFamilySourceFilterMountNode = mountDocument.createElement("section");
promotionFamilySourceFilterMountNode.dataset.workflowOverviewPromotionFamilySourceFilter = "";
const promotionFamilySourceFilterStatusNode = mountDocument.createElement("p");
promotionFamilySourceFilterStatusNode.dataset.workflowOverviewPromotionFamilySourceFilterStatus = "";
const promotionFamilySourceFilterControlsNode = {
...mountDocument.createElement("div"),
children: [],
replaceChildren() {
this.children = [];
},
append(...nodes) {
this.children.push(...nodes);
},
};
promotionFamilySourceFilterControlsNode.dataset.workflowOverviewPromotionFamilySourceFilterControls = "";
const promotionFamilySourceFilterRowsNode = {
...mountDocument.createElement("dl"),
children: [],
replaceChildren() {
this.children = [];
},
append(...nodes) {
this.children.push(...nodes);
},
};
promotionFamilySourceFilterRowsNode.dataset.workflowOverviewPromotionFamilySourceFilterRows = "";
const promotionFamilySourceFilterRenderResult =
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState(
promotionFamilyQtDragonGcodeFilterViewModel,
{
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
},
);
const promotionFamilySourceFilterMountResult =
mountIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState({
viewModel: promotionFamilyQtDragonGcodeFilterViewModel,
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
});
assert.equal(promotionFamilySourceFilterRenderResult.rendered, true);
assert.equal(promotionFamilySourceFilterRenderResult.statusLine, "Ready: 6/17 source files selected");
assert.equal(promotionFamilySourceFilterRenderResult.controlCount, 2);
assert.equal(promotionFamilySourceFilterRenderResult.filterRowCount, 7);
assert.equal(promotionFamilySourceFilterRenderResult.sourceFileCount, 6);
assert.equal(promotionFamilySourceFilterRenderResult.rowCount, 13);
assert.deepEqual(promotionFamilySourceFilterRenderResult.dataset, {
handoffPhase: "ready",
handoffReady: "true",
handoffScope: "workflow-overview-promotion-family-source-filter",
selectedFamilyId: "virtual-hal-promotion-family-qtdragon-on-abort",
selectedSourceKind: "gcode",
});
assert.equal(promotionFamilySourceFilterControlsNode.children.length, 4);
assert.equal(
promotionFamilySourceFilterControlsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterControl === "family" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterControlValue ===
"virtual-hal-promotion-family-qtdragon-on-abort" &&
node.textContent === "Family"
),
true,
);
assert.equal(
promotionFamilySourceFilterControlsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelect === "source-kind" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterValue === "gcode" &&
node.value === "gcode" &&
node.children.some((option) =>
option.value === "gcode" &&
option.selected === true &&
option.dataset?.workflowOverviewPromotionFamilySourceFilterCount === "6"
)
),
true,
);
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterValue ===
"virtual-hal-promotion-family-qtdragon-on-abort" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "family-filter" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelected === "true" &&
node.textContent === "virtual-hal-promotion-family-qtdragon-on-abort: 12 selected"
),
true,
);
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterValue === "gcode" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "source-kind-filter" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelected === "true" &&
node.textContent === "gcode: 6 selected"
),
true,
);
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "source-row" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterFamily ===
"virtual-hal-promotion-family-qtdragon-on-abort" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterSourceKind === "gcode" &&
node.textContent === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"
),
true,
);
assert.equal(promotionFamilySourceFilterMountResult.ready, true);
assert.equal(promotionFamilySourceFilterMountResult.renderResult.rowCount, 13);
const promotionFamilySourceFilterSelectionActionPlan =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan(
promotionFamilyRowsRenderState,
{
familyId: "virtual-hal-promotion-family-rose-engine-rcone-demo",
sourceKind: "all",
previousViewModel: promotionFamilyQtDragonGcodeFilterViewModel,
},
);
assert.equal(promotionFamilySourceFilterSelectionActionPlan.ready, true);
assert.equal(promotionFamilySourceFilterSelectionActionPlan.changed, true);
assert.equal(
promotionFamilySourceFilterSelectionActionPlan.statusLine,
"Ready: source filter changed to virtual-hal-promotion-family-rose-engine-rcone-demo / all",
);
assert.equal(
promotionFamilySourceFilterSelectionActionPlan.nextViewModel.statusLine,
"Ready: 2/17 source files selected",
);
assert.equal(promotionFamilySourceFilterSelectionActionPlan.nextViewModel.filteredSourceFileCount, 2);
assert.equal(promotionFamilySourceFilterSelectionActionPlan.nextViewModel.scopedSourceFileCount, 2);
const sourceFilterFamilySelect = promotionFamilySourceFilterControlsNode.children.find((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelect === "family"
);
const sourceFilterKindSelect = promotionFamilySourceFilterControlsNode.children.find((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelect === "source-kind"
);
sourceFilterFamilySelect.value = "virtual-hal-promotion-family-rose-engine-rcone-demo";
sourceFilterKindSelect.value = "all";
const promotionFamilySourceFilterSelectionChange =
handleIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionChange({
renderState: promotionFamilyRowsRenderState,
viewModel: promotionFamilyQtDragonGcodeFilterViewModel,
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
render: true,
});
assert.equal(promotionFamilySourceFilterSelectionChange.ready, true);
assert.equal(
promotionFamilySourceFilterSelectionChange.selectedFamilyId,
"virtual-hal-promotion-family-rose-engine-rcone-demo",
);
assert.equal(promotionFamilySourceFilterSelectionChange.selectedSourceKind, "all");
assert.equal(promotionFamilySourceFilterSelectionChange.actionPlan.changed, true);
assert.equal(promotionFamilySourceFilterSelectionChange.viewModel.filteredSourceFileCount, 2);
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.sourceFileCount, 2);
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.dataset.selectedSourceKind, "all");
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "source-row" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterFamily ===
"virtual-hal-promotion-family-rose-engine-rcone-demo" &&
node.textContent === "linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"
),
true,
);
renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterState(
promotionFamilyQtDragonGcodeFilterViewModel,
{
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
},
);
let promotionFamilySourceFilterBindingLastChange = null;
const promotionFamilySourceFilterSelectionBinding =
bindIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionControls({
renderState: promotionFamilyRowsRenderState,
viewModel: promotionFamilyQtDragonGcodeFilterViewModel,
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
render: true,
onChange: (changeResult) => {
promotionFamilySourceFilterBindingLastChange = changeResult;
},
});
assert.equal(promotionFamilySourceFilterSelectionBinding.ready, true);
assert.equal(promotionFamilySourceFilterSelectionBinding.boundControlCount, 2);
assert.deepEqual(promotionFamilySourceFilterSelectionBinding.controlIds, ["family", "source-kind"]);
const boundSourceFilterFamilySelect = promotionFamilySourceFilterControlsNode.children.find((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelect === "family"
);
const boundSourceFilterKindSelect = promotionFamilySourceFilterControlsNode.children.find((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterSelect === "source-kind"
);
boundSourceFilterFamilySelect.value = "virtual-hal-promotion-family-rose-engine-rcone-demo";
boundSourceFilterKindSelect.value = "all";
const promotionFamilySourceFilterBoundChange = boundSourceFilterFamilySelect.dispatchEvent("change");
assert.equal(promotionFamilySourceFilterBoundChange.ready, true);
assert.equal(
promotionFamilySourceFilterBoundChange.selectedFamilyId,
"virtual-hal-promotion-family-rose-engine-rcone-demo",
);
assert.equal(promotionFamilySourceFilterBoundChange.renderResult.sourceFileCount, 2);
assert.equal(
promotionFamilySourceFilterBindingLastChange.selectedFamilyId,
"virtual-hal-promotion-family-rose-engine-rcone-demo",
);
assert.equal(promotionFamilySourceFilterSelectionBinding.lastChange.selectedSourceKind, "all");
assert.deepEqual(promotionFamilySourceFilterSelectionBinding.unbind(), { unbound: 2 });
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ value }) =>
value === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ sourceKind, manifestBacked, status, value }) =>
sourceKind === "ini" &&
manifestBacked === true &&
status === "manifest-backed" &&
value === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ sourceKind, manifestBacked, status, value }) =>
sourceKind === "gcode" &&
manifestBacked === true &&
status === "manifest-backed" &&
value === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ value }) =>
value === "linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc"
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ sourceKind, manifestBacked, status, value }) =>
sourceKind === "remap-subroutine" &&
manifestBacked === true &&
status === "manifest-backed" &&
value === "linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc"
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.sourceRows.some(({ value }) =>
value === "linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"
),
true,
);
assert.equal(
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomContract().selectors.mount,
"[data-workflow-overview-promotion-family-rows]",
);
assert.equal(createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomReadiness().ready, false);
const promotionFamilyRowsMountNode = mountDocument.createElement("section");
promotionFamilyRowsMountNode.dataset.workflowOverviewPromotionFamilyRows = "";
const promotionFamilyRowsStatusNode = mountDocument.createElement("p");
promotionFamilyRowsStatusNode.dataset.workflowOverviewPromotionFamilyRowsStatus = "";
const promotionFamilyRowsNode = {
...mountDocument.createElement("dl"),
children: [],
replaceChildren() {
this.children = [];
},
append(...nodes) {
this.children.push(...nodes);
},
};
promotionFamilyRowsNode.dataset.workflowOverviewPromotionFamilyRowList = "";
const promotionFamilyRowsRenderResult =
renderIniPanelShellWorkflowOverviewPromotionFamilyRowsState(
promotionFamilyRowsRenderState,
{
documentRef: mountDocument,
mountNode: promotionFamilyRowsMountNode,
statusNode: promotionFamilyRowsStatusNode,
rowsNode: promotionFamilyRowsNode,
},
);
const promotionFamilyRowsMountResult =
mountIniPanelShellWorkflowOverviewPromotionFamilyRowsState({
renderState: promotionFamilyRowsRenderState,
documentRef: mountDocument,
mountNode: promotionFamilyRowsMountNode,
statusNode: promotionFamilyRowsStatusNode,
rowsNode: promotionFamilyRowsNode,
});
assert.equal(promotionFamilyRowsRenderResult.rendered, true);
assert.equal(promotionFamilyRowsRenderResult.rowCount, 20);
assert.deepEqual(promotionFamilyRowsRenderResult.rowIds, expectedPromotionFamilyRows.map(([id]) => id));
assert.equal(promotionFamilyRowsRenderResult.sourceFileCount, 17);
assert.equal(
promotionFamilyRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilyValue ===
"virtual-hal-promotion-family-qtdragon-on-abort" &&
node.dataset?.workflowOverviewPromotionFamilySourceKindCounts === "ini=6 gcode=6" &&
node.textContent === "6/6 ready; sources=12; diagnostics=ready; ini=6 gcode=6"
),
true,
);
assert.equal(
promotionFamilyRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilyValue ===
"virtual-hal-promotion-family-qtdragon-on-abort-source-1" &&
node.dataset?.workflowOverviewPromotionFamilySourceKind === "ini" &&
node.dataset?.workflowOverviewPromotionFamilyManifestBacked === "true" &&
node.dataset?.workflowOverviewPromotionFamilySourceStatus === "manifest-backed" &&
node.textContent === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"
),
true,
);
assert.equal(promotionFamilyRowsMountResult.ready, true);
assert.equal(promotionFamilyRowsMountResult.renderResult.rowCount, 20);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "saved-session-diagnostics")?.value,
"ready",
@@ -1905,7 +2565,7 @@ const releaseArtifactUrlWorkflowMountResult =
rowsNode: releaseArtifactUrlWorkflowRowsNode,
});
assert.equal(releaseArtifactUrlWorkflowRenderResult.rendered, true);
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 12);
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 22);
assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("browser-diagnostics"),
true,
@@ -1914,12 +2574,16 @@ assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("saved-session-diagnostics"),
true,
);
assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("virtual-hal-promotion-families"),
true,
);
assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("mount-result"),
true,
);
assert.equal(releaseArtifactUrlWorkflowMountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 12);
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 22);
const releaseArtifactUrlWorkflowMissingDom = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
@@ -1978,6 +2642,39 @@ const workflowOverviewApi = {
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel: () => releaseArtifactUrlWorkflowSummary,
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan: () => releaseArtifactUrlWorkflow.actionPlan,
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState: () => releaseArtifactUrlWorkflowRenderState,
getWorkflowOverviewPromotionFamilyRowsRenderState: () => promotionFamilyRowsRenderState,
getWorkflowOverviewPromotionFamilySourceFilterViewModel: () => promotionFamilySourceFilterViewModel,
getWorkflowOverviewPromotionFamilySourceFilterDomContract:
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomContract,
getWorkflowOverviewPromotionFamilySourceFilterDomReadiness: () => (
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterDomReadiness({
documentRef: mountDocument,
mountNode: promotionFamilySourceFilterMountNode,
statusNode: promotionFamilySourceFilterStatusNode,
controlsNode: promotionFamilySourceFilterControlsNode,
rowsNode: promotionFamilySourceFilterRowsNode,
})
),
getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan: () =>
promotionFamilySourceFilterSelectionActionPlan,
handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange: () =>
promotionFamilySourceFilterSelectionChange,
bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls: () =>
promotionFamilySourceFilterSelectionBinding,
renderWorkflowOverviewPromotionFamilySourceFilterState: () => promotionFamilySourceFilterRenderResult,
mountWorkflowOverviewPromotionFamilySourceFilterState: () => promotionFamilySourceFilterMountResult,
getWorkflowOverviewPromotionFamilyRowsDomContract:
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomContract,
getWorkflowOverviewPromotionFamilyRowsDomReadiness: () => (
createIniPanelShellWorkflowOverviewPromotionFamilyRowsDomReadiness({
documentRef: mountDocument,
mountNode: promotionFamilyRowsMountNode,
statusNode: promotionFamilyRowsStatusNode,
rowsNode: promotionFamilyRowsNode,
})
),
renderWorkflowOverviewPromotionFamilyRowsState: () => promotionFamilyRowsRenderResult,
mountWorkflowOverviewPromotionFamilyRowsState: () => promotionFamilyRowsMountResult,
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract:
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness: () => (

View File

@@ -4,6 +4,7 @@ import { basename, dirname, posix, resolve } from "node:path";
import assert from "node:assert/strict";
import {
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,
analyzeIniRuntimeBoundaries,
createLinuxCncInterpSdk,
planSimConfigStaging,
@@ -100,6 +101,7 @@ const runtimeBoundaryNativeAlignmentSummaryPath = resolve(buildDir, "runtime-bou
const runtimeProbeGateAlignmentPath = resolve(buildDir, "runtime-probe-gate-alignment.tsv");
const nativeRuntimeProbeExecutionPlanPath = resolve(buildDir, "native-runtime-probe-execution-plan.tsv");
const nativeRuntimeProbePassEvidenceContractPath = resolve(buildDir, "native-runtime-probe-pass-evidence-contract.tsv");
const promotionCandidatesPath = resolve(buildDir, "promotion-candidates.tsv");
const blockedRuntimePromotionLockPath = resolve(buildDir, "blocked-runtime-promotion-lock.tsv");
const runtimeBoundaryPromotionReadinessPath = resolve(buildDir, "runtime-boundary-promotion-readiness.tsv");
const runtimeBoundaryPromotionBlockersPath = resolve(buildDir, "runtime-boundary-promotion-blockers.tsv");
@@ -155,6 +157,7 @@ const generatedSimConfigInventoryArtifactPaths = [
runtimeProbeGateAlignmentPath,
nativeRuntimeProbeExecutionPlanPath,
nativeRuntimeProbePassEvidenceContractPath,
promotionCandidatesPath,
blockedRuntimePromotionLockPath,
runtimeBoundaryPromotionReadinessPath,
runtimeBoundaryPromotionBlockersPath,
@@ -6087,7 +6090,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) {
const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row]));
assert.equal(
rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count,
"54",
"55",
"WASM artifact documentation completion count must match generated artifact baseline",
);
assert.equal(
@@ -8609,7 +8612,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
);
assert.equal(
artifactNames.length,
54,
55,
"generated sim-config inventory artifact list count drift",
);
assert.deepEqual(
@@ -8628,6 +8631,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
"native-runtime-probe-summary.tsv",
"next-boundary-recommendations.tsv",
"next-boundary-worklist.tsv",
"promotion-candidates.tsv",
"python-remap-boundary-summary.tsv",
"python-remap-family-summary.tsv",
"python-remap-native-runtime-alignment.tsv",
@@ -8703,7 +8707,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
"generated sim-config inventory artifacts must be listed in browser smoke",
);
assert.ok(
browserSmokeText.includes("wasmArtifactNames.length !== 54"),
browserSmokeText.includes("wasmArtifactNames.length !== 55"),
"browser smoke must keep generated WASM artifact fixed-count guard",
);
assert.ok(
@@ -11946,6 +11950,198 @@ function verifyHardBlockedInventorySkips(rows, pathMatrixByPath) {
}
}
function sourcePathToInventoryPath(sourcePath) {
const prefix = "linuxcnc/configs/sim/";
return sourcePath.startsWith(prefix) ? sourcePath.slice(prefix.length) : "";
}
function promotionCandidateRows({
summaryRows,
boundaryRows,
nextBoundaryRecords,
trackedMatrixByPath,
nativeByPath,
}) {
const summaryByPath = new Map(summaryRows.map((row) => {
const [
path,
inventoryStatus,
reason,
className,
nativeStatus,
nativeExpectedFailure,
] = row.split("\t");
return [path, {
path,
inventoryStatus,
reason,
className,
nativeStatus,
nativeExpectedFailure,
}];
}));
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
const nextBoundaryByBlocked = new Map(nextBoundaryRecords.map((row) => [row.blocked, row]));
const evidenceReadyRows = VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES.map((candidate) => {
const inventoryPath = sourcePathToInventoryPath(candidate.gcodePath);
const summary = summaryByPath.get(inventoryPath);
const matrix = trackedMatrixByPath.get(inventoryPath);
const native = nativeByPath.get(inventoryPath);
const boundary = boundaryByPath.get(inventoryPath);
const virtualHalEvidenceReady = candidate.currentNodeInventoryStatus === "PASS" &&
candidate.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
candidate.targetBrowserEvidence === "explicit-browser-diagnostics" &&
candidate.blockedKind === "-" &&
summary?.inventoryStatus === "PASS";
return [
"evidence-ready",
candidate.id,
inventoryPath,
sourcePathToInventoryPath(candidate.iniPath),
summary?.inventoryStatus ?? "-",
summary?.reason ?? "-",
matrix?.layer4Node ?? "-",
matrix?.layer4Browser ?? "-",
summary?.className ?? native?.class ?? "-",
summary?.nativeStatus ?? native?.status ?? "-",
summary?.nativeExpectedFailure ?? native?.expected_failure ?? "-",
matrix?.blocked ?? candidate.blockedKind ?? "-",
candidate.dependencyClass,
flagValue(virtualHalEvidenceReady),
"0",
virtualHalEvidenceReady ? "browser_release_evidence_ready_inventory_baseline_unchanged" : "virtual_hal_evidence_incomplete",
"wasm-port/tests/sdk/node/verify_sdk_surface.sh && wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh",
].map(tsvValue).join("\t");
});
const inventoryReadyRows = [...summaryByPath.values()]
.filter((row) => row.className === "main" && row.inventoryStatus === "SKIP")
.sort((left, right) => left.path.localeCompare(right.path))
.map((row) => {
const boundary = boundaryByPath.get(row.path);
const matrix = trackedMatrixByPath.get(row.path);
const boundaryWork = nextBoundaryByBlocked.get(row.reason);
const promotionAllowed = row.reason === "-" &&
boundary?.recommended_blocked === "-" &&
row.nativeStatus === "PASS";
const blockReason = promotionAllowed
? "-"
: (boundaryWork?.next_action ?? `blocked_by_${row.reason}`);
const recommendedNextCommand = promotionAllowed
? "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh"
: (boundaryWork?.required_native_proof ?? "no_direct_baseline_promotion_candidate");
return [
"inventory-ready",
row.path,
row.path,
boundary?.ini ?? "-",
row.inventoryStatus,
row.reason,
matrix?.layer4Node ?? "-",
matrix?.layer4Browser ?? "-",
row.className,
row.nativeStatus,
row.nativeExpectedFailure,
matrix?.blocked ?? row.reason,
boundary?.dependencies ?? "-",
"0",
flagValue(promotionAllowed),
blockReason,
recommendedNextCommand,
].map(tsvValue).join("\t");
});
return [...evidenceReadyRows, ...inventoryReadyRows];
}
function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) {
const expectedHeaders = [
"candidate_kind",
"candidate_id",
"path",
"ini",
"current_status",
"skip_kind",
"matrix_layer4_node",
"matrix_layer4_browser",
"class",
"native_status",
"native_expected_failure",
"blocked_kind",
"dependency_class",
"virtual_hal_evidence_ready",
"promotion_allowed",
"block_reason",
"recommended_next_command",
];
const parsedRows = parseTsv(
`${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`,
expectedHeaders,
);
const expectedSkippedMain = summaryRows
.map((row) => {
const [path, inventoryStatus, reason, className] = row.split("\t");
return { path, inventoryStatus, reason, className };
})
.filter((row) => row.inventoryStatus === "SKIP" && row.className === "main")
.map((row) => row.path)
.sort();
const evidenceReadyPaths = VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES
.map((candidate) => sourcePathToInventoryPath(candidate.gcodePath))
.sort();
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
const parsedEvidenceReady = parsedRows.filter((row) => row.candidate_kind === "evidence-ready");
const parsedInventoryReady = parsedRows.filter((row) => row.candidate_kind === "inventory-ready");
assert.deepEqual(
parsedEvidenceReady.map((row) => row.path).sort(),
evidenceReadyPaths,
"promotion-candidates must cover virtual HAL evidence-ready rows",
);
assert.deepEqual(
parsedInventoryReady.map((row) => row.path).sort(),
expectedSkippedMain,
"promotion-candidates must cover every skipped main inventory candidate row",
);
assert.equal(
parsedInventoryReady.filter((row) => row.promotion_allowed === "1").length,
0,
"current baseline has no direct skipped-main inventory promotion candidate",
);
for (const row of parsedEvidenceReady) {
assert.equal(row.current_status, "PASS", `${row.path}: evidence-ready row must be a current inventory PASS`);
assert.equal(row.skip_kind, "-", `${row.path}: evidence-ready row must not have a skip kind`);
assert.equal(row.blocked_kind, "-", `${row.path}: evidence-ready row must not be hard-blocked`);
assert.equal(row.virtual_hal_evidence_ready, "1", `${row.path}: evidence-ready row lacks virtual HAL evidence`);
assert.equal(row.promotion_allowed, "0", `${row.path}: evidence-ready row must not claim inventory promotion`);
assert.equal(row.matrix_layer4_node, "INV", `${row.path}: evidence-ready row should remain Node INV`);
assert.equal(row.matrix_layer4_browser, "-", `${row.path}: evidence-ready row should record explicit diagnostics outside matrix browser REP`);
assert.equal(row.block_reason, "browser_release_evidence_ready_inventory_baseline_unchanged");
assert.ok(
row.recommended_next_command.includes("verify_sdk_surface.sh"),
`${row.path}: evidence-ready row lacks virtual HAL gate command`,
);
}
for (const row of parsedInventoryReady) {
assert.equal(row.current_status, "SKIP", `${row.path}: promotion candidate status drift`);
assert.equal(row.class, "main", `${row.path}: promotion candidate must be main class`);
assert.equal(row.blocked_kind, row.skip_kind, `${row.path}: skipped inventory blocked kind drift`);
assert.equal(row.virtual_hal_evidence_ready, "0", `${row.path}: skipped inventory row must not claim virtual HAL evidence ready`);
assert.ok(
["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.skip_kind),
`${row.path}: direct promotion candidate must remain hard-blocked or upstream-demo`,
);
const boundary = boundaryByPath.get(row.path);
assert.ok(boundary, `${row.path}: promotion candidate lacks boundary evidence`);
if (row.skip_kind.startsWith("L4-")) {
assert.notEqual(row.recommended_next_command, "-", `${row.path}: blocked row lacks next proof command`);
assert.notEqual(row.block_reason, "-", `${row.path}: blocked row lacks block reason`);
}
}
return parsedRows;
}
for (const record of nativeRecords) {
const skipReason = blockedKind(record, pathMatrixByPath);
if (skipReason) {
@@ -12425,6 +12621,44 @@ writeFileSync(
].join("\n")}\n`,
);
const promotionCandidateRowsGenerated = promotionCandidateRows({
summaryRows,
boundaryRows: boundarySummaryRecords,
nextBoundaryRecords,
trackedMatrixByPath,
nativeByPath: new Map(nativeRecords.map((record) => [record.path, record])),
});
verifyPromotionCandidateRows(
promotionCandidateRowsGenerated,
summaryRows,
boundarySummaryRecords,
);
writeFileSync(
promotionCandidatesPath,
`${[
[
"candidate_kind",
"candidate_id",
"path",
"ini",
"current_status",
"skip_kind",
"matrix_layer4_node",
"matrix_layer4_browser",
"class",
"native_status",
"native_expected_failure",
"blocked_kind",
"dependency_class",
"virtual_hal_evidence_ready",
"promotion_allowed",
"block_reason",
"recommended_next_command",
].join("\t"),
...promotionCandidateRowsGenerated,
].join("\n")}\n`,
);
writeFileSync(
boundarySummaryPath,
`${[

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
const root = resolve(fileURLToPath(import.meta.url), "../../..");
const mode = process.argv.includes("--write") ? "write" : "check";
const artifactPaths = [
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv",
];
const targetFiles = [
"wasm-port/runtime/sdk/src/linuxcnc-hal.js",
"wasm-port/runtime/sdk/src/project-release-readiness.js",
];
function escapeRegExp(text) {
return String(text).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function sha256(path) {
return createHash("sha256")
.update(readFileSync(resolve(root, path)))
.digest("hex");
}
const hashes = Object.fromEntries(artifactPaths.map((path) => [path, sha256(path)]));
function replaceHashBlock(text) {
let nextText = text;
for (const [artifactPath, hash] of Object.entries(hashes)) {
const pattern = new RegExp(`("${escapeRegExp(artifactPath)}":\\s*\\n\\s*")[a-f0-9]{64}(")`, "g");
nextText = nextText.replace(pattern, `$1${hash}$2`);
}
return nextText;
}
const staleFiles = [];
for (const targetFile of targetFiles) {
const absolutePath = resolve(root, targetFile);
const text = readFileSync(absolutePath, "utf8");
const nextText = replaceHashBlock(text);
if (nextText !== text) {
staleFiles.push(targetFile);
if (mode === "write") {
writeFileSync(absolutePath, nextText);
}
}
}
if (staleFiles.length > 0 && mode === "check") {
console.error(`sim_config_boundary_hashes=stale files=${staleFiles.join(",")}`);
console.error("run: wasm-port/tools/update_sim_config_boundary_hashes.sh --write");
process.exit(1);
}
for (const [artifactPath, hash] of Object.entries(hashes)) {
console.log(`${artifactPath} ${hash}`);
}
console.log(`sim_config_boundary_hashes=${staleFiles.length === 0 ? "ok" : "updated"}`);

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
node "$ROOT_DIR/tools/update_sim_config_boundary_hashes.mjs" "$@"