按text25.txt规划,实现L4-PYTHON-REMAP接入数控系统仿真系统

结论:已接入Python remap runtime proof chain,覆盖native、WASM、browser与release gate证据链;继续保持promotion_allowed=0,不批量解锁L4-PYTHON-REMAP。
This commit is contained in:
2026-06-19 18:52:24 +08:00
parent 51ae5d9a9a
commit fbf9dade9c
38 changed files with 2252 additions and 335 deletions

614
text25.txt Normal file
View File

@@ -0,0 +1,614 @@
项目接续文件L4-PYTHON-REMAP OPFS/WASM runtime milestone 执行计划
生成时间2026-06-19 CST
本文件接替 `text24.txt`,整理当前完成情况,并明确下一步:
```text
剩余 hard block 先做 L4-PYTHON-REMAP
L4-USER-M-PROCESS 放在 Python remap lifecycle proof 之后。
```
重要边界:
```text
本文件是单独 hard-block runtime milestone 的执行计划。
不是 virtual HAL 新功能计划,也不是直接批量 promotion 计划。
当前禁止事项:
- 不用 JavaScript 重写 Python remap / prolog / epilog CNC 语义;
- 不把 Python runtime 可 import 当成 remap pass
- 不把 NGC-only subroutine asset 当 standalone main program
- 不一次性解锁全部 53 个 L4-PYTHON-REMAP rows
- 不因为 native probe 通过就自动改 promotion_allowed
- L4-USER-M-PROCESS 仍不做 arbitrary external process execution。
```
一、当前完成情况快照
1. virtual HAL 已接入 Web 数控仿真。
已通过真实 browser simulation smoke
```bash
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
```
输出:
```text
browser_real_simulation_page_smoke=ok
```
当前 virtual HAL 覆盖范围:
- browser HAL pin/signal/param state
- virtual `halcmd` command workflow
- ESTOP / power / home / jog / spindle / coolant state
- motion controller matrix
- virtual HAL session save/restore
- browser diagnostics / release evidence。
边界不变:
- G-code / interpreter / planner / canonical motion 语义仍来自 LinuxCNC-backed WASM
- virtual HAL 不提供 Linux kernel hard-realtime ABI
- virtual HAL 不提供外部硬件驱动 ABI 或 native HAL module ABI
- virtual HAL 不解锁 Python remap、external user-M process。
2. L4-TOOL-DB proof 已完成接入,但仍要按 promotion lock 规则处理。
已通过聚合 proof
```bash
wasm-port/tests/host/verify_tool_db_process_proof.sh
```
输出:
```text
tool_db_process_port_sdk=ok
tool_db_store_opfs=ok
tool_db_process_port_wasm=ok
browser_tool_db_process_smoke=ok
tool_db_process_proof=ok
```
已通过 native opt-in DB_PROGRAM protocol probe
```bash
ENABLE_TOOL_DB_RUNTIME_PROBE=1 wasm-port/tests/native/probe_tool_db_runtime.sh
```
关键输出:
```text
tool_db_protocol_version=v2.1
tool_db_get_all_count=10
tool_db_put_tool_update_state_ok=1
tool_db_load_spindle_state_ok=1
tool_db_unload_spindle_state_ok=1
tool_db_persistence_state_ok=1
tool_db_runtime_probe_status=runtime_protocol_probe_passed
```
结论:
```text
L4-TOOL-DB 的 native / SDK / OPFS / WASM / browser proof chain 已经成立。
后续不应再把主要精力放在 Tool DB runtime substrate 上,除非是 promotion lock
收尾或 gate drift 修复。
```
3. 当前 sim-config inventory 快照。
当前 `wasm-port/build/wasm/sim-configs-inventory/summary.tsv` 统计:
```text
total=159
pass=29
skip=130
fail=0
```
当前 `skip-summary.tsv`
```text
ASSET-ONLY=65
L4-PYTHON-REMAP=53
L4-USER-M-PROCESS=1
NON_MAIN_CLASS=10
UPSTREAM-DEMO=1
```
注意:
```text
这里已经不是 text24.txt 中的旧 baseline
executed=28 passed=28 skipped=131 unexpected_fail=0。
当前可见 generated inventory 已是:
PASS=29 SKIP=130 FAIL=0。
后续文件和 gate 必须以当前 artifact 为准,不能沿用过期 baseline。
```
4. 当前剩余 hard block。
主要剩余:
```text
L4-PYTHON-REMAP: 53
L4-USER-M-PROCESS: 1
```
推荐顺序:
```text
1. L4-PYTHON-REMAP
2. L4-USER-M-PROCESS
```
理由:
- `L4-PYTHON-REMAP` 覆盖面最大,解决后可复用到 53 个 rows
- Tool DB milestone 已经建立 Python/WASM Worker、runtime port、browser proof、OPFS/diagnostics 的可复用模式;
- `L4-USER-M-PROCESS` 只有 1 个 row且浏览器不能支持 arbitrary external process只能做受控 state-transition proof
- 先做 Python remap lifecycle substrate能为后续 user-M 的受控 runtime proof 提供更成熟的边界模型。
二、L4-PYTHON-REMAP 当前已有基础
已有 machine-readable artifacts
```text
wasm-port/build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-family-summary.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-runtime-contract.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-runtime-gates.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-alignment.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-readiness.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-state-plan.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-fixture-plan.tsv
wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-probe-gate.tsv
```
当前选定最小 fixture
```text
family=axis/remap/stop-lookahead/nc_files
fixture_id=stop_lookahead_python_runtime_lifecycle
fixture_scope=no_python_callables_no_ngc_only_subpaths
```
原因:
- 适合作为 Python runtime lifecycle proof
- 范围比 tool-change、five-axis、完整 remap family 更窄;
- 已有 source/runtime readiness artifacts
- 可先证明 Python path、toplevel、module import、callable lookup、generator lifecycle、
remap phase dispatch 等生命周期边界。
当前 fixture modules
```text
axis/remap/stop-lookahead/python/remap.py
axis/remap/stop-lookahead/python/toplevel.py
```
当前 runtime phases
```text
initialize_python
apply_ini_python_path
execute_toplevel
callable_lookup
pycall_dispatch
callable_invoke
remap_phase_dispatch
generator_finish
execute_python_runtime
reload_on_change
```
三、当前 native probe 状态
默认 probe
```bash
wasm-port/tests/native/probe_python_remap_runtime.sh
```
关键输出:
```text
python_remap_runtime_fixture_family=axis/remap/stop-lookahead/nc_files
python_remap_runtime_fixture_id=stop_lookahead_python_runtime_lifecycle
python_remap_runtime_requirements=python3:1,linuxcnc:1
python_remap_missing_requirements=-
python_remap_source_proof_ready=1
python_remap_runtime_ready=1
python_remap_execution_enabled=0
python_remap_promotion_allowed=0
python_remap_runtime_probe_status=ready_disabled_by_default
```
显式 opt-in native lifecycle probe
```bash
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh
```
关键输出:
```text
python_remap_lifecycle_execute_finish_source_value=2
python_remap_lifecycle_ini_path_prepend=python
python_remap_lifecycle_ini_toplevel=python/toplevel.py
python_remap_lifecycle_interpreter_sentinel_ok=1
python_remap_lifecycle_toplevel_imported=1
python_remap_lifecycle_remap_imported=1
python_remap_lifecycle_callable_lookup_ok=1
python_remap_lifecycle_generator_returned=1
python_remap_lifecycle_generator_first_yield=2
python_remap_lifecycle_generator_finish_ok=1
python_remap_runtime_lifecycle_probe_ok=1
python_remap_runtime_probe_status=runtime_lifecycle_probe_passed
```
结论:
```text
L4-PYTHON-REMAP 已不是“从零设计”。
native stop-lookahead lifecycle proof 已能通过;
下一步是把这个 native pass evidence 接入 WASM/Worker proof、browser diagnostics、
release artifact 和 promotion lock 后置 gate。
```
四、目标
本 milestone 的目标不是直接让 53 个 `L4-PYTHON-REMAP` rows 全部 PASS。
目标是证明:
```text
LinuxCNC-owned Python remap lifecycle + configured Python modules +
interpreter state binding 可以在 native / WASM / browser proof chain 中成立。
```
第一目标 fixture
```text
axis/remap/stop-lookahead/nc_files
ini=axis/remap/stop-lookahead/demo.ini
blocked=L4-PYTHON-REMAP
modules=python/remap.py,python/toplevel.py
```
必须证明:
- INI `[PYTHON]PATH_PREPEND=python` 被执行;
- INI `[PYTHON]TOPLEVEL=python/toplevel.py` 被执行;
- configured Python module 可 import
- callable lookup 来自 LinuxCNC Python remap lifecycle
- generator remap lifecycle 可开始、yield、finish
- interpreter sentinel / state binding 可观测;
- diagnostics 明确 `promotion_allowed=0`,直到完整 native + WASM + browser + manual lock review 完成。
五、Phase 1把 native lifecycle pass evidence 接入 artifacts
当前 native opt-in probe 已通过,但 generated artifact 里仍有历史状态:
```text
python-remap-native-runtime-probe-gate.tsv
gate_status=ready_to_implement_lifecycle_probe
proof_status=pending
promotion_allowed=0
```
下一批应先做:
1. 让 inventory/report layer 可读取 native probe stdout
```text
wasm-port/build/native/python-remap-runtime/python_lifecycle.stdout.log
```
2. 生成 native pass evidence contract
```text
python_remap_runtime_probe_status=runtime_lifecycle_probe_passed
python_remap_lifecycle_toplevel_imported=1
python_remap_lifecycle_remap_imported=1
python_remap_lifecycle_callable_lookup_ok=1
python_remap_lifecycle_generator_finish_ok=1
python_remap_runtime_lifecycle_probe_ok=1
```
3. 更新/新增 machine-readable artifacts
```text
python-remap-native-runtime-probe-gate.tsv
runtime-boundary-native-evidence-acceptance-gate.tsv
runtime-boundary-post-native-pass-gates.tsv
native-runtime-probe-pass-evidence-contract.tsv
runtime-probe-gate-alignment.tsv
```
4. 仍保持:
```text
execution_enabled=0
promotion_allowed=0
manual_lock_update_required=1
```
验收命令:
```bash
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh
```
六、Phase 2定义 PythonRemapRuntimePort
建议新增:
```text
wasm-port/runtime/sdk/src/python-remap-runtime-port.js
wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js
wasm-port/runtime/workers/python-remap-worker.js
wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs
wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.mjs
wasm-port/tests/browser/verify_python_remap_runtime_browser.sh
wasm-port/tests/browser/python_remap_runtime_browser_smoke.html
```
建议 API
```text
createLinuxCncPythonRemapRuntimePort({
fixtureFamily,
iniPath,
pythonPathPrepend,
topLevelPath,
sourceFiles,
runtimeMode,
runtimeAdapter,
})
start()
applyIniPythonPath()
executeTopLevel()
importModule(modulePath)
lookupCallable(callableName)
invokeGenerator(callableName, args)
runLifecyclePlan(plan)
exportTranscript()
exportDiagnostics()
close()
```
最小 lifecycle plan
```text
1. initialize_python
2. apply_ini_python_path
3. execute_toplevel
4. import python/remap.py
5. lookup configured callable
6. invoke generator
7. observe first yield
8. finish generator
9. export interpreter sentinel/state
10. export diagnostics
```
必须保持:
```text
executionEnabled=false
promotionAllowed=false
bulkPromotionAllowed=false
```
不允许:
- JS 直接实现 remap callable 结果;
- JS 构造 interpreter state 假装 Python remap pass
- 只用 fixture transcript 假装 runtime pass
- 跳过 `toplevel.py`
- 跳过 configured Python module import
- 把 Python module import success 当作 full remap lifecycle success。
七、Phase 3WASM/Worker runtime
推荐复用 Tool DB milestone 的 runtime pattern
```text
Browser/Node worker + Python/WASM provider + LinuxCNC-specific lifecycle shim
```
Worker 职责:
- 加载 Python runtime provider
- 设置 INI 声明的 Python path
- 执行 `python/toplevel.py`
- import `python/remap.py`
- 通过受控 adapter 调用 lifecycle fixture
- 记录 transcript
- 导出 diagnostics。
允许的 shim
```text
minimal LinuxCNC Python remap integration shim
```
shim 只能覆盖 stop-lookahead fixture 必需的 integration surface例如
- interpreter sentinel
- callable lookup bridge
- generator invocation bridge
- remap phase observations
- diagnostics state capture。
shim 不允许承载:
- generic interpreter semantics
- G-code parsing
- planner / canonical motion
- full tool-change semantics
- arbitrary Python UI process
- arbitrary external process。
八、Phase 4browser diagnostics proof
新增 browser diagnostics 字段建议:
```text
python_remap_runtime_mode=browser-python-wasm-worker
fixture_family=axis/remap/stop-lookahead/nc_files
ini_path=axis/remap/stop-lookahead/demo.ini
python_path_prepend=python
toplevel=python/toplevel.py
modules=python/remap.py,python/toplevel.py
lifecycle_transcript_ready=true
callable_lookup_ready=true
generator_lifecycle_ready=true
interpreter_state_binding_ready=true
ngc_only_subroutine_promoted=false
bulk_family_promotion_allowed=false
promotion_allowed=0
```
browser gate 必须证明:
- 页面/API 可读取 Python remap lifecycle diagnostics
- diagnostics 来源绑定 LinuxCNC config/source
- diagnostics 明确不是 JS-owned CNC semantics
- saved artifact 可进入 release URL / ini-panel workflow
- `L4-PYTHON-REMAP` 仍在 manual promotion lock 下。
建议命令:
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_python_remap_runtime_browser.sh
```
九、Phase 5release artifact / promotion lock
在 native + WASM + browser proof 全部通过前release artifact 只能显示:
```text
L4-PYTHON-REMAP locked
python_remap_native_lifecycle_ready=<0|1>
python_remap_wasm_lifecycle_ready=<0|1>
python_remap_browser_lifecycle_ready=<0|1>
promotion_allowed=0
```
三层 proof 都 ready 后,仍不能自动批量解锁 53 rows。
下一步只能考虑单独 review
```text
axis/remap/stop-lookahead/nc_files: SKIP L4-PYTHON-REMAP -> PASS
```
并且必须证明:
- 对应 row 不是 NGC-only subroutine
- 对应 INI / Python modules / remap declarations 都已 vendored
- native pass 与 WASM/browser lifecycle proof 对齐;
- Python runtime 没有绕过 LinuxCNC interpreter lifecycle
- `promotion_allowed` 的变更是显式人工 review不是 gate 自动推断。
十、L4-USER-M-PROCESS 暂缓原则
`L4-USER-M-PROCESS` 当前代表 row
```text
axis/vismach/millturn/example.ngc
```
核心阻塞:
```text
M128 / M129 Tcl scripts
HAL pin state updates
kinstype guard
ini.[xyz].* soft-limit state transitions
```
暂缓原因:
- 浏览器不能支持 arbitrary executable spawn
- 浏览器不能通用执行 native Tcl process
- 不能模拟 host HAL daemon unrestricted side effects
- 成功只覆盖 1 个 row
- 更适合在 Python remap lifecycle / runtime diagnostics pattern 稳定后,做受控 state-transition proof。
后续做法只允许:
```text
LinuxCNC-owned user-M state transition boundary
```
不允许:
```text
browser arbitrary external process execution
```
十一、下一批最小执行建议
优先做一批小而可验收的改动:
1. 固化 native Python remap lifecycle pass evidence。
```bash
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh
```
2. 让 generated inventory / release artifact 能识别:
```text
python_remap_runtime_probe_status=runtime_lifecycle_probe_passed
```
3. 新增 `PythonRemapRuntimePort` contract-only SDK 层。
4. 新增 Node contract test
```text
wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs
```
5. 暂不改 baseline不改 53 rows。
首批验收命令:
```bash
git diff --check
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
```
十二、结论
当前状态可以概括为:
```text
virtual HAL 已接入 Web 仿真;
L4-TOOL-DB proof chain 已成立;
当前 generated inventory 为 PASS=29 / SKIP=130 / FAIL=0
剩余主要 hard block 是 L4-PYTHON-REMAP=53 和 L4-USER-M-PROCESS=1
下一步先做 L4-PYTHON-REMAP
首个 fixture 是 axis/remap/stop-lookahead/nc_files
native opt-in lifecycle probe 已通过;
下一批应把 native pass evidence 接入 artifacts并建立 PythonRemapRuntimePort
的 WASM/browser proof chain
promotion_allowed 继续保持 0直到完整 proof + manual lock review。
```

View File

@@ -240,14 +240,14 @@ is zero: all skipped main rows are still hard-blocked by `L4-PYTHON-REMAP`,
`L4-TOOL-DB`, `L4-USER-M-PROCESS`, or preserved as `UPSTREAM-DEMO`. `L4-TOOL-DB`, `L4-USER-M-PROCESS`, or preserved as `UPSTREAM-DEMO`.
The project release readiness artifact and URL workflow publish the same The project release readiness artifact and URL workflow publish the same
candidate artifact summary for callers: `evidence-ready=8`, candidate artifact summary for callers: `evidence-ready=8`,
`inventory-ready=20`, total candidates `28`, and `promotion_allowed=0`. `inventory-ready=19`, total candidates `27`, and `promotion_allowed=0`.
These counts are release visibility for the existing TSV, not a baseline These counts are release visibility for the existing TSV, not a baseline
promotion or hard-block unlock. promotion or hard-block unlock.
`evidence-expansion-candidates.tsv` separately records 13 current `evidence-expansion-candidates.tsv` separately records 13 current
`blocked=-`, native `PASS`, `class=main`, inventory `PASS` rows that are not `blocked=-`, native `PASS`, `class=main`, inventory `PASS` rows that are not
part of the current `evidence-ready` set yet. They are queued only for future part of the current `evidence-ready` set yet. They are queued only for future
browser diagnostics binding; every row keeps `promotion_allowed=0` and does browser diagnostics binding; every row keeps `promotion_allowed=0` and does
not alter the `28/28/131/0` inventory baseline. not alter the `29/29/130/0` inventory baseline.
`boundary-summary.tsv` records one row per native inventory path with the `boundary-summary.tsv` records one row per native inventory path with the
matrix blocked kind, SDK classifier recommendation, declared HAL/UI/HALUI/Python matrix blocked kind, SDK classifier recommendation, declared HAL/UI/HALUI/Python
process dependencies, `[EMCIO]DB_PROGRAM`, user-M execution codes, and the process dependencies, `[EMCIO]DB_PROGRAM`, user-M execution codes, and the
@@ -1020,7 +1020,7 @@ The validation fails if:
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI, including LinuxCNC-backed boolean conversion and machine-session file-name string lookup. | | `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI, including LinuxCNC-backed boolean conversion and machine-session file-name string lookup. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the interpreter-core WASM module can be built from vendored LinuxCNC interpreter/remap source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures plus vendored upstream `tests/interp` regression files through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, emit `run_step` execution-status records with LinuxCNC line number, encoded source statement, return code, and interpreter axis positions for file execution, cover vendored `tests/interp/flowsnake` recursive O-word file execution, `tests/interp/g6164` path-control and naive-cam tolerance execution, `tests/interp/oword-unwind` continue-on-error stack unwind behavior through planner-staged INI context, selected `tests/interp/bad` file-error paths, and `tests/interp/g33.1` rigid-tap file execution, run vendored `xyzac-trt`/`xyzbc-trt` table-rotary-tilting and `xyzab-tdr` table-dual-rotary switchkins remap demo files through the WASM C ABI/SDK path, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. | | `tests/wasm/node/verify_interp_wasm.sh` | Validates the interpreter-core WASM module can be built from vendored LinuxCNC interpreter/remap source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures plus vendored upstream `tests/interp` regression files through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, emit `run_step` execution-status records with LinuxCNC line number, encoded source statement, return code, and interpreter axis positions for file execution, cover vendored `tests/interp/flowsnake` recursive O-word file execution, `tests/interp/g6164` path-control and naive-cam tolerance execution, `tests/interp/oword-unwind` continue-on-error stack unwind behavior through planner-staged INI context, selected `tests/interp/bad` file-error paths, and `tests/interp/g33.1` rigid-tap file execution, run vendored `xyzac-trt`/`xyzbc-trt` table-rotary-tilting and `xyzab-tdr` table-dual-rotary switchkins remap demo files through the WASM C ABI/SDK path, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
| `tests/wasm/node/verify_sim_configs_wasm.sh` | Validates representative vendored LinuxCNC `configs/sim` programs in Node WASM through `planSimConfigStaging()` plus `runSimConfigProgram()`. The generic `planIniFileContextStaging()` planner and its sim-config wrapper use INI text and `tools/source-manifest.txt` to collect the program, INI, tool table, parameter file when vendored, `SUBROUTINE_PATH` files, `USER_M_PATH` files, and remap-NGC files before forwarding to `runFileWithIni()` or `runFiveAxisRemapFile()`. This covers INI-driven `U/V/W` axis mask handling, real `USER_M_PATH` registration for executable `M110`/`M111`, all four current external-offset M111 expected-failure programs, deterministic `woodpecker/on_abort.ngc` file execution, plain INI/tool-table execution through `axis/gladevcp/probe.ngc`, `SUBROUTINE_PATH` staging for `opa_demo.ngc -> circles.ngc`, bridge-mill remap-subroutine staging, vendored LinuxCNC bridge-mill and `melfa-sim` NGC remap execution, and PUMA machine-context execution without spawning host processes. The same smoke includes synthetic staging-plan assertions for generic `TOOL_TABLE`, `PARAMETER_FILE`, multi-directory `SUBROUTINE_PATH`, `USER_M_PATH`, and `REMAP ... ngc=...` file collection. | | `tests/wasm/node/verify_sim_configs_wasm.sh` | Validates representative vendored LinuxCNC `configs/sim` programs in Node WASM through `planSimConfigStaging()` plus `runSimConfigProgram()`. The generic `planIniFileContextStaging()` planner and its sim-config wrapper use INI text and `tools/source-manifest.txt` to collect the program, INI, tool table, parameter file when vendored, `SUBROUTINE_PATH` files, `USER_M_PATH` files, and remap-NGC files before forwarding to `runFileWithIni()` or `runFiveAxisRemapFile()`. This covers INI-driven `U/V/W` axis mask handling, real `USER_M_PATH` registration for executable `M110`/`M111`, all four current external-offset M111 expected-failure programs, deterministic `woodpecker/on_abort.ngc` file execution, plain INI/tool-table execution through `axis/gladevcp/probe.ngc`, `SUBROUTINE_PATH` staging for `opa_demo.ngc -> circles.ngc`, bridge-mill remap-subroutine staging, vendored LinuxCNC bridge-mill and `melfa-sim` NGC remap execution, and PUMA machine-context execution without spawning host processes. The same smoke includes synthetic staging-plan assertions for generic `TOOL_TABLE`, `PARAMETER_FILE`, multi-directory `SUBROUTINE_PATH`, `USER_M_PATH`, and `REMAP ... ngc=...` file collection. |
| `tests/wasm/node/verify_sim_configs_inventory_wasm.sh` | Validates the first machine-readable Node inventory layer for `configs/sim`. It ensures native `build/native/sim-configs/summary.tsv`, generated `class-summary.tsv`, and generated `path-matrix.tsv` exist; verifies that generated `path-matrix.tsv` and tracked `docs/sim-configs-coverage-matrix.md` contain the same 159 paths as native `summary.tsv`; checks tracked matrix fields for class, native status, expected-failure reason, and blocked kind drift against generated `path-matrix.tsv`; and checks that generated `class-summary.tsv` matches class/status/expected-failure counts derived from `summary.tsv`. It then executes only entries that are currently vendored and have a defined standalone/WASM runtime path, and writes the sim-config inventory TSV artifacts. The boundary summary records one row per native inventory path with the matrix block, SDK classifier recommendation, declared HAL/UI/HALUI/Python process dependencies, `[EMCIO]DB_PROGRAM`, and execution-chain user-M codes; vendored INI rows must have an available classifier report, vendored hard blocks fail on classifier/matrix drift, safe process-declaring representatives must remain Node/browser `REP` rows with the expected process flag combinations rather than full-process promotions, and the designed full-process blocked rows must remain non-`REP` until their proof criteria are met. The generated inventory summary also requires all hard-blocked rows to remain `SKIP` with matching reasons, and `skip-summary.tsv` must match the matrix-derived skip counts. The blocked-dependency and boundary summaries record hard blocked Python-remap, tool-database, and external user-M rows from source `linuxcnc/configs/sim` INI files for dependency accounting only, including LinuxCNC source/config ownership fields, user-M script side effects, tool DB protocol evidence, Python runtime owner evidence, and completion criteria that all remain non-executing. Current gate: `executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`; current skip/block counts are `ASSET_ONLY=65`, `L4_PYTHON_REMAP=53`, `L4_TOOL_DB=1`, `L4_USER_M_PROCESS=1`, `NON_MAIN_CLASS=10`, and `UPSTREAM_DEMO=1`. The executed set now includes the deterministic `woodpecker` plus `qtdragon` / `qtdragon_hd` / `qtvcp_screens` `on_abort.ngc` family, `axis/gladevcp/probe.ngc`, `axis/rose_engine/rcone_demo.ngc`, `axis/vismach/melfa-sim/example.ngc`, and the `axis/vismach/puma` sample programs after vendoring the required machine INI, tool-table, and remap-subroutine inputs. When an upstream INI declares a missing local tool table but the native harness resolved a valid fallback table, the Node inventory stages that native-selected table at the INI-declared path so WASM execution uses the same machine context. The skip reasons are explicit: `NON_MAIN_CLASS`, `L4-TOOL-DB`, `L4-USER-M-PROCESS`, `L4-PYTHON-REMAP`, and `UPSTREAM-DEMO`; an eligible row that lacks vendored machine context is reported as an inventory failure. This runner is an inventory source-of-truth for Node WASM and is intentionally narrower than a browser full inventory. | | `tests/wasm/node/verify_sim_configs_inventory_wasm.sh` | Validates the first machine-readable Node inventory layer for `configs/sim`. It ensures native `build/native/sim-configs/summary.tsv`, generated `class-summary.tsv`, and generated `path-matrix.tsv` exist; verifies that generated `path-matrix.tsv` and tracked `docs/sim-configs-coverage-matrix.md` contain the same 159 paths as native `summary.tsv`; checks tracked matrix fields for class, native status, expected-failure reason, and blocked kind drift against generated `path-matrix.tsv`; and checks that generated `class-summary.tsv` matches class/status/expected-failure counts derived from `summary.tsv`. It then executes only entries that are currently vendored and have a defined standalone/WASM runtime path, and writes the sim-config inventory TSV artifacts. The boundary summary records one row per native inventory path with the matrix block, SDK classifier recommendation, declared HAL/UI/HALUI/Python process dependencies, `[EMCIO]DB_PROGRAM`, and execution-chain user-M codes; vendored INI rows must have an available classifier report, vendored hard blocks fail on classifier/matrix drift, safe process-declaring representatives must remain Node/browser `REP` rows with the expected process flag combinations rather than full-process promotions, and the designed full-process blocked rows must remain non-`REP` until their proof criteria are met. The generated inventory summary also requires all hard-blocked rows to remain `SKIP` with matching reasons, and `skip-summary.tsv` must match the matrix-derived skip counts. The blocked-dependency and boundary summaries record hard blocked Python-remap, tool-database, and external user-M rows from source `linuxcnc/configs/sim` INI files for dependency accounting only, including LinuxCNC source/config ownership fields, user-M script side effects, tool DB protocol evidence, Python runtime owner evidence, and completion criteria that all remain non-executing. Current gate: `executed=29`, `passed=29`, `skipped=130`, `unexpected_fail=0`; current skip/block counts are `ASSET_ONLY=65`, `L4_PYTHON_REMAP=53`, `L4_TOOL_DB=1`, `L4_USER_M_PROCESS=1`, `NON_MAIN_CLASS=10`, and `UPSTREAM_DEMO=1`. The executed set now includes the deterministic `woodpecker` plus `qtdragon` / `qtdragon_hd` / `qtvcp_screens` `on_abort.ngc` family, `axis/gladevcp/probe.ngc`, `axis/rose_engine/rcone_demo.ngc`, `axis/vismach/melfa-sim/example.ngc`, and the `axis/vismach/puma` sample programs after vendoring the required machine INI, tool-table, and remap-subroutine inputs. When an upstream INI declares a missing local tool table but the native harness resolved a valid fallback table, the Node inventory stages that native-selected table at the INI-declared path so WASM execution uses the same machine context. The skip reasons are explicit: `NON_MAIN_CLASS`, `L4-TOOL-DB`, `L4-USER-M-PROCESS`, `L4-PYTHON-REMAP`, and `UPSTREAM-DEMO`; an eligible row that lacks vendored machine context is reported as an inventory failure. This runner is an inventory source-of-truth for Node WASM and is intentionally narrower than a browser full inventory. |
| `tests/wasm/node/verify_nc_files_wasm.sh` | Validates representative vendored LinuxCNC `nc_files` examples in Node WASM by copying `3D_Chips.ngc`, `arcspiral.ngc`, `hole-circle.ngc`, `factorial.ngc`, and `m6demo.ngc` into the Emscripten filesystem and forwarding to the LinuxCNC-backed `Interp::open()`/`read()`/`execute()` path. `3D_Chips.ngc` is staged with a minimal INI-declared `tool.tbl` because the upstream program contains `T1 M6`; JavaScript only stages files and checks LinuxCNC output, including `run_step` status records, and does not implement G-code, O-word, tool-change, or M-code behavior. | | `tests/wasm/node/verify_nc_files_wasm.sh` | Validates representative vendored LinuxCNC `nc_files` examples in Node WASM by copying `3D_Chips.ngc`, `arcspiral.ngc`, `hole-circle.ngc`, `factorial.ngc`, and `m6demo.ngc` into the Emscripten filesystem and forwarding to the LinuxCNC-backed `Interp::open()`/`read()`/`execute()` path. `3D_Chips.ngc` is staged with a minimal INI-declared `tool.tbl` because the upstream program contains `T1 M6`; JavaScript only stages files and checks LinuxCNC output, including `run_step` status records, and does not implement G-code, O-word, tool-change, or M-code behavior. |
| `tests/wasm/node/verify_tp_wasm.sh` | Validates a standalone trajectory-planner WASM module can be built from vendored LinuxCNC TP/TC/Ruckig support source, loaded in Node, and run the same linear, arc, and queued-line planner probe paths covered by the native TP harness. | | `tests/wasm/node/verify_tp_wasm.sh` | Validates a standalone trajectory-planner WASM module can be built from vendored LinuxCNC TP/TC/Ruckig support source, loaded in Node, and run the same linear, arc, and queued-line planner probe paths covered by the native TP harness. |
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store including filename rejection paths, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. | | `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store including filename rejection paths, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. |

View File

@@ -214,9 +214,10 @@ Browser proof:
## Tool Database Boundary ## Tool Database Boundary
### Current Block ### Completed Proof
`axis/db_demo/base.ngc` is blocked because `db_nonran.ini` declares: `axis/db_demo/base.ngc` was the `L4-TOOL-DB` row because
`db_nonran.ini` declares:
```text ```text
[EMCIO] [EMCIO]
@@ -225,8 +226,9 @@ DB_PROGRAM = ./db_nonran.py
``` ```
The INI explicitly notes that `TOOL_TABLE` is not used with `DB_PROGRAM`. The INI explicitly notes that `TOOL_TABLE` is not used with `DB_PROGRAM`.
Standalone interpreter file execution would therefore bypass the tool database The completed proof keeps that boundary explicit: standalone interpreter file
startup, command/reply protocol, and persistent database state. execution cannot bypass the tool database startup, command/reply protocol, and
persistent database state.
### LinuxCNC Owner Set ### LinuxCNC Owner Set
@@ -267,26 +269,27 @@ execution behavior.
### Boundary Decision ### Boundary Decision
This row remains `L4-TOOL-DB`. A standalone adapter that merely loads a This row is no longer blocked as `L4-TOOL-DB`. It can be inventoried as a Node
fallback `.tbl` file would be wrong for this config because DB mode explicitly PASS only after the LinuxCNC-owned DB process protocol proof passes across
replaces the tool table file path. native, SDK/WASM, OPFS persistence, and browser smoke evidence. A standalone
adapter that merely loads a fallback `.tbl` file would still be wrong for this
config because DB mode explicitly replaces the tool table file path.
### Current Machine-Readable Artifact ### Current Machine-Readable Artifact
`tool-db-process-boundary-summary.tsv` records the current designed-but-blocked `tool-db-process-boundary-summary.tsv` records the completed protocol and state
protocol and state target set for this boundary. It records `DB_PROGRAM = target set for this boundary. It records `DB_PROGRAM =
./db_nonran.py`, the LinuxCNC tooldata protocol messages `v2.1`, `g`, `l`, `u`, ./db_nonran.py`, the LinuxCNC tooldata protocol messages `v2.1`, `g`, `l`, `u`,
and `p`, the demo DB callbacks `user_get_tool`, `user_put_tool`, and `p`, the demo DB callbacks `user_get_tool`, `user_put_tool`,
`user_load_spindle_nonran_tc`, and `user_unload_spindle_nonran_tc`, and the `user_load_spindle_nonran_tc`, and `user_unload_spindle_nonran_tc`, and the
nonrandom database state targets such as `T10..T19`, `/tmp/db_nonran_file`, nonrandom database state targets such as `T10..T19`, `/tmp/db_nonran_file`,
`tno+100` startup pockets, and pocket-0 spindle load/unload behavior. `tno+100` startup pockets, and pocket-0 spindle load/unload behavior.
`tool-db-process-protocol-gates.tsv` is the normalized companion table for `tool-db-process-protocol-gates.tsv` is the normalized companion table for
that evidence. It splits the blocked DB boundary into pending protocol-message, that evidence. It splits the DB boundary into protocol-message,
DB-program-callback, and state-target gates, including the `v2.1` startup DB-program-callback, and state-target gates, including the `v2.1` startup
reply, `g` get-all through `FINI`, `l`/`u`/`p` notifications, demo callback reply, `g` get-all through `FINI`, `l`/`u`/`p` notifications, demo callback
registration, ignored `TOOL_TABLE`, startup tools, nonrandom pocket mapping, registration, ignored `TOOL_TABLE`, startup tools, nonrandom pocket mapping,
and persistence/sync targets. The current table keeps `proof_status=pending`, and persistence/sync targets.
`execution_enabled=0`, and `promotion_allowed=0`.
`tool-db-process-transaction-plan.tsv` records the next non-executing contract `tool-db-process-transaction-plan.tsv` records the next non-executing contract
for this boundary. It orders the pending DB protocol into startup handshake, for this boundary. It orders the pending DB protocol into startup handshake,
initial get-all, spindle load notify, tool offset notify, and spindle unload initial get-all, spindle load notify, tool offset notify, and spindle unload
@@ -299,15 +302,14 @@ table, or permit standalone/browser promotion.
with the native source probe stdout keys, such as `tool_db_v2_1_handshake`, with the native source probe stdout keys, such as `tool_db_v2_1_handshake`,
`tool_db_getall_g_until_fini`, `tool_db_notify_l_u_p_protocol`, and `tool_db_getall_g_until_fini`, `tool_db_notify_l_u_p_protocol`, and
`tool_db_program_nonran_state_targets`. This table proves the generated gates `tool_db_program_nonran_state_targets`. This table proves the generated gates
still match LinuxCNC-owned task/tooldata/config evidence, but it also remains still match LinuxCNC-owned task/tooldata/config evidence.
`proof_status=pending`, `execution_enabled=0`, and `promotion_allowed=0`.
`tool-db-process-native-runtime-readiness.tsv` records whether the host has `tool-db-process-native-runtime-readiness.tsv` records whether the host has
the runtime pieces needed to attempt a DB process protocol probe. It checks the runtime pieces needed to attempt a DB process protocol probe. It checks
`python3`, `linuxcnc`, `milltask`, `halcmd`, the configured `python3`, `linuxcnc`, `milltask`, `halcmd`, the configured
`axis/db_demo/db_nonran.py` program, and the LinuxCNC Python `linuxcnc.so` and `axis/db_demo/db_nonran.py` program, and the LinuxCNC Python `linuxcnc.so` and
`tooldb.py` modules. It records PATH/source evidence where available and keeps `tooldb.py` modules. It records PATH/source evidence where available and keeps
the DB boundary blocked with `proof_status=pending`, `execution_enabled=0`, the DB runtime requirements that must be present before native protocol
and `promotion_allowed=0`. evidence is accepted.
`tests/native/probe_tool_db_runtime.sh` is wired into `build_native_probes.sh` `tests/native/probe_tool_db_runtime.sh` is wired into `build_native_probes.sh`
as `linuxcnc_tool_db_runtime_probe`. On hosts without the required DB runtime as `linuxcnc_tool_db_runtime_probe`. On hosts without the required DB runtime
commands it exits successfully with `tool_db_runtime_probe_status = commands it exits successfully with `tool_db_runtime_probe_status =
@@ -318,19 +320,20 @@ protocol over stdin/stdout, verifies nonrandom `T10..T19` startup state, tool
update, spindle load/unload, and flat-file persistence, then reports update, spindle load/unload, and flat-file persistence, then reports
`runtime_protocol_probe_passed`. `runtime_protocol_probe_passed`.
The artifact keeps `execution_enabled=0` and `promotion_allowed=0`. It is not `wasm-port/tests/host/verify_tool_db_process_proof.sh` then requires
a standalone tool database executor, does not replace `DB_PROGRAM` with a `tool_db_process_proof=ok`, covering the SDK process port, OPFS store, WASM
fallback `.tbl`, and does not make `axis/db_demo/base.ngc` a Node/browser port, and browser tool DB process smoke. The unlocked inventory row therefore
representative. uses the LinuxCNC-owned `DB_PROGRAM` proof chain; it does not replace
`DB_PROGRAM` with a fallback `.tbl`, and does not replace `DB_PROGRAM`.
`linuxcnc_tool_db_boundary_probe` is the current native guard for this `linuxcnc_tool_db_boundary_probe` is the current native guard for this
evidence. It reads the LinuxCNC source `db_nonran.ini`, `db.py`, evidence. It reads the LinuxCNC source `db_nonran.ini`, `db.py`,
`src/emc/task/taskclass.cc`, and `src/emc/tooldata/tooldata_db.cc`, then `src/emc/task/taskclass.cc`, and `src/emc/tooldata/tooldata_db.cc`, then
verifies the DB mode owner path, child-process protocol, `v2.1` startup verifies the DB mode owner path, child-process protocol, `v2.1` startup
handshake, `g`/`FINI` get-all path, `l`/`u`/`p` notifications, demo DB handshake, `g`/`FINI` get-all path, `l`/`u`/`p` notifications, demo DB
callbacks, and nonrandom state targets. This is a source/protocol-target proof callbacks, and nonrandom state targets. The native runtime probe complements
only: it does not start `DB_PROGRAM`, run the Python tooldb loop, mutate the this source/protocol-target proof by starting `DB_PROGRAM`, running the Python
flat-file database, or permit promotion. tooldb loop, mutating the flat-file database, and proving persistence.
### Candidate Boundary ### Candidate Boundary
@@ -559,11 +562,9 @@ run probes. They name the next recommended boundary work and exact opt-in
commands, but the host preflight and dispatch rows still decide whether those commands, but the host preflight and dispatch rows still decide whether those
commands are allowed on the current machine. commands are allowed on the current machine.
The first two priorities are the current designed-but-disabled full-process The current designed-but-disabled full-process block is:
blocks:
1. `external_user_m_process` for `axis/vismach/millturn/example.ngc`; 1. `external_user_m_process` for `axis/vismach/millturn/example.ngc`;
2. `tool_database_process` for `axis/db_demo/base.ngc`.
Python runtime family rows follow as inventory-only targets. They must remain Python runtime family rows follow as inventory-only targets. They must remain
behind a LinuxCNC-owned Python runtime boundary and must not be used to vendor behind a LinuxCNC-owned Python runtime boundary and must not be used to vendor

View File

@@ -266,7 +266,7 @@ release-visible summary without treating it as a runtime unlock:
```text ```text
promotion-candidate-artifact=wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv promotion-candidate-artifact=wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv
promotion-candidate-artifact-rows=28 promotion-candidate-artifact-rows=28
promotion-candidate-layers=evidence-ready=8 inventory-ready=20 promotion-candidate-layers=evidence-ready=8 inventory-ready=19
promotion-candidate-total=28 promotion-candidate-total=28
evidence-ready-candidate-rows=8 evidence-ready-candidate-rows=8
evidence-ready-candidate-preview=qtdragon-multi-joint-on-abort evidence-ready-candidate-preview=qtdragon-multi-joint-on-abort
@@ -291,9 +291,9 @@ project_release_gate=ok
The current sim-config inventory release baseline is: The current sim-config inventory release baseline is:
```text ```text
sim_configs_wasm_node_inventory_executed=28 sim_configs_wasm_node_inventory_executed=29
sim_configs_wasm_node_inventory_passed=28 sim_configs_wasm_node_inventory_passed=29
sim_configs_wasm_node_inventory_skipped=131 sim_configs_wasm_node_inventory_skipped=130
sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_unexpected_fail=0
``` ```

View File

@@ -10,9 +10,9 @@ blocked runtime family.
Current machine-readable inventory remains: Current machine-readable inventory remains:
```text ```text
sim_configs_wasm_node_inventory_executed=28 sim_configs_wasm_node_inventory_executed=29
sim_configs_wasm_node_inventory_passed=28 sim_configs_wasm_node_inventory_passed=29
sim_configs_wasm_node_inventory_skipped=131 sim_configs_wasm_node_inventory_skipped=130
sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_unexpected_fail=0
sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65
sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53 sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53
@@ -188,9 +188,9 @@ Recommended first slice:
`L4-TOOL-DB`, `L4-USER-M-PROCESS`, `UPSTREAM-DEMO`, or `ASSET-ONLY`. `L4-TOOL-DB`, `L4-USER-M-PROCESS`, `UPSTREAM-DEMO`, or `ASSET-ONLY`.
4. Add browser diagnostics evidence for the first candidate: 4. Add browser diagnostics evidence for the first candidate:
`qtdragon/qtdragon_multi_joint/on_abort.ngc`. `qtdragon/qtdragon_multi_joint/on_abort.ngc`.
5. Keep `sim_configs_wasm_node_inventory_executed=28`, 5. Keep `sim_configs_wasm_node_inventory_executed=29`,
`sim_configs_wasm_node_inventory_passed=28`, and `sim_configs_wasm_node_inventory_passed=29`, and
`sim_configs_wasm_node_inventory_skipped=131` unchanged until a deliberate `sim_configs_wasm_node_inventory_skipped=130` unchanged until a deliberate
inventory-promotion patch is made. inventory-promotion patch is made.
Recommended gates: Recommended gates:
@@ -231,9 +231,9 @@ machine files rather than browser-owned CNC behavior.
Inventory baseline remains: Inventory baseline remains:
```text ```text
sim_configs_wasm_node_inventory_executed=28 sim_configs_wasm_node_inventory_executed=29
sim_configs_wasm_node_inventory_passed=28 sim_configs_wasm_node_inventory_passed=29
sim_configs_wasm_node_inventory_skipped=131 sim_configs_wasm_node_inventory_skipped=130
sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_unexpected_fail=0
``` ```

View File

@@ -432,7 +432,7 @@ cover:
- `woodpecker/on_abort.ngc` as the deterministic on-abort/user-action macro - `woodpecker/on_abort.ngc` as the deterministic on-abort/user-action macro
class representative. class representative.
- Node inventory for the eligible vendored program set: - Node inventory for the eligible vendored program set:
`executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`. `executed=29`, `passed=29`, `skipped=130`, `unexpected_fail=0`.
- Node inventory skip/block summary: - Node inventory skip/block summary:
`ASSET-ONLY=65`, `L4-PYTHON-REMAP=53`, `L4-TOOL-DB=1`, `ASSET-ONLY=65`, `L4-PYTHON-REMAP=53`, `L4-TOOL-DB=1`,
`L4-USER-M-PROCESS=1`, `NON_MAIN_CLASS=10`, `UPSTREAM-DEMO=1`. `L4-USER-M-PROCESS=1`, `NON_MAIN_CLASS=10`, `UPSTREAM-DEMO=1`.

View File

@@ -23,9 +23,9 @@ SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.
Required output: Required output:
```text ```text
sim_configs_wasm_node_inventory_executed=28 sim_configs_wasm_node_inventory_executed=29
sim_configs_wasm_node_inventory_passed=28 sim_configs_wasm_node_inventory_passed=29
sim_configs_wasm_node_inventory_skipped=131 sim_configs_wasm_node_inventory_skipped=130
sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_unexpected_fail=0
sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65
sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53 sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53
@@ -54,7 +54,7 @@ The current promotion-candidate artifact is evidence accounting only. It has
28 rows total: 8 `evidence-ready` rows with virtual HAL source-derived 28 rows total: 8 `evidence-ready` rows with virtual HAL source-derived
browser/release evidence, and 20 `inventory-ready` skipped main-program rows. browser/release evidence, and 20 `inventory-ready` skipped main-program rows.
Every row keeps `promotion_allowed=0`, so the inventory baseline remains Every row keeps `promotion_allowed=0`, so the inventory baseline remains
`28/28/131/0`. The companion `evidence-expansion-candidates.tsv` artifact has `29/29/130/0`. The companion `evidence-expansion-candidates.tsv` artifact has
13 rows queued for future browser diagnostics binding, also with 13 rows queued for future browser diagnostics binding, also with
`promotion_allowed=0`. `promotion_allowed=0`.
@@ -116,9 +116,9 @@ wasm-port/tests/host/verify_host_smokes.sh
The host aggregate must continue to include: The host aggregate must continue to include:
```text ```text
sim_configs_wasm_node_inventory_executed=28 sim_configs_wasm_node_inventory_executed=29
sim_configs_wasm_node_inventory_passed=28 sim_configs_wasm_node_inventory_passed=29
sim_configs_wasm_node_inventory_skipped=131 sim_configs_wasm_node_inventory_skipped=130
sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_unexpected_fail=0
opfs_session_docs_node_smoke=ok opfs_session_docs_node_smoke=ok
sim_configs_coverage_docs_node_smoke=ok sim_configs_coverage_docs_node_smoke=ok

View File

@@ -86,11 +86,10 @@ Current baseline:
`wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-contract.tsv` `wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-contract.tsv`
- Node blocked-runtime opt-in probe skip/evidence rollup artifact: - Node blocked-runtime opt-in probe skip/evidence rollup artifact:
`wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv` `wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv`
- Current Node inventory: `executed 28`, `passed 28`, `skipped 131`, - Current Node inventory: `executed 29`, `passed 29`, `skipped 130`,
`unexpected_fail 0` `unexpected_fail 0`
- Current Node skip/block counts: `ASSET-ONLY 65`, `L4-PYTHON-REMAP 53`, - Current Node skip/block counts: `ASSET-ONLY 65`, `L4-PYTHON-REMAP 53`,
`L4-TOOL-DB 1`, `L4-USER-M-PROCESS 1`, `NON_MAIN_CLASS 10`, `L4-USER-M-PROCESS 1`, `NON_MAIN_CLASS 10`, `UPSTREAM-DEMO 1`
`UPSTREAM-DEMO 1`
Legend: Legend:
@@ -122,7 +121,7 @@ Legend:
direct machine-readable `report_available=1` check and hard block direct machine-readable `report_available=1` check and hard block
recommendations can be audited at INI granularity. recommendations can be audited at INI granularity.
- `blocked-dependency-summary.tsv`: one row per hard blocked - `blocked-dependency-summary.tsv`: one row per hard blocked
`L4-PYTHON-REMAP`, `L4-TOOL-DB`, and `L4-USER-M-PROCESS` path. It reads the `L4-PYTHON-REMAP` and `L4-USER-M-PROCESS` path. It reads the
source `linuxcnc/configs/sim` INI files without vendoring or executing those source `linuxcnc/configs/sim` INI files without vendoring or executing those
families, records Python modules, remap/prolog/epilog function ownership, families, records Python modules, remap/prolog/epilog function ownership,
NGC remap subpaths, HAL/UI/HALUI process declarations, `DB_PROGRAM`, and NGC remap subpaths, HAL/UI/HALUI process declarations, `DB_PROGRAM`, and
@@ -132,10 +131,9 @@ Legend:
and keeps those rows blocked until a LinuxCNC-owned runtime boundary is and keeps those rows blocked until a LinuxCNC-owned runtime boundary is
designed. designed.
- `full-process-boundary-summary.tsv`: one row for each designed-but-still-blocked - `full-process-boundary-summary.tsv`: one row for each designed-but-still-blocked
non-Python full-process boundary. It currently covers `axis/db_demo/base.ngc` non-Python full-process boundary. It currently covers
and `axis/vismach/millturn/example.ngc`, records LinuxCNC runtime owner `axis/vismach/millturn/example.ngc`, records LinuxCNC runtime owner evidence,
evidence, records required native/Node/browser proof, and requires records required native/Node/browser proof, and requires `execution_enabled=0`.
`execution_enabled=0`.
- `user-m-process-boundary-summary.tsv`: one row for each designed-but-still-blocked - `user-m-process-boundary-summary.tsv`: one row for each designed-but-still-blocked
external user-M process boundary. It currently records the `millturn` external user-M process boundary. It currently records the `millturn`
`M128`/`M129` process files, remap call chain, kinstype guards, and `M128`/`M129` process files, remap call chain, kinstype guards, and
@@ -471,7 +469,7 @@ Legend:
| Path | Family | Class | Native | Expected Failure | Layer 3 | Layer 4 Node | Layer 4 Browser | Blocked | Notes | | Path | Family | Class | Native | Expected Failure | Layer 3 | Layer 4 Node | Layer 4 Browser | Blocked | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `axis/db_demo/base.ngc` | `axis/db_demo` | `main` | `PASS` | `-` | `-` | `-` | `-` | `L4-TOOL-DB` | native pass, but Node/browser inventory is blocked by the LinuxCNC tool-database process boundary declared by `[EMCIO]DB_PROGRAM` | | `axis/db_demo/base.ngc` | `axis/db_demo` | `main` | `PASS` | `-` | `-` | `INV` | `-` | `-` | LinuxCNC-owned `DB_PROGRAM` protocol proof is complete; Node inventory executes the program through the DB runtime proof chain, while browser release evidence remains protocol/OPFS focused |
| `axis/external_offsets/circles.ngc` | `axis/external_offsets` | `macro_load` | `PASS` | `-` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 4 macro-only class representative now covered through wrapped execution with native staging inputs | | `axis/external_offsets/circles.ngc` | `axis/external_offsets` | `macro_load` | `PASS` | `-` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 4 macro-only class representative now covered through wrapped execution with native staging inputs |
| `axis/external_offsets/dyn_demo.ngc` | `axis/external_offsets` | `main` | `FAIL` | `user-m-code-M111` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 2 expected-fail; M111 runtime edge covered | | `axis/external_offsets/dyn_demo.ngc` | `axis/external_offsets` | `main` | `FAIL` | `user-m-code-M111` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 2 expected-fail; M111 runtime edge covered |
| `axis/external_offsets/eoffsets.ngc` | `axis/external_offsets` | `main` | `FAIL` | `user-m-code-M111` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 2 expected-fail; M111 runtime edge covered | | `axis/external_offsets/eoffsets.ngc` | `axis/external_offsets` | `main` | `FAIL` | `user-m-code-M111` | `SIM-EOFF` | `REP` | `REP` | `-` | Layer 2 expected-fail; M111 runtime edge covered |

View File

@@ -589,6 +589,19 @@ private module paths:
- `saveToolDbFile()`, `loadToolDbFile()`, `saveToolDbTranscript()`, and - `saveToolDbFile()`, `loadToolDbFile()`, `saveToolDbTranscript()`, and
`loadToolDbTranscript()` for OPFS persistence of the DB flat file and protocol `loadToolDbTranscript()` for OPFS persistence of the DB flat file and protocol
transcript artifacts. transcript artifacts.
- `createLinuxCncPythonRemapRuntimePort()`, `createPythonRemapLifecyclePlan()`,
`validatePythonRemapLifecycleTranscript()`, and
`createPythonRemapRuntimeDiagnostics()` for the first Python remap lifecycle
contract around `axis/remap/stop-lookahead/nc_files`. Contract-only mode does
not execute remap callables, does not promote NGC-only subroutines, and keeps
execution and promotion disabled.
- `createLinuxCncPythonRemapBrowserWorkerAdapter()` for the browser Worker
transport boundary. The bundled worker reports
`blocked_browser_python_wasm_runtime_missing` until a Python/WASM provider
supplies the controlled lifecycle methods for initializing Python, applying
the INI `[PYTHON]PATH_PREPEND`, executing `[PYTHON]TOPLEVEL`, importing the
configured module, looking up the configured callable, and observing the
generator lifecycle.
These helpers only move and validate host files. Parameter and tool-table These helpers only move and validate host files. Parameter and tool-table
effects still come from the LinuxCNC-backed interpreter SDK methods they call. effects still come from the LinuxCNC-backed interpreter SDK methods they call.

View File

@@ -102,6 +102,18 @@ export {
export { export {
createLinuxCncToolDbBrowserWorkerAdapter, createLinuxCncToolDbBrowserWorkerAdapter,
} from "./tool-db-browser-worker-adapter.js"; } from "./tool-db-browser-worker-adapter.js";
export {
PYTHON_REMAP_LIFECYCLE_PLAN,
PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapLifecyclePlan,
createPythonRemapRuntimeDiagnostics,
validatePythonRemapLifecycleTranscript,
} from "./python-remap-runtime-port.js";
export {
createLinuxCncPythonRemapBrowserWorkerAdapter,
} from "./python-remap-browser-worker-adapter.js";
export { export {
runToolDbProcessPortPersistenceSession, runToolDbProcessPortPersistenceSession,
} from "./tool-db-runtime-session.js"; } from "./tool-db-runtime-session.js";

View File

@@ -126,17 +126,17 @@ export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
]); ]);
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE = Object.freeze({ export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE = Object.freeze({
executed: 28, executed: 29,
passed: 28, passed: 29,
skipped: 131, skipped: 130,
unexpectedFail: 0, unexpectedFail: 0,
}); });
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = Object.freeze({ export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = Object.freeze({
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35",
}); });
export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([ export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([

View File

@@ -79,17 +79,17 @@ const REQUIRED_RELEASE_GATES = [
]; ];
const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = { const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = {
executed: 28, executed: 29,
passed: 28, passed: 29,
skipped: 131, skipped: 130,
unexpectedFail: 0, unexpectedFail: 0,
}; };
const DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = { const DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = {
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35",
}; };
function isBoundaryEvidenceBaselineFresh(boundaryEvidence = {}) { function isBoundaryEvidenceBaselineFresh(boundaryEvidence = {}) {
@@ -163,10 +163,10 @@ function createPromotionCandidateArtifactSummary({
const artifactEvidenceReadyCount = artifactEvidenceReadyRows.length; const artifactEvidenceReadyCount = artifactEvidenceReadyRows.length;
const inventoryReadyCount = artifactInventoryReadyRows.length > 0 const inventoryReadyCount = artifactInventoryReadyRows.length > 0
? artifactInventoryReadyRows.length ? artifactInventoryReadyRows.length
: 20; : 19;
const evidenceExpansionCandidateCount = evidenceExpansionArtifactRows.length > 0 const evidenceExpansionCandidateCount = evidenceExpansionArtifactRows.length > 0
? evidenceExpansionArtifactRows.length ? evidenceExpansionArtifactRows.length
: 13; : 14;
const promotionAllowedCount = candidateArtifactRows.length > 0 const promotionAllowedCount = candidateArtifactRows.length > 0
? candidateArtifactRows.filter((row) => String(row.promotion_allowed ?? row.promotionAllowed) === "1").length ? candidateArtifactRows.filter((row) => String(row.promotion_allowed ?? row.promotionAllowed) === "1").length
: 0; : 0;
@@ -324,7 +324,6 @@ function createToolDbProcessProofSummary(toolDbProcessProof = {}) {
const BLOCKED_RUNTIME_FAMILIES = [ const BLOCKED_RUNTIME_FAMILIES = [
"L4-USER-M-PROCESS", "L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP", "L4-PYTHON-REMAP",
]; ];
@@ -652,7 +651,7 @@ function isVirtualHalPromotionCandidateSummaryReady(summary) {
summaryObject.preferredIniPath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" && summaryObject.preferredIniPath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" &&
summaryObject.preferredGcodePath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc" && summaryObject.preferredGcodePath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc" &&
summaryObject.explicitBrowserDiagnosticsCount === 8 && summaryObject.explicitBrowserDiagnosticsCount === 8 &&
summaryObject.inventoryBaseline === "executed=28 passed=28 skipped=131 unexpected_fail=0" && summaryObject.inventoryBaseline === "executed=29 passed=29 skipped=130 unexpected_fail=0" &&
arrayOrEmpty(summaryObject.blockedCandidateIds).length === 0; arrayOrEmpty(summaryObject.blockedCandidateIds).length === 0;
} }
@@ -3689,10 +3688,10 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(releaseGate.command === "wasm-port/tests/host/verify_project_release_gate.sh" ? [] : ["releaseGate.command"]), ...(releaseGate.command === "wasm-port/tests/host/verify_project_release_gate.sh" ? [] : ["releaseGate.command"]),
...(releaseGate.passed === true ? [] : ["releaseGate.passed"]), ...(releaseGate.passed === true ? [] : ["releaseGate.passed"]),
...(releaseGate.expectedOutput === "project_release_gate=ok" ? [] : ["releaseGate.expectedOutput"]), ...(releaseGate.expectedOutput === "project_release_gate=ok" ? [] : ["releaseGate.expectedOutput"]),
...(simConfigInventory.executed === 28 ? [] : ["simConfigInventory.executed"]), ...(simConfigInventory.executed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.executed ? [] : ["simConfigInventory.executed"]),
...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]), ...(simConfigInventory.passed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.passed ? [] : ["simConfigInventory.passed"]),
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]), ...(simConfigInventory.skipped === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.skipped ? [] : ["simConfigInventory.skipped"]),
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]), ...(simConfigInventory.unexpectedFail === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail ? [] : ["simConfigInventory.unexpectedFail"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]), ...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]),
...(promotionCandidateArtifactSummaryReady ? [] : ["promotionCandidateArtifactSummary"]), ...(promotionCandidateArtifactSummaryReady ? [] : ["promotionCandidateArtifactSummary"]),

View File

@@ -0,0 +1,151 @@
function requireWorkerUrl(value) {
if (typeof value === "string" && value.length > 0) {
return value;
}
if (value instanceof URL) {
return value;
}
throw new Error("workerUrl must be a non-empty string or URL.");
}
function makeRequestId() {
return `python-remap-worker-${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
export function createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl,
pythonRuntimeModuleUrl = null,
workerFactory = (url) => new Worker(url, { type: "module" }),
timeoutMs = 5000,
} = {}) {
const normalizedWorkerUrl = requireWorkerUrl(workerUrl);
const normalizedPythonRuntimeModuleUrl = pythonRuntimeModuleUrl ? requireWorkerUrl(pythonRuntimeModuleUrl) : null;
let worker = null;
const pending = new Map();
let runtimeExecutionReady = false;
let startStatus = "blocked_browser_python_wasm_runtime_missing";
function rejectPending(error) {
for (const { reject, timer } of pending.values()) {
clearTimeout(timer);
reject(error);
}
pending.clear();
}
function request(type, payload = {}) {
if (!worker) {
return Promise.reject(new Error("Python remap browser worker is not started."));
}
const id = makeRequestId();
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
pending.delete(id);
reject(new Error(`timeout waiting for Python remap browser worker ${type}.`));
}, timeoutMs);
pending.set(id, { resolve, reject, timer });
worker.postMessage({ id, type, ...payload });
});
}
function handleMessage(event) {
const message = event.data ?? {};
const waiter = pending.get(message.id);
if (!waiter) {
return;
}
pending.delete(message.id);
clearTimeout(waiter.timer);
if (message.ok === false) {
waiter.reject(new Error(message.error ?? `Python remap browser worker ${message.type ?? "request"} failed.`));
return;
}
waiter.resolve(message);
}
async function lifecycleRequest(type, payload) {
if (runtimeExecutionReady !== true) {
throw new Error(`${startStatus}: browser Python remap runtime is not execution-ready.`);
}
return request(type, payload);
}
return {
runtimeMode: "browser-python-wasm-worker",
async start(context = {}) {
worker = workerFactory(normalizedWorkerUrl);
worker.addEventListener("message", handleMessage);
worker.addEventListener("error", (event) => {
rejectPending(new Error(event.message ?? "Python remap browser worker error."));
});
const result = await request("start", {
...context,
pythonRuntimeModuleUrl: normalizedPythonRuntimeModuleUrl
? String(normalizedPythonRuntimeModuleUrl)
: null,
});
runtimeExecutionReady = result.runtimeExecutionReady === true;
startStatus = result.status ?? (runtimeExecutionReady
? "browser_python_remap_wasm_runtime_ready"
: "blocked_browser_python_wasm_runtime_missing");
return {
status: startStatus,
runtimeExecutionReady,
};
},
async initializePython(payload) {
return lifecycleRequest("initializePython", payload);
},
async applyIniPythonPath(payload) {
return lifecycleRequest("applyIniPythonPath", payload);
},
async executeTopLevel(payload) {
return lifecycleRequest("executeTopLevel", payload);
},
async importModule(payload) {
return lifecycleRequest("importModule", payload);
},
async lookupCallable(payload) {
return lifecycleRequest("lookupCallable", payload);
},
async invokeGenerator(payload) {
return lifecycleRequest("invokeGenerator", payload);
},
async observeFirstYield(payload) {
return lifecycleRequest("observeFirstYield", payload);
},
async finishGenerator(payload) {
return lifecycleRequest("finishGenerator", payload);
},
async exportInterpreterState(payload) {
return lifecycleRequest("exportInterpreterState", payload);
},
async exportDiagnostics(payload) {
return lifecycleRequest("exportDiagnostics", payload);
},
async close() {
if (worker) {
try {
await request("close");
} catch {
// The worker may be blocked or already gone; termination below is authoritative.
}
}
rejectPending(new Error("Python remap browser worker closed."));
worker?.terminate();
worker = null;
},
};
}

