解锁L4-TOOL-DB主机运行环境
结论:已识别RIP版LinuxCNC主机运行时,Tool DB native protocol probe可通过,inventory baseline保持28/28/131/0且promotion继续锁定。
This commit is contained in:
269
text23.txt
Normal file
269
text23.txt
Normal file
@@ -0,0 +1,269 @@
|
||||
项目接续文件:OPFS/WASM hard block 移植优先级建议
|
||||
|
||||
生成时间:2026-06-19 CST
|
||||
|
||||
本文件接替 `text22.txt` 的“另开 milestone”建议,专门回答:
|
||||
|
||||
```text
|
||||
在基于 OPFS、WASM 的数控系统仿真中,
|
||||
L4-PYTHON-REMAP、L4-TOOL-DB、L4-USER-M-PROCESS 哪些可以移植,
|
||||
以及应该先移植哪一个。
|
||||
```
|
||||
|
||||
重要边界:
|
||||
|
||||
```text
|
||||
本文件是 future runtime milestone 建议,不改变 text22.txt 的当前阶段铁律。
|
||||
当前阶段仍不得解锁 L4-PYTHON-REMAP / L4-TOOL-DB / L4-USER-M-PROCESS;
|
||||
Node inventory baseline 仍保持 executed=28 passed=28 skipped=131 unexpected_fail=0;
|
||||
promotion_allowed 仍必须保持 0。
|
||||
```
|
||||
|
||||
一、简要结论
|
||||
|
||||
三类 hard block 都可以“部分或项目定义下完整”移植到 OPFS/WASM 环境,但可移植方式不同。
|
||||
|
||||
推荐优先级:
|
||||
|
||||
```text
|
||||
1. L4-TOOL-DB
|
||||
2. L4-PYTHON-REMAP
|
||||
3. L4-USER-M-PROCESS
|
||||
```
|
||||
|
||||
建议先移植 `L4-TOOL-DB`,原因是:
|
||||
|
||||
- 当前只有 1 个 inventory row:`axis/db_demo/base.ngc`;
|
||||
- 协议边界清晰:`DB_PROGRAM`、`v2.1`、`g`、`FINI`、`l`、`p`、`u`;
|
||||
- OPFS 与 tool DB flat-file persistence 天然匹配;
|
||||
- 可用 Worker / WASM Python runtime 隔离 `DB_PROGRAM`;
|
||||
- 已有 `text20.txt` / `text21.txt` 规划和 machine-readable artifacts;
|
||||
- 不需要一次性承诺完整 Python remap lifecycle 或 arbitrary external process;
|
||||
- 成功后能形成第一个 hard-block runtime unlock 的可复用 proof pattern。
|
||||
|
||||
二、可移植性判断
|
||||
|
||||
| Family | 能否移植到 OPFS/WASM | 推荐优先级 | 主要原因 |
|
||||
| --- | --- | ---: | --- |
|
||||
| `L4-TOOL-DB` | 可以,建议先做 | 1 | 协议小、状态明确、OPFS 持久化适配自然、只有 1 个 row。 |
|
||||
| `L4-PYTHON-REMAP` | 可以,但应分阶段做 | 2 | 覆盖面最大,价值高,但需要 LinuxCNC Python remap lifecycle、prolog/epilog、module loading、interpreter state binding。 |
|
||||
| `L4-USER-M-PROCESS` | 只建议做受控子集,最后做 | 3 | 浏览器不能运行 arbitrary external process;必须把 external process 收束成 LinuxCNC-owned state transition proof。 |
|
||||
|
||||
三、为什么先做 L4-TOOL-DB
|
||||
|
||||
`L4-TOOL-DB` 当前 blocked row 是:
|
||||
|
||||
```text
|
||||
axis/db_demo/base.ngc
|
||||
```
|
||||
|
||||
当前阻塞原因不是 G-code 解释器本身,而是 INI 声明:
|
||||
|
||||
```text
|
||||
[EMCIO]
|
||||
DB_PROGRAM = ./db_nonran.py
|
||||
```
|
||||
|
||||
这意味着 `.tbl` fallback 不能算 pass。必须证明 LinuxCNC tool DB process protocol:
|
||||
|
||||
```text
|
||||
startup handshake: v2.1
|
||||
get-all: g ... FINI
|
||||
spindle load notify: l
|
||||
tool offset notify: p
|
||||
spindle unload notify: u
|
||||
flat-file DB persistence
|
||||
```
|
||||
|
||||
在 OPFS/WASM 环境中,推荐定义:
|
||||
|
||||
```text
|
||||
ToolDbProcessPort
|
||||
```
|
||||
|
||||
职责:
|
||||
|
||||
- 按 INI 中的 `DB_PROGRAM` 启动受控 runtime;
|
||||
- 维持 line-based protocol;
|
||||
- 记录 transcript;
|
||||
- 将 DB flat file 映射到 OPFS;
|
||||
- 导出 release/browser diagnostics。
|
||||
|
||||
不允许:
|
||||
|
||||
- 用 `.tbl` fallback 代替 DB_PROGRAM;
|
||||
- 用 JS 直接构造 tool semantics;
|
||||
- 跳过 `tooldb.py` / `db.py` 行为;
|
||||
- 只靠 browser glue 声称 tool DB pass。
|
||||
|
||||
建议 first milestone:
|
||||
|
||||
```text
|
||||
tool-db-opfs-wasm-milestone
|
||||
```
|
||||
|
||||
最小完成定义:
|
||||
|
||||
1. Native `ENABLE_TOOL_DB_RUNTIME_PROBE=1` 可在完整 LinuxCNC host runtime 上通过;
|
||||
2. WASM/Worker 能执行等价 DB protocol loop;
|
||||
3. OPFS 能保存并恢复 DB flat-file state;
|
||||
4. transcript 包含 `v2.1` / `g` / `FINI` / `l` / `p` / `u`;
|
||||
5. browser diagnostics 输出 DB program path、OPFS DB path、transcript hash、state summary;
|
||||
6. release artifact 标明 `.tbl fallback sufficient=false`;
|
||||
7. 只有 native + WASM + browser proof 都齐后,才允许考虑把 `axis/db_demo/base.ngc` 从 `SKIP L4-TOOL-DB` 推进。
|
||||
|
||||
四、L4-PYTHON-REMAP 的移植建议
|
||||
|
||||
`L4-PYTHON-REMAP` 可以移植,但不应该作为第一个解锁对象。
|
||||
|
||||
原因:
|
||||
|
||||
- 当前涉及 53 个 skip/block rows;
|
||||
- 覆盖 Python module loading、remap callable、prolog/epilog、NGC-only subpath、interpreter state、HAL/UI/HALUI assumptions;
|
||||
- 一旦边界设计不严,很容易把 Python runtime 当作 JS/browser-owned CNC semantics;
|
||||
- 需要比 tool DB 更深地嵌入 LinuxCNC interpreter Python plugin lifecycle。
|
||||
|
||||
推荐路线:
|
||||
|
||||
```text
|
||||
先复用 tool DB milestone 中建立的 Python/WASM substrate;
|
||||
再做最小 Python remap lifecycle fixture;
|
||||
最后扩大到 family-level inventory。
|
||||
```
|
||||
|
||||
首个 fixture 仍建议沿用已有 artifact 中的计划:
|
||||
|
||||
```text
|
||||
axis/remap/stop-lookahead/nc_files
|
||||
```
|
||||
|
||||
原因是它更适合作为 lifecycle proof:
|
||||
|
||||
- 有 Python runtime phase;
|
||||
- 当前已有 native runtime fixture plan;
|
||||
- 比完整 tool-change / five-axis Python remap family 更窄;
|
||||
- 可以先证明 Python module import、callable dispatch、interpreter lifecycle 和 state observation。
|
||||
|
||||
Python remap milestone 的完成定义应至少包括:
|
||||
|
||||
1. `python-remap-native-runtime-readiness.tsv` host readiness clear;
|
||||
2. `ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1` native lifecycle probe pass;
|
||||
3. WASM Python runtime 能复现同一 lifecycle;
|
||||
4. remap/prolog/epilog callable 来源绑定 LinuxCNC config/source;
|
||||
5. browser diagnostics 明确 Python runtime mode;
|
||||
6. 不把 NGC-only subroutine asset 当 standalone main program;
|
||||
7. 不因为 Python runtime 可运行就批量解锁全部 53 rows。
|
||||
|
||||
五、L4-USER-M-PROCESS 的移植建议
|
||||
|
||||
`L4-USER-M-PROCESS` 在浏览器中最不适合按 native external process 语义原样移植。
|
||||
|
||||
当前代表 row:
|
||||
|
||||
```text
|
||||
axis/vismach/millturn/example.ngc
|
||||
```
|
||||
|
||||
核心阻塞是 external user-M process:
|
||||
|
||||
```text
|
||||
M128 / M129 Tcl scripts
|
||||
HAL pin state updates
|
||||
kinstype guard
|
||||
ini.[xyz].* soft-limit state transitions
|
||||
```
|
||||
|
||||
浏览器/WASM 环境不能安全、通用地支持:
|
||||
|
||||
- arbitrary executable spawn;
|
||||
- native Tcl process execution;
|
||||
- host HAL daemon mutation;
|
||||
- unrestricted process side effects。
|
||||
|
||||
因此不建议把 `L4-USER-M-PROCESS` 定义成“浏览器运行任意 USER_M_PATH executable”。
|
||||
|
||||
可行路线只能是受控子集:
|
||||
|
||||
```text
|
||||
LinuxCNC-owned user-M state transition boundary
|
||||
```
|
||||
|
||||
例如对 `millturn`:
|
||||
|
||||
- 读取 config-owned `M128` / `M129` source;
|
||||
- 绑定 `M428 -> M128`、`M429 -> M129`;
|
||||
- 证明 `kinstype.is-0` / `kinstype.is-1` guard;
|
||||
- 证明 `ini.x.*` / `ini.y.*` / `ini.z.*` state target;
|
||||
- browser 只执行已证明的 state-transition adapter;
|
||||
- diagnostics 明确 `arbitrary_external_process=false`。
|
||||
|
||||
推荐把它放在第三优先级,原因是:
|
||||
|
||||
- 成功解锁只覆盖 1 个 row;
|
||||
- 需要 HAL/Tcl/user-M process state proof;
|
||||
- 不能形成通用 external process 支持;
|
||||
- 安全边界和用户期望更容易误读。
|
||||
|
||||
六、总体推荐路线
|
||||
|
||||
推荐 roadmap:
|
||||
|
||||
```text
|
||||
Phase A:继续保持 text22 lock
|
||||
Phase B:L4-TOOL-DB OPFS/WASM proof
|
||||
Phase C:复用 Python/WASM substrate,做最小 L4-PYTHON-REMAP lifecycle proof
|
||||
Phase D:做受控 L4-USER-M-PROCESS state-transition proof
|
||||
```
|
||||
|
||||
不要反过来做:
|
||||
|
||||
- 不要先做 `L4-USER-M-PROCESS`,因为它不是通用 browser process model;
|
||||
- 不要直接批量做 `L4-PYTHON-REMAP`,因为 blast radius 太大;
|
||||
- 不要用 `.tbl` fallback 解 `L4-TOOL-DB`;
|
||||
- 不要用 virtual HAL 或 JS glue 替代 LinuxCNC CNC/runtime 语义。
|
||||
|
||||
七、建议的下一步文件/代码工作
|
||||
|
||||
如果要开启第一个 hard-block runtime milestone,建议创建:
|
||||
|
||||
```text
|
||||
text24.txt
|
||||
```
|
||||
|
||||
主题:
|
||||
|
||||
```text
|
||||
L4-TOOL-DB OPFS/WASM runtime milestone execution plan
|
||||
```
|
||||
|
||||
内容应包括:
|
||||
|
||||
1. `ToolDbProcessPort` API 草案;
|
||||
2. Worker/Python runtime 选择;
|
||||
3. OPFS DB file layout;
|
||||
4. transcript schema;
|
||||
5. native/WASM/browser proof gates;
|
||||
6. release diagnostics fields;
|
||||
7. promotion lock 更新条件;
|
||||
8. rollback 条件。
|
||||
|
||||
八、最终建议
|
||||
|
||||
当前问题的直接答案:
|
||||
|
||||
```text
|
||||
都可以研究移植;
|
||||
第一个应该移植 L4-TOOL-DB;
|
||||
第二个做 L4-PYTHON-REMAP;
|
||||
第三个做 L4-USER-M-PROCESS 的受控 state-transition 子集。
|
||||
```
|
||||
|
||||
当前阶段仍不应改变:
|
||||
|
||||
```text
|
||||
L4-PYTHON-REMAP locked
|
||||
L4-TOOL-DB locked
|
||||
L4-USER-M-PROCESS locked
|
||||
promotion_allowed=0
|
||||
baseline=28/28/131/0
|
||||
```
|
||||
414
text24.txt
Normal file
414
text24.txt
Normal file
@@ -0,0 +1,414 @@
|
||||
项目接续文件:L4-TOOL-DB OPFS/WASM runtime milestone 执行计划
|
||||
|
||||
生成时间:2026-06-19 CST
|
||||
|
||||
本文件接替 `text23.txt`,回答“第一个先做 `L4-TOOL-DB`,具体如何做”。
|
||||
|
||||
重要边界:
|
||||
|
||||
```text
|
||||
本文件是单独 hard-block runtime milestone 的执行计划。
|
||||
不是 text22.txt 当前 evidence-based promotion 阶段的一部分。
|
||||
在本 milestone 完成前:
|
||||
axis/db_demo/base.ngc 仍必须保持 SKIP L4-TOOL-DB;
|
||||
promotion_allowed=0;
|
||||
baseline 仍保持 executed=28 passed=28 skipped=131 unexpected_fail=0。
|
||||
```
|
||||
|
||||
一、目标
|
||||
|
||||
目标不是让 `axis/db_demo/base.ngc` 通过 `.tbl` fallback,也不是用 JavaScript 重新实现 tool semantics。
|
||||
|
||||
目标是证明:
|
||||
|
||||
```text
|
||||
LinuxCNC-owned tool DB protocol + DB_PROGRAM behavior + OPFS persistence
|
||||
可以在 native / WASM / browser proof chain 中成立。
|
||||
```
|
||||
|
||||
最小目标 row:
|
||||
|
||||
```text
|
||||
axis/db_demo/base.ngc
|
||||
ini=axis/db_demo/db_nonran.ini
|
||||
blocked=L4-TOOL-DB
|
||||
DB_PROGRAM=./db_nonran.py
|
||||
```
|
||||
|
||||
必须保留的协议:
|
||||
|
||||
```text
|
||||
startup handshake: v2.1
|
||||
get-all: g ... FINI
|
||||
spindle load notify: l
|
||||
tool offset notify: p
|
||||
spindle unload notify: u
|
||||
flat-file persistence
|
||||
```
|
||||
|
||||
二、当前已有基础
|
||||
|
||||
已有 source/protocol accounting:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
当前 probe 状态:
|
||||
|
||||
```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
|
||||
promotion_allowed=0
|
||||
```
|
||||
|
||||
已有 native probe 入口:
|
||||
|
||||
```text
|
||||
wasm-port/tests/native/probe_tool_db_runtime.sh
|
||||
```
|
||||
|
||||
三、Phase 1:native protocol proof 先闭环
|
||||
|
||||
目的:先证明项目记录的 tool DB transaction contract 与真实 LinuxCNC DB runtime 一致。
|
||||
|
||||
1. 准备完整 LinuxCNC host runtime。
|
||||
|
||||
需要 PATH 上存在:
|
||||
|
||||
```text
|
||||
python3
|
||||
linuxcnc
|
||||
milltask
|
||||
halcmd
|
||||
```
|
||||
|
||||
需要 Python 模块可 import:
|
||||
|
||||
```bash
|
||||
python3 -c "import linuxcnc; import tooldb; print('linuxcnc_tooldb_python_modules=ok')"
|
||||
```
|
||||
|
||||
2. 先跑 readiness,不启用执行。
|
||||
|
||||
```bash
|
||||
wasm-port/tests/native/probe_tool_db_runtime.sh
|
||||
```
|
||||
|
||||
可接受输出:
|
||||
|
||||
```text
|
||||
tool_db_runtime_probe_status=ready_disabled_by_default
|
||||
```
|
||||
|
||||
如果仍输出:
|
||||
|
||||
```text
|
||||
tool_db_runtime_probe_status=skipped_missing_host_runtime
|
||||
```
|
||||
|
||||
则先修 host/container runtime,不改项目 fallback。
|
||||
|
||||
3. 显式启用 native runtime probe。
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
该 probe 必须证明:
|
||||
|
||||
- `db_nonran.py` 启动并返回 `v2.1`;
|
||||
- `g` 返回 `T10..T19` 并以 `FINI` 结束;
|
||||
- 初始 pocket 是 `tno+100`;
|
||||
- `p t11 p111 d0.33 z0.11` 后 get-all 能看到状态更新;
|
||||
- `l t14 p0` 后 `T14` 进入 spindle pocket `P0`;
|
||||
- `u t0 p0` 后 `T14` 回到 `P114`;
|
||||
- DB savefile 包含 mutation;
|
||||
- 输出仍保持 `tool_db_execution_enabled=0`、`tool_db_promotion_allowed=0`。
|
||||
|
||||
4. 刷新 inventory artifacts。
|
||||
|
||||
```bash
|
||||
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
|
||||
```
|
||||
|
||||
预期仍不改变 baseline:
|
||||
|
||||
```text
|
||||
executed=28
|
||||
passed=28
|
||||
skipped=131
|
||||
unexpected_fail=0
|
||||
```
|
||||
|
||||
但 tool DB runtime artifacts 应进入 “native proof ready, waiting for Node/browser proof” 类型状态。
|
||||
|
||||
四、Phase 2:定义 WASM ToolDbProcessPort
|
||||
|
||||
目的:把 native child-process edge 改成可在 WASM/browser 中验证的协议端口。
|
||||
|
||||
建议新增抽象:
|
||||
|
||||
```text
|
||||
ToolDbProcessPort
|
||||
```
|
||||
|
||||
建议 API:
|
||||
|
||||
```text
|
||||
createLinuxCncToolDbProcessPort({
|
||||
dbProgramPath,
|
||||
sourceFiles,
|
||||
opfsRoot,
|
||||
runtimeMode,
|
||||
})
|
||||
|
||||
start()
|
||||
writeLine(line)
|
||||
readLine()
|
||||
runTransactionPlan(plan)
|
||||
exportTranscript()
|
||||
close()
|
||||
```
|
||||
|
||||
职责:
|
||||
|
||||
- 按 INI 的 `DB_PROGRAM` 定位 DB program;
|
||||
- 提供 line-based protocol I/O;
|
||||
- 记录 transcript;
|
||||
- 将 DB flat-file 映射到 OPFS;
|
||||
- 导出 diagnostics artifact。
|
||||
|
||||
不允许:
|
||||
|
||||
- 解析 `.tbl` 作为 pass;
|
||||
- JS 直接构造 tool table semantics;
|
||||
- 跳过 `tooldb.py`;
|
||||
- 不跑 `db.py` / `db_nonran.py` 回调;
|
||||
- 只用 fixture transcript 假装 runtime pass。
|
||||
|
||||
五、Phase 3:选择 DB_PROGRAM runtime
|
||||
|
||||
推荐首选:
|
||||
|
||||
```text
|
||||
Web Worker + WASM Python runtime
|
||||
```
|
||||
|
||||
Worker 内加载:
|
||||
|
||||
```text
|
||||
configs/sim/axis/db_demo/db_nonran.py
|
||||
configs/sim/axis/db_demo/db.py
|
||||
lib/python/tooldb.py
|
||||
minimal linuxcnc Python integration shim
|
||||
OPFS-backed DB flat file
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
```text
|
||||
minimal linuxcnc shim 只能覆盖 DB demo 必需 integration surface,
|
||||
例如 linuxcnc.command().load_tool_table 的同步边界记录。
|
||||
不能承载 tool semantics。
|
||||
```
|
||||
|
||||
如果暂时没有 Python/WASM runtime,不要直接改成 JS 版 DB program。可以先做:
|
||||
|
||||
```text
|
||||
tool-db-process-port contract tests
|
||||
transcript schema
|
||||
OPFS persistence store
|
||||
browser diagnostics shell
|
||||
```
|
||||
|
||||
但仍保持:
|
||||
|
||||
```text
|
||||
runtime_execution_ready=0
|
||||
promotion_allowed=0
|
||||
```
|
||||
|
||||
六、Phase 4:OPFS persistence layout
|
||||
|
||||
建议 OPFS 路径:
|
||||
|
||||
```text
|
||||
/machines/<machine-id>/tool-db/db_nonran_file
|
||||
/machines/<machine-id>/tool-db/transcripts/<run-id>.json
|
||||
```
|
||||
|
||||
必须验证:
|
||||
|
||||
1. 初次启动可创建 DB file;
|
||||
2. `p` / `l` / `u` 后 DB file 更新;
|
||||
3. 关闭 session 后重新加载仍能读回状态;
|
||||
4. diagnostics 导出:
|
||||
|
||||
```text
|
||||
db_program_path
|
||||
opfs_db_path
|
||||
transcript_hash
|
||||
db_file_hash
|
||||
startup_tool_count
|
||||
mutation_count
|
||||
tbl_fallback_sufficient=false
|
||||
promotion_allowed=0
|
||||
```
|
||||
|
||||
七、Phase 5:Node/WASM proof
|
||||
|
||||
新增 Node gate 建议:
|
||||
|
||||
```text
|
||||
wasm-port/tests/wasm/node/verify_tool_db_process_port_wasm.sh
|
||||
```
|
||||
|
||||
该 gate 要证明:
|
||||
|
||||
- 从 INI 解析到 `DB_PROGRAM=./db_nonran.py`;
|
||||
- ToolDbProcessPort 可启动 runtime;
|
||||
- transcript 包含 `v2.1` / `g` / `FINI` / `p` / `l` / `u`;
|
||||
- OPFS-equivalent store 可 roundtrip DB file;
|
||||
- `.tbl fallback sufficient=false`;
|
||||
- 输出 `tool_db_process_port_wasm=ok`;
|
||||
- 不改 `verify_sim_configs_inventory_wasm.sh` baseline。
|
||||
|
||||
八、Phase 6:browser diagnostics proof
|
||||
|
||||
新增 browser gate 建议:
|
||||
|
||||
```text
|
||||
wasm-port/tests/browser/verify_tool_db_process_browser.sh
|
||||
```
|
||||
|
||||
browser diagnostics 应显示:
|
||||
|
||||
```text
|
||||
tool_db_runtime_mode=wasm-tool-db-protocol-worker
|
||||
db_program=./db_nonran.py
|
||||
opfs_db_path=/machines/<machine-id>/tool-db/db_nonran_file
|
||||
protocol_transcript_ready=true
|
||||
opfs_persistence_ready=true
|
||||
tbl_fallback_sufficient=false
|
||||
promotion_allowed=0
|
||||
```
|
||||
|
||||
UI 只展示状态和 transcript evidence,不实现 tool semantics。
|
||||
|
||||
九、Phase 7:release artifact / promotion lock
|
||||
|
||||
在 native + Node/WASM + browser proof 都通过前,release artifact 只能显示:
|
||||
|
||||
```text
|
||||
L4-TOOL-DB locked
|
||||
tool_db_native_protocol_ready=<0|1>
|
||||
tool_db_wasm_protocol_ready=<0|1>
|
||||
tool_db_browser_protocol_ready=<0|1>
|
||||
promotion_allowed=0
|
||||
```
|
||||
|
||||
只有三层 proof 都为 ready 后,才能另起变更更新:
|
||||
|
||||
```text
|
||||
runtime-boundary-promotion-readiness.tsv
|
||||
runtime-boundary-promotion-blockers.tsv
|
||||
runtime-boundary-post-native-pass-gates.tsv
|
||||
blocked-runtime-promotion-lock.tsv
|
||||
promotion-candidates.tsv
|
||||
```
|
||||
|
||||
即使到那一步,也要单独 review 是否允许:
|
||||
|
||||
```text
|
||||
axis/db_demo/base.ngc: SKIP L4-TOOL-DB -> PASS
|
||||
```
|
||||
|
||||
十、验收命令建议
|
||||
|
||||
milestone 初期最小验证:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
wasm-port/tests/native/probe_tool_db_runtime.sh
|
||||
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
|
||||
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
|
||||
```
|
||||
|
||||
具备 host runtime 后:
|
||||
|
||||
```bash
|
||||
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
|
||||
```
|
||||
|
||||
实现 WASM/OPFS port 后追加:
|
||||
|
||||
```bash
|
||||
wasm-port/tests/wasm/node/verify_tool_db_process_port_wasm.sh
|
||||
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_tool_db_process_browser.sh
|
||||
```
|
||||
|
||||
十一、首个代码切入点建议
|
||||
|
||||
建议第一批代码不要直接改 inventory pass/fail,而是新增只读 protocol/runtime 层:
|
||||
|
||||
```text
|
||||
wasm-port/runtime/sdk/src/tool-db-process-port.js
|
||||
wasm-port/runtime/opfs/tool-db-store.js
|
||||
wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs
|
||||
wasm-port/tests/opfs/node/verify_tool_db_store.mjs
|
||||
```
|
||||
|
||||
第一批只做:
|
||||
|
||||
- transaction plan parser;
|
||||
- transcript schema;
|
||||
- OPFS/path model;
|
||||
- DB file hash;
|
||||
- diagnostics export;
|
||||
- `.tbl fallback sufficient=false` guard。
|
||||
|
||||
第二批再接 Python/WASM Worker runtime。
|
||||
|
||||
这样可以先建立可测试边界,同时避免误把 JS helper 当作 tool DB runtime pass。
|
||||
|
||||
十二、结论
|
||||
|
||||
`L4-TOOL-DB` 的正确做法是:
|
||||
|
||||
```text
|
||||
先 native protocol proof;
|
||||
再 ToolDbProcessPort;
|
||||
再 WASM/Worker DB_PROGRAM runtime;
|
||||
再 OPFS persistence;
|
||||
再 browser diagnostics;
|
||||
最后才考虑 promotion lock 更新。
|
||||
```
|
||||
|
||||
当前不要做:
|
||||
|
||||
```text
|
||||
不要 .tbl fallback;
|
||||
不要 JS tool semantics;
|
||||
不要直接改 baseline;
|
||||
不要把 protocol contract 当 runtime pass。
|
||||
```
|
||||
@@ -265,9 +265,16 @@ release-visible summary without treating it as a runtime unlock:
|
||||
|
||||
```text
|
||||
promotion-candidate-artifact=wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv
|
||||
promotion-candidate-artifact-rows=28
|
||||
promotion-candidate-layers=evidence-ready=8 inventory-ready=20
|
||||
promotion-candidate-total=28
|
||||
evidence-ready-candidate-rows=8
|
||||
evidence-ready-candidate-preview=qtdragon-multi-joint-on-abort
|
||||
evidence-ready-candidate-preview-gcode=linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc
|
||||
evidence-ready-candidate-promotion-allowed=0
|
||||
evidence-ready-candidate-baseline-changing=no
|
||||
evidence-expansion-candidates=13
|
||||
evidence-expansion-artifact-rows=13
|
||||
evidence-expansion-artifact=wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv
|
||||
promotion-candidate-allowed=0
|
||||
```
|
||||
|
||||
@@ -44,10 +44,29 @@ The generated artifact baseline is:
|
||||
- `build/wasm/sim-configs-inventory/blocked-dependency-summary.tsv`
|
||||
- `build/wasm/sim-configs-inventory/full-process-boundary-summary.tsv`
|
||||
- `build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv`
|
||||
- `build/wasm/sim-configs-inventory/promotion-candidates.tsv`
|
||||
- `build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv`
|
||||
- `build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv`
|
||||
- `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv`
|
||||
- `build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv`
|
||||
|
||||
The current promotion-candidate artifact is evidence accounting only. It has
|
||||
28 rows total: 8 `evidence-ready` rows with virtual HAL source-derived
|
||||
browser/release evidence, and 20 `inventory-ready` skipped main-program rows.
|
||||
Every row keeps `promotion_allowed=0`, so the inventory baseline remains
|
||||
`28/28/131/0`. The companion `evidence-expansion-candidates.tsv` artifact has
|
||||
13 rows queued for future browser diagnostics binding, also with
|
||||
`promotion_allowed=0`.
|
||||
|
||||
SDK and workflow-overview release URL summaries expose the current
|
||||
`evidence-ready` layer as dashboard fields: `evidence-ready-candidate-rows=8`,
|
||||
`evidence-ready-candidate-preview=qtdragon-multi-joint-on-abort`,
|
||||
`evidence-ready-candidate-preview-gcode=linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc`,
|
||||
`evidence-ready-candidate-promotion-allowed=0`, and
|
||||
`evidence-ready-candidate-baseline-changing=no`. These fields are visibility
|
||||
for existing source-derived browser/release evidence, not permission to change
|
||||
the Node inventory baseline.
|
||||
|
||||
## Skip reason map
|
||||
|
||||
The TSV artifacts use hyphenated reason names. The shell output uses
|
||||
|
||||
@@ -134,20 +134,65 @@ function createPromotionFamilySummaryRows(candidateReport = {}) {
|
||||
|
||||
function createPromotionCandidateArtifactSummary({
|
||||
candidateReport = {},
|
||||
evidenceExpansionReport = {},
|
||||
inventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
|
||||
blockedFamilies = BLOCKED_RUNTIME_FAMILIES,
|
||||
promotedBlockedFamilies = [],
|
||||
promotionCandidateRows = [],
|
||||
evidenceExpansionRows = [],
|
||||
} = {}) {
|
||||
const rows = arrayOrEmpty(objectOrEmpty(candidateReport).rows);
|
||||
const candidateArtifactRows = arrayOrEmpty(promotionCandidateRows);
|
||||
const evidenceExpansionArtifactRows = arrayOrEmpty(evidenceExpansionRows);
|
||||
const evidenceReadyCount = rows.filter((row) =>
|
||||
row.currentNodeInventoryStatus === "PASS" &&
|
||||
row.blockedKind === "-" &&
|
||||
row.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
|
||||
row.targetBrowserEvidence === "explicit-browser-diagnostics"
|
||||
).length;
|
||||
const inventoryReadyCount = 20;
|
||||
const evidenceExpansionCandidateCount = 13;
|
||||
const promotionAllowedCount = 0;
|
||||
const artifactEvidenceReadyRows = candidateArtifactRows
|
||||
.filter((row) => row.candidate_kind === "evidence-ready");
|
||||
const artifactInventoryReadyRows = candidateArtifactRows
|
||||
.filter((row) => row.candidate_kind === "inventory-ready");
|
||||
const artifactEvidenceReadyCount = artifactEvidenceReadyRows.length;
|
||||
const inventoryReadyCount = artifactInventoryReadyRows.length > 0
|
||||
? artifactInventoryReadyRows.length
|
||||
: 20;
|
||||
const evidenceExpansionCandidateCount = evidenceExpansionArtifactRows.length > 0
|
||||
? evidenceExpansionArtifactRows.length
|
||||
: 13;
|
||||
const promotionAllowedCount = candidateArtifactRows.length > 0
|
||||
? candidateArtifactRows.filter((row) => String(row.promotion_allowed ?? row.promotionAllowed) === "1").length
|
||||
: 0;
|
||||
const hardBlockRuntimeFamilyRows = artifactInventoryReadyRows
|
||||
.filter((row) => blockedFamilies.includes(row.skip_kind ?? row.blocked_kind ?? row.blockedKind))
|
||||
.map((row, index) => ({
|
||||
candidateId: row.candidate_id ?? row.path ?? "unknown",
|
||||
path: row.path ?? "unknown",
|
||||
skipKind: row.skip_kind ?? row.blocked_kind ?? row.blockedKind ?? "unknown",
|
||||
promotionAllowed: String(row.promotion_allowed ?? row.promotionAllowed ?? "0") === "1",
|
||||
blockReason: row.block_reason ?? row.blockReason ?? "-",
|
||||
recommendedNextCommand: row.recommended_next_command ?? row.recommendedNextCommand ?? "-",
|
||||
detailId: `hard-block-runtime-detail-${index + 1}`,
|
||||
}));
|
||||
const resolvedEvidenceReadyCount = artifactEvidenceReadyCount > 0 ? artifactEvidenceReadyCount : evidenceReadyCount;
|
||||
const hardBlockRuntimePromotionAllowedCount = hardBlockRuntimeFamilyRows
|
||||
.filter((row) => row.promotionAllowed)
|
||||
.length;
|
||||
const hardBlockRuntimeFamilySummaryRows = blockedFamilies.map((family) => {
|
||||
const familyRows = hardBlockRuntimeFamilyRows.filter((row) => row.skipKind === family);
|
||||
const promotionAllowedRows = familyRows.filter((row) => row.promotionAllowed);
|
||||
return {
|
||||
family,
|
||||
candidateCount: familyRows.length,
|
||||
promotionAllowedCount: promotionAllowedRows.length,
|
||||
locked: promotionAllowedRows.length === 0,
|
||||
lockedPaths: familyRows.map((row) => row.path),
|
||||
firstPath: familyRows[0]?.path ?? null,
|
||||
firstBlockReason: familyRows[0]?.blockReason ?? null,
|
||||
nextProof: familyRows[0]?.recommendedNextCommand ?? null,
|
||||
};
|
||||
});
|
||||
const hardBlockRuntimeLockSummary = createHardBlockRuntimeLockSummary(
|
||||
blockedFamilies,
|
||||
promotedBlockedFamilies,
|
||||
@@ -167,17 +212,36 @@ function createPromotionCandidateArtifactSummary({
|
||||
artifactPath: "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv",
|
||||
evidenceExpansionArtifactPath: "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv",
|
||||
layerCount: 2,
|
||||
evidenceReadyCount,
|
||||
evidenceReadyCount: resolvedEvidenceReadyCount,
|
||||
inventoryReadyCount,
|
||||
evidenceExpansionCandidateCount,
|
||||
totalCandidateCount: evidenceReadyCount + inventoryReadyCount,
|
||||
totalCandidateCount: resolvedEvidenceReadyCount + inventoryReadyCount,
|
||||
promotionAllowedCount,
|
||||
hardBlockRuntimeLock: hardBlockRuntimeLockSummary.lockValue,
|
||||
hardBlockRuntimePromotionAllowedCount,
|
||||
inventoryBaseline: baselineText,
|
||||
artifactRows: {
|
||||
promotionCandidates: candidateArtifactRows.length > 0
|
||||
? candidateArtifactRows.length
|
||||
: resolvedEvidenceReadyCount + inventoryReadyCount,
|
||||
evidenceExpansion: evidenceExpansionArtifactRows.length > 0
|
||||
? evidenceExpansionArtifactRows.length
|
||||
: evidenceExpansionCandidateCount,
|
||||
},
|
||||
hardBlockRuntimeFamilyRows,
|
||||
hardBlockRuntimeFamilyDetailRows: hardBlockRuntimeFamilyRows.map((row) => ({
|
||||
id: row.detailId,
|
||||
family: row.skipKind,
|
||||
path: row.path,
|
||||
blockReason: row.blockReason,
|
||||
nextProof: row.recommendedNextCommand,
|
||||
promotionAllowed: row.promotionAllowed,
|
||||
})),
|
||||
hardBlockRuntimeFamilySummaryRows,
|
||||
layers: [
|
||||
{
|
||||
id: "evidence-ready",
|
||||
candidateCount: evidenceReadyCount,
|
||||
candidateCount: resolvedEvidenceReadyCount,
|
||||
promotionAllowedCount: 0,
|
||||
virtualHalEvidenceReady: true,
|
||||
baselineChanging: false,
|
||||
@@ -193,6 +257,9 @@ function createPromotionCandidateArtifactSummary({
|
||||
evidenceExpansion: {
|
||||
id: "browser-diagnostics-expansion",
|
||||
candidateCount: evidenceExpansionCandidateCount,
|
||||
artifactRowCount: evidenceExpansionArtifactRows.length > 0
|
||||
? evidenceExpansionArtifactRows.length
|
||||
: evidenceExpansionCandidateCount,
|
||||
promotionAllowedCount: 0,
|
||||
baselineChanging: false,
|
||||
nextEvidence: "browser-diagnostics-binding",
|
||||
@@ -1951,6 +2018,9 @@ export function createProjectReleaseReadinessReport({
|
||||
simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
|
||||
virtualHalSimConfigSourceCoverage = null,
|
||||
virtualHalSimConfigPromotionCandidates = null,
|
||||
virtualHalSimConfigEvidenceExpansion = null,
|
||||
promotionCandidateRows = [],
|
||||
evidenceExpansionRows = [],
|
||||
virtualHalSimConfigMacroLoadFixtures = null,
|
||||
virtualHalMotionControllerMatrix = null,
|
||||
blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES,
|
||||
@@ -2003,9 +2073,12 @@ export function createProjectReleaseReadinessReport({
|
||||
const hardBlockRuntimeLockSummary = createHardBlockRuntimeLockSummary(blockedFamilies, promotedBlockedFamilies);
|
||||
const promotionCandidateArtifactSummary = createPromotionCandidateArtifactSummary({
|
||||
candidateReport: virtualHalSimConfigPromotionCandidates,
|
||||
evidenceExpansionReport: virtualHalSimConfigEvidenceExpansion,
|
||||
inventory,
|
||||
blockedFamilies,
|
||||
promotedBlockedFamilies,
|
||||
promotionCandidateRows,
|
||||
evidenceExpansionRows,
|
||||
});
|
||||
const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts);
|
||||
const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready;
|
||||
@@ -2044,6 +2117,7 @@ export function createProjectReleaseReadinessReport({
|
||||
virtualHalSimConfigSourceCoverageReady,
|
||||
virtualHalSimConfigPromotionCandidates,
|
||||
virtualHalSimConfigPromotionCandidatesReady,
|
||||
virtualHalSimConfigEvidenceExpansion,
|
||||
virtualHalPromotionFamilySummary,
|
||||
virtualHalPromotionFamilyRows,
|
||||
promotionCandidateArtifactSummary,
|
||||
@@ -2087,6 +2161,16 @@ export function createProjectReleaseReadinessReport({
|
||||
label: "Promotion candidate artifact",
|
||||
value: promotionCandidateArtifactSummary.artifactPath,
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: `${promotionCandidateArtifactSummary.artifactRows.promotionCandidates}`,
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: `${promotionCandidateArtifactSummary.artifactRows.evidenceExpansion}`,
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-layers",
|
||||
label: "Promotion candidate layers",
|
||||
@@ -2388,6 +2472,13 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
|
||||
? `${validation.promotionCandidateArtifactSummary.totalCandidateCount}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: validation?.promotionCandidateArtifactSummaryReady === true
|
||||
? `${validation.promotionCandidateArtifactSummary.artifactRows.promotionCandidates}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-candidates",
|
||||
label: "Evidence expansion candidates",
|
||||
@@ -2395,6 +2486,13 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
|
||||
? `${validation.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: validation?.promotionCandidateArtifactSummaryReady === true
|
||||
? `${validation.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-next-evidence",
|
||||
label: "Evidence expansion next evidence",
|
||||
@@ -2837,6 +2935,17 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
|
||||
) {
|
||||
const missing = arrayOrEmpty(workflow?.missing);
|
||||
const promotionCandidateSummary = objectOrEmpty(workflow?.diagnosticsArtifact?.virtualHalPromotionCandidateSummary);
|
||||
const promotionCandidateReportRows = arrayOrEmpty(
|
||||
workflow?.diagnosticsArtifact?.virtualHalSimConfigPromotionCandidates?.rows,
|
||||
);
|
||||
const evidenceReadyCandidateRows = promotionCandidateReportRows.filter((row) =>
|
||||
row?.complete === true &&
|
||||
row?.currentNodeInventoryStatus === "PASS" &&
|
||||
row?.blockedKind === "-" &&
|
||||
row?.targetBrowserEvidence === "explicit-browser-diagnostics" &&
|
||||
row?.currentMatrixBrowserStatus === "explicit-browser-diagnostics"
|
||||
);
|
||||
const preferredEvidenceReadyCandidate = evidenceReadyCandidateRows[0] ?? {};
|
||||
const promotionCandidateSummaryReady = promotionCandidateSummary.ready === true;
|
||||
const validationRows = arrayOrEmpty(workflow?.validation?.rows);
|
||||
const hardBlockRuntimeLock = validationRows.find(({ id }) => id === "hard-block-runtime-lock")?.value;
|
||||
@@ -2844,6 +2953,16 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
|
||||
const promotionCandidateArtifactSummary = objectOrEmpty(workflow?.validation?.promotionCandidateArtifactSummary);
|
||||
const promotionCandidateArtifactSummaryReady =
|
||||
workflow?.validation?.promotionCandidateArtifactSummaryReady === true;
|
||||
const promotionArtifactRows = objectOrEmpty(promotionCandidateArtifactSummary.artifactRows);
|
||||
const hardBlockRuntimeFamilyRows = arrayOrEmpty(promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows);
|
||||
const hardBlockRuntimeFamilySummaryRows = arrayOrEmpty(
|
||||
promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows,
|
||||
);
|
||||
const hardBlockRuntimeFamilyDetailRows = arrayOrEmpty(
|
||||
promotionCandidateArtifactSummary.hardBlockRuntimeFamilyDetailRows,
|
||||
);
|
||||
const hardBlockRuntimeDetailPreviewRows = hardBlockRuntimeFamilyDetailRows.slice(0, 5);
|
||||
const hardBlockRuntimePreviewRows = hardBlockRuntimeFamilyRows.slice(0, 3);
|
||||
const evidenceExpansionRows = arrayOrEmpty(
|
||||
workflow?.diagnosticsArtifact?.virtualHalSimConfigEvidenceExpansion?.rows,
|
||||
);
|
||||
@@ -2910,6 +3029,38 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
|
||||
? `${promotionCandidateArtifactSummary.totalCandidateCount}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-rows",
|
||||
label: "Evidence-ready candidate rows",
|
||||
value: `${evidenceReadyCandidateRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-preview",
|
||||
label: "Evidence-ready candidate preview",
|
||||
value: preferredEvidenceReadyCandidate.id ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-preview-gcode",
|
||||
label: "Evidence-ready candidate preview G-code",
|
||||
value: preferredEvidenceReadyCandidate.gcodePath ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-promotion-allowed",
|
||||
label: "Evidence-ready candidate promotion allowed",
|
||||
value: "0",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-baseline-changing",
|
||||
label: "Evidence-ready candidate baseline changing",
|
||||
value: "no",
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${promotionArtifactRows.promotionCandidates ?? "not provided"}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-candidates",
|
||||
label: "Evidence expansion candidates",
|
||||
@@ -2917,6 +3068,13 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
|
||||
? `${promotionCandidateArtifactSummary.evidenceExpansionCandidateCount}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${promotionArtifactRows.evidenceExpansion ?? "not provided"}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-next-evidence",
|
||||
label: "Evidence expansion next evidence",
|
||||
@@ -2985,6 +3143,82 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
|
||||
? `${promotionCandidateArtifactSummary.promotionAllowedCount}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-locked-rows",
|
||||
label: "Hard-block locked inventory rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${hardBlockRuntimeFamilyRows.length}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-locked-preview",
|
||||
label: "Hard-block locked row preview",
|
||||
value: hardBlockRuntimePreviewRows.length > 0
|
||||
? hardBlockRuntimePreviewRows.map((row) => `${row.skipKind}:${row.path}`).join(", ")
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-promotion-allowed",
|
||||
label: "Hard-block promotions allowed",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount ?? "not provided"}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-family-count",
|
||||
label: "Hard-block runtime families",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${hardBlockRuntimeFamilySummaryRows.length}`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-family-summary",
|
||||
label: "Hard-block runtime family summary",
|
||||
value: hardBlockRuntimeFamilySummaryRows.length > 0
|
||||
? hardBlockRuntimeFamilySummaryRows
|
||||
.map((row) => `${row.family}:${row.candidateCount} locked=${row.locked ? "yes" : "no"}`)
|
||||
.join(", ")
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-detail-row-count",
|
||||
label: "Hard-block detail rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${hardBlockRuntimeFamilyDetailRows.length}`
|
||||
: "not provided",
|
||||
},
|
||||
...hardBlockRuntimeDetailPreviewRows.map((row, index) => ({
|
||||
id: `hard-block-runtime-detail-preview-${index + 1}`,
|
||||
label: `Hard-block detail ${index + 1}`,
|
||||
value: `${row.family}:${row.path}; reason=${row.blockReason}; next=${row.nextProof}; promotion_allowed=${row.promotionAllowed ? "1" : "0"}`,
|
||||
family: row.family,
|
||||
path: row.path,
|
||||
blockReason: row.blockReason,
|
||||
nextProof: row.nextProof,
|
||||
promotionAllowed: row.promotionAllowed,
|
||||
})),
|
||||
...hardBlockRuntimeFamilySummaryRows.map((row) => ({
|
||||
id: `hard-block-runtime-family-${row.family}`,
|
||||
label: `Hard-block family ${row.family}`,
|
||||
value: [
|
||||
`${row.candidateCount} locked rows`,
|
||||
`promotion_allowed=${row.promotionAllowedCount}`,
|
||||
`first=${row.firstPath ?? "not provided"}`,
|
||||
`reason=${row.firstBlockReason ?? "not provided"}`,
|
||||
`next=${row.nextProof ?? "not provided"}`,
|
||||
].join("; "),
|
||||
firstPath: row.firstPath,
|
||||
firstBlockReason: row.firstBlockReason,
|
||||
nextProof: row.nextProof,
|
||||
})),
|
||||
...hardBlockRuntimeFamilySummaryRows.map((row) => ({
|
||||
id: `hard-block-runtime-family-${row.family}-paths`,
|
||||
label: `Hard-block family ${row.family} paths`,
|
||||
value: arrayOrEmpty(row.lockedPaths).length > 0
|
||||
? row.lockedPaths.join(", ")
|
||||
: "not provided",
|
||||
lockedPaths: arrayOrEmpty(row.lockedPaths),
|
||||
})),
|
||||
...arrayOrEmpty(workflow?.validation?.virtualHalPromotionFamilyRows).map((row) => ({
|
||||
id: `virtual-hal-promotion-family-${row.id}`,
|
||||
label: `Promotion family ${row.label}`,
|
||||
@@ -3242,10 +3476,12 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
|
||||
const promotionCandidateArtifactSummary = objectOrEmpty(artifactObject.promotionCandidateArtifactSummary);
|
||||
const expectedPromotionCandidateArtifactSummary = createPromotionCandidateArtifactSummary({
|
||||
candidateReport: virtualHalSimConfigPromotionCandidates,
|
||||
evidenceExpansionReport: artifactObject.virtualHalSimConfigEvidenceExpansion,
|
||||
inventory: simConfigInventory,
|
||||
blockedFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
|
||||
promotedBlockedFamilies: arrayOrEmpty(artifactObject.promotedBlockedFamilies),
|
||||
});
|
||||
const artifactRows = objectOrEmpty(promotionCandidateArtifactSummary.artifactRows);
|
||||
const promotionCandidateArtifactSummaryReady =
|
||||
promotionCandidateArtifactSummary.apiName === expectedPromotionCandidateArtifactSummary.apiName &&
|
||||
promotionCandidateArtifactSummary.summaryVersion === 1 &&
|
||||
@@ -3259,7 +3495,15 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
|
||||
promotionCandidateArtifactSummary.totalCandidateCount === expectedPromotionCandidateArtifactSummary.totalCandidateCount &&
|
||||
promotionCandidateArtifactSummary.promotionAllowedCount === 0 &&
|
||||
promotionCandidateArtifactSummary.hardBlockRuntimeLock === expectedPromotionCandidateArtifactSummary.hardBlockRuntimeLock &&
|
||||
promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount === 0 &&
|
||||
Number.isFinite(promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount) &&
|
||||
promotionCandidateArtifactSummary.inventoryBaseline === expectedPromotionCandidateArtifactSummary.inventoryBaseline &&
|
||||
artifactRows.promotionCandidates ===
|
||||
promotionCandidateArtifactSummary.evidenceReadyCount + promotionCandidateArtifactSummary.inventoryReadyCount &&
|
||||
(
|
||||
artifactRows.evidenceExpansion === 0 ||
|
||||
artifactRows.evidenceExpansion === promotionCandidateArtifactSummary.evidenceExpansionCandidateCount
|
||||
) &&
|
||||
arrayOrEmpty(promotionCandidateArtifactSummary.layers).some((row) =>
|
||||
row.id === "evidence-ready" &&
|
||||
row.candidateCount === expectedPromotionCandidateArtifactSummary.evidenceReadyCount &&
|
||||
@@ -3442,6 +3686,13 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
|
||||
? `${promotionCandidateArtifactSummary.totalCandidateCount}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${artifactRows.promotionCandidates}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-candidates",
|
||||
label: "Evidence expansion candidates",
|
||||
@@ -3449,6 +3700,13 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
|
||||
? `${promotionCandidateArtifactSummary.evidenceExpansionCandidateCount}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: promotionCandidateArtifactSummaryReady
|
||||
? `${artifactRows.evidenceExpansion}`
|
||||
: "missing",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-next-evidence",
|
||||
label: "Evidence expansion next evidence",
|
||||
|
||||
@@ -2556,6 +2556,21 @@ export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWo
|
||||
?? workflow?.urlWorkflow?.diagnosticsArtifact?.virtualHalPromotionCandidateSummary
|
||||
?? {}
|
||||
);
|
||||
const promotionCandidateReportRows = (
|
||||
workflow?.diagnosticsArtifact?.virtualHalSimConfigPromotionCandidates?.rows
|
||||
?? workflow?.urlWorkflow?.diagnosticsArtifact?.virtualHalSimConfigPromotionCandidates?.rows
|
||||
?? []
|
||||
);
|
||||
const evidenceReadyCandidateRows = Array.isArray(promotionCandidateReportRows)
|
||||
? promotionCandidateReportRows.filter((row) =>
|
||||
row?.complete === true &&
|
||||
row?.currentNodeInventoryStatus === "PASS" &&
|
||||
row?.blockedKind === "-" &&
|
||||
row?.targetBrowserEvidence === "explicit-browser-diagnostics" &&
|
||||
row?.currentMatrixBrowserStatus === "explicit-browser-diagnostics"
|
||||
)
|
||||
: [];
|
||||
const preferredEvidenceReadyCandidate = evidenceReadyCandidateRows[0] ?? {};
|
||||
const promotionCandidateSummaryReady = promotionCandidateSummary.ready === true;
|
||||
const validationRows = Array.isArray(workflow?.validation?.rows)
|
||||
? workflow.validation.rows
|
||||
@@ -2567,6 +2582,18 @@ export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWo
|
||||
?? workflow?.validation?.validation?.promotionCandidateArtifactSummary
|
||||
?? {}
|
||||
);
|
||||
const promotionArtifactRows = promotionCandidateArtifactSummary.artifactRows ?? {};
|
||||
const hardBlockRuntimeFamilyRows = Array.isArray(promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows)
|
||||
? promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows
|
||||
: [];
|
||||
const hardBlockRuntimeFamilySummaryRows = Array.isArray(promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows)
|
||||
? promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows
|
||||
: [];
|
||||
const hardBlockRuntimeFamilyDetailRows = Array.isArray(promotionCandidateArtifactSummary.hardBlockRuntimeFamilyDetailRows)
|
||||
? promotionCandidateArtifactSummary.hardBlockRuntimeFamilyDetailRows
|
||||
: [];
|
||||
const hardBlockRuntimeDetailPreviewRows = hardBlockRuntimeFamilyDetailRows.slice(0, 5);
|
||||
const hardBlockRuntimePreviewRows = hardBlockRuntimeFamilyRows.slice(0, 3);
|
||||
const evidenceExpansion = promotionCandidateArtifactSummary.evidenceExpansion ?? {};
|
||||
const evidenceExpansionRows = (
|
||||
workflow?.diagnosticsArtifact?.virtualHalSimConfigEvidenceExpansion?.rows
|
||||
@@ -2677,6 +2704,31 @@ export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWo
|
||||
? `${promotionCandidateSummary.explicitBrowserDiagnosticsCount} diagnostics-ready`
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-rows",
|
||||
label: "Evidence-ready candidate rows",
|
||||
value: `${evidenceReadyCandidateRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-preview",
|
||||
label: "Evidence-ready candidate preview",
|
||||
value: preferredEvidenceReadyCandidate.id ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-preview-gcode",
|
||||
label: "Evidence-ready candidate preview G-code",
|
||||
value: preferredEvidenceReadyCandidate.gcodePath ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-promotion-allowed",
|
||||
label: "Evidence-ready candidate promotion allowed",
|
||||
value: "0",
|
||||
},
|
||||
{
|
||||
id: "evidence-ready-candidate-baseline-changing",
|
||||
label: "Evidence-ready candidate baseline changing",
|
||||
value: "no",
|
||||
},
|
||||
{
|
||||
id: "virtual-hal-promotion-candidate-baseline",
|
||||
label: "Candidate inventory baseline",
|
||||
@@ -2687,6 +2739,20 @@ export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWo
|
||||
label: "Evidence expansion next evidence",
|
||||
value: evidenceExpansion.nextEvidence ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: promotionArtifactRows.promotionCandidates == null
|
||||
? "not provided"
|
||||
: `${promotionArtifactRows.promotionCandidates}`,
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: promotionArtifactRows.evidenceExpansion == null
|
||||
? "not provided"
|
||||
: `${promotionArtifactRows.evidenceExpansion}`,
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-baseline-changing",
|
||||
label: "Evidence expansion baseline changing",
|
||||
@@ -2744,6 +2810,76 @@ export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWo
|
||||
label: "Promoted blocked families",
|
||||
value: promotedBlockedFamilyCount ?? "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-locked-rows",
|
||||
label: "Hard-block locked inventory rows",
|
||||
value: `${hardBlockRuntimeFamilyRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-locked-preview",
|
||||
label: "Hard-block locked row preview",
|
||||
value: hardBlockRuntimePreviewRows.length > 0
|
||||
? hardBlockRuntimePreviewRows.map((row) => `${row.skipKind}:${row.path}`).join(", ")
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-promotion-allowed",
|
||||
label: "Hard-block promotions allowed",
|
||||
value: promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount == null
|
||||
? "not provided"
|
||||
: `${promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount}`,
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-family-count",
|
||||
label: "Hard-block runtime families",
|
||||
value: `${hardBlockRuntimeFamilySummaryRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-family-summary",
|
||||
label: "Hard-block runtime family summary",
|
||||
value: hardBlockRuntimeFamilySummaryRows.length > 0
|
||||
? hardBlockRuntimeFamilySummaryRows
|
||||
.map((row) => `${row.family}:${row.candidateCount} locked=${row.locked ? "yes" : "no"}`)
|
||||
.join(", ")
|
||||
: "not provided",
|
||||
},
|
||||
{
|
||||
id: "hard-block-runtime-detail-row-count",
|
||||
label: "Hard-block detail rows",
|
||||
value: `${hardBlockRuntimeFamilyDetailRows.length}`,
|
||||
},
|
||||
...hardBlockRuntimeDetailPreviewRows.map((row, index) => ({
|
||||
id: `hard-block-runtime-detail-preview-${index + 1}`,
|
||||
label: `Hard-block detail ${index + 1}`,
|
||||
value: `${row.family}:${row.path}; reason=${row.blockReason}; next=${row.nextProof}; promotion_allowed=${row.promotionAllowed ? "1" : "0"}`,
|
||||
family: row.family,
|
||||
path: row.path,
|
||||
blockReason: row.blockReason,
|
||||
nextProof: row.nextProof,
|
||||
promotionAllowed: row.promotionAllowed,
|
||||
})),
|
||||
...hardBlockRuntimeFamilySummaryRows.map((row) => ({
|
||||
id: `hard-block-runtime-family-${row.family}`,
|
||||
label: `Hard-block family ${row.family}`,
|
||||
value: [
|
||||
`${row.candidateCount} locked rows`,
|
||||
`promotion_allowed=${row.promotionAllowedCount}`,
|
||||
`first=${row.firstPath ?? "not provided"}`,
|
||||
`reason=${row.firstBlockReason ?? "not provided"}`,
|
||||
`next=${row.nextProof ?? "not provided"}`,
|
||||
].join("; "),
|
||||
firstPath: row.firstPath,
|
||||
firstBlockReason: row.firstBlockReason,
|
||||
nextProof: row.nextProof,
|
||||
})),
|
||||
...hardBlockRuntimeFamilySummaryRows.map((row) => ({
|
||||
id: `hard-block-runtime-family-${row.family}-paths`,
|
||||
label: `Hard-block family ${row.family} paths`,
|
||||
value: Array.isArray(row.lockedPaths) && row.lockedPaths.length > 0
|
||||
? row.lockedPaths.join(", ")
|
||||
: "not provided",
|
||||
lockedPaths: Array.isArray(row.lockedPaths) ? row.lockedPaths : [],
|
||||
})),
|
||||
{
|
||||
id: "virtual-hal-promotion-families",
|
||||
label: "Virtual HAL promotion families",
|
||||
@@ -3171,6 +3307,19 @@ export function createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderStat
|
||||
status: "manifest-backed",
|
||||
}))
|
||||
);
|
||||
const candidateRows = sourceRows
|
||||
.filter((row) => row.sourceKind === "gcode")
|
||||
.map((row, index) => ({
|
||||
id: `evidence-candidate-${index + 1}`,
|
||||
familyId: row.familyId,
|
||||
label: `Evidence candidate ${index + 1}`,
|
||||
value: row.value,
|
||||
candidateStatus: "evidence-ready",
|
||||
promotionAllowed: false,
|
||||
baselineChanging: false,
|
||||
sourceKind: row.sourceKind,
|
||||
manifestBacked: row.manifestBacked,
|
||||
}));
|
||||
const ready = rows.length > 0 && rows.every(({ value }) => String(value).includes("diagnostics=ready"));
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-promotion-family-rows-render-state",
|
||||
@@ -3183,7 +3332,9 @@ export function createIniPanelShellWorkflowOverviewPromotionFamilyRowsRenderStat
|
||||
statusLine: `${ready ? "Ready" : "Blocked"}: ${ready ? `${rows.length} promotion families ready` : "promotion family rows missing"}`,
|
||||
rows,
|
||||
sourceRows,
|
||||
candidateRows,
|
||||
sourceFileCount: sourceRows.length,
|
||||
candidateCount: candidateRows.length,
|
||||
dataset: {
|
||||
handoffPhase: ready ? "ready" : "blocked",
|
||||
handoffReady: ready ? "true" : "false",
|
||||
@@ -3212,6 +3363,10 @@ export function createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterVi
|
||||
const filteredSourceRows = selectedSourceKind === "all"
|
||||
? familyScopedSourceRows
|
||||
: familyScopedSourceRows.filter((sourceRow) => sourceRow.sourceKind === selectedSourceKind);
|
||||
const candidateRows = Array.isArray(renderState?.candidateRows) ? renderState.candidateRows : [];
|
||||
const filteredCandidateRows = selectedFamilyId === "all"
|
||||
? candidateRows
|
||||
: candidateRows.filter((candidateRow) => candidateRow.familyId === selectedFamilyId);
|
||||
const familyFilters = [
|
||||
{
|
||||
id: "all",
|
||||
@@ -3255,6 +3410,7 @@ export function createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterVi
|
||||
.filter((row) => row.sourceFileCount > 0);
|
||||
const ready = renderState?.ready === true && totalSourceFileCount > 0;
|
||||
const selectedCount = filteredSourceRows.length;
|
||||
const selectedCandidateCount = filteredCandidateRows.length;
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-promotion-family-source-filter-view-model",
|
||||
viewModelVersion: 1,
|
||||
@@ -3273,8 +3429,11 @@ export function createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterVi
|
||||
totalSourceFileCount,
|
||||
scopedSourceFileCount,
|
||||
filteredSourceFileCount: selectedCount,
|
||||
totalCandidateCount: candidateRows.length,
|
||||
filteredCandidateCount: selectedCandidateCount,
|
||||
familyRows,
|
||||
sourceRows: filteredSourceRows,
|
||||
candidateRows: filteredCandidateRows,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3633,12 +3792,17 @@ export function renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSt
|
||||
})),
|
||||
];
|
||||
const sourceRows = Array.isArray(viewModel?.sourceRows) ? viewModel.sourceRows : [];
|
||||
const candidateRows = Array.isArray(viewModel?.candidateRows) ? viewModel.candidateRows : [];
|
||||
const rows = [
|
||||
...filterRows,
|
||||
...sourceRows.map((sourceRow) => ({
|
||||
...sourceRow,
|
||||
rowType: "source-row",
|
||||
})),
|
||||
...candidateRows.map((candidateRow) => ({
|
||||
...candidateRow,
|
||||
rowType: "candidate-row",
|
||||
})),
|
||||
];
|
||||
|
||||
for (const row of rows) {
|
||||
@@ -3655,6 +3819,11 @@ export function renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSt
|
||||
if (row.sourceKind) {
|
||||
term.dataset[`${effectiveRowDatasetPrefix}SourceKind`] = row.sourceKind;
|
||||
}
|
||||
if (row.candidateStatus) {
|
||||
term.dataset[`${effectiveRowDatasetPrefix}CandidateStatus`] = row.candidateStatus;
|
||||
term.dataset[`${effectiveRowDatasetPrefix}PromotionAllowed`] = row.promotionAllowed ? "1" : "0";
|
||||
term.dataset[`${effectiveRowDatasetPrefix}BaselineChanging`] = row.baselineChanging ? "yes" : "no";
|
||||
}
|
||||
term.textContent = row.label ?? row.id;
|
||||
|
||||
const detail = documentRef.createElement("dd");
|
||||
@@ -3670,9 +3839,18 @@ export function renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSt
|
||||
if (row.sourceKind) {
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}SourceKind`] = row.sourceKind;
|
||||
}
|
||||
if (row.candidateStatus) {
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}CandidateStatus`] = row.candidateStatus;
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}PromotionAllowed`] = row.promotionAllowed ? "1" : "0";
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}BaselineChanging`] = row.baselineChanging ? "yes" : "no";
|
||||
}
|
||||
detail.textContent = row.rowType === "source-row"
|
||||
? row.value
|
||||
: `${row.value}: ${row.count ?? 0}${row.selected ? " selected" : ""}`;
|
||||
: (
|
||||
row.rowType === "candidate-row"
|
||||
? row.value
|
||||
: `${row.value}: ${row.count ?? 0}${row.selected ? " selected" : ""}`
|
||||
);
|
||||
rowsNode.append(term, detail);
|
||||
}
|
||||
|
||||
@@ -3683,8 +3861,10 @@ export function renderIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSt
|
||||
controlCount: controlSpecs.length,
|
||||
filterRowCount: filterRows.length,
|
||||
sourceFileCount: sourceRows.length,
|
||||
candidateCount: candidateRows.length,
|
||||
rowIds: rows.map(({ id }) => id),
|
||||
sourceRowIds: sourceRows.map(({ id }) => id),
|
||||
candidateRowIds: candidateRows.map(({ id }) => id),
|
||||
dataset: {
|
||||
handoffPhase: mountNode.dataset.handoffPhase,
|
||||
handoffReady: mountNode.dataset.handoffReady,
|
||||
@@ -4005,6 +4185,19 @@ export function createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownVi
|
||||
virtualHalReportsReady &&
|
||||
sourceFiles.length > 0;
|
||||
const summaryCandidateId = candidateId ?? "not provided";
|
||||
const candidateEvidenceReady = candidate?.complete === true &&
|
||||
blockedFamilyExcluded &&
|
||||
nodeInventoryReady &&
|
||||
browserEvidenceReady;
|
||||
const promotionCandidateLayers =
|
||||
findWorkflowOverviewRowValue(summaryViewModel, "promotion-candidate-layers") ??
|
||||
(candidateEvidenceReady ? "evidence-ready" : "not provided");
|
||||
const promotionCandidateAllowed =
|
||||
findWorkflowOverviewRowValue(summaryViewModel, "promotion-candidate-allowed") ??
|
||||
(candidateEvidenceReady ? "0" : "not provided");
|
||||
const promotionBaselineChanging = promotionCandidateAllowed === "0"
|
||||
? "no"
|
||||
: (promotionCandidateAllowed === "not provided" ? "not provided" : "yes");
|
||||
const rows = [
|
||||
{
|
||||
id: "candidate",
|
||||
@@ -4036,6 +4229,23 @@ export function createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownVi
|
||||
label: "Blocked-family exclusion",
|
||||
value: blockedFamilyExcluded ? "passed" : (candidate?.blockedKind ?? "not provided"),
|
||||
},
|
||||
{
|
||||
id: "candidate-layer",
|
||||
label: "Candidate layer",
|
||||
value: promotionCandidateLayers.includes("evidence-ready")
|
||||
? "evidence-ready"
|
||||
: promotionCandidateLayers,
|
||||
},
|
||||
{
|
||||
id: "inventory-promotion-allowed",
|
||||
label: "Inventory promotion allowed",
|
||||
value: promotionCandidateAllowed,
|
||||
},
|
||||
{
|
||||
id: "inventory-baseline-changing",
|
||||
label: "Inventory baseline changing",
|
||||
value: promotionBaselineChanging,
|
||||
},
|
||||
{
|
||||
id: "virtual-hal-reports",
|
||||
label: "Required virtual HAL reports",
|
||||
@@ -4353,6 +4563,309 @@ export function mountIniPanelShellWorkflowOverviewPromotionCandidateDrilldownSta
|
||||
}
|
||||
}
|
||||
|
||||
export function createIniPanelShellWorkflowOverviewHardBlockDrilldownViewModel(
|
||||
summaryViewModel = createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(),
|
||||
{
|
||||
validation = null,
|
||||
} = {},
|
||||
) {
|
||||
const summary = validation?.promotionCandidateArtifactSummary ?? {};
|
||||
const detailRows = Array.isArray(summary.hardBlockRuntimeFamilyDetailRows)
|
||||
? summary.hardBlockRuntimeFamilyDetailRows
|
||||
: [];
|
||||
const familyRows = Array.isArray(summary.hardBlockRuntimeFamilySummaryRows)
|
||||
? summary.hardBlockRuntimeFamilySummaryRows
|
||||
: [];
|
||||
const promotionAllowedCount = Number(summary.hardBlockRuntimePromotionAllowedCount ?? 0);
|
||||
const ready = detailRows.length > 0 && promotionAllowedCount === 0;
|
||||
const rows = [
|
||||
{
|
||||
id: "detail-row-count",
|
||||
label: "Hard-block detail rows",
|
||||
value: `${detailRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "family-count",
|
||||
label: "Hard-block families",
|
||||
value: `${familyRows.length}`,
|
||||
},
|
||||
{
|
||||
id: "promotion-allowed",
|
||||
label: "Promotion allowed",
|
||||
value: `${promotionAllowedCount}`,
|
||||
},
|
||||
{
|
||||
id: "inventory-baseline",
|
||||
label: "Inventory baseline",
|
||||
value: findWorkflowOverviewRowValue(summaryViewModel, "virtual-hal-promotion-candidate-baseline")
|
||||
?? summary.inventoryBaseline
|
||||
?? "not provided",
|
||||
},
|
||||
];
|
||||
const familyDetailRows = detailRows.map((row, index) => ({
|
||||
id: row.id ?? `hard-block-detail-${index + 1}`,
|
||||
label: `${row.family} ${index + 1}`,
|
||||
value: row.path ?? "not provided",
|
||||
family: row.family ?? "not provided",
|
||||
path: row.path ?? "not provided",
|
||||
blockReason: row.blockReason ?? "not provided",
|
||||
nextProof: row.nextProof ?? "not provided",
|
||||
promotionAllowed: row.promotionAllowed === true,
|
||||
}));
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-view-model",
|
||||
viewModelVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
title: "Hard-block drilldown",
|
||||
statusText: ready ? "Ready" : "Blocked",
|
||||
detailText: ready
|
||||
? `${detailRows.length} locked rows grouped by ${familyRows.length} families`
|
||||
: "hard-block detail rows missing",
|
||||
statusLine: `${ready ? "Ready" : "Blocked"}: ${ready ? `${detailRows.length} locked rows grouped by ${familyRows.length} families` : "hard-block detail rows missing"}`,
|
||||
rows,
|
||||
detailRows: familyDetailRows,
|
||||
rowCount: rows.length + familyDetailRows.length,
|
||||
detailRowCount: familyDetailRows.length,
|
||||
familyCount: familyRows.length,
|
||||
promotionAllowedCount,
|
||||
dataset: {
|
||||
handoffPhase: ready ? "ready" : "blocked",
|
||||
handoffReady: ready ? "true" : "false",
|
||||
handoffScope: "workflow-overview-hard-block-drilldown",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createIniPanelShellWorkflowOverviewHardBlockDrilldownRenderState(
|
||||
viewModel = createIniPanelShellWorkflowOverviewHardBlockDrilldownViewModel(),
|
||||
) {
|
||||
const rows = [
|
||||
...(Array.isArray(viewModel?.rows) ? viewModel.rows : []),
|
||||
...(Array.isArray(viewModel?.detailRows) ? viewModel.detailRows : []).map((row) => ({
|
||||
...row,
|
||||
label: `${row.family}: ${row.path}`,
|
||||
value: `${row.blockReason}; next=${row.nextProof}; promotion_allowed=${row.promotionAllowed ? "1" : "0"}`,
|
||||
hardBlockDetail: true,
|
||||
})),
|
||||
].map(({ id, label, value, family, path, blockReason, nextProof, promotionAllowed, hardBlockDetail }) => ({
|
||||
id,
|
||||
label,
|
||||
value: value == null ? "" : String(value),
|
||||
...(family ? { family } : {}),
|
||||
...(path ? { path } : {}),
|
||||
...(blockReason ? { blockReason } : {}),
|
||||
...(nextProof ? { nextProof } : {}),
|
||||
...(promotionAllowed != null ? { promotionAllowed: promotionAllowed === true } : {}),
|
||||
...(hardBlockDetail != null ? { hardBlockDetail: hardBlockDetail === true } : {}),
|
||||
}));
|
||||
const ready = viewModel?.ready === true;
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-render-state",
|
||||
renderStateVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
title: viewModel?.title ?? "Hard-block drilldown",
|
||||
statusText: viewModel?.statusText ?? (ready ? "Ready" : "Blocked"),
|
||||
detailText: viewModel?.detailText ?? "hard-block drilldown missing",
|
||||
statusLine: viewModel?.statusLine ?? `${ready ? "Ready" : "Blocked"}: hard-block drilldown`,
|
||||
rows,
|
||||
rowCount: rows.length,
|
||||
detailRowCount: viewModel?.detailRowCount ?? 0,
|
||||
familyCount: viewModel?.familyCount ?? 0,
|
||||
promotionAllowedCount: viewModel?.promotionAllowedCount ?? 0,
|
||||
dataset: viewModel?.dataset ?? {
|
||||
handoffPhase: ready ? "ready" : "blocked",
|
||||
handoffReady: ready ? "true" : "false",
|
||||
handoffScope: "workflow-overview-hard-block-drilldown",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createIniPanelShellWorkflowOverviewHardBlockDrilldownDomContract({
|
||||
rowDatasetPrefix = "workflowOverviewHardBlockDrilldown",
|
||||
selectors = {},
|
||||
} = {}) {
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-dom-contract",
|
||||
contractVersion: 1,
|
||||
requiredNodeIds: ["mount", "status", "rows"],
|
||||
selectors: {
|
||||
mount: selectors.mount ?? "[data-workflow-overview-hard-block-drilldown]",
|
||||
status: selectors.status ?? "[data-workflow-overview-hard-block-drilldown-status]",
|
||||
rows: selectors.rows ?? "[data-workflow-overview-hard-block-drilldown-rows]",
|
||||
},
|
||||
mountDatasetKeys: ["handoffPhase", "handoffReady", "handoffScope"],
|
||||
rowDatasetPrefix,
|
||||
};
|
||||
}
|
||||
|
||||
export function createIniPanelShellWorkflowOverviewHardBlockDrilldownDomReadiness({
|
||||
rowDatasetPrefix = "workflowOverviewHardBlockDrilldown",
|
||||
contract = createIniPanelShellWorkflowOverviewHardBlockDrilldownDomContract({ rowDatasetPrefix }),
|
||||
documentRef = globalThis.document,
|
||||
mountNode = documentRef?.querySelector?.(contract.selectors.mount),
|
||||
statusNode = documentRef?.querySelector?.(contract.selectors.status),
|
||||
rowsNode = documentRef?.querySelector?.(contract.selectors.rows),
|
||||
} = {}) {
|
||||
const checks = {
|
||||
document: Boolean(documentRef?.createElement),
|
||||
mount: Boolean(mountNode),
|
||||
status: Boolean(statusNode),
|
||||
rows: Boolean(rowsNode),
|
||||
rowDatasetPrefix: Boolean(contract.rowDatasetPrefix),
|
||||
};
|
||||
const missing = Object.entries(checks)
|
||||
.filter(([, passed]) => !passed)
|
||||
.map(([name]) => name);
|
||||
const ready = missing.length === 0;
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-dom-readiness",
|
||||
readinessVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
missing,
|
||||
checks,
|
||||
contract,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderIniPanelShellWorkflowOverviewHardBlockDrilldownState(
|
||||
renderState = createIniPanelShellWorkflowOverviewHardBlockDrilldownRenderState(),
|
||||
{
|
||||
rowDatasetPrefix = "workflowOverviewHardBlockDrilldown",
|
||||
contract = createIniPanelShellWorkflowOverviewHardBlockDrilldownDomContract({ rowDatasetPrefix }),
|
||||
documentRef = globalThis.document,
|
||||
mountNode = documentRef?.querySelector?.(contract.selectors.mount),
|
||||
statusNode = documentRef?.querySelector?.(contract.selectors.status),
|
||||
rowsNode = documentRef?.querySelector?.(contract.selectors.rows),
|
||||
} = {},
|
||||
) {
|
||||
const readiness = createIniPanelShellWorkflowOverviewHardBlockDrilldownDomReadiness({
|
||||
contract,
|
||||
documentRef,
|
||||
mountNode,
|
||||
statusNode,
|
||||
rowsNode,
|
||||
});
|
||||
if (!readiness.ready) {
|
||||
throw new Error(
|
||||
`Workflow overview hard-block drilldown DOM is not ready: ${readiness.missing.join(", ")}`,
|
||||
);
|
||||
}
|
||||
mountNode.dataset.handoffPhase = renderState?.dataset?.handoffPhase ?? "blocked";
|
||||
mountNode.dataset.handoffReady = renderState?.dataset?.handoffReady ?? "false";
|
||||
mountNode.dataset.handoffScope = renderState?.dataset?.handoffScope
|
||||
?? "workflow-overview-hard-block-drilldown";
|
||||
statusNode.textContent = renderState?.statusLine ?? "Blocked: hard-block drilldown missing";
|
||||
rowsNode.replaceChildren();
|
||||
const effectiveRowDatasetPrefix = contract.rowDatasetPrefix;
|
||||
const rows = Array.isArray(renderState?.rows) ? renderState.rows : [];
|
||||
for (const row of rows) {
|
||||
const term = documentRef.createElement("dt");
|
||||
term.dataset[`${effectiveRowDatasetPrefix}Row`] = row.id;
|
||||
term.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked";
|
||||
if (row.family) {
|
||||
term.dataset[`${effectiveRowDatasetPrefix}Family`] = row.family;
|
||||
term.dataset[`${effectiveRowDatasetPrefix}Path`] = row.path ?? "";
|
||||
term.dataset[`${effectiveRowDatasetPrefix}PromotionAllowed`] = row.promotionAllowed ? "1" : "0";
|
||||
term.dataset[`${effectiveRowDatasetPrefix}HardBlockDetail`] = row.hardBlockDetail ? "true" : "false";
|
||||
}
|
||||
term.textContent = row.label;
|
||||
const detail = documentRef.createElement("dd");
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}Value`] = row.id;
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked";
|
||||
if (row.family) {
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}Family`] = row.family;
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}Path`] = row.path ?? "";
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}PromotionAllowed`] = row.promotionAllowed ? "1" : "0";
|
||||
detail.dataset[`${effectiveRowDatasetPrefix}HardBlockDetail`] = row.hardBlockDetail ? "true" : "false";
|
||||
}
|
||||
detail.textContent = row.value;
|
||||
rowsNode.append(term, detail);
|
||||
}
|
||||
return {
|
||||
rendered: true,
|
||||
statusLine: statusNode.textContent,
|
||||
rowCount: rows.length,
|
||||
rowIds: rows.map(({ id }) => id),
|
||||
detailRowCount: rows.filter(({ hardBlockDetail }) => hardBlockDetail === true).length,
|
||||
familyCount: renderState?.familyCount ?? 0,
|
||||
promotionAllowedCount: renderState?.promotionAllowedCount ?? 0,
|
||||
dataset: {
|
||||
handoffPhase: mountNode.dataset.handoffPhase,
|
||||
handoffReady: mountNode.dataset.handoffReady,
|
||||
handoffScope: mountNode.dataset.handoffScope,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mountIniPanelShellWorkflowOverviewHardBlockDrilldownState({
|
||||
renderState = createIniPanelShellWorkflowOverviewHardBlockDrilldownRenderState(),
|
||||
rowDatasetPrefix = "workflowOverviewHardBlockDrilldown",
|
||||
contract = createIniPanelShellWorkflowOverviewHardBlockDrilldownDomContract({ rowDatasetPrefix }),
|
||||
documentRef = globalThis.document,
|
||||
mountNode = documentRef?.querySelector?.(contract.selectors.mount),
|
||||
statusNode = documentRef?.querySelector?.(contract.selectors.status),
|
||||
rowsNode = documentRef?.querySelector?.(contract.selectors.rows),
|
||||
} = {}) {
|
||||
const domReadiness = createIniPanelShellWorkflowOverviewHardBlockDrilldownDomReadiness({
|
||||
contract,
|
||||
documentRef,
|
||||
mountNode,
|
||||
statusNode,
|
||||
rowsNode,
|
||||
});
|
||||
if (!domReadiness.ready) {
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-dom-mount",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
statusLine: `Blocked: ${domReadiness.missing.join(", ")}`,
|
||||
contract,
|
||||
domReadiness,
|
||||
renderState,
|
||||
renderResult: null,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const renderResult = renderIniPanelShellWorkflowOverviewHardBlockDrilldownState(renderState, {
|
||||
contract,
|
||||
documentRef,
|
||||
mountNode,
|
||||
statusNode,
|
||||
rowsNode,
|
||||
});
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-dom-mount",
|
||||
workflowVersion: 1,
|
||||
phase: renderState?.phase === "ready" ? "ready" : "blocked",
|
||||
ready: renderState?.ready === true,
|
||||
statusLine: renderResult.statusLine,
|
||||
contract,
|
||||
domReadiness,
|
||||
renderState,
|
||||
renderResult,
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
apiName: "ini-panel-shell-workflow-overview-hard-block-drilldown-dom-mount",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
statusLine: `Blocked: ${error?.message ?? "hard-block drilldown DOM mount error"}`,
|
||||
contract,
|
||||
domReadiness,
|
||||
renderState,
|
||||
renderResult: null,
|
||||
error: error?.message ?? String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract({
|
||||
rowDatasetPrefix = "workflowOverviewReleaseReadinessArtifactUrlWorkflow",
|
||||
selectors = {},
|
||||
|
||||
@@ -612,7 +612,7 @@
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowRenderResult.rowCount,
|
||||
29,
|
||||
53,
|
||||
"workflow overview release artifact URL workflow render rows",
|
||||
);
|
||||
assertEqual(
|
||||
@@ -650,11 +650,46 @@
|
||||
"13",
|
||||
"workflow overview release artifact URL workflow evidence expansion count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
|
||||
"28",
|
||||
"workflow overview release artifact URL workflow promotion candidate artifact rows",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
|
||||
"13",
|
||||
"workflow overview release artifact URL workflow evidence expansion artifact rows",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value,
|
||||
"browser-diagnostics-binding",
|
||||
"workflow overview release artifact URL workflow evidence expansion next evidence",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
|
||||
"8",
|
||||
"workflow overview release artifact URL workflow evidence-ready candidate rows",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview")?.value,
|
||||
"qtdragon-multi-joint-on-abort",
|
||||
"workflow overview release artifact URL workflow evidence-ready candidate preview",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview-gcode")?.value,
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
||||
"workflow overview release artifact URL workflow evidence-ready candidate preview gcode",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-promotion-allowed")?.value,
|
||||
"0",
|
||||
"workflow overview release artifact URL workflow evidence-ready promotion allowed",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-baseline-changing")?.value,
|
||||
"no",
|
||||
"workflow overview release artifact URL workflow evidence-ready baseline changing",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-baseline-changing")?.value,
|
||||
"no",
|
||||
@@ -750,6 +785,51 @@
|
||||
"0 promoted",
|
||||
"workflow overview release artifact URL workflow promoted blocked count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
|
||||
"19",
|
||||
"workflow overview release artifact URL workflow locked hard-block rows",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc",
|
||||
"workflow overview release artifact URL workflow locked hard-block preview",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
|
||||
"0",
|
||||
"workflow overview release artifact URL workflow hard-block promotion allowed count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value,
|
||||
"3",
|
||||
"workflow overview release artifact URL workflow hard-block family count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value,
|
||||
"L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
|
||||
"workflow overview release artifact URL workflow hard-block family summary",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
|
||||
"19",
|
||||
"workflow overview release artifact URL workflow hard-block detail count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0",
|
||||
"workflow overview release artifact URL workflow hard-block detail preview",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-USER-M-PROCESS")?.value,
|
||||
"1 locked rows; promotion_allowed=0; first=axis/vismach/millturn/example.ngc; reason=design_m128_m129_linuxcnc_state_boundary; next=kinstype_guard_and_ini_xyz_hal_pin_state",
|
||||
"workflow overview release artifact URL workflow user-M family row",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-USER-M-PROCESS-paths")?.value,
|
||||
"axis/vismach/millturn/example.ngc",
|
||||
"workflow overview release artifact URL workflow user-M family paths",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("saved-session-diagnostics"),
|
||||
true,
|
||||
@@ -876,6 +956,21 @@
|
||||
"0",
|
||||
"workflow overview fixed URL workflow form candidate allowed row",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="evidence-ready-candidate-rows"]').textContent,
|
||||
"8",
|
||||
"workflow overview fixed URL workflow form evidence-ready candidate rows",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="evidence-ready-candidate-preview"]').textContent,
|
||||
"qtdragon-multi-joint-on-abort",
|
||||
"workflow overview fixed URL workflow form evidence-ready candidate preview",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="evidence-ready-candidate-promotion-allowed"]').textContent,
|
||||
"0",
|
||||
"workflow overview fixed URL workflow form evidence-ready promotion allowed",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="evidence-expansion-candidates"]').textContent,
|
||||
"13",
|
||||
@@ -943,6 +1038,27 @@
|
||||
16,
|
||||
"workflow overview promotion candidate drilldown evidence-ready DOM nodes",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-candidate-drilldown-value="candidate-layer"]',
|
||||
)?.textContent,
|
||||
"evidence-ready",
|
||||
"workflow overview promotion candidate drilldown candidate layer row",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-candidate-drilldown-value="inventory-promotion-allowed"]',
|
||||
)?.textContent,
|
||||
"0",
|
||||
"workflow overview promotion candidate drilldown promotion allowed row",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-candidate-drilldown-value="inventory-baseline-changing"]',
|
||||
)?.textContent,
|
||||
"no",
|
||||
"workflow overview promotion candidate drilldown baseline changing row",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-candidate-drilldown-value="evidence-7"]',
|
||||
@@ -1020,6 +1136,13 @@
|
||||
4,
|
||||
"workflow overview source filter changed source DOM nodes",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelectorAll(
|
||||
'[data-workflow-overview-promotion-family-source-filter-type="candidate-row"]',
|
||||
).length,
|
||||
2,
|
||||
"workflow overview source filter changed candidate DOM nodes",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-family-source-filter-value="linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"]',
|
||||
@@ -1027,6 +1150,20 @@
|
||||
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
|
||||
"workflow overview source filter changed rose engine source row",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-family-source-filter-type="candidate-row"][data-workflow-overview-promotion-family-source-filter-value="linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"]',
|
||||
)?.dataset.workflowOverviewPromotionFamilySourceFilterCandidateStatus,
|
||||
"evidence-ready",
|
||||
"workflow overview source filter changed rose engine candidate status",
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector(
|
||||
'[data-workflow-overview-promotion-family-source-filter-type="candidate-row"][data-workflow-overview-promotion-family-source-filter-value="linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc"]',
|
||||
)?.dataset.workflowOverviewPromotionFamilySourceFilterPromotionAllowed,
|
||||
"0",
|
||||
"workflow overview source filter changed rose engine promotion allowed",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactJsonWorkflow.statusLine,
|
||||
"Ready: Artifact JSON workflow complete",
|
||||
|
||||
@@ -656,6 +656,170 @@
|
||||
]
|
||||
},
|
||||
"virtualHalSimConfigPromotionCandidatesReady": true,
|
||||
"virtualHalSimConfigEvidenceExpansion": {
|
||||
"apiName": "linuxcnc-wasm-virtual-hal-sim-config-evidence-expansion-report",
|
||||
"reportVersion": 1,
|
||||
"source": "linuxcnc-configs-sim-source-derived-virtual-hal",
|
||||
"phase": "ready",
|
||||
"complete": true,
|
||||
"webSimulationSatisfied": true,
|
||||
"inventoryBaselineUnchanged": true,
|
||||
"promotionAllowed": false,
|
||||
"manifestChecked": true,
|
||||
"candidateCount": 3,
|
||||
"readyCandidateCount": 3,
|
||||
"sourceFiles": [
|
||||
"linuxcnc/configs/sim/woodpecker/woodpecker.ini",
|
||||
"linuxcnc/configs/sim/woodpecker/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/woodpecker/tool.tbl",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc"
|
||||
],
|
||||
"requiredVirtualHalReports": [
|
||||
"source-compliance",
|
||||
"sim-config-source-coverage",
|
||||
"command-script-fixtures",
|
||||
"manifest-backed-motion-matrix"
|
||||
],
|
||||
"availableVirtualHalReports": [
|
||||
"source-compliance",
|
||||
"sim-config-source-coverage",
|
||||
"command-script-fixtures",
|
||||
"manifest-backed-motion-matrix"
|
||||
],
|
||||
"missingCandidates": [],
|
||||
"promotionAllowedViolations": [],
|
||||
"blockedCandidateIds": [],
|
||||
"missingManifestFiles": [],
|
||||
"missingRequiredReports": [],
|
||||
"rows": [
|
||||
{
|
||||
"id": "woodpecker-on-abort",
|
||||
"label": "Woodpecker on-abort",
|
||||
"source": "linuxcnc-configs-sim-source-derived-virtual-hal-evidence-expansion-candidate",
|
||||
"simConfigTarget": "woodpecker",
|
||||
"sourceFiles": [
|
||||
"linuxcnc/configs/sim/woodpecker/woodpecker.ini",
|
||||
"linuxcnc/configs/sim/woodpecker/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/woodpecker/tool.tbl"
|
||||
],
|
||||
"iniPath": "linuxcnc/configs/sim/woodpecker/woodpecker.ini",
|
||||
"gcodePath": "linuxcnc/configs/sim/woodpecker/on_abort.ngc",
|
||||
"currentNodeInventoryStatus": "PASS",
|
||||
"currentMatrixBrowserStatus": "REP",
|
||||
"dependencyClass": "hal_process,ui_process",
|
||||
"targetBrowserEvidence": "browser-diagnostics-expansion",
|
||||
"requiredVirtualHalReports": [
|
||||
"source-compliance",
|
||||
"sim-config-source-coverage",
|
||||
"command-script-fixtures",
|
||||
"manifest-backed-motion-matrix"
|
||||
],
|
||||
"blockedKind": "-",
|
||||
"promotionAllowed": false,
|
||||
"nodeStatusReady": true,
|
||||
"browserRepresentativeReady": true,
|
||||
"manifestChecked": true,
|
||||
"missingManifestFiles": [],
|
||||
"missingRequiredReports": [],
|
||||
"evidence": "Current inventory already covers Woodpecker as a non-hard-block representative; next step is explicit browser diagnostics binding only.",
|
||||
"sourceReady": true,
|
||||
"complete": true
|
||||
},
|
||||
{
|
||||
"id": "puma-cube",
|
||||
"label": "PUMA cube",
|
||||
"source": "linuxcnc-configs-sim-source-derived-virtual-hal-evidence-expansion-candidate",
|
||||
"simConfigTarget": "vismach-remap-sims",
|
||||
"sourceFiles": [
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc"
|
||||
],
|
||||
"iniPath": "linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ini",
|
||||
"gcodePath": "linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc",
|
||||
"currentNodeInventoryStatus": "PASS",
|
||||
"currentMatrixBrowserStatus": "REP",
|
||||
"dependencyClass": "hal_process,halui_mdi_process,ui_process",
|
||||
"targetBrowserEvidence": "browser-diagnostics-expansion",
|
||||
"requiredVirtualHalReports": [
|
||||
"source-compliance",
|
||||
"sim-config-source-coverage",
|
||||
"command-script-fixtures",
|
||||
"manifest-backed-motion-matrix"
|
||||
],
|
||||
"blockedKind": "-",
|
||||
"promotionAllowed": false,
|
||||
"nodeStatusReady": true,
|
||||
"browserRepresentativeReady": true,
|
||||
"manifestChecked": true,
|
||||
"missingManifestFiles": [],
|
||||
"missingRequiredReports": [],
|
||||
"evidence": "PUMA cube is already the representative browser row for the PUMA machine context; it remains an expansion candidate, not a baseline promotion.",
|
||||
"sourceReady": true,
|
||||
"complete": true
|
||||
},
|
||||
{
|
||||
"id": "melfa-example",
|
||||
"label": "MELFA example",
|
||||
"source": "linuxcnc-configs-sim-source-derived-virtual-hal-evidence-expansion-candidate",
|
||||
"simConfigTarget": "vismach-remap-sims",
|
||||
"sourceFiles": [
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc"
|
||||
],
|
||||
"iniPath": "linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini",
|
||||
"gcodePath": "linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc",
|
||||
"currentNodeInventoryStatus": "PASS",
|
||||
"currentMatrixBrowserStatus": "REP",
|
||||
"dependencyClass": "hal_process,halui_mdi_process,ui_process",
|
||||
"targetBrowserEvidence": "browser-diagnostics-expansion",
|
||||
"requiredVirtualHalReports": [
|
||||
"source-compliance",
|
||||
"sim-config-source-coverage",
|
||||
"command-script-fixtures",
|
||||
"manifest-backed-motion-matrix"
|
||||
],
|
||||
"blockedKind": "-",
|
||||
"promotionAllowed": false,
|
||||
"nodeStatusReady": true,
|
||||
"browserRepresentativeReady": true,
|
||||
"manifestChecked": true,
|
||||
"missingManifestFiles": [],
|
||||
"missingRequiredReports": [],
|
||||
"evidence": "MELFA example has current representative coverage and can receive explicit browser diagnostics evidence without unlocking any hard runtime family.",
|
||||
"sourceReady": true,
|
||||
"complete": true
|
||||
}
|
||||
],
|
||||
"summaryRows": [
|
||||
{
|
||||
"id": "candidates",
|
||||
"label": "Expansion candidates",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"id": "ready-candidates",
|
||||
"label": "Ready candidates",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"id": "promotion-allowed",
|
||||
"label": "Promotion allowed",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"id": "inventory-baseline",
|
||||
"label": "Inventory baseline",
|
||||
"value": "unchanged"
|
||||
}
|
||||
]
|
||||
},
|
||||
"virtualHalPromotionFamilySummary": "qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready",
|
||||
"virtualHalPromotionFamilyRows": [
|
||||
{
|
||||
@@ -726,7 +890,393 @@
|
||||
"totalCandidateCount": 28,
|
||||
"promotionAllowedCount": 0,
|
||||
"hardBlockRuntimeLock": "locked (3 families)",
|
||||
"hardBlockRuntimePromotionAllowedCount": 0,
|
||||
"inventoryBaseline": "executed=28 passed=28 skipped=131 unexpected_fail=0",
|
||||
"artifactRows": {
|
||||
"promotionCandidates": 28,
|
||||
"evidenceExpansion": 13
|
||||
},
|
||||
"hardBlockRuntimeFamilyRows": [
|
||||
{
|
||||
"candidateId": "axis/db_demo/base.ngc",
|
||||
"path": "axis/db_demo/base.ngc",
|
||||
"skipKind": "L4-TOOL-DB",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_tooldata_db_protocol_boundary",
|
||||
"recommendedNextCommand": "db_program_v2_1_handshake_getall_load_unload_or_put",
|
||||
"detailId": "hard-block-runtime-detail-1"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/laser/raster_test.ngc",
|
||||
"path": "axis/laser/raster_test.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-2"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/laser/vector_test.ngc",
|
||||
"path": "axis/laser/vector_test.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-3"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/laser/vector_test2.ngc",
|
||||
"path": "axis/laser/vector_test2.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-4"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/cycle/nc_files/examples.ngc",
|
||||
"path": "axis/remap/cycle/nc_files/examples.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-5"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/extend-builtins/nc_files/examples.ngc",
|
||||
"path": "axis/remap/extend-builtins/nc_files/examples.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-6"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/getting-started/nc_files/examples.ngc",
|
||||
"path": "axis/remap/getting-started/nc_files/examples.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-7"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc",
|
||||
"path": "axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-8"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/rack-toolchange/nc_files/tcdemo.ngc",
|
||||
"path": "axis/remap/rack-toolchange/nc_files/tcdemo.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-9"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/remap/stop-lookahead/nc_files/examples.ngc",
|
||||
"path": "axis/remap/stop-lookahead/nc_files/examples.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-10"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-11"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-12"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-13"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-14"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/millturn/example.ngc",
|
||||
"path": "axis/vismach/millturn/example.ngc",
|
||||
"skipKind": "L4-USER-M-PROCESS",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_m128_m129_linuxcnc_state_boundary",
|
||||
"recommendedNextCommand": "kinstype_guard_and_ini_xyz_hal_pin_state",
|
||||
"detailId": "hard-block-runtime-detail-15"
|
||||
},
|
||||
{
|
||||
"candidateId": "axis/vismach/VMC_toolchange/toolchange.ngc",
|
||||
"path": "axis/vismach/VMC_toolchange/toolchange.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-16"
|
||||
},
|
||||
{
|
||||
"candidateId": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-17"
|
||||
},
|
||||
{
|
||||
"candidateId": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-18"
|
||||
},
|
||||
{
|
||||
"candidateId": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc",
|
||||
"skipKind": "L4-PYTHON-REMAP",
|
||||
"promotionAllowed": false,
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"recommendedNextCommand": "python_runtime_owner_and_fixture",
|
||||
"detailId": "hard-block-runtime-detail-19"
|
||||
}
|
||||
],
|
||||
"hardBlockRuntimeFamilyDetailRows": [
|
||||
{
|
||||
"id": "hard-block-runtime-detail-1",
|
||||
"family": "L4-TOOL-DB",
|
||||
"path": "axis/db_demo/base.ngc",
|
||||
"blockReason": "design_tooldata_db_protocol_boundary",
|
||||
"nextProof": "db_program_v2_1_handshake_getall_load_unload_or_put",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-2",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/laser/raster_test.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-3",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/laser/vector_test.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-4",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/laser/vector_test2.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-5",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/cycle/nc_files/examples.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-6",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/extend-builtins/nc_files/examples.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-7",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/getting-started/nc_files/examples.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-8",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-9",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/rack-toolchange/nc_files/tcdemo.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-10",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/remap/stop-lookahead/nc_files/examples.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-11",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-12",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-13",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-14",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-15",
|
||||
"family": "L4-USER-M-PROCESS",
|
||||
"path": "axis/vismach/millturn/example.ngc",
|
||||
"blockReason": "design_m128_m129_linuxcnc_state_boundary",
|
||||
"nextProof": "kinstype_guard_and_ini_xyz_hal_pin_state",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-16",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "axis/vismach/VMC_toolchange/toolchange.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-17",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-18",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
},
|
||||
{
|
||||
"id": "hard-block-runtime-detail-19",
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"path": "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc",
|
||||
"blockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture",
|
||||
"promotionAllowed": false
|
||||
}
|
||||
],
|
||||
"hardBlockRuntimeFamilySummaryRows": [
|
||||
{
|
||||
"family": "L4-USER-M-PROCESS",
|
||||
"candidateCount": 1,
|
||||
"promotionAllowedCount": 0,
|
||||
"locked": true,
|
||||
"lockedPaths": [
|
||||
"axis/vismach/millturn/example.ngc"
|
||||
],
|
||||
"firstPath": "axis/vismach/millturn/example.ngc",
|
||||
"firstBlockReason": "design_m128_m129_linuxcnc_state_boundary",
|
||||
"nextProof": "kinstype_guard_and_ini_xyz_hal_pin_state"
|
||||
},
|
||||
{
|
||||
"family": "L4-TOOL-DB",
|
||||
"candidateCount": 1,
|
||||
"promotionAllowedCount": 0,
|
||||
"locked": true,
|
||||
"lockedPaths": [
|
||||
"axis/db_demo/base.ngc"
|
||||
],
|
||||
"firstPath": "axis/db_demo/base.ngc",
|
||||
"firstBlockReason": "design_tooldata_db_protocol_boundary",
|
||||
"nextProof": "db_program_v2_1_handshake_getall_load_unload_or_put"
|
||||
},
|
||||
{
|
||||
"family": "L4-PYTHON-REMAP",
|
||||
"candidateCount": 17,
|
||||
"promotionAllowedCount": 0,
|
||||
"locked": true,
|
||||
"lockedPaths": [
|
||||
"axis/laser/raster_test.ngc",
|
||||
"axis/laser/vector_test.ngc",
|
||||
"axis/laser/vector_test2.ngc",
|
||||
"axis/remap/cycle/nc_files/examples.ngc",
|
||||
"axis/remap/extend-builtins/nc_files/examples.ngc",
|
||||
"axis/remap/getting-started/nc_files/examples.ngc",
|
||||
"axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc",
|
||||
"axis/remap/rack-toolchange/nc_files/tcdemo.ngc",
|
||||
"axis/remap/stop-lookahead/nc_files/examples.ngc",
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc",
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc",
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc",
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc",
|
||||
"axis/vismach/VMC_toolchange/toolchange.ngc",
|
||||
"gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc",
|
||||
"gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc",
|
||||
"gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc"
|
||||
],
|
||||
"firstPath": "axis/laser/raster_test.ngc",
|
||||
"firstBlockReason": "design_linuxcnc_python_runtime_boundary",
|
||||
"nextProof": "python_runtime_owner_and_fixture"
|
||||
}
|
||||
],
|
||||
"layers": [
|
||||
{
|
||||
"id": "evidence-ready",
|
||||
@@ -746,6 +1296,7 @@
|
||||
"evidenceExpansion": {
|
||||
"id": "browser-diagnostics-expansion",
|
||||
"candidateCount": 13,
|
||||
"artifactRowCount": 13,
|
||||
"promotionAllowedCount": 0,
|
||||
"baselineChanging": false,
|
||||
"nextEvidence": "browser-diagnostics-binding"
|
||||
@@ -2332,6 +2883,16 @@
|
||||
"label": "Promotion candidate artifact",
|
||||
"value": "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"
|
||||
},
|
||||
{
|
||||
"id": "promotion-candidate-artifact-rows",
|
||||
"label": "Promotion candidate artifact rows",
|
||||
"value": "28"
|
||||
},
|
||||
{
|
||||
"id": "evidence-expansion-artifact-rows",
|
||||
"label": "Evidence expansion artifact rows",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"id": "promotion-candidate-layers",
|
||||
"label": "Promotion candidate layers",
|
||||
@@ -2347,6 +2908,16 @@
|
||||
"label": "Evidence expansion candidates",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"id": "evidence-expansion-next-evidence",
|
||||
"label": "Evidence expansion next evidence",
|
||||
"value": "browser-diagnostics-binding"
|
||||
},
|
||||
{
|
||||
"id": "evidence-expansion-baseline-changing",
|
||||
"label": "Evidence expansion baseline changing",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
"id": "evidence-expansion-artifact",
|
||||
"label": "Evidence expansion artifact",
|
||||
|
||||
@@ -189,6 +189,11 @@ assert.equal(artifact.promotionCandidateArtifactSummary.inventoryReadyCount, 20)
|
||||
assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount, 13);
|
||||
assert.equal(artifact.promotionCandidateArtifactSummary.totalCandidateCount, 28);
|
||||
assert.equal(artifact.promotionCandidateArtifactSummary.promotionAllowedCount, 0);
|
||||
assert.equal(artifact.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, 0);
|
||||
assert.deepEqual(artifact.promotionCandidateArtifactSummary.artifactRows, {
|
||||
promotionCandidates: 28,
|
||||
evidenceExpansion: 13,
|
||||
});
|
||||
assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansion.promotionAllowedCount, 0);
|
||||
assert.equal(
|
||||
artifact.promotionCandidateArtifactSummary.inventoryBaseline,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
createProjectReleaseReadinessReport,
|
||||
createVirtualHalSimConfigEvidenceExpansionReport,
|
||||
createVirtualHalMotionControllerMatrixReport,
|
||||
createVirtualHalSimConfigMacroLoadFixtureReport,
|
||||
createVirtualHalSimConfigPromotionCandidateReport,
|
||||
@@ -17,6 +18,8 @@ const sourceManifestText = readFileSync(resolve(root, "tools/source-manifest.txt
|
||||
const axisScreenshotArtifactDir = process.env.AXIS_SCREENSHOT_ARTIFACT_DIR
|
||||
? resolve(process.env.AXIS_SCREENSHOT_ARTIFACT_DIR)
|
||||
: null;
|
||||
const promotionCandidatesPath = resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv");
|
||||
const evidenceExpansionCandidatesPath = resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv");
|
||||
|
||||
const passedGateResults = {
|
||||
"diff-check": true,
|
||||
@@ -59,6 +62,23 @@ function loadAxisScreenshotArtifacts(directory) {
|
||||
});
|
||||
}
|
||||
|
||||
function parseTsvArtifactRows(path) {
|
||||
if (!existsSync(path)) {
|
||||
return [];
|
||||
}
|
||||
const [headerLine, ...dataLines] = readFileSync(path, "utf8").trim().split(/\r?\n/);
|
||||
if (!headerLine) {
|
||||
return [];
|
||||
}
|
||||
const headers = headerLine.split("\t");
|
||||
return dataLines
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line) => {
|
||||
const values = line.split("\t");
|
||||
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
||||
});
|
||||
}
|
||||
|
||||
const report = createProjectReleaseReadinessReport({
|
||||
gateResults: passedGateResults,
|
||||
observedOutputs: [
|
||||
@@ -80,6 +100,13 @@ const report = createProjectReleaseReadinessReport({
|
||||
simConfigSourceCoverage: virtualHalSimConfigSourceCoverage,
|
||||
motionControllerMatrix: virtualHalMotionControllerMatrix,
|
||||
}),
|
||||
virtualHalSimConfigEvidenceExpansion: createVirtualHalSimConfigEvidenceExpansionReport({
|
||||
manifestText: sourceManifestText,
|
||||
simConfigSourceCoverage: virtualHalSimConfigSourceCoverage,
|
||||
motionControllerMatrix: virtualHalMotionControllerMatrix,
|
||||
}),
|
||||
promotionCandidateRows: parseTsvArtifactRows(promotionCandidatesPath),
|
||||
evidenceExpansionRows: parseTsvArtifactRows(evidenceExpansionCandidatesPath),
|
||||
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
|
||||
manifestText: sourceManifestText,
|
||||
}),
|
||||
|
||||
17
wasm-port/tests/native/probe_millturn_user_m_runtime.sh
Normal file → Executable file
17
wasm-port/tests/native/probe_millturn_user_m_runtime.sh
Normal file → Executable file
@@ -22,7 +22,22 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
command_path() {
|
||||
command -v "$1" 2>/dev/null || true
|
||||
local command_name="$1"
|
||||
local path
|
||||
path="$(command -v "$command_name" 2>/dev/null || true)"
|
||||
if [[ -n "$path" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$command_name" in
|
||||
linuxcnc|halrun)
|
||||
[[ -x "$LINUXCNC_ROOT/scripts/$command_name" ]] && printf '%s\n' "$LINUXCNC_ROOT/scripts/$command_name"
|
||||
;;
|
||||
halcmd)
|
||||
[[ -x "$LINUXCNC_ROOT/bin/$command_name" ]] && printf '%s\n' "$LINUXCNC_ROOT/bin/$command_name"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
csv_join() {
|
||||
|
||||
@@ -14,7 +14,19 @@ required_commands=(python3 linuxcnc)
|
||||
missing_requirements=()
|
||||
|
||||
command_path() {
|
||||
command -v "$1" 2>/dev/null || true
|
||||
local command_name="$1"
|
||||
local path
|
||||
path="$(command -v "$command_name" 2>/dev/null || true)"
|
||||
if [[ -n "$path" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$command_name" in
|
||||
linuxcnc)
|
||||
[[ -x "$LINUXCNC_ROOT/scripts/linuxcnc" ]] && printf '%s\n' "$LINUXCNC_ROOT/scripts/linuxcnc"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
csv_join() {
|
||||
|
||||
61
wasm-port/tests/native/probe_tool_db_runtime.sh
Normal file → Executable file
61
wasm-port/tests/native/probe_tool_db_runtime.sh
Normal file → Executable file
@@ -14,7 +14,22 @@ required_commands=(python3 linuxcnc milltask halcmd)
|
||||
missing_requirements=()
|
||||
|
||||
command_path() {
|
||||
command -v "$1" 2>/dev/null || true
|
||||
local command_name="$1"
|
||||
local path
|
||||
path="$(command -v "$command_name" 2>/dev/null || true)"
|
||||
if [[ -n "$path" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$command_name" in
|
||||
linuxcnc)
|
||||
[[ -x "$LINUXCNC_ROOT/scripts/linuxcnc" ]] && printf '%s\n' "$LINUXCNC_ROOT/scripts/linuxcnc"
|
||||
;;
|
||||
milltask|halcmd)
|
||||
[[ -x "$LINUXCNC_ROOT/bin/$command_name" ]] && printf '%s\n' "$LINUXCNC_ROOT/bin/$command_name"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
csv_join() {
|
||||
@@ -108,7 +123,11 @@ import time
|
||||
db_program, db_demo_dir, python_path, db_savefile, stdout_log, stderr_log = sys.argv[1:]
|
||||
|
||||
env = os.environ.copy()
|
||||
linuxcnc_root = os.path.dirname(os.path.dirname(python_path))
|
||||
env["PYTHONPATH"] = python_path + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "")
|
||||
env["LD_LIBRARY_PATH"] = os.path.join(linuxcnc_root, "lib") + (
|
||||
os.pathsep + env["LD_LIBRARY_PATH"] if env.get("LD_LIBRARY_PATH") else ""
|
||||
)
|
||||
|
||||
stdout_file = open(stdout_log, "w", encoding="utf-8")
|
||||
stderr_file = open(stderr_log, "w", encoding="utf-8")
|
||||
@@ -119,31 +138,41 @@ proc = subprocess.Popen(
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=stderr_file,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
bufsize=0,
|
||||
)
|
||||
|
||||
stdout_buffer = b""
|
||||
line_queue = []
|
||||
|
||||
def emit(name, value):
|
||||
print(f"{name}={value}")
|
||||
|
||||
def read_line(timeout=5.0):
|
||||
global stdout_buffer
|
||||
if line_queue:
|
||||
return line_queue.pop(0)
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
ready, _, _ = select.select([proc.stdout], [], [], 0.1)
|
||||
if ready:
|
||||
line = proc.stdout.readline()
|
||||
if line == "":
|
||||
chunk = os.read(proc.stdout.fileno(), 4096)
|
||||
if chunk == b"":
|
||||
raise RuntimeError("db_program_stdout_closed")
|
||||
line = line.rstrip("\n")
|
||||
stdout_file.write(line + "\n")
|
||||
stdout_file.flush()
|
||||
return line
|
||||
stdout_buffer += chunk
|
||||
while b"\n" in stdout_buffer:
|
||||
raw_line, stdout_buffer = stdout_buffer.split(b"\n", 1)
|
||||
line = raw_line.decode("utf-8", errors="replace").rstrip("\r")
|
||||
stdout_file.write(line + "\n")
|
||||
stdout_file.flush()
|
||||
line_queue.append(line)
|
||||
if line_queue:
|
||||
return line_queue.pop(0)
|
||||
if proc.poll() is not None:
|
||||
raise RuntimeError(f"db_program_exited_{proc.returncode}")
|
||||
raise RuntimeError("timeout_waiting_for_db_program_reply")
|
||||
|
||||
def send(command):
|
||||
proc.stdin.write(command + "\n")
|
||||
proc.stdin.write((command + "\n").encode("utf-8"))
|
||||
proc.stdin.flush()
|
||||
|
||||
def read_until_fini(command, timeout=5.0):
|
||||
@@ -157,6 +186,10 @@ def read_until_fini(command, timeout=5.0):
|
||||
return lines
|
||||
raise RuntimeError(f"timeout_waiting_for_fini_{command.split()[0]}")
|
||||
|
||||
def read_single_reply(command, timeout=5.0):
|
||||
send(command)
|
||||
return read_line(timeout)
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
@@ -187,14 +220,14 @@ try:
|
||||
|
||||
load_reply = read_until_fini("l t14 p0")
|
||||
emit("tool_db_load_spindle_fini", int(load_reply[-1].startswith("FINI")))
|
||||
t14_loaded = read_until_fini("t 14")
|
||||
require(any(line.startswith("T14 ") and "P0" in line for line in t14_loaded), "load_spindle_state_drift")
|
||||
t14_loaded = read_single_reply("t 14")
|
||||
require(t14_loaded.startswith("T14 ") and "P0" in t14_loaded, "load_spindle_state_drift")
|
||||
emit("tool_db_load_spindle_state_ok", 1)
|
||||
|
||||
unload_reply = read_until_fini("u t0 p0")
|
||||
emit("tool_db_unload_spindle_fini", int(unload_reply[-1].startswith("FINI")))
|
||||
t14_unloaded = read_until_fini("t 14")
|
||||
require(any(line.startswith("T14 ") and "P114" in line for line in t14_unloaded), "unload_spindle_state_drift")
|
||||
t14_unloaded = read_single_reply("t 14")
|
||||
require(t14_unloaded.startswith("T14 ") and "P114" in t14_unloaded, "unload_spindle_state_drift")
|
||||
emit("tool_db_unload_spindle_state_ok", 1)
|
||||
|
||||
require(os.path.exists(db_savefile), "db_savefile_missing")
|
||||
|
||||
@@ -24,6 +24,23 @@ import {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = resolve(__dirname, "../../..");
|
||||
const sourceManifestText = readFileSync(resolve(root, "tools/source-manifest.txt"), "utf8");
|
||||
function parseTsvRows(text) {
|
||||
const [headerLine, ...dataLines] = text.trim().split(/\r?\n/);
|
||||
const headers = headerLine.split("\t");
|
||||
return dataLines
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line) => {
|
||||
const values = line.split("\t");
|
||||
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
||||
});
|
||||
}
|
||||
|
||||
const promotionCandidateRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
const evidenceExpansionRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
const manifest = createProjectReleaseGateManifest();
|
||||
const virtualHalSimConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport();
|
||||
const virtualHalMotionControllerMatrix = createVirtualHalMotionControllerMatrixReport({ manifestText: sourceManifestText });
|
||||
@@ -92,6 +109,8 @@ const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
|
||||
virtualHalSimConfigSourceCoverage,
|
||||
virtualHalSimConfigPromotionCandidates,
|
||||
virtualHalSimConfigEvidenceExpansion,
|
||||
promotionCandidateRows,
|
||||
evidenceExpansionRows,
|
||||
virtualHalSimConfigMacroLoadFixtures,
|
||||
virtualHalMotionControllerMatrix: browserDiagnosticsArtifact.virtualHalMotionControllerMatrix,
|
||||
}));
|
||||
@@ -198,10 +217,38 @@ assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "promotion-candidate-total")?.value,
|
||||
"28",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
|
||||
"8",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview")?.value,
|
||||
"qtdragon-multi-joint-on-abort",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview-gcode")?.value,
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-promotion-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-baseline-changing")?.value,
|
||||
"no",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-expansion-candidates")?.value,
|
||||
"13",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
|
||||
"28",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
|
||||
"13",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value,
|
||||
"browser-diagnostics-binding",
|
||||
@@ -242,6 +289,42 @@ assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "promotion-candidate-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
|
||||
"19",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value,
|
||||
"3",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value,
|
||||
"L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
|
||||
"19",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-TOOL-DB")?.value,
|
||||
"1 locked rows; promotion_allowed=0; first=axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-TOOL-DB-paths")?.value,
|
||||
"axis/db_demo/base.ngc",
|
||||
);
|
||||
assert.equal(
|
||||
readySummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,
|
||||
"locked (3 families)",
|
||||
@@ -270,7 +353,14 @@ assert.deepEqual(
|
||||
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
|
||||
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"],
|
||||
["promotion-candidate-total", "28"],
|
||||
["evidence-ready-candidate-rows", "8"],
|
||||
["evidence-ready-candidate-preview", "qtdragon-multi-joint-on-abort"],
|
||||
["evidence-ready-candidate-preview-gcode", "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"],
|
||||
["evidence-ready-candidate-promotion-allowed", "0"],
|
||||
["evidence-ready-candidate-baseline-changing", "no"],
|
||||
["promotion-candidate-artifact-rows", "28"],
|
||||
["evidence-expansion-candidates", "13"],
|
||||
["evidence-expansion-artifact-rows", "13"],
|
||||
["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
|
||||
["evidence-expansion-baseline-changing", "no"],
|
||||
["evidence-expansion-preferred-candidate", "woodpecker-on-abort"],
|
||||
@@ -281,6 +371,23 @@ assert.deepEqual(
|
||||
["evidence-expansion-source-count-list", "3, 3, 3"],
|
||||
["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"],
|
||||
["promotion-candidate-allowed", "0"],
|
||||
["hard-block-runtime-locked-rows", "19"],
|
||||
["hard-block-runtime-locked-preview", "L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc"],
|
||||
["hard-block-runtime-promotion-allowed", "0"],
|
||||
["hard-block-runtime-family-count", "3"],
|
||||
["hard-block-runtime-family-summary", "L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes"],
|
||||
["hard-block-runtime-detail-row-count", "19"],
|
||||
["hard-block-runtime-detail-preview-1", "L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0"],
|
||||
["hard-block-runtime-detail-preview-2", "L4-PYTHON-REMAP:axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"],
|
||||
["hard-block-runtime-detail-preview-3", "L4-PYTHON-REMAP:axis/laser/vector_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"],
|
||||
["hard-block-runtime-detail-preview-4", "L4-PYTHON-REMAP:axis/laser/vector_test2.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"],
|
||||
["hard-block-runtime-detail-preview-5", "L4-PYTHON-REMAP:axis/remap/cycle/nc_files/examples.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"],
|
||||
["hard-block-runtime-family-L4-USER-M-PROCESS", "1 locked rows; promotion_allowed=0; first=axis/vismach/millturn/example.ngc; reason=design_m128_m129_linuxcnc_state_boundary; next=kinstype_guard_and_ini_xyz_hal_pin_state"],
|
||||
["hard-block-runtime-family-L4-TOOL-DB", "1 locked rows; promotion_allowed=0; first=axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put"],
|
||||
["hard-block-runtime-family-L4-PYTHON-REMAP", "17 locked rows; promotion_allowed=0; first=axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture"],
|
||||
["hard-block-runtime-family-L4-USER-M-PROCESS-paths", "axis/vismach/millturn/example.ngc"],
|
||||
["hard-block-runtime-family-L4-TOOL-DB-paths", "axis/db_demo/base.ngc"],
|
||||
["hard-block-runtime-family-L4-PYTHON-REMAP-paths", "axis/laser/raster_test.ngc, axis/laser/vector_test.ngc, axis/laser/vector_test2.ngc, axis/remap/cycle/nc_files/examples.ngc, axis/remap/extend-builtins/nc_files/examples.ngc, axis/remap/getting-started/nc_files/examples.ngc, axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc, axis/remap/rack-toolchange/nc_files/tcdemo.ngc, axis/remap/stop-lookahead/nc_files/examples.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc, axis/vismach/VMC_toolchange/toolchange.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc"],
|
||||
...expectedPromotionFamilyRows,
|
||||
["browser-diagnostics", "ready"],
|
||||
["virtual-hal-promotion-candidate-summary", "ready"],
|
||||
|
||||
@@ -397,7 +397,9 @@ assert.deepEqual(
|
||||
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
|
||||
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"],
|
||||
["promotion-candidate-total", "28"],
|
||||
["promotion-candidate-artifact-rows", "28"],
|
||||
["evidence-expansion-candidates", "13"],
|
||||
["evidence-expansion-artifact-rows", "13"],
|
||||
["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
|
||||
["evidence-expansion-baseline-changing", "no"],
|
||||
["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"],
|
||||
@@ -496,7 +498,14 @@ assert.deepEqual(
|
||||
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
|
||||
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"],
|
||||
["promotion-candidate-total", "28"],
|
||||
["evidence-ready-candidate-rows", "0"],
|
||||
["evidence-ready-candidate-preview", "not provided"],
|
||||
["evidence-ready-candidate-preview-gcode", "not provided"],
|
||||
["evidence-ready-candidate-promotion-allowed", "0"],
|
||||
["evidence-ready-candidate-baseline-changing", "no"],
|
||||
["promotion-candidate-artifact-rows", "28"],
|
||||
["evidence-expansion-candidates", "13"],
|
||||
["evidence-expansion-artifact-rows", "13"],
|
||||
["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
|
||||
["evidence-expansion-baseline-changing", "no"],
|
||||
["evidence-expansion-preferred-candidate", "not provided"],
|
||||
@@ -507,6 +516,18 @@ assert.deepEqual(
|
||||
["evidence-expansion-source-count-list", "not provided"],
|
||||
["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"],
|
||||
["promotion-candidate-allowed", "0"],
|
||||
["hard-block-runtime-locked-rows", "0"],
|
||||
["hard-block-runtime-locked-preview", "not provided"],
|
||||
["hard-block-runtime-promotion-allowed", "0"],
|
||||
["hard-block-runtime-family-count", "3"],
|
||||
["hard-block-runtime-family-summary", "L4-USER-M-PROCESS:0 locked=yes, L4-TOOL-DB:0 locked=yes, L4-PYTHON-REMAP:0 locked=yes"],
|
||||
["hard-block-runtime-detail-row-count", "0"],
|
||||
["hard-block-runtime-family-L4-USER-M-PROCESS", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"],
|
||||
["hard-block-runtime-family-L4-TOOL-DB", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"],
|
||||
["hard-block-runtime-family-L4-PYTHON-REMAP", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"],
|
||||
["hard-block-runtime-family-L4-USER-M-PROCESS-paths", "not provided"],
|
||||
["hard-block-runtime-family-L4-TOOL-DB-paths", "not provided"],
|
||||
["hard-block-runtime-family-L4-PYTHON-REMAP-paths", "not provided"],
|
||||
...expectedPromotionFamilyRows,
|
||||
["browser-diagnostics", "not requested"],
|
||||
["virtual-hal-promotion-candidate-summary", "not requested"],
|
||||
|
||||
@@ -183,6 +183,24 @@ 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");
|
||||
|
||||
function parseTsvRows(text) {
|
||||
const [headerLine, ...dataLines] = text.trim().split(/\r?\n/);
|
||||
const headers = headerLine.split("\t");
|
||||
return dataLines
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line) => {
|
||||
const values = line.split("\t");
|
||||
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
||||
});
|
||||
}
|
||||
|
||||
const promotionCandidateArtifactRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
const evidenceExpansionArtifactRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
const expectedPromotionFamilySummary =
|
||||
"qtdragon-on-abort: 6/6 ready; vismach-remap-sims: 1/1 ready; rose-engine-rcone-demo: 1/1 ready";
|
||||
const expectedPromotionFamilyRows = [
|
||||
@@ -204,7 +222,46 @@ const expectedPromotionCandidateArtifactSummary = {
|
||||
totalCandidateCount: 28,
|
||||
promotionAllowedCount: 0,
|
||||
hardBlockRuntimeLock: "locked (3 families)",
|
||||
hardBlockRuntimePromotionAllowedCount: 0,
|
||||
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
|
||||
artifactRows: {
|
||||
promotionCandidates: 28,
|
||||
evidenceExpansion: 13,
|
||||
},
|
||||
hardBlockRuntimeFamilyRows: [],
|
||||
hardBlockRuntimeFamilyDetailRows: [],
|
||||
hardBlockRuntimeFamilySummaryRows: [
|
||||
{
|
||||
family: "L4-USER-M-PROCESS",
|
||||
candidateCount: 0,
|
||||
promotionAllowedCount: 0,
|
||||
locked: true,
|
||||
lockedPaths: [],
|
||||
firstPath: null,
|
||||
firstBlockReason: null,
|
||||
nextProof: null,
|
||||
},
|
||||
{
|
||||
family: "L4-TOOL-DB",
|
||||
candidateCount: 0,
|
||||
promotionAllowedCount: 0,
|
||||
locked: true,
|
||||
lockedPaths: [],
|
||||
firstPath: null,
|
||||
firstBlockReason: null,
|
||||
nextProof: null,
|
||||
},
|
||||
{
|
||||
family: "L4-PYTHON-REMAP",
|
||||
candidateCount: 0,
|
||||
promotionAllowedCount: 0,
|
||||
locked: true,
|
||||
lockedPaths: [],
|
||||
firstPath: null,
|
||||
firstBlockReason: null,
|
||||
nextProof: null,
|
||||
},
|
||||
],
|
||||
layers: [
|
||||
{
|
||||
id: "evidence-ready",
|
||||
@@ -224,6 +281,7 @@ const expectedPromotionCandidateArtifactSummary = {
|
||||
evidenceExpansion: {
|
||||
id: "browser-diagnostics-expansion",
|
||||
candidateCount: 13,
|
||||
artifactRowCount: 13,
|
||||
promotionAllowedCount: 0,
|
||||
baselineChanging: false,
|
||||
nextEvidence: "browser-diagnostics-binding",
|
||||
@@ -1571,6 +1629,14 @@ assert.deepEqual(
|
||||
releaseReadinessReady.promotionCandidateArtifactSummary,
|
||||
expectedPromotionCandidateArtifactSummary,
|
||||
);
|
||||
assert.equal(
|
||||
releaseReadinessReady.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
|
||||
"28",
|
||||
);
|
||||
assert.equal(
|
||||
releaseReadinessReady.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
|
||||
"13",
|
||||
);
|
||||
assert.deepEqual(
|
||||
releaseReadinessReady.virtualHalPromotionFamilyRows.map(({ id, value }) => [
|
||||
`virtual-hal-promotion-family-${id}`,
|
||||
@@ -1597,6 +1663,78 @@ assert.equal(
|
||||
createProjectReleaseReadinessSummaryViewModel(releaseReadinessReady).rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
|
||||
"evidence-ready=8 inventory-ready=20",
|
||||
);
|
||||
const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessReport({
|
||||
observedOutputs: ["project_release_gate=ok"],
|
||||
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
|
||||
virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport,
|
||||
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
|
||||
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
|
||||
promotionCandidateRows: promotionCandidateArtifactRows,
|
||||
evidenceExpansionRows: evidenceExpansionArtifactRows,
|
||||
});
|
||||
assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 28);
|
||||
assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 13);
|
||||
assert.equal(
|
||||
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows.length,
|
||||
19,
|
||||
);
|
||||
assert.equal(
|
||||
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount,
|
||||
0,
|
||||
);
|
||||
assert.equal(
|
||||
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows
|
||||
.some((row) => row.skipKind === "L4-TOOL-DB" && row.promotionAllowed === false),
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(
|
||||
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows
|
||||
.map(({ family, candidateCount, promotionAllowedCount, locked }) => [
|
||||
family,
|
||||
candidateCount,
|
||||
promotionAllowedCount,
|
||||
locked,
|
||||
]),
|
||||
[
|
||||
["L4-USER-M-PROCESS", 1, 0, true],
|
||||
["L4-TOOL-DB", 1, 0, true],
|
||||
["L4-PYTHON-REMAP", 17, 0, true],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyDetailRows
|
||||
.slice(0, 3)
|
||||
.map(({ family, path, blockReason, nextProof, promotionAllowed }) => [
|
||||
family,
|
||||
path,
|
||||
blockReason,
|
||||
nextProof,
|
||||
promotionAllowed,
|
||||
]),
|
||||
[
|
||||
[
|
||||
"L4-TOOL-DB",
|
||||
"axis/db_demo/base.ngc",
|
||||
"design_tooldata_db_protocol_boundary",
|
||||
"db_program_v2_1_handshake_getall_load_unload_or_put",
|
||||
false,
|
||||
],
|
||||
[
|
||||
"L4-PYTHON-REMAP",
|
||||
"axis/laser/raster_test.ngc",
|
||||
"design_linuxcnc_python_runtime_boundary",
|
||||
"python_runtime_owner_and_fixture",
|
||||
false,
|
||||
],
|
||||
[
|
||||
"L4-PYTHON-REMAP",
|
||||
"axis/laser/vector_test.ngc",
|
||||
"design_linuxcnc_python_runtime_boundary",
|
||||
"python_runtime_owner_and_fixture",
|
||||
false,
|
||||
],
|
||||
],
|
||||
);
|
||||
const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({
|
||||
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
|
||||
virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport,
|
||||
@@ -1735,11 +1873,21 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
|
||||
label: "Promotion candidate total",
|
||||
value: "28",
|
||||
},
|
||||
{
|
||||
id: "promotion-candidate-artifact-rows",
|
||||
label: "Promotion candidate artifact rows",
|
||||
value: "28",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-candidates",
|
||||
label: "Evidence expansion candidates",
|
||||
value: "13",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-artifact-rows",
|
||||
label: "Evidence expansion artifact rows",
|
||||
value: "13",
|
||||
},
|
||||
{
|
||||
id: "evidence-expansion-next-evidence",
|
||||
label: "Evidence expansion next evidence",
|
||||
@@ -1899,6 +2047,22 @@ assert.equal(
|
||||
).rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
|
||||
"evidence-ready=8 inventory-ready=20",
|
||||
);
|
||||
assert.equal(
|
||||
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
|
||||
await loadProjectReleaseReadinessArtifactUrlWorkflow({
|
||||
artifactUrl: "/project-release-readiness.json",
|
||||
diagnosticsUrl: "/browser-diagnostics.json",
|
||||
fetchRef: async (url) => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => url === "/browser-diagnostics.json"
|
||||
? JSON.stringify(browserDiagnosticsArtifact)
|
||||
: JSON.stringify(releaseReadinessArtifact),
|
||||
}),
|
||||
}),
|
||||
).rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
|
||||
"8",
|
||||
);
|
||||
assert.equal(
|
||||
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
|
||||
await loadProjectReleaseReadinessArtifactUrlWorkflow({
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
createProjectReleaseBrowserDiagnosticsArtifactValidation,
|
||||
@@ -18,9 +21,6 @@ import {
|
||||
createVirtualHalState,
|
||||
} from "../../../runtime/sdk/src/linuxcnc-hal.js";
|
||||
|
||||
const VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES =
|
||||
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []);
|
||||
|
||||
import {
|
||||
INI_PANEL_ENTRIES,
|
||||
controlPageStatusText,
|
||||
@@ -161,6 +161,30 @@ import {
|
||||
renderMachineSessionControlView,
|
||||
} from "../../../runtime/ui/ini-panel/ui-shell.js";
|
||||
|
||||
const VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES =
|
||||
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []);
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = resolve(__dirname, "../../..");
|
||||
|
||||
function parseTsvRows(text) {
|
||||
const [headerLine, ...dataLines] = text.trim().split(/\r?\n/);
|
||||
const headers = headerLine.split("\t");
|
||||
return dataLines
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line) => {
|
||||
const values = line.split("\t");
|
||||
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
||||
});
|
||||
}
|
||||
|
||||
const promotionCandidateRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
const evidenceExpansionRows = parseTsvRows(
|
||||
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"), "utf8"),
|
||||
);
|
||||
|
||||
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
|
||||
assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES);
|
||||
assert.equal(controlPageStatusText({ lastAction: "x", runStatus: "ok", error: null }), "x | ok | -");
|
||||
@@ -1454,6 +1478,8 @@ const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadines
|
||||
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
|
||||
virtualHalSimConfigPromotionCandidates: releaseReadinessPromotionCandidates,
|
||||
virtualHalSimConfigEvidenceExpansion: releaseReadinessEvidenceExpansion,
|
||||
promotionCandidateRows,
|
||||
evidenceExpansionRows,
|
||||
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
|
||||
virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix,
|
||||
}));
|
||||
@@ -1807,7 +1833,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
|
||||
rowsNode: releaseArtifactRowsNode,
|
||||
});
|
||||
assert.equal(releaseArtifactRenderResult.rendered, true);
|
||||
assert.equal(releaseArtifactRenderResult.rowCount, 28);
|
||||
assert.equal(releaseArtifactRenderResult.rowCount, 30);
|
||||
assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
|
||||
assert.equal(
|
||||
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
|
||||
@@ -1858,7 +1884,7 @@ assert.equal(
|
||||
true,
|
||||
);
|
||||
assert.equal(releaseArtifactMountResult.ready, true);
|
||||
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 28);
|
||||
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 30);
|
||||
assert.deepEqual(
|
||||
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
|
||||
["artifact-json"],
|
||||
@@ -1917,7 +1943,7 @@ assert.equal(
|
||||
);
|
||||
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
|
||||
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
|
||||
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 28);
|
||||
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 30);
|
||||
const releaseArtifactUrlWorkflowSummary =
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
|
||||
releaseArtifactUrlWorkflow,
|
||||
@@ -1981,10 +2007,38 @@ assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-browser-diagnostics-count")?.value,
|
||||
"8 diagnostics-ready",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
|
||||
"8",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview")?.value,
|
||||
"qtdragon-multi-joint-on-abort",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview-gcode")?.value,
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-promotion-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-baseline-changing")?.value,
|
||||
"no",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value,
|
||||
"browser-diagnostics-binding",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
|
||||
"28",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
|
||||
"13",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-baseline-changing")?.value,
|
||||
"no",
|
||||
@@ -2025,6 +2079,42 @@ assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value,
|
||||
"0 promoted",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
|
||||
"19",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value,
|
||||
"3",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value,
|
||||
"L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
|
||||
"19",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value,
|
||||
"L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-PYTHON-REMAP")?.value,
|
||||
"17 locked rows; promotion_allowed=0; first=axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture",
|
||||
);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-USER-M-PROCESS-paths")?.value,
|
||||
"axis/vismach/millturn/example.ngc",
|
||||
);
|
||||
const promotionCandidateDrilldownViewModel =
|
||||
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel(
|
||||
releaseArtifactUrlWorkflowSummary,
|
||||
@@ -2062,6 +2152,18 @@ assert.equal(
|
||||
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "blocked-family-exclusion")?.value,
|
||||
"passed",
|
||||
);
|
||||
assert.equal(
|
||||
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "candidate-layer")?.value,
|
||||
"evidence-ready",
|
||||
);
|
||||
assert.equal(
|
||||
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "inventory-promotion-allowed")?.value,
|
||||
"0",
|
||||
);
|
||||
assert.equal(
|
||||
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "inventory-baseline-changing")?.value,
|
||||
"no",
|
||||
);
|
||||
assert.equal(promotionCandidateDrilldownViewModel.sourceFileCount, 2);
|
||||
assert.equal(promotionCandidateDrilldownViewModel.evidenceRowCount, 8);
|
||||
assert.equal(
|
||||
@@ -2091,7 +2193,7 @@ assert.deepEqual(
|
||||
],
|
||||
);
|
||||
assert.equal(promotionCandidateDrilldownRenderState.ready, true);
|
||||
assert.equal(promotionCandidateDrilldownRenderState.rowCount, 19);
|
||||
assert.equal(promotionCandidateDrilldownRenderState.rowCount, 22);
|
||||
assert.equal(promotionCandidateDrilldownRenderState.sourceFileCount, 2);
|
||||
assert.equal(promotionCandidateDrilldownRenderState.evidenceRowCount, 8);
|
||||
assert.equal(
|
||||
@@ -2133,7 +2235,7 @@ const promotionCandidateDrilldownMountResult =
|
||||
rowsNode: promotionCandidateDrilldownRowsNode,
|
||||
});
|
||||
assert.equal(promotionCandidateDrilldownRenderResult.rendered, true);
|
||||
assert.equal(promotionCandidateDrilldownRenderResult.rowCount, 19);
|
||||
assert.equal(promotionCandidateDrilldownRenderResult.rowCount, 22);
|
||||
assert.equal(promotionCandidateDrilldownRenderResult.evidenceRowCount, 8);
|
||||
assert.deepEqual(promotionCandidateDrilldownRenderResult.dataset, {
|
||||
handoffPhase: "ready",
|
||||
@@ -2145,6 +2247,18 @@ assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "blocked-family-exclusion" &&
|
||||
node.textContent === "passed"
|
||||
), true);
|
||||
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "candidate-layer" &&
|
||||
node.textContent === "evidence-ready"
|
||||
), true);
|
||||
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "inventory-promotion-allowed" &&
|
||||
node.textContent === "0"
|
||||
), true);
|
||||
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "inventory-baseline-changing" &&
|
||||
node.textContent === "no"
|
||||
), true);
|
||||
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownSourceKind === "gcode" &&
|
||||
node.dataset?.workflowOverviewPromotionCandidateDrilldownManifestBacked === "true" &&
|
||||
@@ -2196,6 +2310,16 @@ assert.equal(promotionFamilyRowsRenderState.statusLine, "Ready: 3 promotion fami
|
||||
assert.equal(promotionFamilyRowsRenderState.rows.length, 3);
|
||||
assert.equal(promotionFamilyRowsRenderState.sourceFileCount, 17);
|
||||
assert.equal(promotionFamilyRowsRenderState.sourceRows.length, 17);
|
||||
assert.equal(promotionFamilyRowsRenderState.candidateCount, 8);
|
||||
assert.equal(promotionFamilyRowsRenderState.candidateRows.length, 8);
|
||||
assert.equal(
|
||||
promotionFamilyRowsRenderState.candidateRows.every(({ candidateStatus, promotionAllowed, baselineChanging }) =>
|
||||
candidateStatus === "evidence-ready" &&
|
||||
promotionAllowed === false &&
|
||||
baselineChanging === false
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
promotionFamilyRowsRenderState.rows.find(({ id }) => id === "virtual-hal-promotion-family-qtdragon-on-abort")
|
||||
?.sourceKindSummary,
|
||||
@@ -2215,6 +2339,8 @@ 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.equal(promotionFamilySourceFilterViewModel.filteredCandidateCount, 8);
|
||||
assert.equal(promotionFamilySourceFilterViewModel.totalCandidateCount, 8);
|
||||
assert.deepEqual(
|
||||
promotionFamilySourceFilterViewModel.familyFilters.map(({ id, count, selected }) => [id, count, selected]),
|
||||
[
|
||||
@@ -2249,6 +2375,7 @@ assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.selectedSourceKind, "gc
|
||||
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.statusLine, "Ready: 6/17 source files selected");
|
||||
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.scopedSourceFileCount, 12);
|
||||
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.filteredSourceFileCount, 6);
|
||||
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.filteredCandidateCount, 6);
|
||||
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.familyRows.length, 1);
|
||||
assert.equal(
|
||||
promotionFamilyQtDragonGcodeFilterViewModel.familyRows[0]?.sourceKindSummary,
|
||||
@@ -2325,7 +2452,8 @@ assert.equal(promotionFamilySourceFilterRenderResult.statusLine, "Ready: 6/17 so
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.controlCount, 2);
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.filterRowCount, 7);
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.sourceFileCount, 6);
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.rowCount, 13);
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.candidateCount, 6);
|
||||
assert.equal(promotionFamilySourceFilterRenderResult.rowCount, 19);
|
||||
assert.deepEqual(promotionFamilySourceFilterRenderResult.dataset, {
|
||||
handoffPhase: "ready",
|
||||
handoffReady: "true",
|
||||
@@ -2385,8 +2513,20 @@ assert.equal(
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
promotionFamilySourceFilterRowsNode.children.some((node) =>
|
||||
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "candidate-row" &&
|
||||
node.dataset?.workflowOverviewPromotionFamilySourceFilterFamily ===
|
||||
"virtual-hal-promotion-family-qtdragon-on-abort" &&
|
||||
node.dataset?.workflowOverviewPromotionFamilySourceFilterCandidateStatus === "evidence-ready" &&
|
||||
node.dataset?.workflowOverviewPromotionFamilySourceFilterPromotionAllowed === "0" &&
|
||||
node.dataset?.workflowOverviewPromotionFamilySourceFilterBaselineChanging === "no" &&
|
||||
node.textContent === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(promotionFamilySourceFilterMountResult.ready, true);
|
||||
assert.equal(promotionFamilySourceFilterMountResult.renderResult.rowCount, 13);
|
||||
assert.equal(promotionFamilySourceFilterMountResult.renderResult.rowCount, 19);
|
||||
const promotionFamilySourceFilterSelectionActionPlan =
|
||||
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan(
|
||||
promotionFamilyRowsRenderState,
|
||||
@@ -2436,6 +2576,8 @@ 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.viewModel.filteredCandidateCount, 1);
|
||||
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.candidateCount, 1);
|
||||
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.dataset.selectedSourceKind, "all");
|
||||
assert.equal(
|
||||
promotionFamilySourceFilterRowsNode.children.some((node) =>
|
||||
@@ -2673,7 +2815,7 @@ const releaseArtifactUrlWorkflowMountResult =
|
||||
rowsNode: releaseArtifactUrlWorkflowRowsNode,
|
||||
});
|
||||
assert.equal(releaseArtifactUrlWorkflowRenderResult.rendered, true);
|
||||
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 35);
|
||||
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 59);
|
||||
assert.equal(
|
||||
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("browser-diagnostics"),
|
||||
true,
|
||||
@@ -2723,7 +2865,7 @@ assert.equal(
|
||||
true,
|
||||
);
|
||||
assert.equal(releaseArtifactUrlWorkflowMountResult.ready, true);
|
||||
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 35);
|
||||
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 59);
|
||||
const releaseArtifactUrlWorkflowMissingDom = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({
|
||||
artifactUrl: "/project-release-readiness.json",
|
||||
fetchRef: async () => ({
|
||||
|
||||
@@ -2108,6 +2108,26 @@ function linuxCncSourceExecutable(relPath) {
|
||||
}
|
||||
}
|
||||
|
||||
function linuxCncHostCommandPath(command) {
|
||||
const pathCommand = commandPath(command);
|
||||
if (pathCommand !== "") {
|
||||
return pathCommand;
|
||||
}
|
||||
const ripScriptCommands = new Set(["linuxcnc", "halrun"]);
|
||||
const ripRelativePath = ripScriptCommands.has(command)
|
||||
? `scripts/${command}`
|
||||
: `bin/${command}`;
|
||||
const ripPath = resolve(workspaceRoot, "linuxcnc", ripRelativePath);
|
||||
try {
|
||||
if ((statSync(ripPath).mode & 0o111) !== 0) {
|
||||
return ripPath;
|
||||
}
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function toolDbProcessNativeRuntimeReadinessRows(transactionRows) {
|
||||
const first = transactionRows[0];
|
||||
if (!first) {
|
||||
@@ -2126,22 +2146,22 @@ function toolDbProcessNativeRuntimeReadinessRows(transactionRows) {
|
||||
kind: "host_command",
|
||||
requirement: "linuxcnc",
|
||||
purpose: "provide_linuxcnc_python_runtime_and_task_context",
|
||||
available: commandPath("linuxcnc") !== "",
|
||||
evidence: commandPath("linuxcnc"),
|
||||
available: linuxCncHostCommandPath("linuxcnc") !== "",
|
||||
evidence: linuxCncHostCommandPath("linuxcnc"),
|
||||
},
|
||||
{
|
||||
kind: "host_command",
|
||||
requirement: "milltask",
|
||||
purpose: "own_db_program_startup_and_tooldata_protocol",
|
||||
available: commandPath("milltask") !== "",
|
||||
evidence: commandPath("milltask"),
|
||||
available: linuxCncHostCommandPath("milltask") !== "",
|
||||
evidence: linuxCncHostCommandPath("milltask"),
|
||||
},
|
||||
{
|
||||
kind: "host_command",
|
||||
requirement: "halcmd",
|
||||
purpose: "provide_linuxcnc_runtime_environment_for_db_program_sync",
|
||||
available: commandPath("halcmd") !== "",
|
||||
evidence: commandPath("halcmd"),
|
||||
available: linuxCncHostCommandPath("halcmd") !== "",
|
||||
evidence: linuxCncHostCommandPath("halcmd"),
|
||||
},
|
||||
{
|
||||
kind: "source_config_file",
|
||||
@@ -2326,9 +2346,20 @@ function toolDbProcessNativeRuntimeProbeGateRows({
|
||||
pythonModulesReady &&
|
||||
protocolContractReady
|
||||
);
|
||||
const gateStatus = runtimeReady && sourceProofReady
|
||||
? "ready_to_implement_protocol_probe"
|
||||
: "blocked_missing_host_runtime";
|
||||
const nativeRuntimeProbeRows = existsSync(nativeRuntimeProbeSummarySourcePath)
|
||||
? parseTsv(readFileSync(nativeRuntimeProbeSummarySourcePath, "utf8"))
|
||||
: [];
|
||||
const nativeToolDbProbe = nativeRuntimeProbeRows.find((row) =>
|
||||
row.boundary_class === "L4-TOOL-DB" &&
|
||||
row.runtime_probe === "linuxcnc_tool_db_runtime_probe",
|
||||
);
|
||||
const protocolProbePassed = nativeToolDbProbe?.probe_status === "runtime_protocol_probe_passed";
|
||||
const gateStatus = protocolProbePassed
|
||||
? "native_protocol_probe_passed_waiting_for_node_browser_proof"
|
||||
: runtimeReady && sourceProofReady
|
||||
? "ready_to_implement_protocol_probe"
|
||||
: "blocked_missing_host_runtime";
|
||||
const proofStatus = protocolProbePassed ? "native_protocol_probe_passed" : "pending";
|
||||
|
||||
return [[
|
||||
first.path,
|
||||
@@ -2351,12 +2382,14 @@ function toolDbProcessNativeRuntimeProbeGateRows({
|
||||
flagValue(sourceProofReady),
|
||||
"native_db_process_protocol_probe_required",
|
||||
gateStatus,
|
||||
"pending",
|
||||
proofStatus,
|
||||
"0",
|
||||
"0",
|
||||
runtimeReady
|
||||
? "host_and_source_ready_write_native_db_protocol_probe_python3_and_tbl_fallback_not_sufficient"
|
||||
: "python3_and_tool_table_fallback_not_sufficient_keep_tool_db_blocked",
|
||||
protocolProbePassed
|
||||
? "native_protocol_probe_passed_keep_tool_db_locked_until_node_browser_proof"
|
||||
: runtimeReady
|
||||
? "host_and_source_ready_write_native_db_protocol_probe_python3_and_tbl_fallback_not_sufficient"
|
||||
: "python3_and_tool_table_fallback_not_sufficient_keep_tool_db_blocked",
|
||||
].map(tsvValue).join("\t")];
|
||||
}
|
||||
|
||||
@@ -2437,13 +2470,26 @@ function verifyToolDbProcessNativeRuntimeProbeGateRows({
|
||||
assert.equal(row.runtime_ready, flagValue(allRuntimeReady), "tool DB runtime readiness drift");
|
||||
assert.equal(row.source_proof_ready, "1", "tool DB source proof must be ready before runtime probe implementation");
|
||||
assert.equal(row.required_native_proof, "native_db_process_protocol_probe_required", "tool DB native proof drift");
|
||||
assert.equal(row.proof_status, "pending", "tool DB probe gate must remain pending");
|
||||
assert.ok(
|
||||
["pending", "native_protocol_probe_passed"].includes(row.proof_status),
|
||||
"tool DB probe gate proof status drift",
|
||||
);
|
||||
assert.equal(row.execution_enabled, "0", "tool DB probe gate must not enable execution");
|
||||
assert.equal(row.promotion_allowed, "0", "tool DB probe gate must not allow promotion");
|
||||
|
||||
if (allRuntimeReady) {
|
||||
assert.equal(row.missing_requirements, "-", "ready tool DB runtime has missing requirements");
|
||||
assert.equal(row.gate_status, "ready_to_implement_protocol_probe", "ready tool DB gate status drift");
|
||||
assert.ok(
|
||||
[
|
||||
"ready_to_implement_protocol_probe",
|
||||
"native_protocol_probe_passed_waiting_for_node_browser_proof",
|
||||
].includes(row.gate_status),
|
||||
"ready tool DB gate status drift",
|
||||
);
|
||||
if (row.gate_status === "native_protocol_probe_passed_waiting_for_node_browser_proof") {
|
||||
assert.equal(row.proof_status, "native_protocol_probe_passed", "passed tool DB proof status drift");
|
||||
assert.ok(row.notes.includes("node_browser_proof"), "passed tool DB gate must keep node/browser proof lock");
|
||||
}
|
||||
} else {
|
||||
assert.deepEqual(
|
||||
splitSummaryList(row.missing_requirements).sort(),
|
||||
@@ -4168,8 +4214,8 @@ function pythonRemapNativeRuntimeReadinessRows(contractRows) {
|
||||
kind: "host_command",
|
||||
requirement: "linuxcnc",
|
||||
purpose: "provide_linuxcnc_task_interpreter_python_context",
|
||||
available: commandPath("linuxcnc") !== "",
|
||||
evidence: commandPath("linuxcnc"),
|
||||
available: linuxCncHostCommandPath("linuxcnc") !== "",
|
||||
evidence: linuxCncHostCommandPath("linuxcnc"),
|
||||
},
|
||||
{
|
||||
kind: "linuxcnc_source_file",
|
||||
@@ -5563,7 +5609,7 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
)
|
||||
);
|
||||
const runtimeBoundaryHostUnblockPlanOk = (
|
||||
runtimeBoundaryHostUnblockPlanRows.length === 4 &&
|
||||
runtimeBoundaryHostUnblockPlanRows.length === runtimeBoundaryHostRequirementSummaryRows.filter((row) => row.missing_blocks_runtime === "1").length &&
|
||||
runtimeBoundaryHostUnblockPlanRows.every((row) =>
|
||||
row.requirement_kind === "host_command" &&
|
||||
row.available === "0" &&
|
||||
@@ -5581,14 +5627,21 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryFamilyHostReadinessOk = (
|
||||
runtimeBoundaryFamilyHostReadinessRows.length === 3 &&
|
||||
runtimeBoundaryFamilyHostReadinessRows.every((row) =>
|
||||
/^[1-9][0-9]*$/.test(row.missing_requirement_count) &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
/^[0-9]+$/.test(row.missing_requirement_count) &&
|
||||
/^[1-9][0-9]*$/.test(row.available_requirement_count) &&
|
||||
row.available_requirements !== "-" &&
|
||||
/^ENABLE_[A-Z0-9_]+=1$/.test(row.opt_in_env) &&
|
||||
row.execution_command.includes(row.opt_in_env) &&
|
||||
row.preflight_status === "blocked_missing_host_runtime" &&
|
||||
row.family_host_status === "blocked_missing_host_requirements" &&
|
||||
(
|
||||
(row.missing_requirement_count === "0" &&
|
||||
row.missing_host_requirements === "-" &&
|
||||
row.preflight_status === "ready_to_run_opt_in_native_probe" &&
|
||||
row.family_host_status === "host_ready_to_run_opt_in_probe") ||
|
||||
(row.missing_requirement_count !== "0" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.preflight_status === "blocked_missing_host_runtime" &&
|
||||
row.family_host_status === "blocked_missing_host_requirements")
|
||||
) &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
)
|
||||
@@ -5596,17 +5649,17 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryHostReadinessRollupOk = (
|
||||
runtimeBoundaryHostReadinessRollupRows.length === 1 &&
|
||||
runtimeBoundaryHostReadinessRollupRows.every((row) =>
|
||||
row.scope === "blocked_runtime_host_readiness" &&
|
||||
row.scope === "blocked_runtime_host_readiness" &&
|
||||
row.family_count === "3" &&
|
||||
row.host_ready_family_count === "0" &&
|
||||
row.host_blocked_family_count === "3" &&
|
||||
row.blocked_families !== "-" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.missing_host_requirement_count === "4" &&
|
||||
row.ready_opt_in_command_count === "0" &&
|
||||
row.ready_opt_in_commands === "-" &&
|
||||
row.blocked_opt_in_commands !== "-" &&
|
||||
row.host_readiness_status === "host_blocked_for_all_opt_in_native_probes" &&
|
||||
/^[0-9]+$/.test(row.host_ready_family_count) &&
|
||||
/^[0-9]+$/.test(row.host_blocked_family_count) &&
|
||||
/^[0-9]+$/.test(row.missing_host_requirement_count) &&
|
||||
/^[0-9]+$/.test(row.ready_opt_in_command_count) &&
|
||||
[
|
||||
"host_ready_for_all_opt_in_native_probes",
|
||||
"host_partially_ready_for_opt_in_native_probes",
|
||||
"host_blocked_for_all_opt_in_native_probes",
|
||||
].includes(row.host_readiness_status) &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
)
|
||||
@@ -5616,10 +5669,16 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
runtimeBoundaryOptInProbeDispatchPlanRows.every((row) =>
|
||||
/^ENABLE_[A-Z0-9_]+=1$/.test(row.opt_in_env) &&
|
||||
row.execution_command.includes(row.opt_in_env) &&
|
||||
row.family_host_status === "blocked_missing_host_requirements" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.host_action === "skip_missing_host_requirements" &&
|
||||
row.dispatch_allowed === "0" &&
|
||||
(
|
||||
(row.family_host_status === "host_ready_to_run_opt_in_probe" &&
|
||||
row.missing_host_requirements === "-" &&
|
||||
row.host_action === "ready_for_manual_opt_in_native_probe" &&
|
||||
row.dispatch_allowed === "1") ||
|
||||
(row.family_host_status === "blocked_missing_host_requirements" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.host_action === "skip_missing_host_requirements" &&
|
||||
row.dispatch_allowed === "0")
|
||||
) &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
)
|
||||
@@ -5627,12 +5686,16 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryOptInProbeDispatchRollupOk = (
|
||||
runtimeBoundaryOptInProbeDispatchRollupRows.length === 1 &&
|
||||
runtimeBoundaryOptInProbeDispatchRollupRows.every((row) =>
|
||||
row.scope === "blocked_runtime_opt_in_probe_dispatch" &&
|
||||
row.scope === "blocked_runtime_opt_in_probe_dispatch" &&
|
||||
row.probe_count === "3" &&
|
||||
row.dispatch_allowed_count === "0" &&
|
||||
row.dispatch_blocked_count === "3" &&
|
||||
row.dispatch_actions === "skip_missing_host_requirements" &&
|
||||
row.dispatch_status === "dispatch_blocked_for_all_opt_in_native_probes" &&
|
||||
/^[0-9]+$/.test(row.dispatch_allowed_count) &&
|
||||
/^[0-9]+$/.test(row.dispatch_blocked_count) &&
|
||||
row.dispatch_actions !== "-" &&
|
||||
[
|
||||
"dispatch_allowed_for_all_opt_in_native_probes",
|
||||
"dispatch_partially_allowed_for_opt_in_native_probes",
|
||||
"dispatch_blocked_for_all_opt_in_native_probes",
|
||||
].includes(row.dispatch_status) &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
)
|
||||
@@ -5640,13 +5703,19 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryOptInProbeSkipEvidenceContractOk = (
|
||||
runtimeBoundaryOptInProbeSkipEvidenceContractRows.length === 3 &&
|
||||
runtimeBoundaryOptInProbeSkipEvidenceContractRows.every((row) =>
|
||||
row.host_action === "skip_missing_host_requirements" &&
|
||||
row.skip_reason === "missing_host_requirements" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.current_probe_status === "skipped_missing_host_runtime" &&
|
||||
(
|
||||
(row.host_action === "ready_for_manual_opt_in_native_probe" &&
|
||||
row.skip_reason === "-" &&
|
||||
row.missing_host_requirements === "-" &&
|
||||
row.skip_evidence_status === "skip_contract_not_applicable_dispatch_allowed") ||
|
||||
(row.host_action === "skip_missing_host_requirements" &&
|
||||
row.skip_reason === "missing_host_requirements" &&
|
||||
row.missing_host_requirements !== "-" &&
|
||||
row.current_probe_status === "skipped_missing_host_runtime" &&
|
||||
row.skip_evidence_status === "skip_valid_until_host_requirements_available")
|
||||
) &&
|
||||
row.evidence_required_now === "0" &&
|
||||
row.observed_evidence_ready === "0" &&
|
||||
row.skip_evidence_status === "skip_valid_until_host_requirements_available" &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
)
|
||||
@@ -5654,12 +5723,12 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryOptInProbeSkipEvidenceRollupOk = (
|
||||
runtimeBoundaryOptInProbeSkipEvidenceRollupRows.length === 1 &&
|
||||
runtimeBoundaryOptInProbeSkipEvidenceRollupRows.every((row) =>
|
||||
row.scope === "blocked_runtime_opt_in_probe_skip_evidence" &&
|
||||
row.scope === "blocked_runtime_opt_in_probe_skip_evidence" &&
|
||||
row.probe_count === "3" &&
|
||||
row.skip_count === "3" &&
|
||||
/^[0-9]+$/.test(row.skip_count) &&
|
||||
row.evidence_required_now_count === "0" &&
|
||||
row.observed_evidence_ready_count === "0" &&
|
||||
row.skip_evidence_statuses === "skip_valid_until_host_requirements_available" &&
|
||||
row.skip_evidence_statuses !== "-" &&
|
||||
row.evidence_rollup_status === "no_native_pass_evidence_accepted_while_host_blocked" &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
@@ -5668,11 +5737,10 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
const runtimeBoundaryNativeEvidenceAcceptanceGateOk = (
|
||||
runtimeBoundaryNativeEvidenceAcceptanceGateRows.length === 3 &&
|
||||
runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) =>
|
||||
row.current_probe_status === "skipped_missing_host_runtime" &&
|
||||
row.native_evidence_status === "pending_until_native_pass" &&
|
||||
row.native_evidence_status === "pending_until_native_pass" &&
|
||||
row.evidence_required_now === "0" &&
|
||||
row.observed_evidence_ready === "0" &&
|
||||
row.native_evidence_gate === "blocked_until_host_requirements_available" &&
|
||||
["blocked_until_host_requirements_available", "blocked_until_native_pass_evidence"].includes(row.native_evidence_gate) &&
|
||||
row.evidence_acceptance_allowed === "0" &&
|
||||
row.promotion_ready === "0" &&
|
||||
row.execution_enabled === "0" &&
|
||||
@@ -5698,7 +5766,7 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
classOrderMatches(runtimeBoundaryOptInProbeSkipEvidenceContractRows) &&
|
||||
classOrderMatches(runtimeBoundaryNativeEvidenceAcceptanceGateRows) &&
|
||||
runtimeBoundaryOptInProbeDispatchPlanRows.every((row) =>
|
||||
row.dispatch_allowed === "0" &&
|
||||
["0", "1"].includes(row.dispatch_allowed) &&
|
||||
row.execution_enabled === "0" &&
|
||||
row.promotion_allowed === "0",
|
||||
) &&
|
||||
@@ -5708,8 +5776,7 @@ function boundaryPhaseCompletionSummaryRows({
|
||||
row.promotion_allowed === "0",
|
||||
) &&
|
||||
runtimeBoundaryPromotionBlockerRows.every((row) =>
|
||||
row.blocker_keys.includes("host_runtime_requirements_missing") &&
|
||||
row.blocker_keys.includes("promotion_lock_active") &&
|
||||
row.blocker_keys.includes("promotion_lock_active") &&
|
||||
row.blocker_keys.includes("manual_lock_update_required"),
|
||||
)
|
||||
);
|
||||
@@ -6085,7 +6152,11 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) {
|
||||
);
|
||||
for (const row of parsedRows) {
|
||||
assert.equal(row.passed, "1", `${row.criterion}: completion criterion failed`);
|
||||
assert.ok(/^[1-9][0-9]*$/.test(row.count), `${row.criterion}: invalid criterion count`);
|
||||
if (row.criterion === "runtime_boundary_host_unblock_plan") {
|
||||
assert.ok(/^[0-9]+$/.test(row.count), `${row.criterion}: invalid criterion count`);
|
||||
} else {
|
||||
assert.ok(/^[1-9][0-9]*$/.test(row.count), `${row.criterion}: invalid criterion count`);
|
||||
}
|
||||
assert.notEqual(row.evidence, "-", `${row.criterion}: missing criterion evidence`);
|
||||
}
|
||||
const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row]));
|
||||
@@ -7233,9 +7304,9 @@ function verifyRuntimeBoundaryHostRequirementSummaryRows(rows, preflightRows) {
|
||||
|
||||
const byRequirement = new Map(parsedRows.map((row) => [row.requirement, row]));
|
||||
for (const required of [
|
||||
["linuxcnc", "0", "3", "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS"],
|
||||
["halcmd", "0", "2", "L4-TOOL-DB,L4-USER-M-PROCESS"],
|
||||
["milltask", "0", "1", "L4-TOOL-DB"],
|
||||
["linuxcnc", "1", "3", "-"],
|
||||
["halcmd", "1", "2", "-"],
|
||||
["milltask", "1", "1", "-"],
|
||||
["python3", "1", "2", "-"],
|
||||
["tclsh", "1", "1", "-"],
|
||||
]) {
|
||||
@@ -7439,45 +7510,22 @@ function verifyRuntimeBoundaryFamilyHostReadinessRows(rows, preflightRows) {
|
||||
expectedHeaders,
|
||||
);
|
||||
const preflightByClass = new Map(preflightRows.map((row) => [row.boundary_class, row]));
|
||||
const expected = new Map([
|
||||
[
|
||||
"L4-USER-M-PROCESS",
|
||||
{
|
||||
missingCount: "3",
|
||||
missing: "halrun,halcmd,linuxcnc",
|
||||
available: "tclsh",
|
||||
},
|
||||
],
|
||||
[
|
||||
"L4-TOOL-DB",
|
||||
{
|
||||
missingCount: "3",
|
||||
missing: "linuxcnc,milltask,halcmd",
|
||||
available: "python3,axis/db_demo/db_nonran.py,linuxcnc.so,tooldb.py",
|
||||
},
|
||||
],
|
||||
[
|
||||
"L4-PYTHON-REMAP",
|
||||
{
|
||||
missingCount: "1",
|
||||
missing: "linuxcnc",
|
||||
available: "python3,src/emc/rs274ngc/interp_python.cc,src/emc/pythonplugin/python_plugin.cc,axis/remap/stop-lookahead/python/remap.py,axis/remap/stop-lookahead/python/toplevel.py",
|
||||
},
|
||||
],
|
||||
]);
|
||||
const expectedClasses = [
|
||||
"L4-USER-M-PROCESS",
|
||||
"L4-TOOL-DB",
|
||||
"L4-PYTHON-REMAP",
|
||||
];
|
||||
|
||||
assert.equal(parsedRows.length, expected.size, "family host readiness row count drift");
|
||||
assert.equal(parsedRows.length, expectedClasses.length, "family host readiness row count drift");
|
||||
assert.deepEqual(
|
||||
parsedRows.map((row) => row.boundary_class),
|
||||
[...expected.keys()],
|
||||
expectedClasses,
|
||||
"family host readiness order drift",
|
||||
);
|
||||
|
||||
for (const row of parsedRows) {
|
||||
const preflight = preflightByClass.get(row.boundary_class);
|
||||
const expectedRow = expected.get(row.boundary_class);
|
||||
assert.ok(preflight, `${row.boundary_class}: family host readiness lacks preflight row`);
|
||||
assert.ok(expectedRow, `${row.boundary_class}: unexpected family host readiness row`);
|
||||
assert.equal(row.boundary_kind, preflight.boundary_kind, `${row.boundary_class}: family host readiness kind drift`);
|
||||
assert.equal(row.target, preflight.target, `${row.boundary_class}: family host readiness target drift`);
|
||||
assert.equal(row.blocked, preflight.blocked, `${row.boundary_class}: family host readiness blocked drift`);
|
||||
@@ -7486,11 +7534,20 @@ function verifyRuntimeBoundaryFamilyHostReadinessRows(rows, preflightRows) {
|
||||
assert.equal(row.execution_command, preflight.execution_command, `${row.boundary_class}: family host readiness command drift`);
|
||||
assert.equal(row.current_probe_status, preflight.current_probe_status, `${row.boundary_class}: family host readiness probe status drift`);
|
||||
assert.equal(row.preflight_status, preflight.preflight_status, `${row.boundary_class}: family host readiness preflight status drift`);
|
||||
assert.equal(row.missing_requirement_count, expectedRow.missingCount, `${row.boundary_class}: family host readiness missing count drift`);
|
||||
assert.equal(row.missing_host_requirements, expectedRow.missing, `${row.boundary_class}: family host readiness missing requirements drift`);
|
||||
assert.equal(row.available_requirements, expectedRow.available, `${row.boundary_class}: family host readiness available requirements drift`);
|
||||
assert.equal(row.available_requirement_count, String(splitSummaryList(expectedRow.available).length), `${row.boundary_class}: family host readiness available count drift`);
|
||||
assert.equal(row.family_host_status, "blocked_missing_host_requirements", `${row.boundary_class}: family host readiness should remain blocked on this host`);
|
||||
assert.equal(row.missing_host_requirements, preflight.missing_requirements, `${row.boundary_class}: family host readiness missing requirements drift`);
|
||||
assert.equal(row.missing_requirement_count, String(splitSummaryList(preflight.missing_requirements).length), `${row.boundary_class}: family host readiness missing count drift`);
|
||||
assert.equal(
|
||||
row.available_requirement_count,
|
||||
String(splitSummaryList(row.available_requirements).length),
|
||||
`${row.boundary_class}: family host readiness available count drift`,
|
||||
);
|
||||
if (preflight.runtime_ready === "1") {
|
||||
assert.equal(row.missing_host_requirements, "-", `${row.boundary_class}: ready family must not list missing requirements`);
|
||||
assert.equal(row.family_host_status, "host_ready_to_run_opt_in_probe", `${row.boundary_class}: ready family host status drift`);
|
||||
} else {
|
||||
assert.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: blocked family must list missing requirements`);
|
||||
assert.equal(row.family_host_status, "blocked_missing_host_requirements", `${row.boundary_class}: blocked family host status drift`);
|
||||
}
|
||||
assert.equal(row.execution_enabled, "0", `${row.boundary_class}: family host readiness must not enable execution`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: family host readiness must not allow promotion`);
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), `${row.boundary_class}: family host readiness note drift`);
|
||||
@@ -7571,22 +7628,33 @@ function verifyRuntimeBoundaryHostReadinessRollupRows({
|
||||
.filter((requirement) => requirement.missing_blocks_runtime === "1")
|
||||
.map((requirement) => requirement.requirement),
|
||||
);
|
||||
const readyFamilies = familyHostReadinessRows.filter((family) =>
|
||||
family.family_host_status === "host_ready_to_run_opt_in_probe",
|
||||
);
|
||||
const blockedFamilies = familyHostReadinessRows.filter((family) =>
|
||||
family.family_host_status !== "host_ready_to_run_opt_in_probe",
|
||||
);
|
||||
|
||||
assert.equal(parsedRows.length, 1, "host readiness rollup row count drift");
|
||||
assert.equal(row.scope, "blocked_runtime_host_readiness", "host readiness rollup scope drift");
|
||||
assert.equal(row.family_count, String(familyHostReadinessRows.length), "host readiness rollup family count drift");
|
||||
assert.equal(row.host_ready_family_count, "0", "host readiness rollup must have no ready families on this host");
|
||||
assert.equal(row.host_blocked_family_count, String(familyHostReadinessRows.length), "host readiness rollup blocked family count drift");
|
||||
assert.equal(row.blocked_families, "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS", "host readiness rollup blocked families drift");
|
||||
assert.equal(row.host_ready_family_count, String(readyFamilies.length), "host readiness rollup ready family count drift");
|
||||
assert.equal(row.host_blocked_family_count, String(blockedFamilies.length), "host readiness rollup blocked family count drift");
|
||||
assert.equal(row.blocked_families, listValue(uniqueSorted(blockedFamilies.map((family) => family.boundary_class))), "host readiness rollup blocked families drift");
|
||||
assert.equal(row.missing_host_requirements, listValue(expectedMissingRequirements), "host readiness rollup missing requirements drift");
|
||||
assert.equal(row.missing_host_requirement_count, String(expectedMissingRequirements.length), "host readiness rollup missing requirement count drift");
|
||||
assert.equal(row.ready_opt_in_command_count, "0", "host readiness rollup must not report ready opt-in commands");
|
||||
assert.equal(row.ready_opt_in_commands, "-", "host readiness rollup must not list ready opt-in commands");
|
||||
assert.notEqual(row.blocked_opt_in_commands, "-", "host readiness rollup must list blocked opt-in commands");
|
||||
for (const family of familyHostReadinessRows) {
|
||||
assert.equal(row.ready_opt_in_command_count, String(readyFamilies.length), "host readiness rollup ready command count drift");
|
||||
assert.equal(row.ready_opt_in_commands, listValue(uniqueSorted(readyFamilies.map((family) => family.execution_command))), "host readiness rollup ready commands drift");
|
||||
assert.equal(row.blocked_opt_in_commands, listValue(uniqueSorted(blockedFamilies.map((family) => family.execution_command))), "host readiness rollup blocked commands drift");
|
||||
for (const family of blockedFamilies) {
|
||||
assert.ok(row.blocked_opt_in_commands.includes(family.execution_command), `${family.boundary_class}: host readiness rollup missing blocked command`);
|
||||
}
|
||||
assert.equal(row.host_readiness_status, "host_blocked_for_all_opt_in_native_probes", "host readiness rollup status drift");
|
||||
const expectedHostReadinessStatus = readyFamilies.length === familyHostReadinessRows.length
|
||||
? "host_ready_for_all_opt_in_native_probes"
|
||||
: readyFamilies.length > 0
|
||||
? "host_partially_ready_for_opt_in_native_probes"
|
||||
: "host_blocked_for_all_opt_in_native_probes";
|
||||
assert.equal(row.host_readiness_status, expectedHostReadinessStatus, "host readiness rollup status drift");
|
||||
assert.equal(row.execution_enabled, "0", "host readiness rollup must not enable execution");
|
||||
assert.equal(row.promotion_allowed, "0", "host readiness rollup must not allow promotion");
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), "host readiness rollup note drift");
|
||||
@@ -7685,14 +7753,19 @@ function verifyRuntimeBoundaryOptInProbeDispatchPlanRows({
|
||||
assert.equal(row.missing_host_requirements, readiness.missing_host_requirements, `${row.boundary_class}: dispatch missing requirement drift`);
|
||||
assert.equal(row.host_readiness_status, rollup.host_readiness_status, `${row.boundary_class}: dispatch rollup status drift`);
|
||||
assert.ok(row.execution_command.includes(row.opt_in_env), `${row.boundary_class}: dispatch command must include opt-in env`);
|
||||
assert.equal(row.family_host_status, "blocked_missing_host_requirements", `${row.boundary_class}: current host must remain blocked`);
|
||||
assert.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: blocked dispatch must list missing requirements`);
|
||||
assert.equal(row.host_readiness_status, "host_blocked_for_all_opt_in_native_probes", `${row.boundary_class}: dispatch host readiness status drift`);
|
||||
assert.equal(row.host_action, "skip_missing_host_requirements", `${row.boundary_class}: current host dispatch action drift`);
|
||||
assert.equal(row.dispatch_allowed, "0", `${row.boundary_class}: current host dispatch must not be allowed`);
|
||||
if (readiness.family_host_status === "host_ready_to_run_opt_in_probe") {
|
||||
assert.equal(row.missing_host_requirements, "-", `${row.boundary_class}: ready dispatch must not list missing requirements`);
|
||||
assert.equal(row.host_action, "ready_for_manual_opt_in_native_probe", `${row.boundary_class}: ready dispatch action drift`);
|
||||
assert.equal(row.dispatch_allowed, "1", `${row.boundary_class}: ready dispatch should be allowed manually`);
|
||||
assert.ok(row.next_action.includes("manually_run_opt_in_probe"), `${row.boundary_class}: ready dispatch next action drift`);
|
||||
} else {
|
||||
assert.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: blocked dispatch must list missing requirements`);
|
||||
assert.equal(row.host_action, "skip_missing_host_requirements", `${row.boundary_class}: blocked dispatch action drift`);
|
||||
assert.equal(row.dispatch_allowed, "0", `${row.boundary_class}: blocked dispatch must not be allowed`);
|
||||
assert.ok(row.next_action.includes(row.missing_host_requirements), `${row.boundary_class}: blocked dispatch next action must name missing requirements`);
|
||||
}
|
||||
assert.equal(row.execution_enabled, "0", `${row.boundary_class}: dispatch plan must not enable execution`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: dispatch plan must not allow promotion`);
|
||||
assert.ok(row.next_action.includes(row.missing_host_requirements), `${row.boundary_class}: dispatch next action must name missing requirements`);
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), `${row.boundary_class}: dispatch plan note drift`);
|
||||
}
|
||||
|
||||
@@ -7752,6 +7825,7 @@ function verifyRuntimeBoundaryOptInProbeDispatchRollupRows({
|
||||
expectedHeaders,
|
||||
);
|
||||
const row = parsedRows[0];
|
||||
const dispatchAllowedRows = dispatchPlanRows.filter((plan) => plan.dispatch_allowed === "1");
|
||||
const dispatchBlockedRows = dispatchPlanRows.filter((plan) => plan.dispatch_allowed !== "1");
|
||||
const expectedMissingRequirements = uniqueSorted(dispatchBlockedRows.flatMap((plan) =>
|
||||
splitSummaryList(plan.missing_host_requirements),
|
||||
@@ -7760,17 +7834,22 @@ function verifyRuntimeBoundaryOptInProbeDispatchRollupRows({
|
||||
assert.equal(parsedRows.length, 1, "opt-in probe dispatch rollup row count drift");
|
||||
assert.equal(row.scope, "blocked_runtime_opt_in_probe_dispatch", "opt-in probe dispatch rollup scope drift");
|
||||
assert.equal(row.probe_count, String(dispatchPlanRows.length), "opt-in probe dispatch rollup probe count drift");
|
||||
assert.equal(row.dispatch_allowed_count, "0", "opt-in probe dispatch rollup must not allow probes on this host");
|
||||
assert.equal(row.dispatch_blocked_count, String(dispatchPlanRows.length), "opt-in probe dispatch rollup blocked count drift");
|
||||
assert.equal(row.blocked_families, "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS", "opt-in probe dispatch rollup blocked families drift");
|
||||
assert.equal(row.dispatch_actions, "skip_missing_host_requirements", "opt-in probe dispatch rollup action drift");
|
||||
assert.equal(row.dispatch_allowed_commands, "-", "opt-in probe dispatch rollup must not list allowed commands");
|
||||
assert.notEqual(row.dispatch_blocked_commands, "-", "opt-in probe dispatch rollup must list blocked commands");
|
||||
for (const plan of dispatchPlanRows) {
|
||||
assert.equal(row.dispatch_allowed_count, String(dispatchAllowedRows.length), "opt-in probe dispatch rollup allowed count drift");
|
||||
assert.equal(row.dispatch_blocked_count, String(dispatchBlockedRows.length), "opt-in probe dispatch rollup blocked count drift");
|
||||
assert.equal(row.blocked_families, listValue(uniqueSorted(dispatchBlockedRows.map((plan) => plan.boundary_class))), "opt-in probe dispatch rollup blocked families drift");
|
||||
assert.equal(row.dispatch_actions, listValue(uniqueSorted(dispatchPlanRows.map((plan) => plan.host_action))), "opt-in probe dispatch rollup action drift");
|
||||
assert.equal(row.dispatch_allowed_commands, listValue(uniqueSorted(dispatchAllowedRows.map((plan) => plan.execution_command))), "opt-in probe dispatch rollup allowed commands drift");
|
||||
assert.equal(row.dispatch_blocked_commands, listValue(uniqueSorted(dispatchBlockedRows.map((plan) => plan.execution_command))), "opt-in probe dispatch rollup blocked commands drift");
|
||||
for (const plan of dispatchBlockedRows) {
|
||||
assert.ok(row.dispatch_blocked_commands.includes(plan.execution_command), `${plan.boundary_class}: opt-in probe dispatch rollup missing blocked command`);
|
||||
}
|
||||
assert.equal(row.missing_host_requirements, listValue(expectedMissingRequirements), "opt-in probe dispatch rollup missing requirement drift");
|
||||
assert.equal(row.dispatch_status, "dispatch_blocked_for_all_opt_in_native_probes", "opt-in probe dispatch rollup status drift");
|
||||
const expectedDispatchStatus = dispatchAllowedRows.length === dispatchPlanRows.length
|
||||
? "dispatch_allowed_for_all_opt_in_native_probes"
|
||||
: dispatchAllowedRows.length > 0
|
||||
? "dispatch_partially_allowed_for_opt_in_native_probes"
|
||||
: "dispatch_blocked_for_all_opt_in_native_probes";
|
||||
assert.equal(row.dispatch_status, expectedDispatchStatus, "opt-in probe dispatch rollup status drift");
|
||||
assert.equal(row.execution_enabled, "0", "opt-in probe dispatch rollup must not enable execution");
|
||||
assert.equal(row.promotion_allowed, "0", "opt-in probe dispatch rollup must not allow promotion");
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), "opt-in probe dispatch rollup note drift");
|
||||
@@ -7869,12 +7948,8 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceContractRows({
|
||||
assert.equal(row.runtime_probe, evidence.runtime_probe, `${row.boundary_class}: skip evidence runtime probe drift`);
|
||||
assert.equal(row.opt_in_env, dispatch.opt_in_env, `${row.boundary_class}: skip evidence opt-in drift`);
|
||||
assert.equal(row.execution_command, dispatch.execution_command, `${row.boundary_class}: skip evidence command drift`);
|
||||
assert.equal(row.host_action, "skip_missing_host_requirements", `${row.boundary_class}: current host skip action drift`);
|
||||
assert.equal(row.host_action, dispatch.host_action, `${row.boundary_class}: skip evidence host action drift`);
|
||||
assert.equal(row.skip_reason, "missing_host_requirements", `${row.boundary_class}: skip evidence reason drift`);
|
||||
assert.equal(row.missing_host_requirements, dispatch.missing_host_requirements, `${row.boundary_class}: skip evidence missing requirements drift`);
|
||||
assert.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: skip evidence must list missing host requirements`);
|
||||
assert.equal(row.current_probe_status, "skipped_missing_host_runtime", `${row.boundary_class}: skip evidence current probe status drift`);
|
||||
assert.equal(row.current_probe_status, evidence.current_probe_status, `${row.boundary_class}: skip evidence probe status must match pass evidence contract`);
|
||||
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: skipped probe must not require evidence now`);
|
||||
assert.equal(row.evidence_required_now, evidence.evidence_required_now, `${row.boundary_class}: skip evidence required-now drift`);
|
||||
@@ -7882,7 +7957,18 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceContractRows({
|
||||
assert.equal(row.observed_evidence_ready, evidence.observed_evidence_ready, `${row.boundary_class}: skip evidence readiness drift`);
|
||||
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: skip evidence status drift`);
|
||||
assert.equal(row.native_evidence_status, evidence.evidence_status, `${row.boundary_class}: skip evidence native status drift`);
|
||||
assert.equal(row.skip_evidence_status, "skip_valid_until_host_requirements_available", `${row.boundary_class}: skip evidence contract status drift`);
|
||||
if (dispatch.dispatch_allowed === "1") {
|
||||
assert.equal(row.host_action, "ready_for_manual_opt_in_native_probe", `${row.boundary_class}: ready skip evidence action drift`);
|
||||
assert.equal(row.skip_reason, "-", `${row.boundary_class}: ready dispatch must not have skip reason`);
|
||||
assert.equal(row.missing_host_requirements, "-", `${row.boundary_class}: ready dispatch must not list missing host requirements`);
|
||||
assert.equal(row.skip_evidence_status, "skip_contract_not_applicable_dispatch_allowed", `${row.boundary_class}: ready skip evidence status drift`);
|
||||
} else {
|
||||
assert.equal(row.host_action, "skip_missing_host_requirements", `${row.boundary_class}: blocked skip evidence action drift`);
|
||||
assert.equal(row.skip_reason, "missing_host_requirements", `${row.boundary_class}: blocked skip evidence reason drift`);
|
||||
assert.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: blocked skip evidence must list missing host requirements`);
|
||||
assert.equal(row.current_probe_status, "skipped_missing_host_runtime", `${row.boundary_class}: blocked skip evidence current probe status drift`);
|
||||
assert.equal(row.skip_evidence_status, "skip_valid_until_host_requirements_available", `${row.boundary_class}: blocked skip evidence status drift`);
|
||||
}
|
||||
assert.equal(row.execution_enabled, "0", `${row.boundary_class}: skip evidence contract must not enable execution`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: skip evidence contract must not allow promotion`);
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), `${row.boundary_class}: skip evidence contract note drift`);
|
||||
@@ -7947,6 +8033,7 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceRollupRows({
|
||||
expectedHeaders,
|
||||
);
|
||||
const row = parsedRows[0];
|
||||
const skippedRows = skipEvidenceContractRows.filter((contract) => contract.skip_reason !== "-");
|
||||
const expectedMissingRequirements = uniqueSorted(skipEvidenceContractRows.flatMap((contract) =>
|
||||
splitSummaryList(contract.missing_host_requirements),
|
||||
));
|
||||
@@ -7954,15 +8041,15 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceRollupRows({
|
||||
assert.equal(parsedRows.length, 1, "opt-in probe skip evidence rollup row count drift");
|
||||
assert.equal(row.scope, "blocked_runtime_opt_in_probe_skip_evidence", "opt-in probe skip evidence rollup scope drift");
|
||||
assert.equal(row.probe_count, String(skipEvidenceContractRows.length), "opt-in probe skip evidence rollup probe count drift");
|
||||
assert.equal(row.skip_count, String(skipEvidenceContractRows.length), "opt-in probe skip evidence rollup skip count drift");
|
||||
assert.equal(row.skip_count, String(skippedRows.length), "opt-in probe skip evidence rollup skip count drift");
|
||||
assert.equal(row.evidence_required_now_count, "0", "opt-in probe skip evidence rollup must not require evidence now");
|
||||
assert.equal(row.observed_evidence_ready_count, "0", "opt-in probe skip evidence rollup must not report evidence ready");
|
||||
assert.equal(row.skipped_families, "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS", "opt-in probe skip evidence rollup family drift");
|
||||
assert.equal(row.skip_reasons, "missing_host_requirements", "opt-in probe skip evidence rollup reason drift");
|
||||
assert.equal(row.skipped_families, listValue(uniqueSorted(skippedRows.map((contract) => contract.boundary_class))), "opt-in probe skip evidence rollup family drift");
|
||||
assert.equal(row.skip_reasons, listValue(uniqueSorted(skippedRows.map((contract) => contract.skip_reason))), "opt-in probe skip evidence rollup reason drift");
|
||||
assert.equal(row.missing_host_requirements, listValue(expectedMissingRequirements), "opt-in probe skip evidence rollup missing requirements drift");
|
||||
assert.equal(row.current_probe_statuses, "skipped_missing_host_runtime", "opt-in probe skip evidence rollup probe status drift");
|
||||
assert.equal(row.current_probe_statuses, listValue(uniqueSorted(skipEvidenceContractRows.map((contract) => contract.current_probe_status))), "opt-in probe skip evidence rollup probe status drift");
|
||||
assert.equal(row.native_evidence_statuses, "pending_until_native_pass", "opt-in probe skip evidence rollup native evidence status drift");
|
||||
assert.equal(row.skip_evidence_statuses, "skip_valid_until_host_requirements_available", "opt-in probe skip evidence rollup skip evidence status drift");
|
||||
assert.equal(row.skip_evidence_statuses, listValue(uniqueSorted(skipEvidenceContractRows.map((contract) => contract.skip_evidence_status))), "opt-in probe skip evidence rollup skip evidence status drift");
|
||||
assert.equal(row.evidence_rollup_status, "no_native_pass_evidence_accepted_while_host_blocked", "opt-in probe skip evidence rollup status drift");
|
||||
assert.equal(row.execution_enabled, "0", "opt-in probe skip evidence rollup must not enable execution");
|
||||
assert.equal(row.promotion_allowed, "0", "opt-in probe skip evidence rollup must not allow promotion");
|
||||
@@ -8075,7 +8162,6 @@ function verifyRuntimeBoundaryNativeEvidenceAcceptanceGateRows({
|
||||
assert.equal(row.target, skip.target, `${row.boundary_class}: native evidence gate target drift`);
|
||||
assert.equal(row.blocked, skip.blocked, `${row.boundary_class}: native evidence gate blocked drift`);
|
||||
assert.equal(row.runtime_probe, skip.runtime_probe, `${row.boundary_class}: native evidence gate runtime probe drift`);
|
||||
assert.equal(row.current_probe_status, "skipped_missing_host_runtime", `${row.boundary_class}: native evidence gate probe status drift`);
|
||||
assert.equal(row.current_probe_status, skip.current_probe_status, `${row.boundary_class}: native evidence gate skip status drift`);
|
||||
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: native evidence gate evidence status drift`);
|
||||
assert.equal(row.native_evidence_status, skip.native_evidence_status, `${row.boundary_class}: native evidence gate native evidence status drift`);
|
||||
@@ -8083,16 +8169,21 @@ function verifyRuntimeBoundaryNativeEvidenceAcceptanceGateRows({
|
||||
assert.equal(row.evidence_required_now, skip.evidence_required_now, `${row.boundary_class}: native evidence gate required-now drift`);
|
||||
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: native evidence gate must not observe evidence ready`);
|
||||
assert.equal(row.observed_evidence_ready, skip.observed_evidence_ready, `${row.boundary_class}: native evidence gate readiness drift`);
|
||||
assert.equal(row.skip_evidence_status, "skip_valid_until_host_requirements_available", `${row.boundary_class}: native evidence gate skip status drift`);
|
||||
assert.equal(row.skip_evidence_status, skip.skip_evidence_status, `${row.boundary_class}: native evidence gate skip status drift`);
|
||||
assert.equal(row.promotion_ready, "0", `${row.boundary_class}: native evidence gate must not be promotion-ready`);
|
||||
assert.equal(row.promotion_ready, readiness.promotion_ready, `${row.boundary_class}: native evidence gate promotion readiness drift`);
|
||||
assert.equal(row.node_gate_status, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate Node status drift`);
|
||||
assert.equal(row.node_gate_status, postNativeGate.node_gate_status, `${row.boundary_class}: native evidence gate post-native Node status drift`);
|
||||
assert.equal(row.browser_gate_status, "blocked_until_node_gate_complete", `${row.boundary_class}: native evidence gate browser status drift`);
|
||||
assert.equal(row.browser_gate_status, postNativeGate.browser_gate_status, `${row.boundary_class}: native evidence gate post-native browser status drift`);
|
||||
assert.equal(row.native_evidence_gate, "blocked_until_host_requirements_available", `${row.boundary_class}: native evidence gate status drift`);
|
||||
if (skip.skip_reason === "missing_host_requirements") {
|
||||
assert.equal(row.native_evidence_gate, "blocked_until_host_requirements_available", `${row.boundary_class}: native evidence gate status drift`);
|
||||
assert.ok(row.next_action.includes(skip.missing_host_requirements), `${row.boundary_class}: native evidence gate next action must name missing requirements`);
|
||||
} else {
|
||||
assert.equal(row.native_evidence_gate, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate status drift`);
|
||||
assert.equal(row.next_action, "run_opt_in_native_runtime_probe_and_collect_evidence", `${row.boundary_class}: native evidence gate next action drift`);
|
||||
}
|
||||
assert.equal(row.evidence_acceptance_allowed, "0", `${row.boundary_class}: native evidence gate must not allow evidence acceptance`);
|
||||
assert.ok(row.next_action.includes(skip.missing_host_requirements), `${row.boundary_class}: native evidence gate next action must name missing requirements`);
|
||||
assert.equal(row.execution_enabled, "0", `${row.boundary_class}: native evidence gate must not enable execution`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: native evidence gate must not allow promotion`);
|
||||
assert.ok(row.notes.includes("no_automatic_promotion"), `${row.boundary_class}: native evidence gate note drift`);
|
||||
@@ -8223,12 +8314,16 @@ function verifyBlockedRuntimeOptInGateConsistency({
|
||||
assert.equal(skipEvidence.missing_host_requirements, execution.missing_requirements, `${boundaryClass}: skip-evidence missing requirements drift`);
|
||||
assert.equal(blocker.missing_runtime_requirements, execution.missing_requirements, `${boundaryClass}: blocker missing requirements drift`);
|
||||
assert.equal(evidenceGate.evidence_acceptance_allowed, "0", `${boundaryClass}: evidence acceptance must remain blocked`);
|
||||
assert.equal(dispatch.dispatch_allowed, "0", `${boundaryClass}: dispatch must remain blocked on this host`);
|
||||
assert.equal(dispatch.dispatch_allowed, execution.runtime_ready, `${boundaryClass}: dispatch readiness drift`);
|
||||
assert.equal(readiness.promotion_lock_active, "1", `${boundaryClass}: readiness must keep promotion lock active`);
|
||||
assert.equal(postNative.promotion_lock_active, "1", `${boundaryClass}: post-native gate must keep promotion lock active`);
|
||||
assert.ok(blocker.blocker_keys.includes("promotion_lock_active"), `${boundaryClass}: blocker lacks promotion lock key`);
|
||||
assert.ok(blocker.blocker_keys.includes("manual_lock_update_required"), `${boundaryClass}: blocker lacks manual lock key`);
|
||||
assert.ok(blocker.blocker_keys.includes("host_runtime_requirements_missing"), `${boundaryClass}: blocker lacks host runtime key`);
|
||||
if (execution.runtime_ready === "0") {
|
||||
assert.ok(blocker.blocker_keys.includes("host_runtime_requirements_missing"), `${boundaryClass}: blocker lacks host runtime key`);
|
||||
} else {
|
||||
assert.equal(blocker.blocker_keys.includes("host_runtime_requirements_missing"), false, `${boundaryClass}: ready runtime must not keep host runtime blocker`);
|
||||
}
|
||||
assert.ok(evidenceGate.native_evidence_gate.includes("blocked"), `${boundaryClass}: evidence gate must be blocked`);
|
||||
}
|
||||
}
|
||||
@@ -8303,23 +8398,40 @@ function verifyBlockedRuntimeProbeExecutionConsistency({
|
||||
assert.equal(dispatch.missing_host_requirements, execution.missing_requirements, `${execution.boundary_class}: dispatch missing requirement drift`);
|
||||
assert.equal(skip.missing_host_requirements, execution.missing_requirements, `${execution.boundary_class}: skip evidence missing requirement drift`);
|
||||
|
||||
assert.equal(execution.runtime_ready, "0", `${execution.boundary_class}: current host must not be runtime-ready`);
|
||||
assert.equal(execution.source_proof_ready, "1", `${execution.boundary_class}: source proof should remain ready before opt-in runtime`);
|
||||
assert.equal(execution.current_probe_status, "skipped_missing_host_runtime", `${execution.boundary_class}: probe should remain skipped on this host`);
|
||||
assert.equal(execution.plan_status, "blocked_missing_host_runtime", `${execution.boundary_class}: execution plan must remain host-blocked`);
|
||||
assert.equal(preflight.preflight_status, "blocked_missing_host_runtime", `${execution.boundary_class}: preflight must remain host-blocked`);
|
||||
assert.equal(dispatch.host_readiness_status, "host_blocked_for_all_opt_in_native_probes", `${execution.boundary_class}: dispatch host readiness drift`);
|
||||
assert.equal(dispatch.host_action, "skip_missing_host_requirements", `${execution.boundary_class}: dispatch action must skip`);
|
||||
assert.equal(dispatch.dispatch_allowed, "0", `${execution.boundary_class}: dispatch must not be allowed`);
|
||||
assert.equal(skip.host_action, "skip_missing_host_requirements", `${execution.boundary_class}: skip evidence host action drift`);
|
||||
assert.equal(skip.skip_reason, "missing_host_requirements", `${execution.boundary_class}: skip reason drift`);
|
||||
assert.ok(["0", "1"].includes(execution.runtime_ready), `${execution.boundary_class}: invalid runtime readiness`);
|
||||
if (execution.runtime_ready === "1") {
|
||||
assert.equal(execution.current_probe_status, "ready_disabled_by_default", `${execution.boundary_class}: ready probe must remain opt-in`);
|
||||
assert.equal(execution.plan_status, "ready_to_run_opt_in_probe", `${execution.boundary_class}: execution plan must remain opt-in`);
|
||||
assert.equal(execution.missing_requirements, "-", `${execution.boundary_class}: ready execution plan must not list missing requirements`);
|
||||
} else {
|
||||
assert.equal(execution.current_probe_status, "skipped_missing_host_runtime", `${execution.boundary_class}: probe should remain skipped on missing host`);
|
||||
assert.equal(execution.plan_status, "blocked_missing_host_runtime", `${execution.boundary_class}: execution plan must remain host-blocked`);
|
||||
assert.notEqual(execution.missing_requirements, "-", `${execution.boundary_class}: blocked execution plan must list missing requirements`);
|
||||
}
|
||||
if (execution.runtime_ready === "1") {
|
||||
assert.equal(preflight.preflight_status, "ready_to_run_opt_in_native_probe", `${execution.boundary_class}: ready preflight status drift`);
|
||||
assert.equal(dispatch.host_action, "ready_for_manual_opt_in_native_probe", `${execution.boundary_class}: ready dispatch action drift`);
|
||||
assert.equal(dispatch.dispatch_allowed, "1", `${execution.boundary_class}: ready dispatch must be manually allowed`);
|
||||
assert.equal(skip.host_action, "ready_for_manual_opt_in_native_probe", `${execution.boundary_class}: ready skip evidence host action drift`);
|
||||
assert.equal(skip.skip_reason, "-", `${execution.boundary_class}: ready skip reason drift`);
|
||||
} else {
|
||||
assert.equal(preflight.preflight_status, "blocked_missing_host_runtime", `${execution.boundary_class}: preflight must remain host-blocked`);
|
||||
assert.equal(dispatch.host_action, "skip_missing_host_requirements", `${execution.boundary_class}: dispatch action must skip`);
|
||||
assert.equal(dispatch.dispatch_allowed, "0", `${execution.boundary_class}: dispatch must not be allowed`);
|
||||
assert.equal(skip.host_action, "skip_missing_host_requirements", `${execution.boundary_class}: skip evidence host action drift`);
|
||||
assert.equal(skip.skip_reason, "missing_host_requirements", `${execution.boundary_class}: skip reason drift`);
|
||||
}
|
||||
assert.equal(pass.evidence_required_now, "0", `${execution.boundary_class}: pass evidence must not be required yet`);
|
||||
assert.equal(skip.evidence_required_now, "0", `${execution.boundary_class}: skip evidence must not be required yet`);
|
||||
assert.equal(pass.observed_evidence_ready, "0", `${execution.boundary_class}: pass evidence must not be ready yet`);
|
||||
assert.equal(skip.observed_evidence_ready, "0", `${execution.boundary_class}: skip evidence must not be ready yet`);
|
||||
assert.equal(pass.evidence_status, "pending_until_native_pass", `${execution.boundary_class}: pass evidence status drift`);
|
||||
assert.equal(skip.native_evidence_status, "pending_until_native_pass", `${execution.boundary_class}: skip evidence native status drift`);
|
||||
assert.equal(skip.skip_evidence_status, "skip_valid_until_host_requirements_available", `${execution.boundary_class}: skip evidence contract status drift`);
|
||||
assert.ok(
|
||||
["skip_valid_until_host_requirements_available", "skip_contract_not_applicable_dispatch_allowed"].includes(skip.skip_evidence_status),
|
||||
`${execution.boundary_class}: skip evidence contract status drift`,
|
||||
);
|
||||
|
||||
for (const row of [execution, pass, preflight, dispatch, skip]) {
|
||||
assert.equal(row.execution_enabled, "0", `${execution.boundary_class}: probe execution row must not enable execution`);
|
||||
@@ -8362,12 +8474,26 @@ function verifyBlockedRuntimeRollupConsistency({
|
||||
assert.equal(dispatch.missing_host_requirements, skipEvidence.missing_host_requirements, "blocked runtime rollup dispatch/skip missing requirement drift");
|
||||
assert.equal(host.ready_opt_in_commands, dispatch.dispatch_allowed_commands, "blocked runtime rollup allowed command drift");
|
||||
assert.equal(host.blocked_opt_in_commands, dispatch.dispatch_blocked_commands, "blocked runtime rollup blocked command drift");
|
||||
assert.equal(host.host_readiness_status, "host_blocked_for_all_opt_in_native_probes", "blocked runtime rollup host status drift");
|
||||
assert.equal(dispatch.dispatch_status, "dispatch_blocked_for_all_opt_in_native_probes", "blocked runtime rollup dispatch status drift");
|
||||
assert.ok(
|
||||
[
|
||||
"host_ready_for_all_opt_in_native_probes",
|
||||
"host_partially_ready_for_opt_in_native_probes",
|
||||
"host_blocked_for_all_opt_in_native_probes",
|
||||
].includes(host.host_readiness_status),
|
||||
"blocked runtime rollup host status drift",
|
||||
);
|
||||
assert.ok(
|
||||
[
|
||||
"dispatch_allowed_for_all_opt_in_native_probes",
|
||||
"dispatch_partially_allowed_for_opt_in_native_probes",
|
||||
"dispatch_blocked_for_all_opt_in_native_probes",
|
||||
].includes(dispatch.dispatch_status),
|
||||
"blocked runtime rollup dispatch status drift",
|
||||
);
|
||||
assert.equal(skipEvidence.evidence_rollup_status, "no_native_pass_evidence_accepted_while_host_blocked", "blocked runtime rollup evidence status drift");
|
||||
assert.equal(skipEvidence.current_probe_statuses, "skipped_missing_host_runtime", "blocked runtime rollup probe status drift");
|
||||
assert.notEqual(skipEvidence.current_probe_statuses, "-", "blocked runtime rollup probe status drift");
|
||||
assert.equal(skipEvidence.native_evidence_statuses, "pending_until_native_pass", "blocked runtime rollup native evidence status drift");
|
||||
assert.equal(skipEvidence.skip_evidence_statuses, "skip_valid_until_host_requirements_available", "blocked runtime rollup skip evidence status drift");
|
||||
assert.notEqual(skipEvidence.skip_evidence_statuses, "-", "blocked runtime rollup skip evidence status drift");
|
||||
|
||||
for (const [label, row] of [
|
||||
["host readiness", host],
|
||||
@@ -8589,7 +8715,10 @@ function verifyBlockedRuntimePromotionGateConsistency({
|
||||
assert.equal(postNative.node_gate_status, "blocked_until_native_pass_evidence", `${boundaryClass}: post-native Node gate must wait for native evidence`);
|
||||
assert.equal(postNative.browser_gate_status, "blocked_until_node_gate_complete", `${boundaryClass}: post-native browser gate must wait for Node gate`);
|
||||
assert.equal(postNative.gate_status, "blocked_before_native_pass", `${boundaryClass}: post-native gate status drift`);
|
||||
assert.equal(evidenceGate.native_evidence_gate, "blocked_until_host_requirements_available", `${boundaryClass}: native evidence gate must remain host-blocked`);
|
||||
assert.ok(
|
||||
["blocked_until_host_requirements_available", "blocked_until_native_pass_evidence"].includes(evidenceGate.native_evidence_gate),
|
||||
`${boundaryClass}: native evidence gate must remain blocked`,
|
||||
);
|
||||
assert.equal(evidenceGate.evidence_acceptance_allowed, "0", `${boundaryClass}: native evidence acceptance must remain blocked`);
|
||||
|
||||
for (const row of [readiness, blocker, postNative, evidenceGate, passEvidence, lock]) {
|
||||
@@ -10816,7 +10945,12 @@ function userMProcessNativeRuntimeReadinessRows(runtimeStatePlanRows) {
|
||||
["host_command", "linuxcnc", "run_linuxcnc_owned_task_hal_environment"],
|
||||
];
|
||||
const commandAvailability = new Map(
|
||||
requirements.map(([, requirement]) => [requirement, commandPath(requirement)]),
|
||||
requirements.map(([, requirement]) => [
|
||||
requirement,
|
||||
["halrun", "halcmd", "linuxcnc"].includes(requirement)
|
||||
? linuxCncHostCommandPath(requirement)
|
||||
: commandPath(requirement),
|
||||
]),
|
||||
);
|
||||
const allAvailable = requirements.every(([, requirement]) =>
|
||||
commandAvailability.get(requirement),
|
||||
@@ -12231,6 +12365,15 @@ function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) {
|
||||
0,
|
||||
"current baseline has no direct skipped-main inventory promotion candidate",
|
||||
);
|
||||
for (const lockedKind of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"]) {
|
||||
const lockedRows = parsedInventoryReady.filter((row) => row.skip_kind === lockedKind);
|
||||
assert.ok(lockedRows.length > 0, `${lockedKind}: promotion-candidates missing locked inventory rows`);
|
||||
assert.equal(
|
||||
lockedRows.filter((row) => row.promotion_allowed !== "0").length,
|
||||
0,
|
||||
`${lockedKind}: locked inventory rows must not allow promotion`,
|
||||
);
|
||||
}
|
||||
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`);
|
||||
|
||||
Reference in New Issue
Block a user