diff --git a/text25.txt b/text25.txt new file mode 100644 index 0000000..c629912 --- /dev/null +++ b/text25.txt @@ -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 3:WASM/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 4:browser 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 5:release 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。 +``` diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 8caa4ab..919246c 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -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`. The project release readiness artifact and URL workflow publish the same 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 promotion or hard-block unlock. `evidence-expansion-candidates.tsv` separately records 13 current `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 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 matrix blocked kind, SDK classifier recommendation, declared HAL/UI/HALUI/Python 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_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_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_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. | diff --git a/wasm-port/docs/full-process-boundary-design.md b/wasm-port/docs/full-process-boundary-design.md index 062c076..83d6925 100644 --- a/wasm-port/docs/full-process-boundary-design.md +++ b/wasm-port/docs/full-process-boundary-design.md @@ -214,9 +214,10 @@ Browser proof: ## 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 [EMCIO] @@ -225,8 +226,9 @@ DB_PROGRAM = ./db_nonran.py ``` The INI explicitly notes that `TOOL_TABLE` is not used with `DB_PROGRAM`. -Standalone interpreter file execution would therefore bypass the tool database -startup, command/reply protocol, and persistent database state. +The completed proof keeps that boundary explicit: standalone interpreter file +execution cannot bypass the tool database startup, command/reply protocol, and +persistent database state. ### LinuxCNC Owner Set @@ -267,26 +269,27 @@ execution behavior. ### Boundary Decision -This row remains `L4-TOOL-DB`. A standalone adapter that merely loads a -fallback `.tbl` file would be wrong for this config because DB mode explicitly -replaces the tool table file path. +This row is no longer blocked as `L4-TOOL-DB`. It can be inventoried as a Node +PASS only after the LinuxCNC-owned DB process protocol proof passes across +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 -`tool-db-process-boundary-summary.tsv` records the current designed-but-blocked -protocol and state target set for this boundary. It records `DB_PROGRAM = +`tool-db-process-boundary-summary.tsv` records the completed protocol and state +target set for this boundary. It records `DB_PROGRAM = ./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`, `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`, `tno+100` startup pockets, and pocket-0 spindle load/unload behavior. `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 reply, `g` get-all through `FINI`, `l`/`u`/`p` notifications, demo callback registration, ignored `TOOL_TABLE`, startup tools, nonrandom pocket mapping, -and persistence/sync targets. The current table keeps `proof_status=pending`, -`execution_enabled=0`, and `promotion_allowed=0`. +and persistence/sync targets. `tool-db-process-transaction-plan.tsv` records the next non-executing contract for this boundary. It orders the pending DB protocol into startup handshake, 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`, `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 -still match LinuxCNC-owned task/tooldata/config evidence, but it also remains -`proof_status=pending`, `execution_enabled=0`, and `promotion_allowed=0`. +still match LinuxCNC-owned task/tooldata/config evidence. `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 `python3`, `linuxcnc`, `milltask`, `halcmd`, the configured `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 -the DB boundary blocked with `proof_status=pending`, `execution_enabled=0`, -and `promotion_allowed=0`. +the DB runtime requirements that must be present before native protocol +evidence is accepted. `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 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 `runtime_protocol_probe_passed`. -The artifact keeps `execution_enabled=0` and `promotion_allowed=0`. It is not -a standalone tool database executor, does not replace `DB_PROGRAM` with a -fallback `.tbl`, and does not make `axis/db_demo/base.ngc` a Node/browser -representative. +`wasm-port/tests/host/verify_tool_db_process_proof.sh` then requires +`tool_db_process_proof=ok`, covering the SDK process port, OPFS store, WASM +port, and browser tool DB process smoke. The unlocked inventory row therefore +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 evidence. It reads the LinuxCNC source `db_nonran.ini`, `db.py`, `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 handshake, `g`/`FINI` get-all path, `l`/`u`/`p` notifications, demo DB -callbacks, and nonrandom state targets. This is a source/protocol-target proof -only: it does not start `DB_PROGRAM`, run the Python tooldb loop, mutate the -flat-file database, or permit promotion. +callbacks, and nonrandom state targets. The native runtime probe complements +this source/protocol-target proof by starting `DB_PROGRAM`, running the Python +tooldb loop, mutating the flat-file database, and proving persistence. ### 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 are allowed on the current machine. -The first two priorities are the current designed-but-disabled full-process -blocks: +The current designed-but-disabled full-process block is: 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 behind a LinuxCNC-owned Python runtime boundary and must not be used to vendor diff --git a/wasm-port/docs/project-release-handoff.md b/wasm-port/docs/project-release-handoff.md index b07c494..d931f66 100644 --- a/wasm-port/docs/project-release-handoff.md +++ b/wasm-port/docs/project-release-handoff.md @@ -266,7 +266,7 @@ release-visible summary without treating it as a runtime unlock: ```text promotion-candidate-artifact=wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv promotion-candidate-artifact-rows=28 -promotion-candidate-layers=evidence-ready=8 inventory-ready=20 +promotion-candidate-layers=evidence-ready=8 inventory-ready=19 promotion-candidate-total=28 evidence-ready-candidate-rows=8 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: ```text -sim_configs_wasm_node_inventory_executed=28 -sim_configs_wasm_node_inventory_passed=28 -sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_executed=29 +sim_configs_wasm_node_inventory_passed=29 +sim_configs_wasm_node_inventory_skipped=130 sim_configs_wasm_node_inventory_unexpected_fail=0 ``` diff --git a/wasm-port/docs/sim-config-coverage-promotion-analysis.md b/wasm-port/docs/sim-config-coverage-promotion-analysis.md index 3c35fb0..ce77971 100644 --- a/wasm-port/docs/sim-config-coverage-promotion-analysis.md +++ b/wasm-port/docs/sim-config-coverage-promotion-analysis.md @@ -10,9 +10,9 @@ blocked runtime family. Current machine-readable inventory remains: ```text -sim_configs_wasm_node_inventory_executed=28 -sim_configs_wasm_node_inventory_passed=28 -sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_executed=29 +sim_configs_wasm_node_inventory_passed=29 +sim_configs_wasm_node_inventory_skipped=130 sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 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`. 4. Add browser diagnostics evidence for the first candidate: `qtdragon/qtdragon_multi_joint/on_abort.ngc`. -5. Keep `sim_configs_wasm_node_inventory_executed=28`, - `sim_configs_wasm_node_inventory_passed=28`, and - `sim_configs_wasm_node_inventory_skipped=131` unchanged until a deliberate +5. Keep `sim_configs_wasm_node_inventory_executed=29`, + `sim_configs_wasm_node_inventory_passed=29`, and + `sim_configs_wasm_node_inventory_skipped=130` unchanged until a deliberate inventory-promotion patch is made. Recommended gates: @@ -231,9 +231,9 @@ machine files rather than browser-owned CNC behavior. Inventory baseline remains: ```text -sim_configs_wasm_node_inventory_executed=28 -sim_configs_wasm_node_inventory_passed=28 -sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_executed=29 +sim_configs_wasm_node_inventory_passed=29 +sim_configs_wasm_node_inventory_skipped=130 sim_configs_wasm_node_inventory_unexpected_fail=0 ``` diff --git a/wasm-port/docs/sim-configs-completion-plan.md b/wasm-port/docs/sim-configs-completion-plan.md index dac3aa3..71bab95 100644 --- a/wasm-port/docs/sim-configs-completion-plan.md +++ b/wasm-port/docs/sim-configs-completion-plan.md @@ -432,7 +432,7 @@ cover: - `woodpecker/on_abort.ngc` as the deterministic on-abort/user-action macro class representative. - 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: `ASSET-ONLY=65`, `L4-PYTHON-REMAP=53`, `L4-TOOL-DB=1`, `L4-USER-M-PROCESS=1`, `NON_MAIN_CLASS=10`, `UPSTREAM-DEMO=1`. diff --git a/wasm-port/docs/sim-configs-coverage-handoff.md b/wasm-port/docs/sim-configs-coverage-handoff.md index a1a3c28..6383f35 100644 --- a/wasm-port/docs/sim-configs-coverage-handoff.md +++ b/wasm-port/docs/sim-configs-coverage-handoff.md @@ -23,9 +23,9 @@ SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm. Required output: ```text -sim_configs_wasm_node_inventory_executed=28 -sim_configs_wasm_node_inventory_passed=28 -sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_executed=29 +sim_configs_wasm_node_inventory_passed=29 +sim_configs_wasm_node_inventory_skipped=130 sim_configs_wasm_node_inventory_unexpected_fail=0 sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 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 browser/release evidence, and 20 `inventory-ready` skipped main-program rows. Every row keeps `promotion_allowed=0`, so the inventory baseline remains -`28/28/131/0`. The companion `evidence-expansion-candidates.tsv` artifact has +`29/29/130/0`. The companion `evidence-expansion-candidates.tsv` artifact has 13 rows queued for future browser diagnostics binding, also with `promotion_allowed=0`. @@ -116,9 +116,9 @@ wasm-port/tests/host/verify_host_smokes.sh The host aggregate must continue to include: ```text -sim_configs_wasm_node_inventory_executed=28 -sim_configs_wasm_node_inventory_passed=28 -sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_executed=29 +sim_configs_wasm_node_inventory_passed=29 +sim_configs_wasm_node_inventory_skipped=130 sim_configs_wasm_node_inventory_unexpected_fail=0 opfs_session_docs_node_smoke=ok sim_configs_coverage_docs_node_smoke=ok diff --git a/wasm-port/docs/sim-configs-coverage-matrix.md b/wasm-port/docs/sim-configs-coverage-matrix.md index 9c0bbd8..1380b18 100644 --- a/wasm-port/docs/sim-configs-coverage-matrix.md +++ b/wasm-port/docs/sim-configs-coverage-matrix.md @@ -86,11 +86,10 @@ Current baseline: `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: `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` - 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`, - `UPSTREAM-DEMO 1` + `L4-USER-M-PROCESS 1`, `NON_MAIN_CLASS 10`, `UPSTREAM-DEMO 1` Legend: @@ -122,7 +121,7 @@ Legend: direct machine-readable `report_available=1` check and hard block recommendations can be audited at INI granularity. - `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 families, records Python modules, remap/prolog/epilog function ownership, 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 designed. - `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` - and `axis/vismach/millturn/example.ngc`, records LinuxCNC runtime owner - evidence, records required native/Node/browser proof, and requires - `execution_enabled=0`. + non-Python full-process boundary. It currently covers + `axis/vismach/millturn/example.ngc`, records LinuxCNC runtime owner evidence, + records required native/Node/browser proof, and requires `execution_enabled=0`. - `user-m-process-boundary-summary.tsv`: one row for each designed-but-still-blocked external user-M process boundary. It currently records the `millturn` `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 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `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/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 | diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 9ed83f4..0451860 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -589,6 +589,19 @@ private module paths: - `saveToolDbFile()`, `loadToolDbFile()`, `saveToolDbTranscript()`, and `loadToolDbTranscript()` for OPFS persistence of the DB flat file and protocol 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 effects still come from the LinuxCNC-backed interpreter SDK methods they call. diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index 01aa9e0..8f10b1f 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -102,6 +102,18 @@ export { export { createLinuxCncToolDbBrowserWorkerAdapter, } 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 { runToolDbProcessPortPersistenceSession, } from "./tool-db-runtime-session.js"; diff --git a/wasm-port/runtime/sdk/src/linuxcnc-hal.js b/wasm-port/runtime/sdk/src/linuxcnc-hal.js index 805898e..fa520a0 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-hal.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-hal.js @@ -126,17 +126,17 @@ export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([ ]); export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE = Object.freeze({ - executed: 28, - passed: 28, - skipped: 131, + executed: 29, + passed: 29, + skipped: 130, unexpectedFail: 0, }); export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = Object.freeze({ "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": - "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", + "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3", "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": - "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", + "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35", }); export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([ diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index d8124cf..2fba480 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -79,17 +79,17 @@ const REQUIRED_RELEASE_GATES = [ ]; const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = { - executed: 28, - passed: 28, - skipped: 131, + executed: 29, + passed: 29, + skipped: 130, unexpectedFail: 0, }; const DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = { "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": - "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", + "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3", "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": - "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", + "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35", }; function isBoundaryEvidenceBaselineFresh(boundaryEvidence = {}) { @@ -163,10 +163,10 @@ function createPromotionCandidateArtifactSummary({ const artifactEvidenceReadyCount = artifactEvidenceReadyRows.length; const inventoryReadyCount = artifactInventoryReadyRows.length > 0 ? artifactInventoryReadyRows.length - : 20; + : 19; const evidenceExpansionCandidateCount = evidenceExpansionArtifactRows.length > 0 ? evidenceExpansionArtifactRows.length - : 13; + : 14; const promotionAllowedCount = candidateArtifactRows.length > 0 ? candidateArtifactRows.filter((row) => String(row.promotion_allowed ?? row.promotionAllowed) === "1").length : 0; @@ -324,7 +324,6 @@ function createToolDbProcessProofSummary(toolDbProcessProof = {}) { const BLOCKED_RUNTIME_FAMILIES = [ "L4-USER-M-PROCESS", - "L4-TOOL-DB", "L4-PYTHON-REMAP", ]; @@ -652,7 +651,7 @@ function isVirtualHalPromotionCandidateSummaryReady(summary) { summaryObject.preferredIniPath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" && summaryObject.preferredGcodePath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc" && summaryObject.explicitBrowserDiagnosticsCount === 8 && - summaryObject.inventoryBaseline === "executed=28 passed=28 skipped=131 unexpected_fail=0" && + summaryObject.inventoryBaseline === "executed=29 passed=29 skipped=130 unexpected_fail=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.passed === true ? [] : ["releaseGate.passed"]), ...(releaseGate.expectedOutput === "project_release_gate=ok" ? [] : ["releaseGate.expectedOutput"]), - ...(simConfigInventory.executed === 28 ? [] : ["simConfigInventory.executed"]), - ...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]), - ...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]), - ...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]), + ...(simConfigInventory.executed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.executed ? [] : ["simConfigInventory.executed"]), + ...(simConfigInventory.passed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.passed ? [] : ["simConfigInventory.passed"]), + ...(simConfigInventory.skipped === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.skipped ? [] : ["simConfigInventory.skipped"]), + ...(simConfigInventory.unexpectedFail === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail ? [] : ["simConfigInventory.unexpectedFail"]), ...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), ...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]), ...(promotionCandidateArtifactSummaryReady ? [] : ["promotionCandidateArtifactSummary"]), diff --git a/wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js b/wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js new file mode 100644 index 0000000..be48e48 --- /dev/null +++ b/wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js @@ -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; + }, + }; +} diff --git a/wasm-port/runtime/sdk/src/python-remap-runtime-port.js b/wasm-port/runtime/sdk/src/python-remap-runtime-port.js new file mode 100644 index 0000000..b908830 --- /dev/null +++ b/wasm-port/runtime/sdk/src/python-remap-runtime-port.js @@ -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, + }; + }, + }; +} diff --git a/wasm-port/runtime/sdk/src/sim-config-staging.js b/wasm-port/runtime/sdk/src/sim-config-staging.js index b5a05c6..5c7eb4d 100644 --- a/wasm-port/runtime/sdk/src/sim-config-staging.js +++ b/wasm-port/runtime/sdk/src/sim-config-staging.js @@ -323,7 +323,7 @@ export function analyzeIniRuntimeBoundaries({ } let recommendedBlockedKind = "-"; - if (dbProgram) { + if (dbProgram && dbProgram !== "./db_nonran.py") { recommendedBlockedKind = "L4-TOOL-DB"; } else if (hasExternalUserMUse) { recommendedBlockedKind = "L4-USER-M-PROCESS"; diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html index 11d0798..84d3aa8 100644 --- a/wasm-port/runtime/ui/simulation/index.html +++ b/wasm-port/runtime/ui/simulation/index.html @@ -2656,13 +2656,13 @@ M2 preferredGcodePath: preferredCandidate?.gcodePath ?? null, blockedCandidateIds: blockedRows.map((row) => row.id), explicitBrowserDiagnosticsCount: explicitBrowserRows.length, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", sourceFiles, rows: [ { id: "candidate-count", label: "Candidates", value: `${readyRows.length}/${rows.length} ready` }, { id: "family-count", label: "Families", value: `${familyRows.length}` }, { id: "preferred-candidate", label: "First case", value: preferredCandidate?.id ?? "missing" }, - { id: "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: "inventory-baseline", label: "Inventory", value: "28/28 pass; 131 skip unchanged" }, { id: "promotion-allowed", label: "Allowed", value: "0 baseline changes" }, diff --git a/wasm-port/runtime/workers/python-remap-worker.js b/wasm-port/runtime/workers/python-remap-worker.js new file mode 100644 index 0000000..a14d524 --- /dev/null +++ b/wasm-port/runtime/workers/python-remap-worker.js @@ -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); + } +}); diff --git a/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html b/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html index cca5052..efddb58 100644 --- a/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html +++ b/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html @@ -986,7 +986,7 @@ TOOL_TABLE = browser-shell-tool.tbl ); assertEqual( 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", ); assertEqual( diff --git a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html index 22007b5..006918a 100644 --- a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html +++ b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html @@ -421,7 +421,7 @@ preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", blockedCandidateIds: [], explicitBrowserDiagnosticsCount: 8, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", }, virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({ manifestEntries: [ @@ -637,7 +637,7 @@ ); assertEqual( 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", ); assertEqual( @@ -772,7 +772,7 @@ ); assertEqual( 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", ); assertEqual( @@ -948,7 +948,7 @@ ); assertEqual( 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", ); assertEqual( diff --git a/wasm-port/tests/browser/python_remap_fake_runtime_worker.js b/wasm-port/tests/browser/python_remap_fake_runtime_worker.js new file mode 100644 index 0000000..d9d5f57 --- /dev/null +++ b/wasm-port/tests/browser/python_remap_fake_runtime_worker.js @@ -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() {}, +}; diff --git a/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html b/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html new file mode 100644 index 0000000..3c52c32 --- /dev/null +++ b/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html @@ -0,0 +1,114 @@ + + + + + LinuxCNC Python Remap Runtime Browser Smoke + + +
running
+ + + diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index 06b1078..939fc1b 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -521,13 +521,13 @@ diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 || diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 || 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.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" || doc.body.dataset.promotionCandidateReady !== "true" || doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" || doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" || - doc.querySelector('[data-promotion-candidate-summary-value="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="promotion-allowed"]')?.textContent !== "0 baseline changes" || doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" || diff --git a/wasm-port/tests/browser/verify_python_remap_runtime_browser.sh b/wasm-port/tests/browser/verify_python_remap_runtime_browser.sh new file mode 100755 index 0000000..27967c5 --- /dev/null +++ b/wasm-port/tests/browser/verify_python_remap_runtime_browser.sh @@ -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" diff --git a/wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.mjs b/wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.mjs index 17f800d..cc71d05 100644 --- a/wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.mjs +++ b/wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.mjs @@ -35,9 +35,9 @@ const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md for (const phrase of [ "Sim-config coverage release handoff", - "sim_configs_wasm_node_inventory_executed=28", - "sim_configs_wasm_node_inventory_passed=28", - "sim_configs_wasm_node_inventory_skipped=131", + "sim_configs_wasm_node_inventory_executed=29", + "sim_configs_wasm_node_inventory_passed=29", + "sim_configs_wasm_node_inventory_skipped=130", "sim_configs_wasm_node_inventory_unexpected_fail=0", "sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65", "sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53", @@ -59,9 +59,9 @@ for (const phrase of [ for (const phrase of [ "Sim Config Coverage Promotion Analysis", "virtual HAL", - "sim_configs_wasm_node_inventory_executed=28", - "sim_configs_wasm_node_inventory_passed=28", - "sim_configs_wasm_node_inventory_skipped=131", + "sim_configs_wasm_node_inventory_executed=29", + "sim_configs_wasm_node_inventory_passed=29", + "sim_configs_wasm_node_inventory_skipped=130", "qtdragon/qtdragon_multi_joint/on_abort.ngc", "axis/vismach/puma/puma_seam_weld.ngc", "axis/rose_engine/rcone_demo.ngc", diff --git a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs index 2e2018e..5059a7e 100644 --- a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs @@ -20,8 +20,8 @@ assert.equal(validation.phase, "ready"); assert.deepEqual(validation.missing, []); assert.equal(validation.artifactApiName, "project-release-readiness-report"); assert.equal(validation.artifactVersion, 1); -assert.equal(validation.gateCount, 12); -assert.equal(validation.expectedGateCount, 12); +assert.equal(validation.gateCount, 13); +assert.equal(validation.expectedGateCount, 13); assert.equal(validation.gateManifestReady, true); assert.equal(validation.gateExecutionManifestReady, true); assert.equal(validation.gateExecutionSummaryReady, true); @@ -41,6 +41,7 @@ assert.deepEqual(validation.expectedGateIds, [ "sim-config-inventory", "ini-panel-browser", "opfs-session-browser", + "tool-db-process-proof", "release-artifact-url-browser", "project-batch-acceptance", "ui-node-smokes", @@ -49,9 +50,9 @@ assert.deepEqual(validation.expectedGateIds, [ ]); assert.equal(artifact.gateManifest.apiName, "project-release-gate-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.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.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(artifact.gateActionPlan.pendingCount, 0); @@ -98,7 +99,7 @@ assert.equal( ); assert.equal( artifact.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, - "locked (3 families)", + "locked (2 families)", ); assert.equal( artifact.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, @@ -143,12 +144,12 @@ assert.equal( row.promotionAllowed === false && row.excludedFromPositiveFixtures === true && row.boundaryEvidenceReady === true && - row.boundaryEvidence.baselineSummary.executed === 28 && - row.boundaryEvidence.baselineSummary.passed === 28 && - row.boundaryEvidence.baselineSummary.skipped === 131 && + row.boundaryEvidence.baselineSummary.executed === 29 && + row.boundaryEvidence.baselineSummary.passed === 29 && + row.boundaryEvidence.baselineSummary.skipped === 130 && row.boundaryEvidence.baselineSummary.unexpectedFail === 0 && - row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" && - row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" && + row.boundaryEvidence.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"] === "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35" && row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/industrial_lathe_wear/industrial_lathe_wear.ini") && row.boundaryEvidence.iniBoundarySummary.vendored === 0 ), @@ -158,12 +159,12 @@ assert.equal( artifact.virtualHalSimConfigMacroLoadFixtures.blockedRows.some((row) => row.id === "silverdragon-tool-sensor-python-ui-boundary" && row.boundaryEvidenceReady === true && - row.boundaryEvidence.baselineSummary.executed === 28 && - row.boundaryEvidence.baselineSummary.passed === 28 && - row.boundaryEvidence.baselineSummary.skipped === 131 && + row.boundaryEvidence.baselineSummary.executed === 29 && + row.boundaryEvidence.baselineSummary.passed === 29 && + row.boundaryEvidence.baselineSummary.skipped === 130 && row.boundaryEvidence.baselineSummary.unexpectedFail === 0 && - row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" && - row.boundaryEvidence.sourceArtifactHashes["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" && + row.boundaryEvidence.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"] === "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35" && row.boundaryEvidence.boundarySummary.dependencies.includes("missing_vendored_ini:configs/sim/gscreen/silverdragon/silverdragon.ini") && row.boundaryEvidence.iniBoundarySummary.vendored === 0 ), @@ -185,19 +186,19 @@ assert.equal( "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv", ); assert.equal(artifact.promotionCandidateArtifactSummary.evidenceReadyCount, 8); -assert.equal(artifact.promotionCandidateArtifactSummary.inventoryReadyCount, 20); -assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount, 13); -assert.equal(artifact.promotionCandidateArtifactSummary.totalCandidateCount, 28); +assert.equal(artifact.promotionCandidateArtifactSummary.inventoryReadyCount, 19); +assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansionCandidateCount, 14); +assert.equal(artifact.promotionCandidateArtifactSummary.totalCandidateCount, 27); assert.equal(artifact.promotionCandidateArtifactSummary.promotionAllowedCount, 0); assert.equal(artifact.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, 0); assert.deepEqual(artifact.promotionCandidateArtifactSummary.artifactRows, { - promotionCandidates: 28, - evidenceExpansion: 13, + promotionCandidates: 27, + evidenceExpansion: 14, }); assert.equal(artifact.promotionCandidateArtifactSummary.evidenceExpansion.promotionAllowedCount, 0); assert.equal( 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"); if (artifact.axisScreenshotArtifactSummary.artifactCount > 0) { diff --git a/wasm-port/tests/host/write_project_release_readiness_artifact.mjs b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs index df35e88..3b18161 100644 --- a/wasm-port/tests/host/write_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs @@ -29,6 +29,7 @@ const passedGateResults = { "sim-config-inventory": true, "ini-panel-browser": true, "opfs-session-browser": true, + "tool-db-process-proof": true, "release-artifact-url-browser": true, "project-batch-acceptance": true, "ui-node-smokes": true, @@ -88,6 +89,7 @@ const report = createProjectReleaseReadinessReport({ "sim_configs_wasm_node_inventory_unexpected_fail=0", "browser_ini_shell_integration_workflow_smoke=ok", "browser_opfs_session_workflow_smoke=ok", + "tool_db_process_proof=ok", "browser_release_artifact_url_workflow_smoke=ok", "project_batch_acceptance_workflow_node_smoke=ok", "ui_node_smokes=ok", diff --git a/wasm-port/tests/native/verify_sim_configs.sh b/wasm-port/tests/native/verify_sim_configs.sh index de85f6d..24fc9b0 100755 --- a/wasm-port/tests/native/verify_sim_configs.sh +++ b/wasm-port/tests/native/verify_sim_configs.sh @@ -434,9 +434,7 @@ awk -F '\t' ' runtime_family = $1 sub("/[^/]+$", "", runtime_family) blocked = "" - if ($1 ~ /^axis\/db_demo\//) { - blocked = "L4-TOOL-DB" - } else if ($1 ~ /^axis\/vismach\/millturn\// && $2 == "main") { + if ($1 ~ /^axis\/vismach\/millturn\// && $2 == "main") { blocked = "L4-USER-M-PROCESS" } else if ($4 == "upstream-demo-missing-motion-gcode") { blocked = "UPSTREAM-DEMO" diff --git a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs index 16c27bc..05c58a3 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs @@ -81,7 +81,7 @@ const browserDiagnosticsArtifact = { preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", blockedCandidateIds: [], explicitBrowserDiagnosticsCount: 8, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", }, virtualHalSimConfigMacroLoadFixtures, virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }), @@ -203,7 +203,7 @@ assert.equal( ); assert.equal( 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( readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact")?.value, @@ -211,11 +211,11 @@ assert.equal( ); assert.equal( readySummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value, - "evidence-ready=8 inventory-ready=20", + "evidence-ready=8 inventory-ready=19", ); assert.equal( readySummary.rows.find(({ id }) => id === "promotion-candidate-total")?.value, - "28", + "27", ); assert.equal( readySummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value, @@ -239,15 +239,15 @@ assert.equal( ); assert.equal( readySummary.rows.find(({ id }) => id === "evidence-expansion-candidates")?.value, - "13", + "14", ); assert.equal( readySummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value, - "28", + "27", ); assert.equal( readySummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value, - "13", + "14", ); assert.equal( readySummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value, @@ -291,11 +291,11 @@ assert.equal( ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value, - "19", + "18", ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc", + "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( readySummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value, @@ -303,31 +303,23 @@ assert.equal( ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value, - "3", + "2", ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value, - "L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", + "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value, - "19", + "18", ); assert.equal( readySummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0", -); -assert.equal( - readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-TOOL-DB")?.value, - "1 locked rows; promotion_allowed=0; first=axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put", -); -assert.equal( - readySummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-TOOL-DB-paths")?.value, - "axis/db_demo/base.ngc", + "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-lock")?.value, - "locked (3 families)", + "locked (2 families)", ); assert.equal( readySummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, @@ -351,16 +343,16 @@ assert.deepEqual( ["artifact-validation", "ready"], ["virtual-hal-promotion-families", expectedPromotionFamilySummary], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], - ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], - ["promotion-candidate-total", "28"], + ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"], + ["promotion-candidate-total", "27"], ["evidence-ready-candidate-rows", "8"], ["evidence-ready-candidate-preview", "qtdragon-multi-joint-on-abort"], ["evidence-ready-candidate-preview-gcode", "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"], ["evidence-ready-candidate-promotion-allowed", "0"], ["evidence-ready-candidate-baseline-changing", "no"], - ["promotion-candidate-artifact-rows", "28"], - ["evidence-expansion-candidates", "13"], - ["evidence-expansion-artifact-rows", "13"], + ["promotion-candidate-artifact-rows", "27"], + ["evidence-expansion-candidates", "14"], + ["evidence-expansion-artifact-rows", "14"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-preferred-candidate", "woodpecker-on-abort"], @@ -371,22 +363,20 @@ assert.deepEqual( ["evidence-expansion-source-count-list", "3, 3, 3"], ["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"], ["promotion-candidate-allowed", "0"], - ["hard-block-runtime-locked-rows", "19"], - ["hard-block-runtime-locked-preview", "L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc"], + ["hard-block-runtime-locked-rows", "18"], + ["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-family-count", "3"], - ["hard-block-runtime-family-summary", "L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes"], - ["hard-block-runtime-detail-row-count", "19"], - ["hard-block-runtime-detail-preview-1", "L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0"], - ["hard-block-runtime-detail-preview-2", "L4-PYTHON-REMAP:axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"], - ["hard-block-runtime-detail-preview-3", "L4-PYTHON-REMAP:axis/laser/vector_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"], - ["hard-block-runtime-detail-preview-4", "L4-PYTHON-REMAP:axis/laser/vector_test2.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"], - ["hard-block-runtime-detail-preview-5", "L4-PYTHON-REMAP:axis/remap/cycle/nc_files/examples.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0"], + ["hard-block-runtime-family-count", "2"], + ["hard-block-runtime-family-summary", "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes"], + ["hard-block-runtime-detail-row-count", "18"], + ["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/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/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-TOOL-DB", "1 locked rows; promotion_allowed=0; first=axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put"], ["hard-block-runtime-family-L4-PYTHON-REMAP", "17 locked rows; promotion_allowed=0; first=axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture"], ["hard-block-runtime-family-L4-USER-M-PROCESS-paths", "axis/vismach/millturn/example.ngc"], - ["hard-block-runtime-family-L4-TOOL-DB-paths", "axis/db_demo/base.ngc"], ["hard-block-runtime-family-L4-PYTHON-REMAP-paths", "axis/laser/raster_test.ngc, axis/laser/vector_test.ngc, axis/laser/vector_test2.ngc, axis/remap/cycle/nc_files/examples.ngc, axis/remap/extend-builtins/nc_files/examples.ngc, axis/remap/getting-started/nc_files/examples.ngc, axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc, axis/remap/rack-toolchange/nc_files/tcdemo.ngc, axis/remap/stop-lookahead/nc_files/examples.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_back_and_forth.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.ngc, axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/simple_example.ngc, axis/vismach/VMC_toolchange/toolchange.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzac.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/boat-xyzbc.ngc, gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc"], ["tool-db-process-proof", "ready"], ["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-source-file-count", "17 source files"], ["virtual-hal-promotion-browser-diagnostics-count", "8 diagnostics-ready"], - ["virtual-hal-promotion-candidate-baseline", "executed=28 passed=28 skipped=131 unexpected_fail=0"], - ["hard-block-runtime-lock", "locked (3 families)"], + ["virtual-hal-promotion-candidate-baseline", "executed=29 passed=29 skipped=130 unexpected_fail=0"], + ["hard-block-runtime-lock", "locked (2 families)"], ["promoted-blocked-family-count", "0 promoted"], ["saved-session-diagnostics", "ready"], ["next-command", "none"], diff --git a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs index 0ac9f87..7d1cada 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs @@ -282,7 +282,7 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { { id: "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", @@ -292,7 +292,7 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { { id: "evidence-expansion-candidates", label: "Evidence expansion candidates", - value: "13", + value: "14", }, { id: "evidence-expansion-next-evidence", @@ -327,12 +327,12 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { { id: "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", label: "Hard-block runtime lock", - value: "locked (3 families)", + value: "locked (2 families)", }, { id: "promoted-blocked-family-count", @@ -362,9 +362,9 @@ assert.equal( virtualHalSimConfigPromotionCandidates, virtualHalSimConfigMacroLoadFixtures, virtualHalMotionControllerMatrix, - promotedRuntimeFamilies: ["L4-TOOL-DB"], + promotedRuntimeFamilies: ["L4-PYTHON-REMAP"], })).rows.find(({ id }) => id === "blocked-runtime-families")?.value, - "promoted: L4-TOOL-DB", + "promoted: L4-PYTHON-REMAP", ); const artifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel( @@ -413,11 +413,11 @@ assert.deepEqual( ["virtual-hal-sim-config-source-coverage", "ready"], ["virtual-hal-sim-config-promotion-candidates", "ready"], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], - ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], - ["promotion-candidate-total", "28"], - ["promotion-candidate-artifact-rows", "28"], - ["evidence-expansion-candidates", "13"], - ["evidence-expansion-artifact-rows", "13"], + ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"], + ["promotion-candidate-total", "27"], + ["promotion-candidate-artifact-rows", "27"], + ["evidence-expansion-candidates", "14"], + ["evidence-expansion-artifact-rows", "14"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-artifact", "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"], @@ -516,16 +516,16 @@ assert.deepEqual( ["artifact-validation", "ready"], ["virtual-hal-promotion-families", expectedPromotionFamilySummary], ["promotion-candidate-artifact", "wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv"], - ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=20"], - ["promotion-candidate-total", "28"], + ["promotion-candidate-layers", "evidence-ready=8 inventory-ready=19"], + ["promotion-candidate-total", "27"], ["evidence-ready-candidate-rows", "0"], ["evidence-ready-candidate-preview", "not provided"], ["evidence-ready-candidate-preview-gcode", "not provided"], ["evidence-ready-candidate-promotion-allowed", "0"], ["evidence-ready-candidate-baseline-changing", "no"], - ["promotion-candidate-artifact-rows", "28"], - ["evidence-expansion-candidates", "13"], - ["evidence-expansion-artifact-rows", "13"], + ["promotion-candidate-artifact-rows", "27"], + ["evidence-expansion-candidates", "14"], + ["evidence-expansion-artifact-rows", "14"], ["evidence-expansion-next-evidence", "browser-diagnostics-binding"], ["evidence-expansion-baseline-changing", "no"], ["evidence-expansion-preferred-candidate", "not provided"], @@ -539,14 +539,12 @@ assert.deepEqual( ["hard-block-runtime-locked-rows", "0"], ["hard-block-runtime-locked-preview", "not provided"], ["hard-block-runtime-promotion-allowed", "0"], - ["hard-block-runtime-family-count", "3"], - ["hard-block-runtime-family-summary", "L4-USER-M-PROCESS:0 locked=yes, L4-TOOL-DB:0 locked=yes, L4-PYTHON-REMAP:0 locked=yes"], + ["hard-block-runtime-family-count", "2"], + ["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-family-L4-USER-M-PROCESS", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"], - ["hard-block-runtime-family-L4-TOOL-DB", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"], ["hard-block-runtime-family-L4-PYTHON-REMAP", "0 locked rows; promotion_allowed=0; first=not provided; reason=not provided; next=not provided"], ["hard-block-runtime-family-L4-USER-M-PROCESS-paths", "not provided"], - ["hard-block-runtime-family-L4-TOOL-DB-paths", "not provided"], ["hard-block-runtime-family-L4-PYTHON-REMAP-paths", "not provided"], ["tool-db-process-proof", "ready"], ["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-browser-diagnostics-count", "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"], ["saved-session-diagnostics", "not requested"], ["next-command", "none"], diff --git a/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs b/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs new file mode 100644 index 0000000..18da808 --- /dev/null +++ b/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs @@ -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); diff --git a/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh b/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh new file mode 100755 index 0000000..92ab76c --- /dev/null +++ b/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh @@ -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" diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index f86dca0..5c3615c 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -92,9 +92,14 @@ import { createProjectReleaseReadinessSummaryViewModel, createLinuxCncToolDbBrowserWorkerAdapter, createLinuxCncToolDbProcessPort, + createLinuxCncPythonRemapBrowserWorkerAdapter, + createLinuxCncPythonRemapRuntimePort, + createPythonRemapLifecyclePlan, + createPythonRemapRuntimeDiagnostics, createToolDbProcessDiagnostics, createToolDbTransactionPlan, runToolDbProcessPortPersistenceSession, + validatePythonRemapLifecycleTranscript, createVirtualHalBridgeActionPlan, createVirtualHalBridgeReadiness, createVirtualHalCommandScriptFixtureReport, @@ -230,16 +235,16 @@ const expectedPromotionCandidateArtifactSummary = { evidenceExpansionArtifactPath: "wasm-port/build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv", layerCount: 2, evidenceReadyCount: 8, - inventoryReadyCount: 20, - evidenceExpansionCandidateCount: 13, - totalCandidateCount: 28, + inventoryReadyCount: 19, + evidenceExpansionCandidateCount: 14, + totalCandidateCount: 27, promotionAllowedCount: 0, - hardBlockRuntimeLock: "locked (3 families)", + hardBlockRuntimeLock: "locked (2 families)", hardBlockRuntimePromotionAllowedCount: 0, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", artifactRows: { - promotionCandidates: 28, - evidenceExpansion: 13, + promotionCandidates: 27, + evidenceExpansion: 14, }, hardBlockRuntimeFamilyRows: [], hardBlockRuntimeFamilyDetailRows: [], @@ -254,16 +259,6 @@ const expectedPromotionCandidateArtifactSummary = { firstBlockReason: null, nextProof: null, }, - { - family: "L4-TOOL-DB", - candidateCount: 0, - promotionAllowedCount: 0, - locked: true, - lockedPaths: [], - firstPath: null, - firstBlockReason: null, - nextProof: null, - }, { family: "L4-PYTHON-REMAP", candidateCount: 0, @@ -285,7 +280,7 @@ const expectedPromotionCandidateArtifactSummary = { }, { id: "inventory-ready", - candidateCount: 20, + candidateCount: 19, promotionAllowedCount: 0, virtualHalEvidenceReady: false, baselineChanging: false, @@ -293,8 +288,8 @@ const expectedPromotionCandidateArtifactSummary = { ], evidenceExpansion: { id: "browser-diagnostics-expansion", - candidateCount: 13, - artifactRowCount: 13, + candidateCount: 14, + artifactRowCount: 14, promotionAllowedCount: 0, baselineChanging: false, nextEvidence: "browser-diagnostics-binding", @@ -525,9 +520,14 @@ const requiredExports = [ ["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath], ["createLinuxCncToolDbBrowserWorkerAdapter", createLinuxCncToolDbBrowserWorkerAdapter], ["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort], + ["createLinuxCncPythonRemapBrowserWorkerAdapter", createLinuxCncPythonRemapBrowserWorkerAdapter], + ["createLinuxCncPythonRemapRuntimePort", createLinuxCncPythonRemapRuntimePort], + ["createPythonRemapLifecyclePlan", createPythonRemapLifecyclePlan], + ["createPythonRemapRuntimeDiagnostics", createPythonRemapRuntimeDiagnostics], ["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics], ["createToolDbTransactionPlan", createToolDbTransactionPlan], ["runToolDbProcessPortPersistenceSession", runToolDbProcessPortPersistenceSession], + ["validatePythonRemapLifecycleTranscript", validatePythonRemapLifecycleTranscript], ["validateToolDbTranscript", validateToolDbTranscript], ["saveToolDbFile", saveToolDbFile], ["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_AUDIT_CANDIDATES.length >= 2, true); assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE, { - executed: 28, - passed: 28, - skipped: 131, + executed: 29, + passed: 29, + skipped: 130, unexpectedFail: 0, }); assert.deepEqual(VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES, { "wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv": - "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9", + "6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3", "wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv": - "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae", + "17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35", }); const simConfigMacroLoadFixtureReport = createVirtualHalSimConfigMacroLoadFixtureReport({ manifestText: sourceManifestText, @@ -1250,7 +1250,7 @@ assert.equal(createVirtualHalSimConfigMacroLoadFixtureReport({ ).boundaryEvidence, baselineSummary: { ...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", blockedCandidateIds: [], explicitBrowserDiagnosticsCount: 8, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", }, virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport, virtualHalCommandScriptFixtures: commandScriptFixtureReport, @@ -1418,7 +1418,7 @@ browserDiagnosticsArtifact.virtualHalSessionDiagnostics = { }; const browserDiagnosticsArtifactValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDiagnosticsArtifact); 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.simConfigSourceCoverageReady, 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.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( releaseReadinessWaiting.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, - "locked (3 families)", + "locked (2 families)", ); assert.equal( releaseReadinessWaiting.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, @@ -1657,11 +1657,11 @@ assert.deepEqual( ); assert.equal( releaseReadinessReady.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value, - "28", + "27", ); assert.equal( releaseReadinessReady.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value, - "13", + "14", ); assert.deepEqual( releaseReadinessReady.virtualHalPromotionFamilyRows.map(({ id, value }) => [ @@ -1713,7 +1713,7 @@ assert.equal( ); assert.equal( 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({ observedOutputs: ["project_release_gate=ok"], @@ -1724,11 +1724,11 @@ const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessRepo promotionCandidateRows: promotionCandidateArtifactRows, evidenceExpansionRows: evidenceExpansionArtifactRows, }); -assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 28); -assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 13); +assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 27); +assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 14); assert.equal( releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows.length, - 19, + 18, ); assert.equal( releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimePromotionAllowedCount, @@ -1736,8 +1736,8 @@ assert.equal( ); assert.equal( releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows - .some((row) => row.skipKind === "L4-TOOL-DB" && row.promotionAllowed === false), - true, + .some((row) => row.skipKind === "L4-TOOL-DB"), + false, ); assert.deepEqual( releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.hardBlockRuntimeFamilySummaryRows @@ -1749,7 +1749,6 @@ assert.deepEqual( ]), [ ["L4-USER-M-PROCESS", 1, 0, true], - ["L4-TOOL-DB", 1, 0, true], ["L4-PYTHON-REMAP", 17, 0, true], ], ); @@ -1764,13 +1763,6 @@ assert.deepEqual( promotionAllowed, ]), [ - [ - "L4-TOOL-DB", - "axis/db_demo/base.ngc", - "design_tooldata_db_protocol_boundary", - "db_program_v2_1_handshake_getall_load_unload_or_put", - false, - ], [ "L4-PYTHON-REMAP", "axis/laser/raster_test.ngc", @@ -1785,6 +1777,13 @@ assert.deepEqual( "python_runtime_owner_and_fixture", false, ], + [ + "L4-PYTHON-REMAP", + "axis/laser/vector_test2.ngc", + "design_linuxcnc_python_runtime_boundary", + "python_runtime_owner_and_fixture", + false, + ], ], ); const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({ @@ -1863,7 +1862,6 @@ assert.deepEqual(releaseReadinessArtifactValidation, { axisScreenshotArtifactCount: 0, blockedRuntimeFamilies: [ "L4-USER-M-PROCESS", - "L4-TOOL-DB", "L4-PYTHON-REMAP", ], rows: [ @@ -1920,27 +1918,27 @@ assert.deepEqual(releaseReadinessArtifactValidation, { { id: "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", label: "Promotion candidate total", - value: "28", + value: "27", }, { id: "promotion-candidate-artifact-rows", label: "Promotion candidate artifact rows", - value: "28", + value: "27", }, { id: "evidence-expansion-candidates", label: "Evidence expansion candidates", - value: "13", + value: "14", }, { id: "evidence-expansion-artifact-rows", label: "Evidence expansion artifact rows", - value: "13", + value: "14", }, { id: "evidence-expansion-next-evidence", @@ -1998,12 +1996,12 @@ assert.deepEqual(releaseReadinessArtifactValidation, { { id: "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", label: "Hard-block runtime lock", - value: "locked (3 families)", + value: "locked (2 families)", }, { id: "promoted-blocked-family-count", @@ -2109,7 +2107,7 @@ assert.equal( }), }), ).rows.find(({ id }) => id === "promotion-candidate-layers")?.value, - "evidence-ready=8 inventory-ready=20", + "evidence-ready=8 inventory-ready=19", ); assert.equal( createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel( @@ -2138,7 +2136,7 @@ assert.equal( }), }), ).rows.find(({ id }) => id === "evidence-expansion-candidates")?.value, - "13", + "14", ); assert.deepEqual( createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel( @@ -2290,14 +2288,14 @@ assert.equal(createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadi assert.equal( createProjectReleaseReadinessReport({ observedOutputs: ["project_release_gate=ok"], - promotedRuntimeFamilies: ["L4-TOOL-DB"], + promotedRuntimeFamilies: ["L4-PYTHON-REMAP"], }).missing.includes("blocked-runtime-families"), true, ); assert.equal( createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({ observedOutputs: ["project_release_gate=ok"], - promotedRuntimeFamilies: ["L4-TOOL-DB"], + promotedRuntimeFamilies: ["L4-PYTHON-REMAP"], })).rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, "violated (1 promoted)", ); diff --git a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs index dde200d..9a90afc 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs @@ -1507,7 +1507,7 @@ const browserDiagnosticsArtifact = { preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc", blockedCandidateIds: [], explicitBrowserDiagnosticsCount: 8, - inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0", + inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0", }, virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures, virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: browserDiagnosticsVirtualHalState }), @@ -2073,7 +2073,7 @@ assert.equal( ); assert.equal( 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( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, diff --git a/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.mjs b/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.mjs new file mode 100644 index 0000000..2dfd0a0 --- /dev/null +++ b/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.mjs @@ -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"); diff --git a/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh b/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh new file mode 100755 index 0000000..4e9023a --- /dev/null +++ b/wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh @@ -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" diff --git a/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs b/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs index 121a5f2..e2e2e74 100644 --- a/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs @@ -556,12 +556,12 @@ function verifyTrackedBaselineText(markdownText, nativeRecords, inventory, skipR "tracked matrix Node executed drift", ); assert.equal( - numberFromTrackedBaseline(markdownText, /`passed ([0-9]+)`/, "Node passed"), + numberFromTrackedBaseline(markdownText, /Current Node inventory: `executed [0-9]+`, `passed ([0-9]+)`/, "Node passed"), inventory.passed, "tracked matrix Node passed drift", ); 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, "tracked matrix Node skipped drift", ); @@ -605,7 +605,6 @@ function verifyRequiredClassRepresentatives(records) { "gmoccapy/macros/on_abort.ngc", ]; const requiredFullProcessBlocked = new Map([ - ["axis/db_demo/base.ngc", "L4-TOOL-DB"], ["axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"], ]); @@ -2937,13 +2936,13 @@ function linuxCncOwnerForBlockedKind(blocked) { } function blockedDependencySummaryRow(record, pathMatrixByPath) { - const blocked = pathMatrixByPath.get(record.path)?.blocked ?? "-"; + const matrixBlocked = pathMatrixByPath.get(record.path)?.blocked ?? "-"; const sourceAvailable = sourceConfigPathExists(record.ini); if (!sourceAvailable) { return [ record.path, record.ini, - blocked, + matrixBlocked, posix.dirname(record.path), "0", "-", @@ -2993,6 +2992,7 @@ function blockedDependencySummaryRow(record, pathMatrixByPath) { const uiProcess = displayValues.some((value) => !/^axis$/i.test(value)); const haluiMdiProcess = iniValues(entries, "HALUI", "MDI_COMMAND").length > 0; const dbProgram = iniValues(entries, "EMCIO", "DB_PROGRAM")[0] ?? ""; + const blocked = matrixBlocked === "-" && dbProgram ? "L4-TOOL-DB" : matrixBlocked; const toolDbEvidence = toolDbProtocolEvidence({ iniText, dbProgram }); const userMCodes = uniqueSorted(sourceExecutionTextsForBlocked(record).flatMap(userMCodesInExecutionText)); const userMProcessFiles = userMProcessFilesForCodes(record.ini, entries, userMCodes); @@ -3195,31 +3195,6 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) { "`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) { @@ -3256,7 +3231,6 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) { "native-proof-alignment-summary.tsv", "consumed by generated worklist and native proof-gate", "`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", ]) { assert.ok( @@ -3268,6 +3242,19 @@ function verifyFullProcessBoundaryDesign(markdownText, rows) { markdownText.includes("Do not implement HAL, task, HALUI, Tcl, Python, tool-database, or external"), "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) { @@ -5240,6 +5227,14 @@ function boundaryPhaseCompletionSummaryRows({ const blockedFamiliesUnpromotedOk = hardBlockedRows.every((row) => { const tracked = trackedByPath.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 ( tracked && inventory && @@ -5554,15 +5549,15 @@ function boundaryPhaseCompletionSummaryRows({ const runtimeBoundaryPostNativePassGatesOk = ( runtimeBoundaryPostNativePassGateRows.length === 3 && runtimeBoundaryPostNativePassGateRows.every((row) => - row.native_pass_ready === "0" && - row.native_evidence_ready === "0" && - row.node_gate_status === "blocked_until_native_pass_evidence" && + ["0", "1"].includes(row.native_pass_ready) && + ["0", "1"].includes(row.native_evidence_ready) && + ["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.manual_lock_update_required === "1" && row.promotion_lock_active === "1" && row.execution_enabled === "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 = ( @@ -5707,15 +5702,18 @@ function boundaryPhaseCompletionSummaryRows({ (row.host_action === "ready_for_manual_opt_in_native_probe" && row.skip_reason === "-" && row.missing_host_requirements === "-" && - row.skip_evidence_status === "skip_contract_not_applicable_dispatch_allowed") || + [ + "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.skip_reason === "missing_host_requirements" && row.missing_host_requirements !== "-" && row.current_probe_status === "skipped_missing_host_runtime" && row.skip_evidence_status === "skip_valid_until_host_requirements_available") ) && - row.evidence_required_now === "0" && - row.observed_evidence_ready === "0" && + /^[0-9]+$/.test(row.evidence_required_now) && + /^[0-9]+$/.test(row.observed_evidence_ready) && row.execution_enabled === "0" && row.promotion_allowed === "0", ) @@ -5726,10 +5724,10 @@ function boundaryPhaseCompletionSummaryRows({ row.scope === "blocked_runtime_opt_in_probe_skip_evidence" && row.probe_count === "3" && /^[0-9]+$/.test(row.skip_count) && - row.evidence_required_now_count === "0" && - row.observed_evidence_ready_count === "0" && + /^[0-9]+$/.test(row.evidence_required_now_count) && + /^[0-9]+$/.test(row.observed_evidence_ready_count) && 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.promotion_allowed === "0", ) @@ -5737,11 +5735,15 @@ function boundaryPhaseCompletionSummaryRows({ const runtimeBoundaryNativeEvidenceAcceptanceGateOk = ( runtimeBoundaryNativeEvidenceAcceptanceGateRows.length === 3 && runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) => - row.native_evidence_status === "pending_until_native_pass" && - row.evidence_required_now === "0" && - row.observed_evidence_ready === "0" && - ["blocked_until_host_requirements_available", "blocked_until_native_pass_evidence"].includes(row.native_evidence_gate) && - row.evidence_acceptance_allowed === "0" && + ["pending_until_native_pass", "native_pass_evidence_observed"].includes(row.native_evidence_status) && + /^[0-1]$/.test(row.evidence_required_now) && + /^[0-1]$/.test(row.observed_evidence_ready) && + [ + "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.execution_enabled === "0" && row.promotion_allowed === "0", @@ -5771,7 +5773,7 @@ function boundaryPhaseCompletionSummaryRows({ row.promotion_allowed === "0", ) && runtimeBoundaryNativeEvidenceAcceptanceGateRows.every((row) => - row.evidence_acceptance_allowed === "0" && + ["0", "1"].includes(row.evidence_acceptance_allowed) && row.execution_enabled === "0" && row.promotion_allowed === "0", ) && @@ -7865,10 +7867,13 @@ function runtimeBoundaryOptInProbeSkipEvidenceContractRows({ return dispatchPlanRows.map((row) => { const evidence = evidenceByClass.get(row.boundary_class); - const skipReason = row.dispatch_allowed === "1" ? "-" : "missing_host_requirements"; - const skipEvidenceStatus = row.dispatch_allowed === "1" - ? "skip_contract_not_applicable_dispatch_allowed" - : "skip_valid_until_host_requirements_available"; + const nativeEvidenceObserved = evidence?.evidence_status === "native_pass_evidence_observed"; + const skipReason = nativeEvidenceObserved || row.dispatch_allowed === "1" ? "-" : "missing_host_requirements"; + const skipEvidenceStatus = nativeEvidenceObserved + ? "native_pass_evidence_observed_skip_contract_closed" + : row.dispatch_allowed === "1" + ? "skip_contract_not_applicable_dispatch_allowed" + : "skip_valid_until_host_requirements_available"; return [ 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.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.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.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.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`); - 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.skip_reason, "-", `${row.boundary_class}: ready dispatch must not have skip reason`); assert.equal(row.missing_host_requirements, "-", `${row.boundary_class}: ready dispatch must not list missing host requirements`); assert.equal(row.skip_evidence_status, "skip_contract_not_applicable_dispatch_allowed", `${row.boundary_class}: ready skip evidence status drift`); } else { + assert.equal(row.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.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`); @@ -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.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.evidence_required_now_count, "0", "opt-in probe skip evidence rollup must not require evidence now"); - assert.equal(row.observed_evidence_ready_count, "0", "opt-in probe skip evidence rollup must not report evidence ready"); + assert.equal(row.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, 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.skip_reasons, listValue(uniqueSorted(skippedRows.map((contract) => contract.skip_reason))), "opt-in probe skip evidence rollup reason drift"); assert.equal(row.missing_host_requirements, listValue(expectedMissingRequirements), "opt-in probe skip evidence rollup missing requirements drift"); assert.equal(row.current_probe_statuses, 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.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.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"); @@ -8163,27 +8185,39 @@ function verifyRuntimeBoundaryNativeEvidenceAcceptanceGateRows({ assert.equal(row.blocked, skip.blocked, `${row.boundary_class}: native evidence gate blocked drift`); assert.equal(row.runtime_probe, skip.runtime_probe, `${row.boundary_class}: native evidence gate runtime probe drift`); assert.equal(row.current_probe_status, 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.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.observed_evidence_ready, "0", `${row.boundary_class}: native evidence gate must not observe evidence ready`); assert.equal(row.observed_evidence_ready, skip.observed_evidence_ready, `${row.boundary_class}: native evidence gate readiness drift`); assert.equal(row.skip_evidence_status, skip.skip_evidence_status, `${row.boundary_class}: native evidence gate skip status drift`); assert.equal(row.promotion_ready, "0", `${row.boundary_class}: native evidence gate must not be promotion-ready`); assert.equal(row.promotion_ready, readiness.promotion_ready, `${row.boundary_class}: native evidence gate promotion readiness drift`); - assert.equal(row.node_gate_status, "blocked_until_native_pass_evidence", `${row.boundary_class}: native evidence gate Node status drift`); assert.equal(row.node_gate_status, postNativeGate.node_gate_status, `${row.boundary_class}: native evidence gate post-native Node status drift`); assert.equal(row.browser_gate_status, "blocked_until_node_gate_complete", `${row.boundary_class}: native evidence gate browser status drift`); assert.equal(row.browser_gate_status, postNativeGate.browser_gate_status, `${row.boundary_class}: native evidence gate post-native browser status drift`); - 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.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 { + 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.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.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`); @@ -8313,7 +8347,11 @@ function verifyBlockedRuntimeOptInGateConsistency({ 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(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(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`); @@ -8324,7 +8362,11 @@ function verifyBlockedRuntimeOptInGateConsistency({ } else { assert.equal(blocker.blocker_keys.includes("host_runtime_requirements_missing"), false, `${boundaryClass}: ready runtime must not keep host runtime blocker`); } - assert.ok(evidenceGate.native_evidence_gate.includes("blocked"), `${boundaryClass}: evidence gate must be blocked`); + 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.ok(["0", "1"].includes(execution.runtime_ready), `${execution.boundary_class}: invalid runtime readiness`); if (execution.runtime_ready === "1") { - assert.equal(execution.current_probe_status, "ready_disabled_by_default", `${execution.boundary_class}: ready probe must remain opt-in`); - assert.equal(execution.plan_status, "ready_to_run_opt_in_probe", `${execution.boundary_class}: execution plan must remain opt-in`); + assert.ok( + [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`); } else { 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.skip_reason, "missing_host_requirements", `${execution.boundary_class}: skip reason drift`); } - assert.equal(pass.evidence_required_now, "0", `${execution.boundary_class}: pass evidence must not be required yet`); - assert.equal(skip.evidence_required_now, "0", `${execution.boundary_class}: skip evidence must not be required yet`); - assert.equal(pass.observed_evidence_ready, "0", `${execution.boundary_class}: pass evidence must not be ready yet`); - assert.equal(skip.observed_evidence_ready, "0", `${execution.boundary_class}: skip evidence must not be ready yet`); - assert.equal(pass.evidence_status, "pending_until_native_pass", `${execution.boundary_class}: pass evidence status drift`); - assert.equal(skip.native_evidence_status, "pending_until_native_pass", `${execution.boundary_class}: skip evidence native status drift`); + if (execution.current_probe_status === execution.expected_pass_status) { + assert.equal(pass.evidence_required_now, "1", `${execution.boundary_class}: pass evidence should be required after native pass`); + assert.equal(skip.evidence_required_now, "1", `${execution.boundary_class}: skip evidence should reflect native pass evidence`); + assert.equal(pass.observed_evidence_ready, "1", `${execution.boundary_class}: pass evidence must be ready after native pass`); + assert.equal(skip.observed_evidence_ready, "1", `${execution.boundary_class}: skip evidence must reflect ready native evidence`); + 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( - ["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`, ); @@ -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_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(skipEvidence.evidence_required_now_count, "0", "blocked runtime rollup must not require evidence on host-blocked probes"); - 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.evidence_required_now_count), "blocked runtime rollup evidence required count drift"); + 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(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"); @@ -8490,9 +8554,12 @@ function verifyBlockedRuntimeRollupConsistency({ ].includes(dispatch.dispatch_status), "blocked runtime rollup dispatch status drift", ); - assert.equal(skipEvidence.evidence_rollup_status, "no_native_pass_evidence_accepted_while_host_blocked", "blocked runtime rollup evidence status drift"); + assert.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.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"); 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.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`); - assert.equal(readiness.native_evidence_ready, "0", `${boundaryClass}: current host must not report native evidence ready`); + if (readiness.current_probe_status === readiness.expected_pass_status) { + 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.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.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.ok(blocker.blocker_keys.includes("native_runtime_probe_not_passed"), `${boundaryClass}: native pass blocker missing`); - assert.ok(blocker.blocker_keys.includes("native_pass_evidence_not_ready"), `${boundaryClass}: native evidence blocker missing`); + if (readiness.native_pass_ready !== "1") { + 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("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("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(postNative.browser_gate_status, "blocked_until_node_gate_complete", `${boundaryClass}: post-native browser gate must wait for Node gate`); - assert.equal(postNative.gate_status, "blocked_before_native_pass", `${boundaryClass}: post-native gate status drift`); - assert.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( + postNative.node_gate_status, + readiness.native_evidence_ready === "1" + ? "pending_node_inventory_promotion_gate" + : "blocked_until_native_pass_evidence", + `${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]) { 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) { 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") { verifyExpectedOutput( `inventory_${record.path}`, @@ -11773,7 +11881,7 @@ const iniBoundarySummaryRecords = verifyIniBoundarySummaryRows( const blockedDependencyRecords = nativeRecords.filter((record) => ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"].includes( pathMatrixByPath.get(record.path)?.blocked ?? "-", - ), + ) || record.path === "axis/db_demo/base.ngc", ); const blockedDependencySummaryRows = blockedDependencyRecords.map((record) => blockedDependencySummaryRow(record, pathMatrixByPath), @@ -12287,7 +12395,7 @@ function verifyEvidenceExpansionCandidateRows(rows, summaryRows, boundaryRows, t expectedPaths, "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) { const boundary = boundaryByPath.get(row.path); assert.ok(boundary, `${row.path}: evidence expansion candidate lacks boundary evidence`); @@ -12365,7 +12473,7 @@ function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) { 0, "current baseline has no direct skipped-main inventory promotion candidate", ); - for (const lockedKind of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"]) { + for (const lockedKind of ["L4-PYTHON-REMAP", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"]) { const lockedRows = parsedInventoryReady.filter((row) => row.skip_kind === lockedKind); assert.ok(lockedRows.length > 0, `${lockedKind}: promotion-candidates missing locked inventory rows`); assert.equal( @@ -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.virtual_hal_evidence_ready, "0", `${row.path}: skipped inventory row must not claim virtual HAL evidence ready`); assert.ok( - ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.skip_kind), + ["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`, ); const boundary = boundaryByPath.get(row.path); diff --git a/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh b/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh index 36249ea..51d53a6 100755 --- a/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +++ b/wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh @@ -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_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_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_SOURCE_PROOF_INPUTS=( "$ROOT_DIR/tools/build_native_probes.sh" @@ -27,11 +29,38 @@ native_source_proof_refresh_required() { 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 "$ROOT_DIR/tests/native/verify_sim_configs.sh" fi 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 mkdir -p "$NATIVE_BUILD_DIR" diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh index b2fd72c..428c025 100755 --- a/wasm-port/tools/build_native_probes.sh +++ b/wasm-port/tools/build_native_probes.sh @@ -171,6 +171,9 @@ native_input_fingerprint() { "$CXX" --version 2>/dev/null | sed -n '1p' || true printf 'CC=%s\n' "$CC" "$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 \ "$ROOT_DIR/tools/build_native_probes.sh" \ "$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" run_logged_command \ linuxcnc_millturn_user_m_runtime_probe \ + env \ + ENABLE_MILLTURN_USER_M_RUNTIME_PROBE="${ENABLE_MILLTURN_USER_M_RUNTIME_PROBE:-0}" \ bash \ "$MILLTURN_USER_M_RUNTIME_PROBE" else @@ -1362,6 +1367,8 @@ if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_tool_db_boundary_probe.exitcod "$BUILD_DIR/linuxcnc_tool_db_boundary_probe" run_logged_command \ linuxcnc_tool_db_runtime_probe \ + env \ + ENABLE_TOOL_DB_RUNTIME_PROBE="${ENABLE_TOOL_DB_RUNTIME_PROBE:-0}" \ bash \ "$TOOL_DB_RUNTIME_PROBE" else @@ -1382,6 +1389,8 @@ if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_python_remap_boundary_probe.ex "$BUILD_DIR/linuxcnc_python_remap_boundary_probe" run_logged_command \ linuxcnc_python_remap_runtime_probe \ + env \ + ENABLE_PYTHON_REMAP_RUNTIME_PROBE="${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}" \ bash \ "$PYTHON_REMAP_RUNTIME_PROBE" else