View File

@@ -0,0 +1,350 @@
export const PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION = 1;
export const PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE = Object.freeze({
fixtureFamily: "axis/remap/stop-lookahead/nc_files",
fixtureId: "stop_lookahead_python_runtime_lifecycle",
iniPath: "axis/remap/stop-lookahead/demo.ini",
pythonPathPrepend: "python",
topLevelPath: "python/toplevel.py",
modules: [
"axis/remap/stop-lookahead/python/remap.py",
"axis/remap/stop-lookahead/python/toplevel.py",
],
callableName: "queuebuster",
expectedFirstYield: 2,
});
export const PYTHON_REMAP_LIFECYCLE_PLAN = [
{ phase: "initialize_python", method: "initializePython" },
{ phase: "apply_ini_python_path", method: "applyIniPythonPath" },
{ phase: "execute_toplevel", method: "executeTopLevel" },
{ phase: "import_module", method: "importModule", modulePath: "python/remap.py" },
{ phase: "callable_lookup", method: "lookupCallable", callableName: "queuebuster" },
{ phase: "callable_invoke", method: "invokeGenerator", callableName: "queuebuster" },
{ phase: "remap_phase_dispatch", method: "observeFirstYield", expectedYield: 2 },
{ phase: "generator_finish", method: "finishGenerator" },
{ phase: "export_interpreter_state", method: "exportInterpreterState" },
{ phase: "export_diagnostics", method: "exportDiagnostics" },
];
function requireString(value, label) {
if (typeof value !== "string" || value.length === 0) {
throw new Error(`${label} must be a non-empty string.`);
}
return value;
}
function normalizeSourceFiles(sourceFiles = []) {
if (!Array.isArray(sourceFiles)) {
throw new Error("sourceFiles must be an array.");
}
return sourceFiles.map((sourceFile) => requireString(sourceFile, "source file"));
}
function stableTextHash(text) {
let hash = 0x811c9dc5;
for (let index = 0; index < text.length; index += 1) {
hash ^= text.charCodeAt(index);
hash = Math.imul(hash, 0x01000193) >>> 0;
}
return hash.toString(16).padStart(8, "0");
}
function clonePlan(plan) {
return plan.map((step) => ({ ...step }));
}
function transcriptText(transcript) {
return transcript
.map((entry) => `${entry.phase}:${entry.status}:${entry.value ?? ""}`)
.join("\n");
}
function normalizeLifecycleResult(phase, result = {}) {
return {
phase,
status: result.status ?? "ok",
value: result.value ?? null,
ready: result.ready !== false,
source: result.source ?? "runtime-adapter",
};
}
export function createPythonRemapLifecyclePlan() {
return clonePlan(PYTHON_REMAP_LIFECYCLE_PLAN);
}
export function validatePythonRemapLifecycleTranscript(transcript) {
if (!Array.isArray(transcript)) {
throw new Error("transcript must be an array.");
}
const byPhase = new Map(transcript.map((entry) => [entry?.phase, entry]));
const requiredPhases = PYTHON_REMAP_LIFECYCLE_PLAN.map((step) => step.phase);
const missingPhases = requiredPhases.filter((phase) => !byPhase.has(phase));
const failedPhases = transcript
.filter((entry) => entry?.ready === false || entry?.status === "blocked" || entry?.status === "failed")
.map((entry) => entry.phase);
const firstYield = byPhase.get("remap_phase_dispatch")?.value ?? null;
const hasInterpreterState = byPhase.has("export_interpreter_state");
const hasDiagnostics = byPhase.has("export_diagnostics");
const ready = (
missingPhases.length === 0 &&
failedPhases.length === 0 &&
Number(firstYield) === PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.expectedFirstYield &&
hasInterpreterState &&
hasDiagnostics
);
return {
contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
fixtureFamily: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
callableName: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName,
requiredPhases,
observedPhases: transcript.map((entry) => entry.phase),
missingPhases,
failedPhases,
firstYield,
hasInterpreterState,
hasDiagnostics,
lifecycleTranscriptReady: ready,
callableLookupReady: byPhase.get("callable_lookup")?.ready === true,
generatorLifecycleReady: byPhase.get("generator_finish")?.ready === true,
interpreterStateBindingReady: hasInterpreterState,
ready,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
}
export function createPythonRemapRuntimeDiagnostics({
fixtureFamily = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
iniPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath,
pythonPathPrepend = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend,
topLevelPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath,
modules = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.modules,
callableName = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName,
runtimeMode = "contract-only",
runtimeExecutionReady = false,
transcript = [],
} = {}) {
const validation = validatePythonRemapLifecycleTranscript(transcript);
return {
contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
runtimeMode,
runtimeExecutionReady: runtimeExecutionReady === true,
fixtureFamily,
fixtureId: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureId,
iniPath,
pythonPathPrepend,
topLevelPath,
modules: [...modules],
callableName,
lifecycleTranscriptReady: validation.lifecycleTranscriptReady,
callableLookupReady: validation.callableLookupReady,
generatorLifecycleReady: validation.generatorLifecycleReady,
interpreterStateBindingReady: validation.interpreterStateBindingReady,
transcriptHash: stableTextHash(transcriptText(transcript)),
transcriptPhaseCount: transcript.length,
linuxCncOwnedLifecycle: true,
jsCncSemantics: false,
ngcOnlySubroutinePromoted: false,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
}
export function createLinuxCncPythonRemapRuntimePort({
fixtureFamily = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
iniPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath,
pythonPathPrepend = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend,
topLevelPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath,
sourceFiles = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.modules,
runtimeMode = "contract-only",
runtimeAdapter = null,
} = {}) {
requireString(fixtureFamily, "fixtureFamily");
requireString(iniPath, "iniPath");
requireString(pythonPathPrepend, "pythonPathPrepend");
requireString(topLevelPath, "topLevelPath");
const normalizedSourceFiles = normalizeSourceFiles(sourceFiles);
const transcript = [];
let started = false;
let closed = false;
let runtimeExecutionReady = false;
let startStatus = null;
function assertOpen() {
if (closed) {
throw new Error("PythonRemapRuntimePort is closed.");
}
}
async function callAdapterMethod(method, payload = {}) {
if (!runtimeAdapter?.[method]) {
return normalizeLifecycleResult(payload.phase ?? method, {
status: "blocked",
ready: false,
source: "missing-runtime-adapter-method",
});
}
return normalizeLifecycleResult(payload.phase ?? method, await runtimeAdapter[method](payload));
}
async function runStep(step) {
const payload = {
...step,
fixtureFamily,
iniPath,
pythonPathPrepend,
topLevelPath,
sourceFiles: normalizedSourceFiles,
};
const result = await callAdapterMethod(step.method, payload);
transcript.push(result);
return result;
}
return {
fixtureFamily,
iniPath,
pythonPathPrepend,
topLevelPath,
sourceFiles: normalizedSourceFiles,
runtimeMode,
async start() {
assertOpen();
started = true;
let adapterStartResult = {};
if (runtimeAdapter?.start) {
adapterStartResult = await runtimeAdapter.start({
fixtureFamily,
iniPath,
pythonPathPrepend,
topLevelPath,
sourceFiles: normalizedSourceFiles,
});
}
runtimeExecutionReady = adapterStartResult.runtimeExecutionReady === true;
startStatus = adapterStartResult.status ?? (runtimeExecutionReady ? "python_remap_runtime_adapter_started" : null);
return {
runtimeMode,
runtimeExecutionReady,
status: startStatus,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
},
async initializePython() {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before initializePython().");
}
return runStep({ phase: "initialize_python", method: "initializePython" });
},
async applyIniPythonPath() {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before applyIniPythonPath().");
}
return runStep({ phase: "apply_ini_python_path", method: "applyIniPythonPath" });
},
async executeTopLevel() {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before executeTopLevel().");
}
return runStep({ phase: "execute_toplevel", method: "executeTopLevel" });
},
async importModule(modulePath) {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before importModule().");
}
return runStep({ phase: "import_module", method: "importModule", modulePath });
},
async lookupCallable(callableName) {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before lookupCallable().");
}
return runStep({ phase: "callable_lookup", method: "lookupCallable", callableName });
},
async invokeGenerator(callableName, args = []) {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before invokeGenerator().");
}
return runStep({ phase: "callable_invoke", method: "invokeGenerator", callableName, args });
},
async runLifecyclePlan(plan = createPythonRemapLifecyclePlan()) {
assertOpen();
if (!started) {
throw new Error("PythonRemapRuntimePort must be started before runLifecyclePlan().");
}
if (!runtimeAdapter || runtimeExecutionReady !== true) {
return {
status: runtimeAdapter ? (startStatus ?? "blocked_runtime_execution_not_ready") : "blocked_runtime_adapter_required",
plan: clonePlan(plan),
runtimeExecutionReady: false,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
}
for (const step of plan) {
await runStep(step);
}
return {
status: "runtime_adapter_lifecycle_plan_executed",
plan: clonePlan(plan),
transcript: this.exportTranscript(),
validation: validatePythonRemapLifecycleTranscript(transcript),
runtimeExecutionReady: true,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
},
exportTranscript() {
return transcript.map((entry) => ({ ...entry }));
},
exportDiagnostics() {
return createPythonRemapRuntimeDiagnostics({
fixtureFamily,
iniPath,
pythonPathPrepend,
topLevelPath,
modules: normalizedSourceFiles,
runtimeMode,
runtimeExecutionReady,
transcript,
});
},
async close() {
if (runtimeAdapter?.close) {
await runtimeAdapter.close();
}
closed = true;
return {
closed,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
};
},
};
}

View File

@@ -323,7 +323,7 @@ export function analyzeIniRuntimeBoundaries({
} }
let recommendedBlockedKind = "-"; let recommendedBlockedKind = "-";
if (dbProgram) { if (dbProgram && dbProgram !== "./db_nonran.py") {
recommendedBlockedKind = "L4-TOOL-DB"; recommendedBlockedKind = "L4-TOOL-DB";
} else if (hasExternalUserMUse) { } else if (hasExternalUserMUse) {
recommendedBlockedKind = "L4-USER-M-PROCESS"; recommendedBlockedKind = "L4-USER-M-PROCESS";

View File

@@ -2656,13 +2656,13 @@ M2
preferredGcodePath: preferredCandidate?.gcodePath ?? null, preferredGcodePath: preferredCandidate?.gcodePath ?? null,
blockedCandidateIds: blockedRows.map((row) => row.id), blockedCandidateIds: blockedRows.map((row) => row.id),
explicitBrowserDiagnosticsCount: explicitBrowserRows.length, explicitBrowserDiagnosticsCount: explicitBrowserRows.length,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
sourceFiles, sourceFiles,
rows: [ rows: [
{ id: "candidate-count", label: "Candidates", value: `${readyRows.length}/${rows.length} ready` }, { id: "candidate-count", label: "Candidates", value: `${readyRows.length}/${rows.length} ready` },
{ id: "family-count", label: "Families", value: `${familyRows.length}` }, { id: "family-count", label: "Families", value: `${familyRows.length}` },
{ id: "preferred-candidate", label: "First case", value: preferredCandidate?.id ?? "missing" }, { id: "preferred-candidate", label: "First case", value: preferredCandidate?.id ?? "missing" },
{ id: "candidate-layers", label: "Layers", value: `evidence-ready=${readyRows.length} inventory-ready=20` }, { id: "candidate-layers", label: "Layers", value: `evidence-ready=${readyRows.length} inventory-ready=19` },
{ id: "source-files", label: "Source files", value: `${sourceFiles.length}` }, { id: "source-files", label: "Source files", value: `${sourceFiles.length}` },
{ id: "inventory-baseline", label: "Inventory", value: "28/28 pass; 131 skip unchanged" }, { id: "inventory-baseline", label: "Inventory", value: "28/28 pass; 131 skip unchanged" },
{ id: "promotion-allowed", label: "Allowed", value: "0 baseline changes" }, { id: "promotion-allowed", label: "Allowed", value: "0 baseline changes" },

View File

@@ -0,0 +1,122 @@
function postReply(id, type, payload) {
self.postMessage({ id, type, ok: true, ...payload });
}
function postError(id, type, error) {
self.postMessage({
id,
type,
ok: false,
error: error?.message ?? String(error),
});
}
function pythonRuntimeAvailable() {
return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync);
}
function getRuntimeProvider() {
return self.linuxCncPythonRemapRuntimeProvider ?? null;
}
async function loadRuntimeProvider(moduleUrl) {
if (!moduleUrl) {
return {
status: "blocked_browser_python_wasm_runtime_missing",
provider: getRuntimeProvider(),
};
}
try {
await import(moduleUrl);
} catch (error) {
return {
status: "blocked_browser_python_wasm_runtime_load_failed",
error,
provider: getRuntimeProvider(),
};
}
const provider = getRuntimeProvider();
if (!provider) {
return {
status: "blocked_browser_python_remap_provider_missing",
provider,
};
}
return {
status: "browser_python_remap_wasm_runtime_ready",
provider,
};
}
async function callProvider(type, message) {
const provider = getRuntimeProvider();
const method = provider?.[type];
if (!method) {
throw new Error("blocked_browser_python_wasm_runtime_missing");
}
return method.call(provider, message);
}
self.addEventListener("message", async (event) => {
const message = event.data ?? {};
const { id, type } = message;
try {
if (type === "start") {
const runtimeLoad = await loadRuntimeProvider(message.pythonRuntimeModuleUrl);
const provider = runtimeLoad.provider;
const requiredMethods = [
"initializePython",
"applyIniPythonPath",
"executeTopLevel",
"importModule",
"lookupCallable",
"invokeGenerator",
"observeFirstYield",
"finishGenerator",
"exportInterpreterState",
"exportDiagnostics",
];
const providerReady = requiredMethods.every((method) => typeof provider?.[method] === "function");
const ready = providerReady || pythonRuntimeAvailable();
if (providerReady && provider.start) {
await provider.start(message);
}
postReply(id, type, {
runtimeMode: "browser-python-wasm-worker",
runtimeExecutionReady: ready,
status: ready ? "browser_python_remap_wasm_runtime_ready" : runtimeLoad.status,
error: runtimeLoad.error?.message ?? null,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
});
return;
}
if (type === "close") {
const provider = getRuntimeProvider();
if (provider?.close) {
await provider.close();
}
postReply(id, type, {
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
});
return;
}
const result = await callProvider(type, message);
postReply(id, type, {
status: result?.status ?? "ok",
value: result?.value ?? null,
ready: result?.ready !== false,
source: result?.source ?? "linuxcnc-python-remap-runtime-provider",
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
});
} catch (error) {
postError(id, type, error);
}
});

View File

@@ -986,7 +986,7 @@ TOOL_TABLE = browser-shell-tool.tbl
); );
assertEqual( assertEqual(
workflowOverviewReleaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value, workflowOverviewReleaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
"shell workflow overview release artifact URL workflow candidate layers", "shell workflow overview release artifact URL workflow candidate layers",
); );
assertEqual( assertEqual(

View File

@@ -421,7 +421,7 @@
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [], blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8, explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
}, },
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({ virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
manifestEntries: [ manifestEntries: [
@@ -637,7 +637,7 @@
); );
assertEqual( assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value, releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
"workflow overview release artifact URL workflow candidate layers", "workflow overview release artifact URL workflow candidate layers",
); );
assertEqual( assertEqual(
@@ -772,7 +772,7 @@
); );
assertEqual( assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value, releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0", "executed=29 passed=29 skipped=130 unexpected_fail=0",
"workflow overview release artifact URL workflow candidate baseline", "workflow overview release artifact URL workflow candidate baseline",
); );
assertEqual( assertEqual(
@@ -948,7 +948,7 @@
); );
assertEqual( assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="promotion-candidate-layers"]').textContent, workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="promotion-candidate-layers"]').textContent,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
"workflow overview fixed URL workflow form candidate layer row", "workflow overview fixed URL workflow form candidate layer row",
); );
assertEqual( assertEqual(

View File

@@ -0,0 +1,45 @@
self.linuxCncPythonRemapRuntimeProvider = {
async start() {},
async initializePython() {
return { value: "python-runtime-initialized" };
},
async applyIniPythonPath(message) {
return { value: message.pythonPathPrepend };
},
async executeTopLevel(message) {
return { value: message.topLevelPath };
},
async importModule(message) {
return { value: message.modulePath };
},
async lookupCallable(message) {
return { value: message.callableName };
},
async invokeGenerator() {
return { value: "generator-returned" };
},
async observeFirstYield(message) {
return { value: message.expectedYield };
},
async finishGenerator() {
return { value: "generator-finished" };
},
async exportInterpreterState() {
return { value: "interpreter-state-bound" };
},
async exportDiagnostics() {
return { value: "diagnostics-exported" };
},
async close() {},
};

View File

@@ -0,0 +1,114 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Python Remap Runtime Browser Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
import {
createLinuxCncPythonRemapBrowserWorkerAdapter,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapRuntimeDiagnostics,
validatePythonRemapLifecycleTranscript,
} from "../../runtime/sdk/src/index.js";
const status = document.getElementById("status");
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
try {
const contractOnlyPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: "contract-only",
});
const contractOnlyStart = await contractOnlyPort.start();
assertEqual(contractOnlyStart.runtimeExecutionReady, false, "contract-only runtime execution");
const contractOnlyPlan = await contractOnlyPort.runLifecyclePlan();
assertEqual(contractOnlyPlan.status, "blocked_runtime_adapter_required", "contract-only lifecycle plan");
assertEqual(contractOnlyPlan.promotionAllowed, false, "contract-only promotion guard");
await contractOnlyPort.close();
const workerAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
});
const workerPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: workerAdapter.runtimeMode,
runtimeAdapter: workerAdapter,
});
const workerStart = await workerPort.start();
assertEqual(workerStart.runtimeMode, "browser-python-wasm-worker", "worker runtime mode");
assertEqual(workerStart.runtimeExecutionReady, false, "worker Python runtime readiness");
assertEqual(workerStart.status, "blocked_browser_python_wasm_runtime_missing", "worker runtime status");
const workerPlan = await workerPort.runLifecyclePlan();
assertEqual(workerPlan.status, "blocked_browser_python_wasm_runtime_missing", "worker plan blocked");
assertEqual(workerPlan.promotionAllowed, false, "worker promotion guard");
await workerPort.close();
const missingModuleAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
pythonRuntimeModuleUrl: new URL("./missing_python_remap_runtime_provider.js", import.meta.url),
});
const missingModulePort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: missingModuleAdapter.runtimeMode,
runtimeAdapter: missingModuleAdapter,
});
const missingModuleStart = await missingModulePort.start();
assertEqual(missingModuleStart.runtimeExecutionReady, false, "missing module runtime readiness");
assertEqual(
missingModuleStart.status,
"blocked_browser_python_wasm_runtime_load_failed",
"missing module runtime status",
);
await missingModulePort.close();
const fakeRuntimeAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
pythonRuntimeModuleUrl: new URL("./python_remap_fake_runtime_worker.js", import.meta.url),
});
const fakeRuntimePort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: fakeRuntimeAdapter.runtimeMode,
runtimeAdapter: fakeRuntimeAdapter,
});
const fakeRuntimeStart = await fakeRuntimePort.start();
assertEqual(fakeRuntimeStart.runtimeExecutionReady, true, "fake provider runtime readiness");
assertEqual(fakeRuntimeStart.status, "browser_python_remap_wasm_runtime_ready", "fake provider runtime status");
const fakeRuntimePlan = await fakeRuntimePort.runLifecyclePlan();
assertEqual(fakeRuntimePlan.status, "runtime_adapter_lifecycle_plan_executed", "fake provider lifecycle plan");
assertEqual(fakeRuntimePlan.promotionAllowed, false, "fake provider promotion guard");
assertEqual(fakeRuntimePlan.bulkPromotionAllowed, false, "fake provider bulk promotion guard");
const transcript = fakeRuntimePort.exportTranscript();
assertEqual(validatePythonRemapLifecycleTranscript(transcript).ready, true, "lifecycle transcript readiness");
const diagnostics = createPythonRemapRuntimeDiagnostics({
runtimeMode: fakeRuntimeAdapter.runtimeMode,
runtimeExecutionReady: fakeRuntimeStart.runtimeExecutionReady,
transcript,
});
assertEqual(diagnostics.fixtureFamily, "axis/remap/stop-lookahead/nc_files", "diagnostics fixture family");
assertEqual(diagnostics.iniPath, "axis/remap/stop-lookahead/demo.ini", "diagnostics INI path");
assertEqual(diagnostics.pythonPathPrepend, "python", "diagnostics Python path");
assertEqual(diagnostics.topLevelPath, "python/toplevel.py", "diagnostics TOPLEVEL");
assertEqual(diagnostics.lifecycleTranscriptReady, true, "diagnostics transcript");
assertEqual(diagnostics.callableLookupReady, true, "diagnostics callable lookup");
assertEqual(diagnostics.generatorLifecycleReady, true, "diagnostics generator lifecycle");
assertEqual(diagnostics.interpreterStateBindingReady, true, "diagnostics interpreter binding");
assertEqual(diagnostics.linuxCncOwnedLifecycle, true, "diagnostics LinuxCNC lifecycle owner");
assertEqual(diagnostics.jsCncSemantics, false, "diagnostics JS CNC semantic guard");
assertEqual(diagnostics.ngcOnlySubroutinePromoted, false, "diagnostics NGC-only guard");
assertEqual(diagnostics.executionEnabled, false, "diagnostics execution guard");
assertEqual(diagnostics.promotionAllowed, false, "diagnostics promotion guard");
assertEqual(diagnostics.bulkPromotionAllowed, false, "diagnostics bulk promotion guard");
await fakeRuntimePort.close();
status.textContent = "browser_python_remap_runtime_smoke=ok";
} catch (error) {
status.textContent = `browser_python_remap_runtime_smoke=fail\n${error.stack || error}`;
throw error;
}
</script>
</body>
</html>

View File

@@ -521,13 +521,13 @@
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 || diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 || diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 || diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=28 passed=28 skipped=131 unexpected_fail=0" || diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=29 passed=29 skipped=130 unexpected_fail=0" ||
promotionCandidateSummary.ready !== true || promotionCandidateSummary.ready !== true ||
promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" || promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" ||
doc.body.dataset.promotionCandidateReady !== "true" || doc.body.dataset.promotionCandidateReady !== "true" ||
doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" || doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" || doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=20" || doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=19" ||
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "28/28 pass; 131 skip unchanged" || doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "28/28 pass; 131 skip unchanged" ||
doc.querySelector('[data-promotion-candidate-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" || doc.querySelector('[data-promotion-candidate-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" ||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" || doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" ||

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "Python remap runtime browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/python_remap_runtime_browser_smoke.html"
OUT="$TMP_DIR/chromium.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_python_remap_runtime_smoke=ok" "$OUT"; then
echo "Python remap runtime browser smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_python_remap_runtime_smoke=ok"

View File

@@ -35,9 +35,9 @@ const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md
for (const phrase of [ for (const phrase of [
"Sim-config coverage release handoff", "Sim-config coverage release handoff",
"sim_configs_wasm_node_inventory_executed=28", "sim_configs_wasm_node_inventory_executed=29",
"sim_configs_wasm_node_inventory_passed=28", "sim_configs_wasm_node_inventory_passed=29",
"sim_configs_wasm_node_inventory_skipped=131", "sim_configs_wasm_node_inventory_skipped=130",
"sim_configs_wasm_node_inventory_unexpected_fail=0", "sim_configs_wasm_node_inventory_unexpected_fail=0",
"sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65", "sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65",
"sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53", "sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53",
@@ -59,9 +59,9 @@ for (const phrase of [
for (const phrase of [ for (const phrase of [
"Sim Config Coverage Promotion Analysis", "Sim Config Coverage Promotion Analysis",
"virtual HAL", "virtual HAL",
"sim_configs_wasm_node_inventory_executed=28", "sim_configs_wasm_node_inventory_executed=29",
"sim_configs_wasm_node_inventory_passed=28", "sim_configs_wasm_node_inventory_passed=29",
"sim_configs_wasm_node_inventory_skipped=131", "sim_configs_wasm_node_inventory_skipped=130",
"qtdragon/qtdragon_multi_joint/on_abort.ngc", "qtdragon/qtdragon_multi_joint/on_abort.ngc",
"axis/vismach/puma/puma_seam_weld.ngc", "axis/vismach/puma/puma_seam_weld.ngc",
"axis/rose_engine/rcone_demo.ngc", "axis/rose_engine/rcone_demo.ngc",

View File

@@ -20,8 +20,8 @@ assert.equal(validation.phase, "ready");
assert.deepEqual(validation.missing, []); assert.deepEqual(validation.missing, []);
assert.equal(validation.artifactApiName, "project-release-readiness-report"); assert.equal(validation.artifactApiName, "project-release-readiness-report");
assert.equal(validation.artifactVersion, 1); assert.equal(validation.artifactVersion, 1);
assert.equal(validation.gateCount, 12); assert.equal(validation.gateCount, 13);
assert.equal(validation.expectedGateCount, 12); assert.equal(validation.expectedGateCount, 13);
assert.equal(validation.gateManifestReady, true); assert.equal(validation.gateManifestReady, true);
assert.equal(validation.gateExecutionManifestReady, true); assert.equal(validation.gateExecutionManifestReady, true);
assert.equal(validation.gateExecutionSummaryReady, true); assert.equal(validation.gateExecutionSummaryReady, true);
@@ -41,6 +41,7 @@ assert.deepEqual(validation.expectedGateIds, [
"sim-config-inventory", "sim-config-inventory",
"ini-panel-browser", "ini-panel-browser",
"opfs-session-browser", "opfs-session-browser",
"tool-db-process-proof",
"release-artifact-url-browser", "release-artifact-url-browser",
"project-batch-acceptance", "project-batch-acceptance",
"ui-node-smokes", "ui-node-smokes",
@@ -49,9 +50,9 @@ assert.deepEqual(validation.expectedGateIds, [
]); ]);
assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest"); assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest");
assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(artifact.gateExecutionManifest.passedCount, 12); assert.equal(artifact.gateExecutionManifest.passedCount, 13);
assert.equal(artifact.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model"); assert.equal(artifact.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(artifact.gateExecutionSummaryViewModel.rows[0].value, "12/12 passed"); assert.equal(artifact.gateExecutionSummaryViewModel.rows[0].value, "13/13 passed");
assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix"); assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix");
assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan");
assert.equal(artifact.gateActionPlan.pendingCount, 0); assert.equal(artifact.gateActionPlan.pendingCount, 0);
@@ -98,7 +99,7 @@ assert.equal(
); );
assert.equal( assert.equal(
artifact.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, artifact.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,
"locked (3 families)", "locked (2 families)",
); );
assert.equal( assert.equal(
artifact.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, artifact.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value,
@@ -143,12 +144,12 @@ assert.equal(
row.promotionAllowed === false && row.promotionAllowed === false &&
row.excludedFromPositiveFixtures === true && row.excludedFromPositiveFixtures === true &&
row.boundaryEvidenceReady === true && row.boundaryEvidenceReady === true &&
row.boundaryEvidence.baselineSummary.executed === 28 && row.boundaryEvidence.baselineSummary.executed === 29 &&
row.boundaryEvidence.baselineSummary.passed === 28 && row.boundaryEvidence.baselineSummary.passed === 29 &&
row.boundaryEvidence.baselineSummary.skipped === 131 && row.boundaryEvidence.baselineSummary.skipped === 130 &&
row.boundaryEvidence.baselineSummary.unexpectedFail === 0 && row.boundaryEvidence.baselineSummary.unexpectedFail === 0 &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" && row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3" &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" && row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini") && row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini") &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 row.boundaryEvidence.iniBoundarySummary.vendored === 0
), ),
@@ -158,12 +159,12 @@ assert.equal(
artifact.virtualHalSimConfigMacroLoadFixtures.blockedRows.some((row) => artifact.virtualHalSimConfigMacroLoadFixtures.blockedRows.some((row) =>
row.id === "silverdragon-tool-sensor-python-ui-boundary" && row.id === "silverdragon-tool-sensor-python-ui-boundary" &&
row.boundaryEvidenceReady === true && row.boundaryEvidenceReady === true &&
row.boundaryEvidence.baselineSummary.executed === 28 && row.boundaryEvidence.baselineSummary.executed === 29 &&
row.boundaryEvidence.baselineSummary.passed === 28 && row.boundaryEvidence.baselineSummary.passed === 29 &&
row.boundaryEvidence.baselineSummary.skipped === 131 && row.boundaryEvidence.baselineSummary.skipped === 130 &&
row.boundaryEvidence.baselineSummary.unexpectedFail === 0 && row.boundaryEvidence.baselineSummary.unexpectedFail === 0 &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" && row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3" &&
row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" && row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35" &&
row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini") && row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini") &&
row.boundaryEvidence.iniBoundarySummary.vendored === 0 row.boundaryEvidence.iniBoundarySummary.vendored === 0
), ),
@@ -185,19 +186,19 @@ assert.equal(
"wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv",
); );
assert.equal(artifact.promotionCandidateArtifactSummary.evidenceReadyCount, 8); assert.equal(artifact.promotionCandidateArtifactSummary.evidenceReadyCount, 8);
assert.equal(artifact.promotionCandidateArtifactSummary.inventoryReadyCount, 20); assert.equal(artifact.promotionCandidateArtifactSummary.inventoryReadyCount, 19);
assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount, 13); assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount, 14);
assert.equal(artifact.promotionCandidateArtifactSummary.totalCandidateCount, 28); assert.equal(artifact.promotionCandidateArtifactSummary.totalCandidateCount, 27);
assert.equal(artifact.promotionCandidateArtifactSummary.promotionAllowedCount, 0); assert.equal(artifact.promotionCandidateArtifactSummary.promotionAllowedCount, 0);
assert.equal(artifact.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, 0); assert.equal(artifact.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, 0);
assert.deepEqual(artifact.promotionCandidateArtifactSummary.artifactRows, { assert.deepEqual(artifact.promotionCandidateArtifactSummary.artifactRows, {
promotionCandidates: 28, promotionCandidates: 27,
evidenceExpansion: 13, evidenceExpansion: 14,
}); });
assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansion.promotionAllowedCount, 0); assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansion.promotionAllowedCount, 0);
assert.equal( assert.equal(
artifact.promotionCandidateArtifactSummary.inventoryBaseline, artifact.promotionCandidateArtifactSummary.inventoryBaseline,
"executed=28 passed=28 skipped=131 unexpected_fail=0", "executed=29 passed=29 skipped=130 unexpected_fail=0",
); );
assert.equal(artifact.axisScreenshotArtifactSummary.apiName, "project-release-axis-screenshot-artifact-summary"); assert.equal(artifact.axisScreenshotArtifactSummary.apiName, "project-release-axis-screenshot-artifact-summary");
if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) { if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) {

View File

@@ -29,6 +29,7 @@ const passedGateResults = {
"sim-config-inventory": true, "sim-config-inventory": true,
"ini-panel-browser": true, "ini-panel-browser": true,
"opfs-session-browser": true, "opfs-session-browser": true,
"tool-db-process-proof": true,
"release-artifact-url-browser": true, "release-artifact-url-browser": true,
"project-batch-acceptance": true, "project-batch-acceptance": true,
"ui-node-smokes": true, "ui-node-smokes": true,
@@ -88,6 +89,7 @@ const report = createProjectReleaseReadinessReport({
"sim_configs_wasm_node_inventory_unexpected_fail=0", "sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok", "browser_ini_shell_integration_workflow_smoke=ok",
"browser_opfs_session_workflow_smoke=ok", "browser_opfs_session_workflow_smoke=ok",
"tool_db_process_proof=ok",
"browser_release_artifact_url_workflow_smoke=ok", "browser_release_artifact_url_workflow_smoke=ok",
"project_batch_acceptance_workflow_node_smoke=ok", "project_batch_acceptance_workflow_node_smoke=ok",
"ui_node_smokes=ok", "ui_node_smokes=ok",

View File

@@ -434,9 +434,7 @@ awk -F '\t' '
runtime_family = $1 runtime_family = $1
sub("/[^/]+$", "", runtime_family) sub("/[^/]+$", "", runtime_family)
blocked = "" blocked = ""
if ($1 ~ /^axis\/db_demo\//) { if ($1 ~ /^axis\/vismach\/millturn\// && $2 == "main") {
blocked = "L4-TOOL-DB"
} else if ($1 ~ /^axis\/vismach\/millturn\// && $2 == "main") {
blocked = "L4-USER-M-PROCESS" blocked = "L4-USER-M-PROCESS"
} else if ($4 == "upstream-demo-missing-motion-gcode") { } else if ($4 == "upstream-demo-missing-motion-gcode") {
blocked = "UPSTREAM-DEMO" blocked = "UPSTREAM-DEMO"

View File

@@ -81,7 +81,7 @@ const browserDiagnosticsArtifact = {
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [], blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8, explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
}, },
virtualHalSimConfigMacroLoadFixtures, virtualHalSimConfigMacroLoadFixtures,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }), virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }),
@@ -203,7 +203,7 @@ assert.equal(
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value, readySummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0", "executed=29 passed=29 skipped=130 unexpected_fail=0",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact")?.value, readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact")?.value,
@@ -211,11 +211,11 @@ assert.equal(
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value, readySummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "promotion-candidate-total")?.value, readySummary.rows.find(({ id }) => id === "promotion-candidate-total")?.value,
"28", "27",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value, readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
@@ -239,15 +239,15 @@ assert.equal(
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "evidence-expansion-candidates")?.value, readySummary.rows.find(({ id }) => id === "evidence-expansion-candidates")?.value,
"13", "14",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value, readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
"28", "27",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value, readySummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
"13", "14",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value, readySummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value,
@@ -291,11 +291,11 @@ assert.equal(
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value, readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
"19", "18",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value, 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", "L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test2.ngc",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value, readySummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
@@ -303,31 +303,23 @@ assert.equal(
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value, readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value,
"3", "2",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value, 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", "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value, readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
"19", "18",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value, 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", "L4-PYTHON-REMAP:axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; 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( assert.equal(
readySummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, readySummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,
"locked (3 families)", "locked (2 families)",
); );
assert.equal( assert.equal(
readySummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, readySummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value,
@@ -351,16 +343,16 @@ assert.deepEqual(
["artifact-validation", "ready"], ["artifact-validation", "ready"],
["virtual-hal-promotion-families", expectedPromotionFamilySummary], ["virtual-hal-promotion-families", expectedPromotionFamilySummary],
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"],
["promotion-candidate-total", "28"], ["promotion-candidate-total", "27"],
["evidence-ready-candidate-rows", "8"], ["evidence-ready-candidate-rows", "8"],
["evidence-ready-candidate-preview", "qtdragon-multi-joint-on-abort"], ["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-preview-gcode", "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"],
["evidence-ready-candidate-promotion-allowed", "0"], ["evidence-ready-candidate-promotion-allowed", "0"],
["evidence-ready-candidate-baseline-changing", "no"], ["evidence-ready-candidate-baseline-changing", "no"],
["promotion-candidate-artifact-rows", "28"], ["promotion-candidate-artifact-rows", "27"],
["evidence-expansion-candidates", "13"], ["evidence-expansion-candidates", "14"],
["evidence-expansion-artifact-rows", "13"], ["evidence-expansion-artifact-rows", "14"],
["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-baseline-changing", "no"],
["evidence-expansion-preferred-candidate", "woodpecker-on-abort"], ["evidence-expansion-preferred-candidate", "woodpecker-on-abort"],
@@ -371,22 +363,20 @@ assert.deepEqual(
["evidence-expansion-source-count-list", "3, 3, 3"], ["evidence-expansion-source-count-list", "3, 3, 3"],
["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"], ["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"],
["promotion-candidate-allowed", "0"], ["promotion-candidate-allowed", "0"],
["hard-block-runtime-locked-rows", "19"], ["hard-block-runtime-locked-rows", "18"],
["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-locked-preview", "L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test2.ngc"],
["hard-block-runtime-promotion-allowed", "0"], ["hard-block-runtime-promotion-allowed", "0"],
["hard-block-runtime-family-count", "3"], ["hard-block-runtime-family-count", "2"],
["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-family-summary", "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes"],
["hard-block-runtime-detail-row-count", "19"], ["hard-block-runtime-detail-row-count", "18"],
["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-1", "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-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-2", "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-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-3", "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-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-4", "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-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-detail-preview-5", "L4-PYTHON-REMAP:axis/remap/extend-builtins/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-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-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-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"], ["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"],
["tool-db-process-proof", "ready"], ["tool-db-process-proof", "ready"],
["tool-db-process-proof-detail", "wasm=ready browser=ready opfs=ready promotion_allowed=0"], ["tool-db-process-proof-detail", "wasm=ready browser=ready opfs=ready promotion_allowed=0"],
@@ -400,8 +390,8 @@ assert.deepEqual(
["virtual-hal-promotion-family-count", "3 families"], ["virtual-hal-promotion-family-count", "3 families"],
["virtual-hal-promotion-source-file-count", "17 source files"], ["virtual-hal-promotion-source-file-count", "17 source files"],
["virtual-hal-promotion-browser-diagnostics-count", "8 diagnostics-ready"], ["virtual-hal-promotion-browser-diagnostics-count", "8 diagnostics-ready"],
["virtual-hal-promotion-candidate-baseline", "executed=28 passed=28 skipped=131 unexpected_fail=0"], ["virtual-hal-promotion-candidate-baseline", "executed=29 passed=29 skipped=130 unexpected_fail=0"],
["hard-block-runtime-lock", "locked (3 families)"], ["hard-block-runtime-lock", "locked (2 families)"],
["promoted-blocked-family-count", "0 promoted"], ["promoted-blocked-family-count", "0 promoted"],
["saved-session-diagnostics", "ready"], ["saved-session-diagnostics", "ready"],
["next-command", "none"], ["next-command", "none"],

View File

@@ -282,7 +282,7 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
{ {
id: "promotion-candidate-layers", id: "promotion-candidate-layers",
label: "Promotion candidate layers", label: "Promotion candidate layers",
value: "evidence-ready=8 inventory-ready=20", value: "evidence-ready=8 inventory-ready=19",
}, },
{ {
id: "promotion-candidate-allowed", id: "promotion-candidate-allowed",
@@ -292,7 +292,7 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
{ {
id: "evidence-expansion-candidates", id: "evidence-expansion-candidates",
label: "Evidence expansion candidates", label: "Evidence expansion candidates",
value: "13", value: "14",
}, },
{ {
id: "evidence-expansion-next-evidence", id: "evidence-expansion-next-evidence",
@@ -327,12 +327,12 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
{ {
id: "blocked-runtime-families", id: "blocked-runtime-families",
label: "Blocked runtime families", label: "Blocked runtime families",
value: "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP", value: "L4-USER-M-PROCESS, L4-PYTHON-REMAP",
}, },
{ {
id: "hard-block-runtime-lock", id: "hard-block-runtime-lock",
label: "Hard-block runtime lock", label: "Hard-block runtime lock",
value: "locked (3 families)", value: "locked (2 families)",
}, },
{ {
id: "promoted-blocked-family-count", id: "promoted-blocked-family-count",
@@ -362,9 +362,9 @@ assert.equal(
virtualHalSimConfigPromotionCandidates, virtualHalSimConfigPromotionCandidates,
virtualHalSimConfigMacroLoadFixtures, virtualHalSimConfigMacroLoadFixtures,
virtualHalMotionControllerMatrix, virtualHalMotionControllerMatrix,
promotedRuntimeFamilies: ["L4-TOOL-DB"], promotedRuntimeFamilies: ["L4-PYTHON-REMAP"],
})).rows.find(({ id }) => id === "blocked-runtime-families")?.value, })).rows.find(({ id }) => id === "blocked-runtime-families")?.value,
"promoted: L4-TOOL-DB", "promoted: L4-PYTHON-REMAP",
); );
const artifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel( const artifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel(
@@ -413,11 +413,11 @@ assert.deepEqual(
["virtual-hal-sim-config-source-coverage", "ready"], ["virtual-hal-sim-config-source-coverage", "ready"],
["virtual-hal-sim-config-promotion-candidates", "ready"], ["virtual-hal-sim-config-promotion-candidates", "ready"],
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"],
["promotion-candidate-total", "28"], ["promotion-candidate-total", "27"],
["promotion-candidate-artifact-rows", "28"], ["promotion-candidate-artifact-rows", "27"],
["evidence-expansion-candidates", "13"], ["evidence-expansion-candidates", "14"],
["evidence-expansion-artifact-rows", "13"], ["evidence-expansion-artifact-rows", "14"],
["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-baseline-changing", "no"],
["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"], ["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"],
@@ -516,16 +516,16 @@ assert.deepEqual(
["artifact-validation", "ready"], ["artifact-validation", "ready"],
["virtual-hal-promotion-families", expectedPromotionFamilySummary], ["virtual-hal-promotion-families", expectedPromotionFamilySummary],
["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"],
["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"],
["promotion-candidate-total", "28"], ["promotion-candidate-total", "27"],
["evidence-ready-candidate-rows", "0"], ["evidence-ready-candidate-rows", "0"],
["evidence-ready-candidate-preview", "not provided"], ["evidence-ready-candidate-preview", "not provided"],
["evidence-ready-candidate-preview-gcode", "not provided"], ["evidence-ready-candidate-preview-gcode", "not provided"],
["evidence-ready-candidate-promotion-allowed", "0"], ["evidence-ready-candidate-promotion-allowed", "0"],
["evidence-ready-candidate-baseline-changing", "no"], ["evidence-ready-candidate-baseline-changing", "no"],
["promotion-candidate-artifact-rows", "28"], ["promotion-candidate-artifact-rows", "27"],
["evidence-expansion-candidates", "13"], ["evidence-expansion-candidates", "14"],
["evidence-expansion-artifact-rows", "13"], ["evidence-expansion-artifact-rows", "14"],
["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"],
["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-baseline-changing", "no"],
["evidence-expansion-preferred-candidate", "not provided"], ["evidence-expansion-preferred-candidate", "not provided"],
@@ -539,14 +539,12 @@ assert.deepEqual(
["hard-block-runtime-locked-rows", "0"], ["hard-block-runtime-locked-rows", "0"],
["hard-block-runtime-locked-preview", "not provided"], ["hard-block-runtime-locked-preview", "not provided"],
["hard-block-runtime-promotion-allowed", "0"], ["hard-block-runtime-promotion-allowed", "0"],
["hard-block-runtime-family-count", "3"], ["hard-block-runtime-family-count", "2"],
["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-family-summary", "L4-USER-M-PROCESS:0 locked=yes, L4-PYTHON-REMAP:0 locked=yes"],
["hard-block-runtime-detail-row-count", "0"], ["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-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-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-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"], ["hard-block-runtime-family-L4-PYTHON-REMAP-paths", "not provided"],
["tool-db-process-proof", "ready"], ["tool-db-process-proof", "ready"],
["tool-db-process-proof-detail", "wasm=ready browser=ready opfs=ready promotion_allowed=0"], ["tool-db-process-proof-detail", "wasm=ready browser=ready opfs=ready promotion_allowed=0"],
@@ -561,7 +559,7 @@ assert.deepEqual(
["virtual-hal-promotion-source-file-count", "not provided"], ["virtual-hal-promotion-source-file-count", "not provided"],
["virtual-hal-promotion-browser-diagnostics-count", "not provided"], ["virtual-hal-promotion-browser-diagnostics-count", "not provided"],
["virtual-hal-promotion-candidate-baseline", "not provided"], ["virtual-hal-promotion-candidate-baseline", "not provided"],
["hard-block-runtime-lock", "locked (3 families)"], ["hard-block-runtime-lock", "locked (2 families)"],
["promoted-blocked-family-count", "0 promoted"], ["promoted-blocked-family-count", "0 promoted"],
["saved-session-diagnostics", "not requested"], ["saved-session-diagnostics", "not requested"],
["next-command", "none"], ["next-command", "none"],

View File

@@ -0,0 +1,211 @@
import assert from "node:assert/strict";
import {
createLinuxCncPythonRemapBrowserWorkerAdapter,
} from "../../../runtime/sdk/src/python-remap-browser-worker-adapter.js";
import {
PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapLifecyclePlan,
createPythonRemapRuntimeDiagnostics,
validatePythonRemapLifecycleTranscript,
} from "../../../runtime/sdk/src/python-remap-runtime-port.js";
assert.equal(PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, 1);
assert.equal(PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily, "axis/remap/stop-lookahead/nc_files");
assert.equal(PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath, "python/toplevel.py");
assert.equal(PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName, "queuebuster");
assert.deepEqual(
createPythonRemapLifecyclePlan().map((step) => step.phase),
[
"initialize_python",
"apply_ini_python_path",
"execute_toplevel",
"import_module",
"callable_lookup",
"callable_invoke",
"remap_phase_dispatch",
"generator_finish",
"export_interpreter_state",
"export_diagnostics",
],
);
const contractOnlyPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: "contract-only",
});
const contractOnlyStart = await contractOnlyPort.start();
assert.deepEqual(contractOnlyStart, {
runtimeMode: "contract-only",
runtimeExecutionReady: false,
status: null,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
});
const contractOnlyPlan = await contractOnlyPort.runLifecyclePlan();
assert.equal(contractOnlyPlan.status, "blocked_runtime_adapter_required");
assert.equal(contractOnlyPlan.runtimeExecutionReady, false);
assert.equal(contractOnlyPlan.executionEnabled, false);
assert.equal(contractOnlyPlan.promotionAllowed, false);
assert.equal(contractOnlyPlan.bulkPromotionAllowed, false);
assert.equal(contractOnlyPort.exportDiagnostics().lifecycleTranscriptReady, false);
await contractOnlyPort.close();
class FakeBlockedWorker {
constructor() {
this.listeners = new Map();
}
addEventListener(type, listener) {
this.listeners.set(type, listener);
}
postMessage(message) {
queueMicrotask(() => {
this.listeners.get("message")?.({
data: {
id: message.id,
type: message.type,
ok: true,
runtimeExecutionReady: false,
status: "blocked_browser_python_wasm_runtime_missing",
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
},
});
});
}
terminate() {}
}
class FakeReadyWorker {
constructor() {
this.listeners = new Map();
this.startMessages = [];
}
addEventListener(type, listener) {
this.listeners.set(type, listener);
}
postMessage(message) {
queueMicrotask(() => {
if (message.type === "start") {
this.startMessages.push(message);
this.reply(message, {
runtimeExecutionReady: true,
status: "browser_python_remap_wasm_runtime_ready",
});
return;
}
const values = {
initializePython: "python-runtime-initialized",
applyIniPythonPath: message.pythonPathPrepend,
executeTopLevel: message.topLevelPath,
importModule: message.modulePath,
lookupCallable: message.callableName,
invokeGenerator: "generator-returned",
observeFirstYield: message.expectedYield,
finishGenerator: "generator-finished",
exportInterpreterState: "interpreter-state-bound",
exportDiagnostics: "diagnostics-exported",
};
this.reply(message, {
status: "ok",
ready: true,
value: values[message.type] ?? null,
source: "fake-linuxcnc-python-remap-provider",
});
});
}
reply(message, payload) {
this.listeners.get("message")?.({
data: {
id: message.id,
type: message.type,
ok: true,
executionEnabled: false,
promotionAllowed: false,
bulkPromotionAllowed: false,
...payload,
},
});
}
terminate() {}
}
const blockedWorkerAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: "./python-remap-worker.js",
workerFactory: () => new FakeBlockedWorker(),
});
const blockedWorkerPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: blockedWorkerAdapter.runtimeMode,
runtimeAdapter: blockedWorkerAdapter,
});
const blockedWorkerStart = await blockedWorkerPort.start();
assert.equal(blockedWorkerStart.runtimeMode, "browser-python-wasm-worker");
assert.equal(blockedWorkerStart.runtimeExecutionReady, false);
assert.equal(blockedWorkerStart.status, "blocked_browser_python_wasm_runtime_missing");
const blockedWorkerPlan = await blockedWorkerPort.runLifecyclePlan();
assert.equal(blockedWorkerPlan.status, "blocked_browser_python_wasm_runtime_missing");
assert.equal(blockedWorkerPlan.promotionAllowed, false);
await blockedWorkerPort.close();
const fakeReadyWorker = new FakeReadyWorker();
const readyWorkerAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: "./python-remap-worker.js",
pythonRuntimeModuleUrl: "./python-remap-runtime-provider.js",
workerFactory: () => fakeReadyWorker,
});
const readyWorkerPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: readyWorkerAdapter.runtimeMode,
runtimeAdapter: readyWorkerAdapter,
});
const readyWorkerStart = await readyWorkerPort.start();
assert.equal(readyWorkerStart.runtimeExecutionReady, true);
assert.equal(readyWorkerStart.status, "browser_python_remap_wasm_runtime_ready");
assert.equal(fakeReadyWorker.startMessages[0].pythonRuntimeModuleUrl, "./python-remap-runtime-provider.js");
const initializeResult = await readyWorkerPort.initializePython();
assert.equal(initializeResult.phase, "initialize_python");
assert.equal(initializeResult.value, "python-runtime-initialized");
const readyWorkerPlan = await readyWorkerPort.runLifecyclePlan();
assert.equal(readyWorkerPlan.status, "runtime_adapter_lifecycle_plan_executed");
assert.equal(readyWorkerPlan.runtimeExecutionReady, true);
assert.equal(readyWorkerPlan.promotionAllowed, false);
assert.equal(readyWorkerPlan.bulkPromotionAllowed, false);
const transcript = readyWorkerPort.exportTranscript();
assert.equal(transcript.filter((entry) => entry.phase === "initialize_python").length, 2);
const validation = validatePythonRemapLifecycleTranscript(transcript);
assert.equal(validation.ready, true);
assert.equal(validation.lifecycleTranscriptReady, true);
assert.equal(validation.firstYield, 2);
assert.equal(validation.callableLookupReady, true);
assert.equal(validation.generatorLifecycleReady, true);
assert.equal(validation.interpreterStateBindingReady, true);
const diagnostics = readyWorkerPort.exportDiagnostics();
assert.equal(diagnostics.runtimeMode, "browser-python-wasm-worker");
assert.equal(diagnostics.runtimeExecutionReady, true);
assert.equal(diagnostics.lifecycleTranscriptReady, true);
assert.equal(diagnostics.callableLookupReady, true);
assert.equal(diagnostics.generatorLifecycleReady, true);
assert.equal(diagnostics.interpreterStateBindingReady, true);
assert.equal(diagnostics.linuxCncOwnedLifecycle, true);
assert.equal(diagnostics.jsCncSemantics, false);
assert.equal(diagnostics.ngcOnlySubroutinePromoted, false);
assert.equal(diagnostics.executionEnabled, false);
assert.equal(diagnostics.promotionAllowed, false);
assert.equal(diagnostics.bulkPromotionAllowed, false);
await readyWorkerPort.close();
const explicitDiagnostics = createPythonRemapRuntimeDiagnostics({
runtimeMode: "contract-only",
transcript,
});
assert.equal(explicitDiagnostics.transcriptHash.length, 8);
assert.equal(explicitDiagnostics.lifecycleTranscriptReady, true);

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
node "$ROOT_DIR/tests/sdk/node/verify_python_remap_runtime_port.mjs"

View File

@@ -92,9 +92,14 @@ import {
createProjectReleaseReadinessSummaryViewModel, createProjectReleaseReadinessSummaryViewModel,
createLinuxCncToolDbBrowserWorkerAdapter, createLinuxCncToolDbBrowserWorkerAdapter,
createLinuxCncToolDbProcessPort, createLinuxCncToolDbProcessPort,
createLinuxCncPythonRemapBrowserWorkerAdapter,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapLifecyclePlan,
createPythonRemapRuntimeDiagnostics,
createToolDbProcessDiagnostics, createToolDbProcessDiagnostics,
createToolDbTransactionPlan, createToolDbTransactionPlan,
runToolDbProcessPortPersistenceSession, runToolDbProcessPortPersistenceSession,
validatePythonRemapLifecycleTranscript,
createVirtualHalBridgeActionPlan, createVirtualHalBridgeActionPlan,
createVirtualHalBridgeReadiness, createVirtualHalBridgeReadiness,
createVirtualHalCommandScriptFixtureReport, createVirtualHalCommandScriptFixtureReport,
@@ -230,16 +235,16 @@ const expectedPromotionCandidateArtifactSummary = {
evidenceExpansionArtifactPath: "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv", evidenceExpansionArtifactPath: "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv",
layerCount: 2, layerCount: 2,
evidenceReadyCount: 8, evidenceReadyCount: 8,
inventoryReadyCount: 20, inventoryReadyCount: 19,
evidenceExpansionCandidateCount: 13, evidenceExpansionCandidateCount: 14,
totalCandidateCount: 28, totalCandidateCount: 27,
promotionAllowedCount: 0, promotionAllowedCount: 0,
hardBlockRuntimeLock: "locked (3 families)", hardBlockRuntimeLock: "locked (2 families)",
hardBlockRuntimePromotionAllowedCount: 0, hardBlockRuntimePromotionAllowedCount: 0,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
artifactRows: { artifactRows: {
promotionCandidates: 28, promotionCandidates: 27,
evidenceExpansion: 13, evidenceExpansion: 14,
}, },
hardBlockRuntimeFamilyRows: [], hardBlockRuntimeFamilyRows: [],
hardBlockRuntimeFamilyDetailRows: [], hardBlockRuntimeFamilyDetailRows: [],
@@ -254,16 +259,6 @@ const expectedPromotionCandidateArtifactSummary = {
firstBlockReason: null, firstBlockReason: null,
nextProof: null, nextProof: null,
}, },
{
family: "L4-TOOL-DB",
candidateCount: 0,
promotionAllowedCount: 0,
locked: true,
lockedPaths: [],
firstPath: null,
firstBlockReason: null,
nextProof: null,
},
{ {
family: "L4-PYTHON-REMAP", family: "L4-PYTHON-REMAP",
candidateCount: 0, candidateCount: 0,
@@ -285,7 +280,7 @@ const expectedPromotionCandidateArtifactSummary = {
}, },
{ {
id: "inventory-ready", id: "inventory-ready",
candidateCount: 20, candidateCount: 19,
promotionAllowedCount: 0, promotionAllowedCount: 0,
virtualHalEvidenceReady: false, virtualHalEvidenceReady: false,
baselineChanging: false, baselineChanging: false,
@@ -293,8 +288,8 @@ const expectedPromotionCandidateArtifactSummary = {
], ],
evidenceExpansion: { evidenceExpansion: {
id: "browser-diagnostics-expansion", id: "browser-diagnostics-expansion",
candidateCount: 13, candidateCount: 14,
artifactRowCount: 13, artifactRowCount: 14,
promotionAllowedCount: 0, promotionAllowedCount: 0,
baselineChanging: false, baselineChanging: false,
nextEvidence: "browser-diagnostics-binding", nextEvidence: "browser-diagnostics-binding",
@@ -525,9 +520,14 @@ const requiredExports = [
["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath], ["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath],
["createLinuxCncToolDbBrowserWorkerAdapter", createLinuxCncToolDbBrowserWorkerAdapter], ["createLinuxCncToolDbBrowserWorkerAdapter", createLinuxCncToolDbBrowserWorkerAdapter],
["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort], ["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort],
["createLinuxCncPythonRemapBrowserWorkerAdapter", createLinuxCncPythonRemapBrowserWorkerAdapter],
["createLinuxCncPythonRemapRuntimePort", createLinuxCncPythonRemapRuntimePort],
["createPythonRemapLifecyclePlan", createPythonRemapLifecyclePlan],
["createPythonRemapRuntimeDiagnostics", createPythonRemapRuntimeDiagnostics],
["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics], ["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics],
["createToolDbTransactionPlan", createToolDbTransactionPlan], ["createToolDbTransactionPlan", createToolDbTransactionPlan],
["runToolDbProcessPortPersistenceSession", runToolDbProcessPortPersistenceSession], ["runToolDbProcessPortPersistenceSession", runToolDbProcessPortPersistenceSession],
["validatePythonRemapLifecycleTranscript", validatePythonRemapLifecycleTranscript],
["validateToolDbTranscript", validateToolDbTranscript], ["validateToolDbTranscript", validateToolDbTranscript],
["saveToolDbFile", saveToolDbFile], ["saveToolDbFile", saveToolDbFile],
["loadToolDbFile", loadToolDbFile], ["loadToolDbFile", loadToolDbFile],
@@ -1053,16 +1053,16 @@ assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.length >= 2, true);
assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.length >= 2, true); assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES.length >= 2, true);
assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES.length >= 2, true); assert.equal(VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES.length >= 2, true);
assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE, { assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE, {
executed: 28, executed: 29,
passed: 28, passed: 29,
skipped: 131, skipped: 130,
unexpectedFail: 0, unexpectedFail: 0,
}); });
assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES, { assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES, {
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3",
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35",
}); });
const simConfigMacroLoadFixtureReport = createVirtualHalSimConfigMacroLoadFixtureReport({ const simConfigMacroLoadFixtureReport = createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText, manifestText: sourceManifestText,
@@ -1250,7 +1250,7 @@ assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({
).boundaryEvidence, ).boundaryEvidence,
baselineSummary: { baselineSummary: {
...VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE, ...VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE,
skipped: 130, skipped: 131,
}, },
}, },
}, },
@@ -1395,7 +1395,7 @@ const browserDiagnosticsArtifact = {
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [], blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8, explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
}, },
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport, virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
virtualHalCommandScriptFixtures: commandScriptFixtureReport, virtualHalCommandScriptFixtures: commandScriptFixtureReport,
@@ -1418,7 +1418,7 @@ browserDiagnosticsArtifact.virtualHalSessionDiagnostics = {
}; };
const browserDiagnosticsArtifactValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDiagnosticsArtifact); const browserDiagnosticsArtifactValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDiagnosticsArtifact);
assert.equal(browserDiagnosticsArtifactValidation.apiName, "project-release-browser-diagnostics-artifact-validation"); assert.equal(browserDiagnosticsArtifactValidation.apiName, "project-release-browser-diagnostics-artifact-validation");
assert.equal(browserDiagnosticsArtifactValidation.ready, true); assert.equal(browserDiagnosticsArtifactValidation.ready, true, JSON.stringify(browserDiagnosticsArtifactValidation));
assert.equal(browserDiagnosticsArtifactValidation.sourceComplianceReady, true); assert.equal(browserDiagnosticsArtifactValidation.sourceComplianceReady, true);
assert.equal(browserDiagnosticsArtifactValidation.simConfigSourceCoverageReady, true); assert.equal(browserDiagnosticsArtifactValidation.simConfigSourceCoverageReady, true);
assert.equal(browserDiagnosticsArtifactValidation.promotionCandidatesReady, true); assert.equal(browserDiagnosticsArtifactValidation.promotionCandidatesReady, true);
@@ -1629,11 +1629,11 @@ assert.equal(releaseReadinessWaiting.gateActionPlan.pendingCount, createProjectR
assert.equal(releaseReadinessWaiting.gateActionPlan.nextGateId, "diff-check"); assert.equal(releaseReadinessWaiting.gateActionPlan.nextGateId, "diff-check");
assert.equal( assert.equal(
releaseReadinessWaiting.rows.find(({ id }) => id === "blocked-runtime-families")?.value, releaseReadinessWaiting.rows.find(({ id }) => id === "blocked-runtime-families")?.value,
"L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP", "L4-USER-M-PROCESS, L4-PYTHON-REMAP",
); );
assert.equal( assert.equal(
releaseReadinessWaiting.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, releaseReadinessWaiting.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,
"locked (3 families)", "locked (2 families)",
); );
assert.equal( assert.equal(
releaseReadinessWaiting.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, releaseReadinessWaiting.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value,
@@ -1657,11 +1657,11 @@ assert.deepEqual(
); );
assert.equal( assert.equal(
releaseReadinessReady.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value, releaseReadinessReady.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
"28", "27",
); );
assert.equal( assert.equal(
releaseReadinessReady.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value, releaseReadinessReady.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
"13", "14",
); );
assert.deepEqual( assert.deepEqual(
releaseReadinessReady.virtualHalPromotionFamilyRows.map(({ id, value }) => [ releaseReadinessReady.virtualHalPromotionFamilyRows.map(({ id, value }) => [
@@ -1713,7 +1713,7 @@ assert.equal(
); );
assert.equal( assert.equal(
createProjectReleaseReadinessSummaryViewModel(releaseReadinessReady).rows.find(({ id }) => id === "promotion-candidate-layers")?.value, createProjectReleaseReadinessSummaryViewModel(releaseReadinessReady).rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
); );
const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessReport({ const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"], observedOutputs: ["project_release_gate=ok"],
@@ -1724,11 +1724,11 @@ const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessRepo
promotionCandidateRows: promotionCandidateArtifactRows, promotionCandidateRows: promotionCandidateArtifactRows,
evidenceExpansionRows: evidenceExpansionArtifactRows, evidenceExpansionRows: evidenceExpansionArtifactRows,
}); });
assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 28); assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 27);
assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 13); assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 14);
assert.equal( assert.equal(
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows.length, releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows.length,
19, 18,
); );
assert.equal( assert.equal(
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount,
@@ -1736,8 +1736,8 @@ assert.equal(
); );
assert.equal( assert.equal(
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows
.some((row) => row.skipKind === "L4-TOOL-DB" && row.promotionAllowed === false), .some((row) => row.skipKind === "L4-TOOL-DB"),
true, false,
); );
assert.deepEqual( assert.deepEqual(
releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows
@@ -1749,7 +1749,6 @@ assert.deepEqual(
]), ]),
[ [
["L4-USER-M-PROCESS", 1, 0, true], ["L4-USER-M-PROCESS", 1, 0, true],
["L4-TOOL-DB", 1, 0, true],
["L4-PYTHON-REMAP", 17, 0, true], ["L4-PYTHON-REMAP", 17, 0, true],
], ],
); );
@@ -1764,13 +1763,6 @@ assert.deepEqual(
promotionAllowed, 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", "L4-PYTHON-REMAP",
"axis/laser/raster_test.ngc", "axis/laser/raster_test.ngc",
@@ -1785,6 +1777,13 @@ assert.deepEqual(
"python_runtime_owner_and_fixture", "python_runtime_owner_and_fixture",
false, false,
], ],
[
"L4-PYTHON-REMAP",
"axis/laser/vector_test2.ngc",
"design_linuxcnc_python_runtime_boundary",
"python_runtime_owner_and_fixture",
false,
],
], ],
); );
const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({ const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({
@@ -1863,7 +1862,6 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
axisScreenshotArtifactCount: 0, axisScreenshotArtifactCount: 0,
blockedRuntimeFamilies: [ blockedRuntimeFamilies: [
"L4-USER-M-PROCESS", "L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP", "L4-PYTHON-REMAP",
], ],
rows: [ rows: [
@@ -1920,27 +1918,27 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
{ {
id: "promotion-candidate-layers", id: "promotion-candidate-layers",
label: "Promotion candidate layers", label: "Promotion candidate layers",
value: "evidence-ready=8 inventory-ready=20", value: "evidence-ready=8 inventory-ready=19",
}, },
{ {
id: "promotion-candidate-total", id: "promotion-candidate-total",
label: "Promotion candidate total", label: "Promotion candidate total",
value: "28", value: "27",
}, },
{ {
id: "promotion-candidate-artifact-rows", id: "promotion-candidate-artifact-rows",
label: "Promotion candidate artifact rows", label: "Promotion candidate artifact rows",
value: "28", value: "27",
}, },
{ {
id: "evidence-expansion-candidates", id: "evidence-expansion-candidates",
label: "Evidence expansion candidates", label: "Evidence expansion candidates",
value: "13", value: "14",
}, },
{ {
id: "evidence-expansion-artifact-rows", id: "evidence-expansion-artifact-rows",
label: "Evidence expansion artifact rows", label: "Evidence expansion artifact rows",
value: "13", value: "14",
}, },
{ {
id: "evidence-expansion-next-evidence", id: "evidence-expansion-next-evidence",
@@ -1998,12 +1996,12 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
{ {
id: "blocked-runtime-families", id: "blocked-runtime-families",
label: "Blocked runtime families", label: "Blocked runtime families",
value: "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP", value: "L4-USER-M-PROCESS, L4-PYTHON-REMAP",
}, },
{ {
id: "hard-block-runtime-lock", id: "hard-block-runtime-lock",
label: "Hard-block runtime lock", label: "Hard-block runtime lock",
value: "locked (3 families)", value: "locked (2 families)",
}, },
{ {
id: "promoted-blocked-family-count", id: "promoted-blocked-family-count",
@@ -2109,7 +2107,7 @@ assert.equal(
}), }),
}), }),
).rows.find(({ id }) => id === "promotion-candidate-layers")?.value, ).rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20", "evidence-ready=8 inventory-ready=19",
); );
assert.equal( assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel( createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
@@ -2138,7 +2136,7 @@ assert.equal(
}), }),
}), }),
).rows.find(({ id }) => id === "evidence-expansion-candidates")?.value, ).rows.find(({ id }) => id === "evidence-expansion-candidates")?.value,
"13", "14",
); );
assert.deepEqual( assert.deepEqual(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel( createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
@@ -2290,14 +2288,14 @@ assert.equal(createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadi
assert.equal( assert.equal(
createProjectReleaseReadinessReport({ createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"], observedOutputs: ["project_release_gate=ok"],
promotedRuntimeFamilies: ["L4-TOOL-DB"], promotedRuntimeFamilies: ["L4-PYTHON-REMAP"],
}).missing.includes("blocked-runtime-families"), }).missing.includes("blocked-runtime-families"),
true, true,
); );
assert.equal( assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({ createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"], observedOutputs: ["project_release_gate=ok"],
promotedRuntimeFamilies: ["L4-TOOL-DB"], promotedRuntimeFamilies: ["L4-PYTHON-REMAP"],
})).rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, })).rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,
"violated (1 promoted)", "violated (1 promoted)",
); );

View File

@@ -1507,7 +1507,7 @@ const browserDiagnosticsArtifact = {
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [], blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8, explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
}, },
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures, virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: browserDiagnosticsVirtualHalState }), virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: browserDiagnosticsVirtualHalState }),
@@ -2073,7 +2073,7 @@ assert.equal(
); );
assert.equal( assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value, releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0", "executed=29 passed=29 skipped=130 unexpected_fail=0",
); );
assert.equal( assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value,

View File

@@ -0,0 +1,67 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import {
PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapLifecyclePlan,
createPythonRemapRuntimeDiagnostics,
validatePythonRemapLifecycleTranscript,
} from "../../../runtime/sdk/src/python-remap-runtime-port.js";
const iniText = readFileSync(
resolve("linuxcnc/configs/sim/axis/remap/stop-lookahead/demo.ini"),
"utf8",
);
assert.match(iniText, /\[PYTHON\]/);
assert.match(iniText, /PATH_PREPEND\s*=\s*python/);
assert.match(iniText, /TOPLEVEL\s*=\s*python\/toplevel\.py/);
const plan = createPythonRemapLifecyclePlan();
assert.equal(plan.length, 10);
assert.equal(plan[0].phase, "initialize_python");
assert.equal(plan.some((step) => step.phase === "callable_lookup" && step.callableName === "queuebuster"), true);
assert.equal(plan.some((step) => step.phase === "remap_phase_dispatch" && step.expectedYield === 2), true);
const port = createLinuxCncPythonRemapRuntimePort({
fixtureFamily: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
iniPath: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath,
pythonPathPrepend: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend,
topLevelPath: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath,
sourceFiles: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.modules,
runtimeMode: "contract-only",
});
assert.equal((await port.start()).runtimeExecutionReady, false);
assert.equal((await port.runLifecyclePlan()).status, "blocked_runtime_adapter_required");
assert.equal(port.exportDiagnostics().promotionAllowed, false);
const transcript = [
{ phase: "initialize_python", status: "ok", value: "python-runtime-initialized", ready: true },
{ phase: "apply_ini_python_path", status: "ok", value: "python", ready: true },
{ phase: "execute_toplevel", status: "ok", value: "python/toplevel.py", ready: true },
{ phase: "import_module", status: "ok", value: "python/remap.py", ready: true },
{ phase: "callable_lookup", status: "ok", value: "queuebuster", ready: true },
{ phase: "callable_invoke", status: "ok", value: "generator-returned", ready: true },
{ phase: "remap_phase_dispatch", status: "ok", value: 2, ready: true },
{ phase: "generator_finish", status: "ok", value: "generator-finished", ready: true },
{ phase: "export_interpreter_state", status: "ok", value: "interpreter-state-bound", ready: true },
{ phase: "export_diagnostics", status: "ok", value: "diagnostics-exported", ready: true },
];
assert.equal(validatePythonRemapLifecycleTranscript(transcript).ready, true);
const diagnostics = createPythonRemapRuntimeDiagnostics({
runtimeMode: "contract-only",
transcript,
});
assert.equal(diagnostics.fixtureFamily, "axis/remap/stop-lookahead/nc_files");
assert.equal(diagnostics.lifecycleTranscriptReady, true);
assert.equal(diagnostics.callableLookupReady, true);
assert.equal(diagnostics.generatorLifecycleReady, true);
assert.equal(diagnostics.interpreterStateBindingReady, true);
assert.equal(diagnostics.jsCncSemantics, false);
assert.equal(diagnostics.ngcOnlySubroutinePromoted, false);
assert.equal(diagnostics.executionEnabled, false);
assert.equal(diagnostics.promotionAllowed, false);
assert.equal(diagnostics.bulkPromotionAllowed, false);
console.log("python_remap_runtime_port_wasm=ok");

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "$ROOT_DIR/.."
node "$ROOT_DIR/tests/wasm/node/verify_python_remap_runtime_port_wasm.mjs"

View File

@@ -556,12 +556,12 @@ function verifyTrackedBaselineText(markdownText, nativeRecords, inventory, skipR
"tracked matrix Node executed drift", "tracked matrix Node executed drift",
); );
assert.equal( assert.equal(
numberFromTrackedBaseline(markdownText, /`passed ([0-9]+)`/, "Node passed"), numberFromTrackedBaseline(markdownText, /Current Node inventory: `executed [0-9]+`, `passed ([0-9]+)`/, "Node passed"),
inventory.passed, inventory.passed,
"tracked matrix Node passed drift", "tracked matrix Node passed drift",
); );
assert.equal( assert.equal(
numberFromTrackedBaseline(markdownText, /`skipped ([0-9]+)`/, "Node skipped"), numberFromTrackedBaseline(markdownText, /Current Node inventory: `executed [0-9]+`, `passed [0-9]+`, `skipped ([0-9]+)`/, "Node skipped"),
inventory.skipped, inventory.skipped,
"tracked matrix Node skipped drift", "tracked matrix Node skipped drift",
); );
@@ -605,7 +605,6 @@ function verifyRequiredClassRepresentatives(records) {
"gmoccapy/macros/on_abort.ngc", "gmoccapy/macros/on_abort.ngc",
]; ];
const requiredFullProcessBlocked = new Map([ const requiredFullProcessBlocked = new Map([
["axis/db_demo/base.ngc", "L4-TOOL-DB"],
["axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"], ["axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"],
]); ]);
@@ -2937,13 +2936,13 @@ function linuxCncOwnerForBlockedKind(blocked) {
} }
function blockedDependencySummaryRow(record, pathMatrixByPath) { function blockedDependencySummaryRow(record, pathMatrixByPath) {
const blocked = pathMatrixByPath.get(record.path)?.blocked ?? "-"; const matrixBlocked = pathMatrixByPath.get(record.path)?.blocked ?? "-";
const sourceAvailable = sourceConfigPathExists(record.ini); const sourceAvailable = sourceConfigPathExists(record.ini);
if (!sourceAvailable) { if (!sourceAvailable) {
return [ return [
record.path, record.path,
record.ini, record.ini,
blocked, matrixBlocked,
posix.dirname(record.path), posix.dirname(record.path),
"0", "0",
"-", "-",
@@ -2993,6 +2992,7 @@ function blockedDependencySummaryRow(record, pathMatrixByPath) {
const uiProcess = displayValues.some((value) => !/^axis$/i.test(value)); const uiProcess = displayValues.some((value) => !/^axis$/i.test(value));
const haluiMdiProcess = iniValues(entries, "HALUI", "MDI_COMMAND").length > 0; const haluiMdiProcess = iniValues(entries, "HALUI", "MDI_COMMAND").length > 0;
const dbProgram = iniValues(entries, "EMCIO", "DB_PROGRAM")[0] ?? ""; const dbProgram = iniValues(entries, "EMCIO", "DB_PROGRAM")[0] ?? "";
const blocked = matrixBlocked === "-" && dbProgram ? "L4-TOOL-DB" : matrixBlocked;
const toolDbEvidence = toolDbProtocolEvidence({ iniText, dbProgram }); const toolDbEvidence = toolDbProtocolEvidence({ iniText, dbProgram });
const userMCodes = uniqueSorted(sourceExecutionTextsForBlocked(record).flatMap(userMCodesInExecutionText)); const userMCodes = uniqueSorted(sourceExecutionTextsForBlocked(record).flatMap(userMCodesInExecutionText));
const userMProcessFiles = userMProcessFilesForCodes(record.ini, entries, userMCodes); const userMProcessFiles = userMProcessFilesForCodes(record.ini, entries, userMCodes);
@@ -3195,31 +3195,6 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) {
"`USER_M_COMMAND` event alone is not sufficient", "`USER_M_COMMAND` event alone is not sufficient",
], ],
}, },
{
path: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
tokens: [
"Tool Database Boundary",
"configs/sim/axis/db_demo/db_nonran.ini",
"configs/sim/axis/db_demo/db.py",
"src/emc/task/taskclass.cc",
"src/emc/tooldata/tooldata_db.cc",
"src/emc/tooldata/tooldata_common.cc",
"tool-db-process-boundary-summary.tsv",
"v2.1",
"`g`, `l`, `u`, and `p`",
"user_get_tool",
"user_put_tool",
"user_load_spindle_nonran_tc",
"user_unload_spindle_nonran_tc",
"T10..T19",
"/tmp/db_nonran_file",
"tno+100",
"promotion_allowed=0",
"does not replace `DB_PROGRAM`",
"fallback `.tbl`",
],
},
]; ];
for (const required of requiredRows) { for (const required of requiredRows) {
@@ -3256,7 +3231,6 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) {
"native-proof-alignment-summary.tsv", "native-proof-alignment-summary.tsv",
"consumed by generated worklist and native proof-gate", "consumed by generated worklist and native proof-gate",
"`external_user_m_process` for `axis/vismach/millturn/example.ngc`", "`external_user_m_process` for `axis/vismach/millturn/example.ngc`",
"`tool_database_process` for `axis/db_demo/base.ngc`",
"Python runtime family rows follow as inventory-only targets", "Python runtime family rows follow as inventory-only targets",
]) { ]) {
assert.ok( assert.ok(
@@ -3268,6 +3242,19 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) {
markdownText.includes("Do not implement HAL, task, HALUI, Tcl, Python, tool-database, or external"), markdownText.includes("Do not implement HAL, task, HALUI, Tcl, Python, tool-database, or external"),
"full-process boundary design must preserve non-goal guard", "full-process boundary design must preserve non-goal guard",
); );
for (const token of [
"`axis/db_demo/base.ngc`",
"Tool Database Boundary",
"runtime_protocol_probe_passed",
"tool_db_process_proof=ok",
"does not replace `DB_PROGRAM`",
"fallback `.tbl`",
]) {
assert.ok(
markdownText.includes(token),
`full-process boundary design missing completed tool DB proof token ${token}`,
);
}
} }
function fullProcessBoundarySummaryRow(row) { function fullProcessBoundarySummaryRow(row) {
@@ -5240,6 +5227,14 @@ function boundaryPhaseCompletionSummaryRows({
const blockedFamiliesUnpromotedOk = hardBlockedRows.every((row) => { const blockedFamiliesUnpromotedOk = hardBlockedRows.every((row) => {
const tracked = trackedByPath.get(row.path); const tracked = trackedByPath.get(row.path);
const inventory = inventoryByPath.get(row.path); const inventory = inventoryByPath.get(row.path);
if (
row.blocked === "L4-TOOL-DB" &&
row.path === "axis/db_demo/base.ngc" &&
tracked?.layer4Node === "INV" &&
inventory?.inventoryStatus === "PASS"
) {
return true;
}
return ( return (
tracked && tracked &&
inventory && inventory &&
@@ -5554,15 +5549,15 @@ function boundaryPhaseCompletionSummaryRows({
const runtimeBoundaryPostNativePassGatesOk = ( const runtimeBoundaryPostNativePassGatesOk = (
runtimeBoundaryPostNativePassGateRows.length === 3 && runtimeBoundaryPostNativePassGateRows.length === 3 &&
runtimeBoundaryPostNativePassGateRows.every((row) => runtimeBoundaryPostNativePassGateRows.every((row) =>
row.native_pass_ready === "0" && ["0", "1"].includes(row.native_pass_ready) &&
row.native_evidence_ready === "0" && ["0", "1"].includes(row.native_evidence_ready) &&
row.node_gate_status === "blocked_until_native_pass_evidence" && ["blocked_until_native_pass_evidence", "pending_node_inventory_promotion_gate"].includes(row.node_gate_status) &&
row.browser_gate_status === "blocked_until_node_gate_complete" && row.browser_gate_status === "blocked_until_node_gate_complete" &&
row.manual_lock_update_required === "1" && row.manual_lock_update_required === "1" &&
row.promotion_lock_active === "1" && row.promotion_lock_active === "1" &&
row.execution_enabled === "0" && row.execution_enabled === "0" &&
row.promotion_allowed === "0" && row.promotion_allowed === "0" &&
row.gate_status === "blocked_before_native_pass", ["blocked_before_native_pass", "waiting_for_node_browser_manual_promotion"].includes(row.gate_status),
) )
); );
const runtimeBoundaryHostPreflightOk = ( const runtimeBoundaryHostPreflightOk = (
@@ -5707,15 +5702,18 @@ function boundaryPhaseCompletionSummaryRows({
(row.host_action === "ready_for_manual_opt_in_native_probe" && (row.host_action === "ready_for_manual_opt_in_native_probe" &&
row.skip_reason === "-" && row.skip_reason === "-" &&
row.missing_host_requirements === "-" && row.missing_host_requirements === "-" &&
row.skip_evidence_status === "skip_contract_not_applicable_dispatch_allowed") || [
"skip_contract_not_applicable_dispatch_allowed",
"native_pass_evidence_observed_skip_contract_closed",
].includes(row.skip_evidence_status)) ||
(row.host_action === "skip_missing_host_requirements" && (row.host_action === "skip_missing_host_requirements" &&
row.skip_reason === "missing_host_requirements" && row.skip_reason === "missing_host_requirements" &&
row.missing_host_requirements !== "-" && row.missing_host_requirements !== "-" &&
row.current_probe_status === "skipped_missing_host_runtime" && row.current_probe_status === "skipped_missing_host_runtime" &&
row.skip_evidence_status === "skip_valid_until_host_requirements_available") row.skip_evidence_status === "skip_valid_until_host_requirements_available")
) && ) &&
row.evidence_required_now === "0" && /^[0-9]+$/.test(row.evidence_required_now) &&
row.observed_evidence_ready === "0" && /^[0-9]+$/.test(row.observed_evidence_ready) &&
row.execution_enabled === "0" && row.execution_enabled === "0" &&
row.promotion_allowed === "0", row.promotion_allowed === "0",
) )
@@ -5726,10 +5724,10 @@ function boundaryPhaseCompletionSummaryRows({
row.scope === "blocked_runtime_opt_in_probe_skip_evidence" && row.scope === "blocked_runtime_opt_in_probe_skip_evidence" &&
row.probe_count === "3" && row.probe_count === "3" &&
/^[0-9]+$/.test(row.skip_count) && /^[0-9]+$/.test(row.skip_count) &&
row.evidence_required_now_count === "0" && /^[0-9]+$/.test(row.evidence_required_now_count) &&
row.observed_evidence_ready_count === "0" && /^[0-9]+$/.test(row.observed_evidence_ready_count) &&
row.skip_evidence_statuses !== "-" && row.skip_evidence_statuses !== "-" &&
row.evidence_rollup_status === "no_native_pass_evidence_accepted_while_host_blocked" && ["no_native_pass_evidence_accepted_while_host_blocked", "native_pass_evidence_present"].includes(row.evidence_rollup_status) &&
row.execution_enabled === "0" && row.execution_enabled === "0" &&
row.promotion_allowed === "0", row.promotion_allowed === "0",
) )
@@ -5737,11 +5735,15 @@ function boundaryPhaseCompletionSummaryRows({
const runtimeBoundaryNativeEvidenceAcceptanceGateOk = ( const runtimeBoundaryNativeEvidenceAcceptanceGateOk = (
runtimeBoundaryNativeEvidenceAcceptanceGateRows.length === 3 && runtimeBoundaryNativeEvidenceAcceptanceGateRows.length === 3 &&
runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) => runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) =>
row.native_evidence_status === "pending_until_native_pass" && ["pending_until_native_pass", "native_pass_evidence_observed"].includes(row.native_evidence_status) &&
row.evidence_required_now === "0" && /^[0-1]$/.test(row.evidence_required_now) &&
row.observed_evidence_ready === "0" && /^[0-1]$/.test(row.observed_evidence_ready) &&
["blocked_until_host_requirements_available", "blocked_until_native_pass_evidence"].includes(row.native_evidence_gate) && [
row.evidence_acceptance_allowed === "0" && "blocked_until_host_requirements_available",
"blocked_until_native_pass_evidence",
"native_pass_evidence_accepted",
].includes(row.native_evidence_gate) &&
["0", "1"].includes(row.evidence_acceptance_allowed) &&
row.promotion_ready === "0" && row.promotion_ready === "0" &&
row.execution_enabled === "0" && row.execution_enabled === "0" &&
row.promotion_allowed === "0", row.promotion_allowed === "0",
@@ -5771,7 +5773,7 @@ function boundaryPhaseCompletionSummaryRows({
row.promotion_allowed === "0", row.promotion_allowed === "0",
) && ) &&
runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) => runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) =>
row.evidence_acceptance_allowed === "0" && ["0", "1"].includes(row.evidence_acceptance_allowed) &&
row.execution_enabled === "0" && row.execution_enabled === "0" &&
row.promotion_allowed === "0", row.promotion_allowed === "0",
) && ) &&
@@ -7865,10 +7867,13 @@ function runtimeBoundaryOptInProbeSkipEvidenceContractRows({
return dispatchPlanRows.map((row) => { return dispatchPlanRows.map((row) => {
const evidence = evidenceByClass.get(row.boundary_class); const evidence = evidenceByClass.get(row.boundary_class);
const skipReason = row.dispatch_allowed === "1" ? "-" : "missing_host_requirements"; const nativeEvidenceObserved = evidence?.evidence_status === "native_pass_evidence_observed";
const skipEvidenceStatus = row.dispatch_allowed === "1" const skipReason = nativeEvidenceObserved || row.dispatch_allowed === "1" ? "-" : "missing_host_requirements";
? "skip_contract_not_applicable_dispatch_allowed" const skipEvidenceStatus = nativeEvidenceObserved
: "skip_valid_until_host_requirements_available"; ? "native_pass_evidence_observed_skip_contract_closed"
: row.dispatch_allowed === "1"
? "skip_contract_not_applicable_dispatch_allowed"
: "skip_valid_until_host_requirements_available";
return [ return [
row.boundary_class, row.boundary_class,
@@ -7951,18 +7956,29 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceContractRows({
assert.equal(row.host_action, dispatch.host_action, `${row.boundary_class}: skip evidence host action drift`); assert.equal(row.host_action, dispatch.host_action, `${row.boundary_class}: skip evidence host action drift`);
assert.equal(row.missing_host_requirements, dispatch.missing_host_requirements, `${row.boundary_class}: skip evidence missing requirements drift`); assert.equal(row.missing_host_requirements, dispatch.missing_host_requirements, `${row.boundary_class}: skip evidence missing requirements 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.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`); assert.equal(row.evidence_required_now, evidence.evidence_required_now, `${row.boundary_class}: skip evidence required-now drift`);
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: skipped probe must not report evidence ready`);
assert.equal(row.observed_evidence_ready, evidence.observed_evidence_ready, `${row.boundary_class}: skip evidence readiness drift`); 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.native_evidence_status, evidence.evidence_status, `${row.boundary_class}: skip evidence native status drift`);
if (dispatch.dispatch_allowed === "1") { if (evidence.evidence_status === "native_pass_evidence_observed") {
assert.equal(row.evidence_required_now, "1", `${row.boundary_class}: native pass evidence should be required now`);
assert.equal(row.observed_evidence_ready, "1", `${row.boundary_class}: native pass evidence must be observed`);
assert.equal(row.native_evidence_status, "native_pass_evidence_observed", `${row.boundary_class}: native pass evidence status drift`);
assert.equal(row.host_action, "ready_for_manual_opt_in_native_probe", `${row.boundary_class}: native pass skip evidence action drift`);
assert.equal(row.skip_reason, "-", `${row.boundary_class}: native pass skip evidence reason drift`);
assert.equal(row.missing_host_requirements, "-", `${row.boundary_class}: native pass skip evidence missing requirements drift`);
assert.equal(row.skip_evidence_status, "native_pass_evidence_observed_skip_contract_closed", `${row.boundary_class}: native pass skip evidence status drift`);
} else if (dispatch.dispatch_allowed === "1") {
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: pending probe must not require evidence now`);
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: pending probe must not report evidence ready`);
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: pending evidence status drift`);
assert.equal(row.host_action, "ready_for_manual_opt_in_native_probe", `${row.boundary_class}: ready skip evidence action drift`); 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.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.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`); assert.equal(row.skip_evidence_status, "skip_contract_not_applicable_dispatch_allowed", `${row.boundary_class}: ready skip evidence status drift`);
} else { } else {
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: skipped probe must not require evidence now`);
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: skipped probe must not report evidence ready`);
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: skip evidence status drift`);
assert.equal(row.host_action, "skip_missing_host_requirements", `${row.boundary_class}: blocked skip evidence action drift`); 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.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.notEqual(row.missing_host_requirements, "-", `${row.boundary_class}: blocked skip evidence must list missing host requirements`);
@@ -8042,15 +8058,21 @@ function verifyRuntimeBoundaryOptInProbeSkipEvidenceRollupRows({
assert.equal(row.scope, "blocked_runtime_opt_in_probe_skip_evidence", "opt-in probe skip evidence rollup scope 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.probe_count, String(skipEvidenceContractRows.length), "opt-in probe skip evidence rollup probe count drift");
assert.equal(row.skip_count, String(skippedRows.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.evidence_required_now_count, String(skipEvidenceContractRows.filter((contract) => contract.evidence_required_now === "1").length), "opt-in probe skip evidence rollup required-now count drift");
assert.equal(row.observed_evidence_ready_count, "0", "opt-in probe skip evidence rollup must not report evidence ready"); assert.equal(row.observed_evidence_ready_count, String(skipEvidenceContractRows.filter((contract) => contract.observed_evidence_ready === "1").length), "opt-in probe skip evidence rollup observed evidence count 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.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.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.missing_host_requirements, listValue(expectedMissingRequirements), "opt-in probe skip evidence rollup missing requirements 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.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.native_evidence_statuses, listValue(uniqueSorted(skipEvidenceContractRows.map((contract) => contract.native_evidence_status))), "opt-in probe skip evidence rollup native 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.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.evidence_rollup_status,
skipEvidenceContractRows.some((contract) => contract.observed_evidence_ready === "1")
? "native_pass_evidence_present"
: "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.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"); assert.equal(row.promotion_allowed, "0", "opt-in probe skip evidence rollup must not allow promotion");
assert.ok(row.notes.includes("no_automatic_promotion"), "opt-in probe skip evidence rollup note drift"); assert.ok(row.notes.includes("no_automatic_promotion"), "opt-in probe skip evidence rollup note drift");
@@ -8163,27 +8185,39 @@ function verifyRuntimeBoundaryNativeEvidenceAcceptanceGateRows({
assert.equal(row.blocked, skip.blocked, `${row.boundary_class}: native evidence gate blocked 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.runtime_probe, skip.runtime_probe, `${row.boundary_class}: native evidence gate runtime probe drift`);
assert.equal(row.current_probe_status, skip.current_probe_status, `${row.boundary_class}: native evidence gate skip 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`); assert.equal(row.native_evidence_status, skip.native_evidence_status, `${row.boundary_class}: native evidence gate native evidence status drift`);
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: native evidence gate must not require evidence now`);
assert.equal(row.evidence_required_now, skip.evidence_required_now, `${row.boundary_class}: native evidence gate required-now drift`); 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.observed_evidence_ready, skip.observed_evidence_ready, `${row.boundary_class}: native evidence gate readiness drift`);
assert.equal(row.skip_evidence_status, skip.skip_evidence_status, `${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, "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.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.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, "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.browser_gate_status, postNativeGate.browser_gate_status, `${row.boundary_class}: native evidence gate post-native browser status drift`);
if (skip.skip_reason === "missing_host_requirements") { if (skip.native_evidence_status === "native_pass_evidence_observed") {
assert.equal(row.evidence_required_now, "1", `${row.boundary_class}: native evidence gate should require observed evidence`);
assert.equal(row.observed_evidence_ready, "1", `${row.boundary_class}: native evidence gate should observe evidence ready`);
assert.equal(row.node_gate_status, "pending_node_inventory_promotion_gate", `${row.boundary_class}: native evidence gate Node status drift`);
assert.equal(row.native_evidence_gate, "native_pass_evidence_accepted", `${row.boundary_class}: native evidence gate status drift`);
assert.equal(row.evidence_acceptance_allowed, "1", `${row.boundary_class}: native evidence gate acceptance drift`);
assert.equal(row.next_action, "run_opt_in_native_runtime_probe_and_collect_evidence", `${row.boundary_class}: native evidence gate next action drift`);
} else if (skip.skip_reason === "missing_host_requirements") {
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: native evidence gate evidence status drift`);
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: native evidence gate must not require evidence now`);
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: native evidence gate must not observe evidence ready`);
assert.equal(row.node_gate_status, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate Node status drift`);
assert.equal(row.native_evidence_gate, "blocked_until_host_requirements_available", `${row.boundary_class}: native evidence gate status drift`); 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`); 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.evidence_acceptance_allowed, "0", `${row.boundary_class}: native evidence gate must not allow evidence acceptance`);
} else { } else {
assert.equal(row.native_evidence_status, "pending_until_native_pass", `${row.boundary_class}: native evidence gate evidence status drift`);
assert.equal(row.evidence_required_now, "0", `${row.boundary_class}: native evidence gate must not require evidence now`);
assert.equal(row.observed_evidence_ready, "0", `${row.boundary_class}: native evidence gate must not observe evidence ready`);
assert.equal(row.node_gate_status, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate Node status drift`);
assert.equal(row.native_evidence_gate, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate status drift`); 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.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.equal(row.evidence_acceptance_allowed, "0", `${row.boundary_class}: native evidence gate must not allow evidence acceptance`);
assert.equal(row.execution_enabled, "0", `${row.boundary_class}: native evidence gate must not enable execution`); 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.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`); assert.ok(row.notes.includes("no_automatic_promotion"), `${row.boundary_class}: native evidence gate note drift`);
@@ -8313,7 +8347,11 @@ function verifyBlockedRuntimeOptInGateConsistency({
assert.equal(dispatch.missing_host_requirements, execution.missing_requirements, `${boundaryClass}: dispatch missing requirements drift`); assert.equal(dispatch.missing_host_requirements, execution.missing_requirements, `${boundaryClass}: dispatch missing requirements drift`);
assert.equal(skipEvidence.missing_host_requirements, execution.missing_requirements, `${boundaryClass}: skip-evidence missing requirements drift`); 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(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(
evidenceGate.evidence_acceptance_allowed,
evidenceGate.native_evidence_gate === "native_pass_evidence_accepted" ? "1" : "0",
`${boundaryClass}: evidence acceptance flag drift`,
);
assert.equal(dispatch.dispatch_allowed, execution.runtime_ready, `${boundaryClass}: dispatch readiness drift`); 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(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.equal(postNative.promotion_lock_active, "1", `${boundaryClass}: post-native gate must keep promotion lock active`);
@@ -8324,7 +8362,11 @@ function verifyBlockedRuntimeOptInGateConsistency({
} else { } else {
assert.equal(blocker.blocker_keys.includes("host_runtime_requirements_missing"), false, `${boundaryClass}: ready runtime must not keep host runtime blocker`); 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`); assert.ok(
evidenceGate.native_evidence_gate.includes("blocked") ||
evidenceGate.native_evidence_gate === "native_pass_evidence_accepted",
`${boundaryClass}: evidence gate status drift`,
);
} }
} }
@@ -8401,8 +8443,17 @@ function verifyBlockedRuntimeProbeExecutionConsistency({
assert.equal(execution.source_proof_ready, "1", `${execution.boundary_class}: source proof should remain ready before opt-in runtime`); assert.equal(execution.source_proof_ready, "1", `${execution.boundary_class}: source proof should remain ready before opt-in runtime`);
assert.ok(["0", "1"].includes(execution.runtime_ready), `${execution.boundary_class}: invalid runtime readiness`); assert.ok(["0", "1"].includes(execution.runtime_ready), `${execution.boundary_class}: invalid runtime readiness`);
if (execution.runtime_ready === "1") { 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.ok(
assert.equal(execution.plan_status, "ready_to_run_opt_in_probe", `${execution.boundary_class}: execution plan must remain opt-in`); [execution.expected_pass_status, "ready_disabled_by_default"].includes(execution.current_probe_status),
`${execution.boundary_class}: ready probe status drift`,
);
assert.equal(
execution.plan_status,
execution.current_probe_status === execution.expected_pass_status
? "native_probe_passed_waiting_for_node_browser_promotion_proof"
: "ready_to_run_opt_in_probe",
`${execution.boundary_class}: execution plan opt-in/pass status drift`,
);
assert.equal(execution.missing_requirements, "-", `${execution.boundary_class}: ready execution plan must not list missing requirements`); assert.equal(execution.missing_requirements, "-", `${execution.boundary_class}: ready execution plan must not list missing requirements`);
} else { } else {
assert.equal(execution.current_probe_status, "skipped_missing_host_runtime", `${execution.boundary_class}: probe should remain skipped on missing host`); assert.equal(execution.current_probe_status, "skipped_missing_host_runtime", `${execution.boundary_class}: probe should remain skipped on missing host`);
@@ -8422,14 +8473,27 @@ function verifyBlockedRuntimeProbeExecutionConsistency({
assert.equal(skip.host_action, "skip_missing_host_requirements", `${execution.boundary_class}: skip evidence host action drift`); 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(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`); if (execution.current_probe_status === execution.expected_pass_status) {
assert.equal(skip.evidence_required_now, "0", `${execution.boundary_class}: skip evidence must not be required yet`); assert.equal(pass.evidence_required_now, "1", `${execution.boundary_class}: pass evidence should be required after native pass`);
assert.equal(pass.observed_evidence_ready, "0", `${execution.boundary_class}: pass evidence must not be ready yet`); assert.equal(skip.evidence_required_now, "1", `${execution.boundary_class}: skip evidence should reflect native pass evidence`);
assert.equal(skip.observed_evidence_ready, "0", `${execution.boundary_class}: skip evidence must not be ready yet`); assert.equal(pass.observed_evidence_ready, "1", `${execution.boundary_class}: pass evidence must be ready after native pass`);
assert.equal(pass.evidence_status, "pending_until_native_pass", `${execution.boundary_class}: pass evidence status drift`); assert.equal(skip.observed_evidence_ready, "1", `${execution.boundary_class}: skip evidence must reflect ready native evidence`);
assert.equal(skip.native_evidence_status, "pending_until_native_pass", `${execution.boundary_class}: skip evidence native status drift`); assert.equal(pass.evidence_status, "native_pass_evidence_observed", `${execution.boundary_class}: pass evidence status drift`);
assert.equal(skip.native_evidence_status, "native_pass_evidence_observed", `${execution.boundary_class}: skip evidence native status drift`);
} else {
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.ok( assert.ok(
["skip_valid_until_host_requirements_available", "skip_contract_not_applicable_dispatch_allowed"].includes(skip.skip_evidence_status), [
"skip_valid_until_host_requirements_available",
"skip_contract_not_applicable_dispatch_allowed",
"native_pass_evidence_observed_skip_contract_closed",
].includes(skip.skip_evidence_status),
`${execution.boundary_class}: skip evidence contract status drift`, `${execution.boundary_class}: skip evidence contract status drift`,
); );
@@ -8466,8 +8530,8 @@ function verifyBlockedRuntimeRollupConsistency({
assert.equal(host.host_ready_family_count, dispatch.dispatch_allowed_count, "blocked runtime rollup ready/dispatch-allowed count drift"); assert.equal(host.host_ready_family_count, dispatch.dispatch_allowed_count, "blocked runtime rollup ready/dispatch-allowed count drift");
assert.equal(host.host_blocked_family_count, dispatch.dispatch_blocked_count, "blocked runtime rollup blocked/dispatch-blocked count drift"); assert.equal(host.host_blocked_family_count, dispatch.dispatch_blocked_count, "blocked runtime rollup blocked/dispatch-blocked count drift");
assert.equal(dispatch.dispatch_blocked_count, skipEvidence.skip_count, "blocked runtime rollup dispatch-blocked/skip count drift"); assert.equal(dispatch.dispatch_blocked_count, skipEvidence.skip_count, "blocked runtime rollup dispatch-blocked/skip count drift");
assert.equal(skipEvidence.evidence_required_now_count, "0", "blocked runtime rollup must not require evidence on host-blocked probes"); assert.ok(/^[0-9]+$/.test(skipEvidence.evidence_required_now_count), "blocked runtime rollup evidence required count drift");
assert.equal(skipEvidence.observed_evidence_ready_count, "0", "blocked runtime rollup must not accept observed evidence on host-blocked probes"); assert.ok(/^[0-9]+$/.test(skipEvidence.observed_evidence_ready_count), "blocked runtime rollup evidence ready count drift");
assert.equal(host.blocked_families, dispatch.blocked_families, "blocked runtime rollup blocked family drift"); assert.equal(host.blocked_families, dispatch.blocked_families, "blocked runtime rollup blocked family drift");
assert.equal(dispatch.blocked_families, skipEvidence.skipped_families, "blocked runtime rollup skipped family drift"); assert.equal(dispatch.blocked_families, skipEvidence.skipped_families, "blocked runtime rollup skipped family drift");
assert.equal(host.missing_host_requirements, dispatch.missing_host_requirements, "blocked runtime rollup host/dispatch missing requirement drift"); assert.equal(host.missing_host_requirements, dispatch.missing_host_requirements, "blocked runtime rollup host/dispatch missing requirement drift");
@@ -8490,9 +8554,12 @@ function verifyBlockedRuntimeRollupConsistency({
].includes(dispatch.dispatch_status), ].includes(dispatch.dispatch_status),
"blocked runtime rollup dispatch status drift", "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.ok(
["no_native_pass_evidence_accepted_while_host_blocked", "native_pass_evidence_present"].includes(skipEvidence.evidence_rollup_status),
"blocked runtime rollup evidence status drift",
);
assert.notEqual(skipEvidence.current_probe_statuses, "-", "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.notEqual(skipEvidence.native_evidence_statuses, "-", "blocked runtime rollup native evidence status drift");
assert.notEqual(skipEvidence.skip_evidence_statuses, "-", "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 [ for (const [label, row] of [
@@ -8699,27 +8766,53 @@ function verifyBlockedRuntimePromotionGateConsistency({
assert.equal(evidenceGate.node_gate_status, postNative.node_gate_status, `${boundaryClass}: evidence gate Node status drift`); assert.equal(evidenceGate.node_gate_status, postNative.node_gate_status, `${boundaryClass}: evidence gate Node status drift`);
assert.equal(evidenceGate.browser_gate_status, postNative.browser_gate_status, `${boundaryClass}: evidence gate browser status drift`); assert.equal(evidenceGate.browser_gate_status, postNative.browser_gate_status, `${boundaryClass}: evidence gate browser status drift`);
assert.equal(readiness.native_pass_ready, "0", `${boundaryClass}: current host must not report native pass ready`); if (readiness.current_probe_status === readiness.expected_pass_status) {
assert.equal(readiness.native_evidence_ready, "0", `${boundaryClass}: current host must not report native evidence ready`); assert.equal(readiness.native_pass_ready, "1", `${boundaryClass}: native pass flag drift`);
assert.equal(readiness.native_evidence_ready, "1", `${boundaryClass}: native evidence flag drift`);
} else {
assert.equal(readiness.native_pass_ready, "0", `${boundaryClass}: current host must not report native pass ready`);
assert.equal(readiness.native_evidence_ready, "0", `${boundaryClass}: current host must not report native evidence ready`);
}
assert.equal(readiness.node_inventory_gate_complete, "0", `${boundaryClass}: Node promotion gate must remain incomplete`); assert.equal(readiness.node_inventory_gate_complete, "0", `${boundaryClass}: Node promotion gate must remain incomplete`);
assert.equal(readiness.browser_smoke_gate_complete, "0", `${boundaryClass}: browser promotion gate must remain incomplete`); assert.equal(readiness.browser_smoke_gate_complete, "0", `${boundaryClass}: browser promotion gate must remain incomplete`);
assert.equal(readiness.promotion_lock_active, "1", `${boundaryClass}: promotion lock must remain active`); assert.equal(readiness.promotion_lock_active, "1", `${boundaryClass}: promotion lock must remain active`);
assert.equal(readiness.manual_lock_update_required, "1", `${boundaryClass}: manual lock update must remain required`); assert.equal(readiness.manual_lock_update_required, "1", `${boundaryClass}: manual lock update must remain required`);
assert.equal(readiness.promotion_ready, "0", `${boundaryClass}: promotion must remain not ready`); assert.equal(readiness.promotion_ready, "0", `${boundaryClass}: promotion must remain not ready`);
assert.ok(blocker.blocker_keys.includes("native_runtime_probe_not_passed"), `${boundaryClass}: native pass blocker missing`); if (readiness.native_pass_ready !== "1") {
assert.ok(blocker.blocker_keys.includes("native_pass_evidence_not_ready"), `${boundaryClass}: native evidence blocker missing`); assert.ok(blocker.blocker_keys.includes("native_runtime_probe_not_passed"), `${boundaryClass}: native pass blocker missing`);
}
if (readiness.native_evidence_ready !== "1") {
assert.ok(blocker.blocker_keys.includes("native_pass_evidence_not_ready"), `${boundaryClass}: native evidence blocker missing`);
}
assert.ok(blocker.blocker_keys.includes("node_inventory_gate_not_complete"), `${boundaryClass}: Node gate blocker missing`); assert.ok(blocker.blocker_keys.includes("node_inventory_gate_not_complete"), `${boundaryClass}: Node gate blocker missing`);
assert.ok(blocker.blocker_keys.includes("browser_smoke_gate_not_complete"), `${boundaryClass}: browser gate blocker missing`); assert.ok(blocker.blocker_keys.includes("browser_smoke_gate_not_complete"), `${boundaryClass}: browser gate blocker missing`);
assert.ok(blocker.blocker_keys.includes("promotion_lock_active"), `${boundaryClass}: promotion lock blocker missing`); assert.ok(blocker.blocker_keys.includes("promotion_lock_active"), `${boundaryClass}: promotion lock blocker missing`);
assert.ok(blocker.blocker_keys.includes("manual_lock_update_required"), `${boundaryClass}: manual lock blocker missing`); assert.ok(blocker.blocker_keys.includes("manual_lock_update_required"), `${boundaryClass}: manual lock blocker missing`);
assert.equal(postNative.node_gate_status, "blocked_until_native_pass_evidence", `${boundaryClass}: post-native Node gate must wait for native evidence`); assert.equal(
assert.equal(postNative.browser_gate_status, "blocked_until_node_gate_complete", `${boundaryClass}: post-native browser gate must wait for Node gate`); postNative.node_gate_status,
assert.equal(postNative.gate_status, "blocked_before_native_pass", `${boundaryClass}: post-native gate status drift`); readiness.native_evidence_ready === "1"
assert.ok( ? "pending_node_inventory_promotion_gate"
["blocked_until_host_requirements_available", "blocked_until_native_pass_evidence"].includes(evidenceGate.native_evidence_gate), : "blocked_until_native_pass_evidence",
`${boundaryClass}: native evidence gate must remain blocked`, `${boundaryClass}: post-native Node gate status drift`,
); );
assert.equal(evidenceGate.evidence_acceptance_allowed, "0", `${boundaryClass}: native evidence acceptance must remain blocked`); 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,
readiness.native_evidence_ready === "1"
? "waiting_for_node_browser_manual_promotion"
: "blocked_before_native_pass",
`${boundaryClass}: post-native gate status drift`,
);
if (readiness.native_evidence_ready === "1") {
assert.equal(evidenceGate.native_evidence_gate, "native_pass_evidence_accepted", `${boundaryClass}: native evidence gate acceptance drift`);
assert.equal(evidenceGate.evidence_acceptance_allowed, "1", `${boundaryClass}: native evidence acceptance flag drift`);
} else {
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]) { for (const row of [readiness, blocker, postNative, evidenceGate, passEvidence, lock]) {
assert.equal(row.execution_enabled, "0", `${boundaryClass}: promotion consistency row must not enable execution`); assert.equal(row.execution_enabled, "0", `${boundaryClass}: promotion consistency row must not enable execution`);
@@ -11583,6 +11676,21 @@ function executionModeFor(record) {
function verifyInventoryCase(record, output, executionMode) { function verifyInventoryCase(record, output, executionMode) {
if (record.status === "PASS") { if (record.status === "PASS") {
if (record.path === "axis/db_demo/base.ngc") {
verifyExpectedOutput(
`inventory_${record.path}`,
output,
[
"file_open=0",
"file_saw_error=0",
"canon_event=MESSAGE: fini",
"canon_event=FINISH",
"absent=file_error_text=",
].join("\n"),
);
return;
}
if (executionMode === "fiveAxisRemap") { if (executionMode === "fiveAxisRemap") {
verifyExpectedOutput( verifyExpectedOutput(
`inventory_${record.path}`, `inventory_${record.path}`,
@@ -11773,7 +11881,7 @@ const iniBoundarySummaryRecords = verifyIniBoundarySummaryRows(
const blockedDependencyRecords = nativeRecords.filter((record) => const blockedDependencyRecords = nativeRecords.filter((record) =>
["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"].includes( ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"].includes(
pathMatrixByPath.get(record.path)?.blocked ?? "-", pathMatrixByPath.get(record.path)?.blocked ?? "-",
), ) || record.path === "axis/db_demo/base.ngc",
); );
const blockedDependencySummaryRows = blockedDependencyRecords.map((record) => const blockedDependencySummaryRows = blockedDependencyRecords.map((record) =>
blockedDependencySummaryRow(record, pathMatrixByPath), blockedDependencySummaryRow(record, pathMatrixByPath),
@@ -12287,7 +12395,7 @@ function verifyEvidenceExpansionCandidateRows(rows, summaryRows, boundaryRows, t
expectedPaths, expectedPaths,
"evidence expansion candidates must cover every non-hard-block PASS main candidate outside evidence-ready", "evidence expansion candidates must cover every non-hard-block PASS main candidate outside evidence-ready",
); );
assert.equal(parsedRows.length, 13, "current evidence expansion candidate count drift"); assert.equal(parsedRows.length, 14, "current evidence expansion candidate count drift");
for (const row of parsedRows) { for (const row of parsedRows) {
const boundary = boundaryByPath.get(row.path); const boundary = boundaryByPath.get(row.path);
assert.ok(boundary, `${row.path}: evidence expansion candidate lacks boundary evidence`); assert.ok(boundary, `${row.path}: evidence expansion candidate lacks boundary evidence`);
@@ -12365,7 +12473,7 @@ function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) {
0, 0,
"current baseline has no direct skipped-main inventory promotion candidate", "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"]) { for (const lockedKind of ["L4-PYTHON-REMAP", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"]) {
const lockedRows = parsedInventoryReady.filter((row) => row.skip_kind === lockedKind); const lockedRows = parsedInventoryReady.filter((row) => row.skip_kind === lockedKind);
assert.ok(lockedRows.length > 0, `${lockedKind}: promotion-candidates missing locked inventory rows`); assert.ok(lockedRows.length > 0, `${lockedKind}: promotion-candidates missing locked inventory rows`);
assert.equal( assert.equal(
@@ -12394,7 +12502,7 @@ function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) {
assert.equal(row.blocked_kind, row.skip_kind, `${row.path}: skipped inventory blocked kind drift`); assert.equal(row.blocked_kind, row.skip_kind, `${row.path}: skipped inventory blocked kind drift`);
assert.equal(row.virtual_hal_evidence_ready, "0", `${row.path}: skipped inventory row must not claim virtual HAL evidence ready`); assert.equal(row.virtual_hal_evidence_ready, "0", `${row.path}: skipped inventory row must not claim virtual HAL evidence ready`);
assert.ok( assert.ok(
["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.skip_kind), ["L4-PYTHON-REMAP", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.skip_kind),
`${row.path}: direct promotion candidate must remain hard-blocked or upstream-demo`, `${row.path}: direct promotion candidate must remain hard-blocked or upstream-demo`,
); );
const boundary = boundaryByPath.get(row.path); const boundary = boundaryByPath.get(row.path);

View File

@@ -6,6 +6,8 @@ NATIVE_SUMMARY="$ROOT_DIR/build/native/sim-configs/summary.tsv"
NATIVE_CLASS_SUMMARY="$ROOT_DIR/build/native/sim-configs/class-summary.tsv" NATIVE_CLASS_SUMMARY="$ROOT_DIR/build/native/sim-configs/class-summary.tsv"
NATIVE_PATH_MATRIX="$ROOT_DIR/build/native/sim-configs/path-matrix.tsv" NATIVE_PATH_MATRIX="$ROOT_DIR/build/native/sim-configs/path-matrix.tsv"
NATIVE_SOURCE_PROOF_SUMMARY="$ROOT_DIR/build/native/native-source-proof-summary.tsv" NATIVE_SOURCE_PROOF_SUMMARY="$ROOT_DIR/build/native/native-source-proof-summary.tsv"
NATIVE_RUNTIME_PROBE_SUMMARY="$ROOT_DIR/build/native/native-runtime-probe-summary.tsv"
PYTHON_REMAP_LIFECYCLE_STDOUT="$ROOT_DIR/build/native/python-remap-runtime/python_lifecycle.stdout.log"
NATIVE_BUILD_DIR="$ROOT_DIR/build/native/sim-configs" NATIVE_BUILD_DIR="$ROOT_DIR/build/native/sim-configs"
NATIVE_SOURCE_PROOF_INPUTS=( NATIVE_SOURCE_PROOF_INPUTS=(
"$ROOT_DIR/tools/build_native_probes.sh" "$ROOT_DIR/tools/build_native_probes.sh"
@@ -27,11 +29,38 @@ native_source_proof_refresh_required() {
return 1 return 1
} }
python_remap_lifecycle_pass_observed() {
[[ -f "$PYTHON_REMAP_LIFECYCLE_STDOUT" ]] || return 1
grep -Fq "python_remap_lifecycle_interpreter_sentinel_ok=1" "$PYTHON_REMAP_LIFECYCLE_STDOUT" &&
grep -Fq "python_remap_lifecycle_toplevel_imported=1" "$PYTHON_REMAP_LIFECYCLE_STDOUT" &&
grep -Fq "python_remap_lifecycle_remap_imported=1" "$PYTHON_REMAP_LIFECYCLE_STDOUT" &&
grep -Fq "python_remap_lifecycle_callable_lookup_ok=1" "$PYTHON_REMAP_LIFECYCLE_STDOUT" &&
grep -Fq "python_remap_lifecycle_generator_first_yield=2" "$PYTHON_REMAP_LIFECYCLE_STDOUT" &&
grep -Fq "python_remap_runtime_lifecycle_probe_ok=1" "$PYTHON_REMAP_LIFECYCLE_STDOUT"
}
native_runtime_probe_refresh_required() {
python_remap_lifecycle_pass_observed || return 1
if [[ ! -f "$NATIVE_RUNTIME_PROBE_SUMMARY" ]]; then
return 0
fi
if [[ "$PYTHON_REMAP_LIFECYCLE_STDOUT" -nt "$NATIVE_RUNTIME_PROBE_SUMMARY" ]]; then
return 0
fi
! grep -Fq $'L4-PYTHON-REMAP\tpython_runtime\taxis/remap/stop-lookahead/nc_files\tlinuxcnc_python_remap_runtime_probe\tlinuxcnc_python_runtime_lifecycle_probe_required\tENABLE_PYTHON_REMAP_RUNTIME_PROBE=1\t1\t1\truntime_lifecycle_probe_passed' "$NATIVE_RUNTIME_PROBE_SUMMARY"
}
if [[ ! -f "$NATIVE_SUMMARY" || ! -f "$NATIVE_CLASS_SUMMARY" || ! -f "$NATIVE_PATH_MATRIX" ]]; then if [[ ! -f "$NATIVE_SUMMARY" || ! -f "$NATIVE_CLASS_SUMMARY" || ! -f "$NATIVE_PATH_MATRIX" ]]; then
"$ROOT_DIR/tests/native/verify_sim_configs.sh" "$ROOT_DIR/tests/native/verify_sim_configs.sh"
fi fi
if native_source_proof_refresh_required; then if native_source_proof_refresh_required; then
"$ROOT_DIR/tools/build_native_probes.sh" if python_remap_lifecycle_pass_observed; then
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 "$ROOT_DIR/tools/build_native_probes.sh"
else
"$ROOT_DIR/tools/build_native_probes.sh"
fi
elif native_runtime_probe_refresh_required; then
ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 "$ROOT_DIR/tools/build_native_probes.sh"
fi fi
mkdir -p "$NATIVE_BUILD_DIR" mkdir -p "$NATIVE_BUILD_DIR"

View File

@@ -171,6 +171,9 @@ native_input_fingerprint() {
"$CXX" --version 2>/dev/null | sed -n '1p' || true "$CXX" --version 2>/dev/null | sed -n '1p' || true
printf 'CC=%s\n' "$CC" printf 'CC=%s\n' "$CC"
"$CC" --version 2>/dev/null | sed -n '1p' || true "$CC" --version 2>/dev/null | sed -n '1p' || true
printf 'ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=%s\n' "${ENABLE_MILLTURN_USER_M_RUNTIME_PROBE:-0}"
printf 'ENABLE_TOOL_DB_RUNTIME_PROBE=%s\n' "${ENABLE_TOOL_DB_RUNTIME_PROBE:-0}"
printf 'ENABLE_PYTHON_REMAP_RUNTIME_PROBE=%s\n' "${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}"
sha256sum \ sha256sum \
"$ROOT_DIR/tools/build_native_probes.sh" \ "$ROOT_DIR/tools/build_native_probes.sh" \
"$ROOT_DIR/tools/source-manifest.txt" "$ROOT_DIR/tools/source-manifest.txt"
@@ -1341,6 +1344,8 @@ if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_millturn_user_m_boundary_probe
"$BUILD_DIR/linuxcnc_millturn_user_m_boundary_probe" "$BUILD_DIR/linuxcnc_millturn_user_m_boundary_probe"
run_logged_command \ run_logged_command \
linuxcnc_millturn_user_m_runtime_probe \ linuxcnc_millturn_user_m_runtime_probe \
env \
ENABLE_MILLTURN_USER_M_RUNTIME_PROBE="${ENABLE_MILLTURN_USER_M_RUNTIME_PROBE:-0}" \
bash \ bash \
"$MILLTURN_USER_M_RUNTIME_PROBE" "$MILLTURN_USER_M_RUNTIME_PROBE"
else else
@@ -1362,6 +1367,8 @@ if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_tool_db_boundary_probe.exitcod
"$BUILD_DIR/linuxcnc_tool_db_boundary_probe" "$BUILD_DIR/linuxcnc_tool_db_boundary_probe"
run_logged_command \ run_logged_command \
linuxcnc_tool_db_runtime_probe \ linuxcnc_tool_db_runtime_probe \
env \
ENABLE_TOOL_DB_RUNTIME_PROBE="${ENABLE_TOOL_DB_RUNTIME_PROBE:-0}" \
bash \ bash \
"$TOOL_DB_RUNTIME_PROBE" "$TOOL_DB_RUNTIME_PROBE"
else else
@@ -1382,6 +1389,8 @@ if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_python_remap_boundary_probe.ex
"$BUILD_DIR/linuxcnc_python_remap_boundary_probe" "$BUILD_DIR/linuxcnc_python_remap_boundary_probe"
run_logged_command \ run_logged_command \
linuxcnc_python_remap_runtime_probe \ linuxcnc_python_remap_runtime_probe \
env \
ENABLE_PYTHON_REMAP_RUNTIME_PROBE="${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}" \
bash \ bash \
"$PYTHON_REMAP_RUNTIME_PROBE" "$PYTHON_REMAP_RUNTIME_PROBE"
else else