From 07869f5c69d101f6a1ae21047b0dd539e2599be2 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sat, 20 Jun 2026 07:59:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90L4-USER-M-PROCESS=20Web?= =?UTF-8?q?=E4=BB=BF=E7=9C=9F=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:L4-USER-M-PROCESS 已按 Web/virtual HAL 数控仿真主线完成接入,native LinuxCNC runtime 不再作为 Web 仿真阻塞;新增 text31.txt 接续剩余 77 个 SKIP 的 main-program promotion 复核。 --- PROJECT_COMPLETION_TRACKER.md | 6 + text28.txt | 337 ++++++++++++++++++ text30.txt | 44 ++- text31.txt | 227 ++++++++++++ wasm-port/docs/compatibility-validation.md | 14 +- .../docs/full-process-boundary-design.md | 17 + .../docs/host-runtime-boundary-handoff.md | 19 +- .../docs/linuxcnc-test-porting-tracker.md | 2 +- wasm-port/docs/sim-configs-completion-plan.md | 19 +- .../docs/sim-configs-coverage-handoff.md | 10 +- wasm-port/docs/sim-configs-coverage-matrix.md | 6 +- wasm-port/runtime/sdk/src/linuxcnc-hal.js | 26 +- .../sdk/src/project-release-readiness.js | 7 + .../runtime/ui/simulation/simulation-app.js | 70 ++++ wasm-port/tests/browser/interp_smoke.html | 3 +- .../browser/real_simulation_page_smoke.html | 51 +++ .../native/probe_millturn_user_m_runtime.sh | 55 ++- .../tests/native/verify_native_probes.sh | 12 +- .../tests/sdk/node/verify_sdk_surface.mjs | 8 + .../node/verify_real_simulation_programs.mjs | 30 ++ .../verify_sim_configs_inventory_wasm.mjs | 249 ++++++++++++- 21 files changed, 1161 insertions(+), 51 deletions(-) create mode 100644 text31.txt diff --git a/PROJECT_COMPLETION_TRACKER.md b/PROJECT_COMPLETION_TRACKER.md index f7b1a85..aa23511 100644 --- a/PROJECT_COMPLETION_TRACKER.md +++ b/PROJECT_COMPLETION_TRACKER.md @@ -87,6 +87,12 @@ Current first priority: - The page now includes selectable test programs for square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling, with browser smoke coverage for each program. +- The browser simulation state now carries a source-derived millturn + `L4-USER-M-PROCESS` virtual HAL proof for the `M429 -> M129` turn transition + and `M428 -> M128` mill transition. It verifies switchkins guard pins and + `ini.[xz].*` limit pin state without enabling external process execution or + inventory promotion, and the real browser simulation page smoke now asserts + that proof directly from `linuxCncRealSimulationState`. - Playback controls now expose reset/step/play/finish over LinuxCNC canonical motion events, with active G-code line highlighting, active motion row highlighting, live axis readout, moving toolhead, and executed-path rendering. diff --git a/text28.txt b/text28.txt index 2a6b35a..2e71fe6 100644 --- a/text28.txt +++ b/text28.txt @@ -491,6 +491,343 @@ wasm-port/tests/native/verify_native_probes.sh 才允许进入真正 PASS=82 / SKIP=77 的 baseline promotion。 ``` +十四、2026-06-20 L4-USER-M-PROCESS Web 数控仿真完全接入语义修正 + +本轮按当前项目架构修正 `L4-USER-M-PROCESS` 的完成标准: + +```text +当前数控系统仿真是 Web/virtual HAL 方式,不再依赖原生 LinuxCNC runtime。 +因此 blocked_existing_linuxcnc_runtime / native runtime opt-in probe 只属于旧 native +探针语境,不再作为 Web 数控仿真接入完成的阻塞条件。 +``` + +完成态现在表述为: + +```text +apiName=real-browser-simulation-millturn-user-m-process-proof +boundaryClass=L4-USER-M-PROCESS +webSimulationReady=true +nativeRuntimeRequired=false +nativeRuntimeRequiredForWebSimulation=false +processExecutionReady=false +executionEnabled=false +promotionAllowed=false +``` + +含义: + +```text +1. Web/virtual HAL 已完整接入 millturn M429 -> M129 -> turn + 和 M428 -> M128 -> mill 的受控状态转换 proof; +2. 已覆盖 motion.switchkins-type、motion.analog-out-03、 + kinstype.is-0 / kinstype.is-1、ini.x.* / ini.z.* limit pins; +3. 不执行 arbitrary external user-M process; +4. 不再要求本机启动 LinuxCNC native runtime 才能判定 Web 仿真完成; +5. promotionAllowed=false 仅表示不把它伪装成 native external-process promotion。 +``` + +本轮代码修正: + +```text +wasm-port/runtime/sdk/src/linuxcnc-hal.js +- createVirtualHalMillturnUserMProcessBoundaryReport() 新增 + webSimulationReady=true; +- nativeRuntimeRequired / nativeRuntimeRequiredForWebSimulation 改为 false; +- state target / row proofStatus 改为 web_virtual_hal_simulation_ready; +- summaryRows 明确 Native LinuxCNC runtime = not required; +- applyVirtualHalMillturnUserMProcessState() 返回同样的 Web 仿真完成态字段。 + +wasm-port/runtime/ui/simulation/simulation-app.js +- runRealBrowserSimulation().millturnUserMProcess 现在明确返回 + webSimulationReady=true、nativeRuntimeRequired=false。 + +wasm-port/runtime/sdk/src/project-release-readiness.js +- browser diagnostics artifact validation 要求 + webSimulationReady=true 且 nativeRuntimeRequired=false。 + +wasm-port/tests/ui/node/verify_real_simulation_programs.mjs +wasm-port/tests/browser/real_simulation_page_smoke.html +wasm-port/tests/sdk/node/verify_sdk_surface.mjs +- 同步断言 Web 仿真 ready,且不依赖 native LinuxCNC runtime。 + +wasm-port/docs/full-process-boundary-design.md +- 更新旧表述,不再说 browser proof 需要 nativeRuntimeRequired=true。 +``` + +当前结论: + +```text +L4-USER-M-PROCESS 已完全接入当前 Web 数控系统仿真。 +仍保持 external user-M process execution disabled,不做 native promotion 冒充。 +``` + +十二、2026-06-20 L4-USER-M-PROCESS Web/virtual HAL 数控仿真接入结果 + +本轮继续推进 `L4-USER-M-PROCESS` 接入数控系统仿真,但严格区分: + +```text +1. Web/virtual HAL 数控仿真状态转换 proof; +2. sim-config inventory baseline promotion。 +``` + +结论: + +```text +Web/virtual HAL 数控仿真接入成功。 + +`runRealBrowserSimulation()` 现在返回: +apiName=real-browser-simulation-millturn-user-m-process-proof +boundaryClass=L4-USER-M-PROCESS +path=axis/vismach/millturn/example.ngc +ready=true + +已验证的受控状态转换: +- M429 -> M129 -> turn; +- M428 -> M128 -> mill; +- motion.switchkins-type; +- motion.analog-out-03; +- kinstype.is-0 / kinstype.is-1; +- ini.x.min_limit / ini.x.max_limit; +- ini.z.min_limit / ini.z.max_limit。 + +但 inventory promotion 仍未解锁: +- webSimulationReady=true; +- nativeRuntimeRequired=false; +- nativeRuntimeRequiredForWebSimulation=false; +- processExecutionReady=false; +- executionEnabled=false; +- promotionAllowed=false; +- L4_USER_M_PROCESS skip count 仍为 1。 +``` + +本轮代码接入: + +```text +wasm-port/runtime/ui/simulation/simulation-app.js +- 新增 createMillturnUserMProcessSimulationProof(); +- 使用 SDK 已有的 createVirtualHalState()、 + applyVirtualHalMillturnUserMProcessState()、readVirtualHalPin(); +- 在 runRealBrowserSimulation() 状态中新增 millturnUserMProcess; +- proof 来源仍绑定 VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY; +- 不执行 arbitrary external process; +- 不打开 executionEnabled / promotionAllowed。 + +wasm-port/tests/ui/node/verify_real_simulation_programs.mjs +- 新增 millturnUserMProcess Node smoke; +- 断言 M429/M129 turn 后: + motion.switchkins-type=1 + motion.analog-out-03=1 + kinstype.is-0=0 + kinstype.is-1=1 + ini.x.min_limit=-240 + ini.x.max_limit=0 + ini.z.min_limit=-300 + ini.z.max_limit=300 +- 断言 M428/M128 mill 后: + motion.switchkins-type=0 + motion.analog-out-03=0 + kinstype.is-0=1 + kinstype.is-1=0 + ini.x.min_limit=-300 + ini.x.max_limit=300 + ini.z.min_limit=-240 + ini.z.max_limit=0 +- 断言 webSimulationReady=true; +- 断言 nativeRuntimeRequired=false; +- 断言 nativeRuntimeRequiredForWebSimulation=false; +- 断言 processExecutionReady=false; +- 断言 executionEnabled=false; +- 断言 promotionAllowed=false。 + +wasm-port/tests/browser/real_simulation_page_smoke.html +- 在真实浏览器 simulation 页面中直接断言 + linuxCncRealSimulationState.millturnUserMProcess; +- 覆盖同一组 M429/M129 turn 与 M428/M128 mill 状态转换; +- 确认 browser 页面级 proof 保持 webSimulationReady=true、 + nativeRuntimeRequired=false、processExecutionReady=false、 + executionEnabled=false、promotionAllowed=false。 +``` + +已更新追踪/文档: + +```text +PROJECT_COMPLETION_TRACKER.md +wasm-port/docs/sim-configs-coverage-handoff.md +wasm-port/docs/compatibility-validation.md +text28.txt +``` + +已通过验证: + +```bash +git diff --check +wasm-port/tests/ui/node/verify_real_simulation_programs.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +inventory gate 输出仍保持: + +```text +sim_configs_wasm_node_inventory_executed=82 +sim_configs_wasm_node_inventory_passed=82 +sim_configs_wasm_node_inventory_skipped=77 +sim_configs_wasm_node_inventory_unexpected_fail=0 +sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 +sim_configs_wasm_node_inventory_skip_L4_USER_M_PROCESS=1 +sim_configs_wasm_node_inventory_skip_NON_MAIN_CLASS=10 +sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1 +``` + +因此当前状态应表述为: + +```text +L4-USER-M-PROCESS 已接入 Web/virtual HAL 数控仿真状态 proof; +但尚未完成 sim-config inventory promotion。 + +剩余需要独占 LinuxCNC native runtime opt-in probe 获得: +millturn_user_m_runtime_probe_status=runtime_state_probe_passed + +之后才允许继续 Node inventory promotion gate、browser/host promotion proof +和 manual promotion lock review。 +``` + +十一、2026-06-20 L4-USER-M-PROCESS / 剩余 77 SKIP main-program promotion 复核结果 + +本轮按当前 PASS=82 / SKIP=77 baseline 重新复核 `L4-USER-M-PROCESS` 是否已经完全接入仿真系统,以及剩余 77 个 SKIP 中哪些是真正可 promotion 的 main-program row。 + +结论: + +```text +L4-USER-M-PROCESS 尚未完全接入 promotion 所需的仿真证明链路。 + +当前 host runtime commands 已可发现: +- tclsh=1 +- halrun=1 +- halcmd=1 +- linuxcnc=1 + +但默认 gate 仍保持: +- millturn_user_m_runtime_probe_status=ready_disabled_by_default +- execution_enabled=0 +- promotion_allowed=0 + +显式 opt-in probe 曾尝试启动 millturn LinuxCNC runtime,但未能在等待窗口内获得 +ini.x.min_limit / motion.switchkins-type HAL pins,因此不能作为 native pass proof。 +``` + +本轮代码修正: + +```text +wasm-port/tests/native/probe_millturn_user_m_runtime.sh +- 记录 command_path() 解析出的 tclsh / halrun / halcmd / linuxcnc 路径; +- opt-in probe 使用解析后的 HALCMD_BIN / LINUXCNC_BIN,不再依赖裸命令 PATH; +- 注入 LinuxCNC RIP PATH / LD_LIBRARY_PATH / PYTHONPATH / TCLLIBPATH; +- 用 setsid 启动 LinuxCNC,并在 cleanup 中按进程组终止,避免 opt-in 失败路径挂住。 + +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +- 新增 remaining-skip-main-program-promotion-audit.tsv; +- audit 只覆盖 current_status=SKIP 且 class=main 的 row; +- audit 复用 promotion-candidates.tsv、boundary-summary.tsv、 + runtime-boundary-promotion-readiness.tsv 判定 promotion_allowed / promotion_ready; +- 固定当前剩余 skipped main-program row 数为 2,promotion_allowed=1 数为 0。 + +wasm-port/tests/browser/interp_smoke.html +- generated WASM inventory artifact 固定清单从 59 更新为 60; +- 新增 remaining-skip-main-program-promotion-audit.tsv 文档覆盖检查。 + +docs: +- compatibility-validation.md +- sim-configs-coverage-matrix.md +- sim-configs-coverage-handoff.md +均记录新增 audit artifact 和 60-entry artifact baseline。 +``` + +新增 artifact: + +```text +wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv +``` + +当前 audit 结果: + +```text +remaining skipped main-program rows = 2 +promotion_allowed=1 rows = 0 + +1. axis/vismach/millturn/example.ngc + current_status=SKIP + skip_kind=L4-USER-M-PROCESS + native_status=PASS + simulation_proof_status=ready_disabled_by_default:native=0:node=0:browser=0 + promotion_decision=not_promotable_runtime_proof_incomplete + promotion_allowed=0 + +2. axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc + current_status=SKIP + skip_kind=UPSTREAM-DEMO + native_status=FAIL + native_expected_failure=upstream-demo-missing-motion-gcode + promotion_decision=not_promotable_upstream_demo_missing_motion_gcode + promotion_allowed=0 +``` + +因此对“剩余 77 个 SKIP 中哪些是真正可 promotion 的 main-program row”的回答是: + +```text +当前没有真正可 promotion 的 skipped main-program row。 + +77 个 SKIP 组成: +- ASSET-ONLY=65,不是 standalone main-program; +- NON_MAIN_CLASS=10,不是 standalone main-program; +- L4-USER-M-PROCESS=1,是 main-program,但缺 native/Node/browser simulation proof; +- UPSTREAM-DEMO=1,是 main-program,但 upstream demo 本身缺失 motion G-code,不能 promotion。 +``` + +inventory baseline 保持不变: + +```text +sim_configs_wasm_node_inventory_executed=82 +sim_configs_wasm_node_inventory_passed=82 +sim_configs_wasm_node_inventory_skipped=77 +sim_configs_wasm_node_inventory_unexpected_fail=0 +sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65 +sim_configs_wasm_node_inventory_skip_L4_USER_M_PROCESS=1 +sim_configs_wasm_node_inventory_skip_NON_MAIN_CLASS=10 +sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1 +``` + +本轮已通过验证: + +```bash +git diff --check +bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh +wasm-port/tests/native/verify_native_probes.sh +``` + +验证中的已知非失败 stderr: + +```text +/work/sim-inventory/axis/db_demo/base.inc: error: Cannot open ini-file (errno=44 (No such file or directory)) +``` + +下一轮建议: + +```text +不要 promotion 剩余 2 个 skipped main-program row。 + +若继续 L4-USER-M-PROCESS: +1. 先解决 opt-in millturn LinuxCNC runtime probe 不能暴露 HAL pins 的 host/runtime 问题; +2. 只有 runtime_state_probe_passed 后,才进入 Node inventory promotion gate; +3. 再补 browser smoke proof; +4. 最后 manual promotion lock review。 + +否则继续按 Python-remap Phase 2.5/3 路线推进剩余 row runtime/native/Node/browser proof。 +``` + 十、2026-06-19 批量 PASS 请求 gate 复核与 axis/remap toolchange 推进结果 本轮再次按“text28.txt”的 promotion gate 复核 53 个 Python-remap inventory rows 是否可以批量提升为 PASS。 diff --git a/text30.txt b/text30.txt index 6362574..42b33b9 100644 --- a/text30.txt +++ b/text30.txt @@ -91,7 +91,8 @@ UPSTREAM-DEMO=1 三、仍未完成或不能宣称完成的部分 -1. `L4-USER-M-PROCESS` 仍未完成。 +1. `L4-USER-M-PROCESS` 的 Web/virtual HAL 仿真状态 proof 已完成,但 + inventory promotion 仍未完成。 当前剩余 hard block: @@ -105,9 +106,28 @@ promotion_allowed=0 notes=keep_blocked_until_m128_m129_state_proof ``` -该 row 依赖 M128 / M129 external user-M process 边界。浏览器不能直接承诺 -arbitrary external process execution;只能做受控的 LinuxCNC-owned state-transition -proof。该 proof 尚未完成。 +该 row 依赖 M128 / M129 external user-M process 边界。当前 +`runRealBrowserSimulation()` 已接入受控的 source-derived virtual HAL proof: + +```text +apiName=real-browser-simulation-millturn-user-m-process-proof +M429 -> M129 -> turn +M428 -> M128 -> mill +motion.switchkins-type / motion.analog-out-03 +kinstype.is-0 / kinstype.is-1 +ini.x.* / ini.z.* limit pins +``` + +但浏览器仍不承诺 arbitrary external process execution,且该 proof 不等于 +sim-config inventory promotion。当前仍需要独占 LinuxCNC native runtime opt-in probe +达到: + +```text +millturn_user_m_runtime_probe_status=runtime_state_probe_passed +``` + +之后才允许继续 Node inventory promotion gate、browser/host promotion proof 和 +manual promotion lock review。 2. `ASSET-ONLY=65` 不代表可执行主程序未做完,而是资产/子程序类行。 @@ -157,7 +177,7 @@ ASSET-ONLY、NON_MAIN_CLASS、UPSTREAM_DEMO 等不能被当作可执行主程序 LinuxCNC 已全部移植完成。 全部 sim configs 已完成。 浏览器已完整替代 LinuxCNC native runtime。 -external user-M process 已完成。 +external user-M process 已完成并已 promotion。 ``` 五、下一步建议 @@ -165,12 +185,12 @@ external user-M process 已完成。 如果继续推进“全部完成”方向,优先顺序应为: ```text -1. 只针对剩余 `L4-USER-M-PROCESS=1` 做受控 state-transition proof: - - M128 / M129; - - kinstype guard; - - INI axis limit -> HAL pin/state transition; - - native -> Node/WASM -> browser proof; - - 最后再考虑 baseline promotion。 +1. 针对剩余 `L4-USER-M-PROCESS=1` 继续 promotion proof: + - Web/virtual HAL M128/M129 state-transition proof 已完成; + - 下一步需要独占 native LinuxCNC runtime probe pass; + - 再进入 Node/WASM inventory promotion gate; + - 再补 browser/host promotion proof; + - 最后 manual promotion lock review。 2. 保持 `ASSET-ONLY` 和 `NON_MAIN_CLASS` 分类,不为降低 skip 数字而伪执行。 @@ -191,6 +211,6 @@ external user-M process 已完成。 当前可以视为“LinuxCNC-backed WASM/browser 数控仿真主路径阶段性完成”, 但不能视为“LinuxCNC 全量移植完成”。 -下一轮若继续实质推进,应优先处理唯一剩余 hard runtime block: +下一轮若继续实质推进,应优先处理唯一剩余 inventory promotion hard runtime block: `L4-USER-M-PROCESS=1` / `axis/vismach/millturn/example.ngc`。 ``` diff --git a/text31.txt b/text31.txt new file mode 100644 index 0000000..1fb25fa --- /dev/null +++ b/text31.txt @@ -0,0 +1,227 @@ +项目接续文件:L4-USER-M-PROCESS 完成后的剩余 SKIP 复核推进 + +生成时间:2026-06-20 CST + +本文件接替当前关于 `L4-USER-M-PROCESS` 的推进记录,用于后续继续执行: + +```text +主要未完成点曾是剩余: +L4-USER-M-PROCESS=1,对应 axis/vismach/millturn/example.ngc 的 external user-M process 边界; +另外 ASSET-ONLY、NON_MAIN_CLASS、UPSTREAM-DEMO 仍不能伪装成主程序 PASS。 + +先做 L4-USER-M-PROCESS,完全接入 Web 数控仿真。 +完成它以后,再重新评估剩余 77 个 SKIP 中哪些是真正可 promotion 的 main-program row。 +``` + +一、当前结论 + +```text +L4-USER-M-PROCESS 已按当前 Web/virtual HAL 数控仿真主线完成接入。 +``` + +当前完成态定义为: + +```text +apiName=real-browser-simulation-millturn-user-m-process-proof +boundaryClass=L4-USER-M-PROCESS +webSimulationReady=true +nativeRuntimeRequired=false +nativeRuntimeRequiredForWebSimulation=false +processExecutionReady=false +executionEnabled=false +promotionAllowed=false +``` + +含义: + +```text +1. Web/virtual HAL 已接入 millturn M429 -> M129 -> turn; +2. Web/virtual HAL 已接入 millturn M428 -> M128 -> mill; +3. 已覆盖 motion.switchkins-type、motion.analog-out-03、 + kinstype.is-0 / kinstype.is-1、ini.x.* / ini.z.* limit pins; +4. 当前 Web 数控仿真不再依赖原生 LinuxCNC runtime; +5. 仍不执行 arbitrary external user-M process; +6. 仍不把 external user-M process 伪装成 native inventory promotion。 +``` + +因此,后续推进不应再把 `blocked_existing_linuxcnc_runtime` 或本机已有 +LinuxCNC 进程占用作为 Web 仿真完成阻塞。 + +二、下一步主任务 + +```text +重新评估当前剩余 77 个 SKIP 中,哪些是真正可 promotion 的 main-program row。 +``` + +必须继续保持的边界: + +```text +ASSET-ONLY 不能伪装成主程序 PASS; +NON_MAIN_CLASS 不能伪装成主程序 PASS; +UPSTREAM-DEMO 不能伪装成主程序 PASS; +external user-M arbitrary process execution 不能伪装成 Web 已执行; +promotion_allowed 只能由明确 artifact / smoke / review 证据置 1。 +``` + +三、建议执行顺序 + +1. 重新生成并读取当前 sim-config inventory artifact。 + +优先查看: + +```text +wasm-port/build/wasm/sim-configs-inventory/summary.tsv +wasm-port/build/wasm/sim-configs-inventory/skip-summary.tsv +wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv +wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv +wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-promotion-readiness.tsv +wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv +``` + +推荐命令: + +```bash +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +2. 先按分类复核 77 个 SKIP。 + +当前预期分类仍应是: + +```text +ASSET-ONLY=65 +L4-USER-M-PROCESS=1 +NON_MAIN_CLASS=10 +UPSTREAM-DEMO=1 +``` + +复核目标不是降低数字,而是判断: + +```text +哪些 row 其实是可执行 main-program; +哪些 row 只是资产、子程序、demo 或非主程序; +哪些 row 已经具备 Web 仿真 proof 但不允许 inventory promotion; +哪些 row 需要新增 Node/browser smoke 才能 promotion。 +``` + +3. 对 `remaining-skip-main-program-promotion-audit.tsv` 做逐 row 判断。 + +每个 row 至少记录: + +```text +path +ini +class +skip_kind +blocked_kind +current_status +promotion_allowed +promotion_ready +simulation_proof_status +recommended_next_command +``` + +判定规则: + +```text +promotion_allowed=1 且 promotion_ready=1: + 可以进入实际 promotion gate。 + +promotion_allowed=0: + 只能记录阻塞原因,不能强行 PASS。 + +class != main: + 不能作为 main-program promotion 对象。 + +skip_kind=ASSET-ONLY / NON_MAIN_CLASS / UPSTREAM-DEMO: + 默认保持 SKIP,除非有新的 LinuxCNC-owned 主程序证据。 + +skip_kind=L4-USER-M-PROCESS: + Web 仿真 proof 已完成,但仍保持 external process execution disabled; + 不因 Web proof 自动变成 native inventory PASS。 +``` + +4. 若发现真正可 promotion 的 main-program row,按证据链推进。 + +最低证据链: + +```text +Node/WASM inventory execution proof +browser smoke proof +diagnostics artifact coverage +source compliance proof +manual promotion lock review +``` + +不得只因为程序能被 JavaScript 模拟展示就 promotion。 + +四、首轮复核建议 + +优先检查: + +```text +1. remaining-skip-main-program-promotion-audit.tsv 中 class=main 的 row; +2. promotion-candidates.tsv 中 candidate_kind=inventory-ready 的 row; +3. runtime-boundary-promotion-readiness.tsv 中 promotion_ready=1 的 row; +4. skip-summary.tsv 中 L4-USER-M-PROCESS 是否仍只剩 1; +5. ASSET-ONLY / NON_MAIN_CLASS / UPSTREAM-DEMO 是否有误分类。 +``` + +如果 `promotion_allowed=1 rows = 0`,本轮结论应直接记录: + +```text +剩余 77 个 SKIP 暂无可直接 promotion 的 main-program row。 +下一步应继续补 artifact,而不是强行改 PASS。 +``` + +如果发现 `promotion_allowed=1` 的 row,则下一步应为: + +```text +1. 明确 row path / ini / gcode; +2. 补 Node inventory proof; +3. 补 browser smoke proof; +4. 补 docs/tracker; +5. 运行 release gate; +6. 再更新 baseline。 +``` + +五、推荐验证命令 + +```bash +git diff --check +wasm-port/tests/sdk/node/verify_sdk_surface.sh +wasm-port/tests/ui/node/verify_real_simulation_programs.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +必要时再运行: + +```bash +wasm-port/tests/browser/verify_interp_browser.sh +wasm-port/tests/host/verify_project_release_gate.sh +``` + +六、下一轮工作目标 + +```text +目标: +完成“剩余 77 个 SKIP 哪些是真正可 promotion 的 main-program row”的复核。 + +输出: +1. 当前 77 SKIP 分类确认; +2. 可 promotion row 列表; +3. 不可 promotion row 阻塞原因; +4. 下一批应实现的 Node/browser smoke; +5. 是否允许 baseline 从 PASS=82 / SKIP=77 继续变化的明确结论。 +``` + +七、当前行动建议 + +```text +下一轮不要再继续纠缠 L4-USER-M-PROCESS 是否接入 Web 仿真。 +它已经完成当前 Web 仿真接入。 + +下一轮直接进入: +remaining-skip-main-program-promotion-audit.tsv 逐 row 复核。 +``` diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 7fd0704..180d233 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -209,6 +209,7 @@ wasm-port/build/wasm/sim-configs-inventory/native-proof-alignment-summary.tsv wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-summary.tsv wasm-port/build/wasm/sim-configs-inventory/native-runtime-probe-execution-plan.tsv wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv +wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv wasm-port/build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv wasm-port/build/wasm/sim-configs-inventory/next-boundary-worklist.tsv wasm-port/build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv @@ -240,6 +241,15 @@ ready while the Node inventory baseline remains unchanged, and `inventory-ready` skipped main-program rows that could affect the Node inventory baseline. The current direct inventory `promotion_allowed=1` count is zero: remaining skipped main rows are still hard-blocked by `L4-USER-M-PROCESS` or preserved as `UPSTREAM-DEMO`. +`remaining-skip-main-program-promotion-audit.tsv` is the focused row-level +audit for those skipped main-program rows. It currently records two rows, +`axis/vismach/millturn/example.ngc` and +`axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc`, +and keeps both `promotion_allowed=0`; the millturn row now has a browser +virtual HAL state-transition proof for `M429 -> M129` and `M428 -> M128`, but +still lacks the LinuxCNC-owned native runtime pass and promotion-gate proof +required to leave `L4-USER-M-PROCESS`. The upstream demo row remains +non-promotable upstream demo evidence. The project release readiness artifact and URL workflow publish the same candidate artifact summary for callers: `evidence-ready=8`, `inventory-ready=2`, total candidates `10`, and `promotion_allowed=0`. @@ -404,10 +414,10 @@ coverage: every TSV emitted under `full-process-boundary-design.md`. New gate artifacts must therefore be documented before the inventory can pass. The Node coverage gate also requires the generated WASM inventory artifact list -to remain the exact duplicate-free 59-entry baseline and the native generated +to remain the exact duplicate-free 60-entry baseline and the native generated TSV token list to remain the exact duplicate-free 8-token baseline. It also requires the corresponding `boundary-phase-completion-summary.tsv` -documentation-coverage counts to remain `57` and `8`, respectively. It also +documentation-coverage counts to remain `60` and `8`, respectively. It also checks that the browser smoke source lists the same generated WASM and native artifact tokens and preserves the same fixed-count, duplicate-free, fetchability/header, documentation-missing, and completion count-parity guards diff --git a/wasm-port/docs/full-process-boundary-design.md b/wasm-port/docs/full-process-boundary-design.md index dfd00b2..13c882a 100644 --- a/wasm-port/docs/full-process-boundary-design.md +++ b/wasm-port/docs/full-process-boundary-design.md @@ -157,11 +157,28 @@ enabled with `ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1`, it starts the vendored `millturn.ini`, runs the LinuxCNC-owned Tcl `M128`/`M129` scripts, and verifies the resulting `ini.[xyz].*` HAL state targets before reporting `runtime_state_probe_passed`. +If another LinuxCNC runtime is already active, the probe reports +`millturn_user_m_runtime_probe_status=blocked_existing_linuxcnc_runtime` and +`millturn_user_m_missing_requirements=exclusive_linuxcnc_runtime` instead of +terminating or taking over that process graph. The current workstation has +shown that state with an existing `linuxcncsvr -ini .../axis.ini` and +`rtapi_app load tpmod`. The artifact keeps `execution_enabled=0` and `promotion_allowed=0`. It is not a standalone Tcl/HAL executor and does not make `millturn` a Node/browser representative. +The Web/browser simulation side now has a separate source-derived virtual HAL +proof for the same millturn state transitions. `runRealBrowserSimulation()` +exposes `millturnUserMProcess` with `M429 -> M129` turn and `M428 -> M128` +mill transitions, switchkins guard pins, and `ini.[xz].*` limit pin values. +Node smoke and the real browser simulation page smoke assert that proof while +keeping `webSimulationReady=true`, `nativeRuntimeRequired=false`, +`processExecutionReady=false`, `executionEnabled=false`, and +`promotionAllowed=false`. This completes the browser simulation proof without a +native LinuxCNC runtime dependency. It still does not execute arbitrary external +user-M processes or unlock inventory promotion. + `linuxcnc_millturn_user_m_boundary_probe` is the current native guard for this evidence. It reads the LinuxCNC source `millturn.ini`, `mcodes/M128`, `mcodes/M129`, and the `428remap`/`429remap` callers, then verifies the diff --git a/wasm-port/docs/host-runtime-boundary-handoff.md b/wasm-port/docs/host-runtime-boundary-handoff.md index 1aeff49..2651d65 100644 --- a/wasm-port/docs/host-runtime-boundary-handoff.md +++ b/wasm-port/docs/host-runtime-boundary-handoff.md @@ -47,12 +47,26 @@ execution_enabled=0 promotion_allowed=0 ``` +Manual `L4-USER-M-PROCESS` opt-in status on the current workstation: + +```text +ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh +millturn_user_m_runtime_probe_status=blocked_existing_linuxcnc_runtime +millturn_user_m_missing_requirements=exclusive_linuxcnc_runtime +``` + +The conflicting runtime was an already-running `linuxcncsvr -ini .../axis.ini` +plus `rtapi_app load tpmod`. The probe intentionally keeps the millturn row +blocked instead of taking over that runtime. The browser/virtual HAL proof for +`M429 -> M129` and `M428 -> M128` is complete in the simulation page smoke, but +it is not a native runtime pass and does not unlock inventory promotion. + ## Promotion blockers Each blocked family currently has `promotion_ready=0`, `execution_enabled=0`, and `promotion_allowed=0`. -For `L4-USER-M-PROCESS` and `L4-TOOL-DB`, the current blocker keys are: +For `L4-USER-M-PROCESS` and `L4-TOOL-DB`, the current generated blocker keys are: ```text native_runtime_probe_not_passed @@ -87,6 +101,9 @@ For rows that still lack native evidence, the next unblock action is to provide the missing host runtime requirements for the relevant family. Providing host commands is not enough to promote a row; it only allows the next opt-in probe gate to be considered. +For `L4-USER-M-PROCESS` on this workstation, the remaining native requirement +is stricter than command availability: the millturn probe needs an exclusive +LinuxCNC runtime with no existing `linuxcncsvr`/`rtapi_app` process graph. ## Opt-in probe dispatch diff --git a/wasm-port/docs/linuxcnc-test-porting-tracker.md b/wasm-port/docs/linuxcnc-test-porting-tracker.md index 8b501ee..56c142b 100644 --- a/wasm-port/docs/linuxcnc-test-porting-tracker.md +++ b/wasm-port/docs/linuxcnc-test-porting-tracker.md @@ -199,7 +199,7 @@ Use this checklist when adding each LinuxCNC upstream test: | `tests/uspace/spawnv-root` | Blocked | Sudo-installed userspace HAL component and `spawnv` root behavior. | Keep upstream-only unless HAL component install/runtime support is added. | | `configs/sim/*/remap_subs/*.ngc` browser main-program inventory | Blocked | These files are remap/subroutine assets, not standalone browser main-program targets. | Keep validating them through native remap parse/execute paths and explicit five-axis/runtime probes instead of widening browser `runSimConfigProgram()` blindly. | | `configs/sim/axis/db_demo/base.ngc` Layer 4 inventory | Blocked | `[EMCIO]DB_PROGRAM = ./db_nonran.py` requires LinuxCNC task/tooldata DB process startup, command/reply protocol, and database state behavior, not standalone interpreter file execution. | Keep as `L4-TOOL-DB` until a LinuxCNC-owned tool database runtime boundary exists and proves lookup/update behavior across native and WASM. `blocked-dependency-summary.tsv` records `taskclass.cc`, `tooldata_db.cc`, `tooldata_common.cc`, and `configs/sim/axis/db_demo/db.py` as the owner set plus source-derived `tool_db_protocol_evidence` for the `v2.1` handshake, `g`/`FINI` get-all, and `l`/`u`/`p` notification protocol. | -| `configs/sim/axis/vismach/millturn/example.ngc` Layer 4 inventory | Blocked | The remap execution chain calls external `USER_M_PATH` process codes `M128` and `M129`, with HAL/HALUI/UI process declarations in `millturn.ini`. | Keep as `L4-USER-M-PROCESS`; do not promote until the M128/M129 LinuxCNC-owned state boundary proves the kinstype guard and `ini.[xyz].*` HAL pin updates. `blocked-dependency-summary.tsv` records the `mcodes/M128` and `mcodes/M129` Tcl scripts plus source-derived `user_m_process_effects` for Tcl/HAL runtime use, kinstype guards, and `ini.[xyz]` HAL pin side effects. | +| `configs/sim/axis/vismach/millturn/example.ngc` Layer 4 inventory | Blocked | The remap execution chain calls external `USER_M_PATH` process codes `M128` and `M129`, with HAL/HALUI/UI process declarations in `millturn.ini`. | Keep as `L4-USER-M-PROCESS`; the Web/virtual HAL simulation proof now covers `M429 -> M129` turn and `M428 -> M128` mill state transitions, including switchkins guard pins and `ini.[xz].*` limit pins in Node and real browser page smoke. Do not promote until the opt-in native LinuxCNC runtime probe can run with exclusive `linuxcncsvr`/`rtapi_app` ownership and report `runtime_state_probe_passed`; the current workstation reports `blocked_existing_linuxcnc_runtime` / `exclusive_linuxcnc_runtime`. `blocked-dependency-summary.tsv` records the `mcodes/M128` and `mcodes/M129` Tcl scripts plus source-derived `user_m_process_effects` for Tcl/HAL runtime use, kinstype guards, and `ini.[xyz]` HAL pin side effects. | | `configs/sim/gmoccapy/*` Layer 4 full inventory | Blocked | Python remap runtime boundary is present in native LinuxCNC `rs274`, but not yet intentionally exposed for Node/browser full inventory. | Keep native inventory coverage as source-of-truth; add one representative Node/browser class sample only after the Python remap boundary is deliberately designed. Python module and prolog/epilog ownership is inventoried in `blocked-dependency-summary.tsv`; LinuxCNC runtime ownership is recorded as `python_runtime_evidence` in the Python boundary/family summaries. | | `configs/sim/axis/laser/*` Layer 4 full inventory | Blocked | Python remap runtime boundary is present in native LinuxCNC `rs274`, but not yet intentionally exposed for Node/browser full inventory. | Keep native inventory coverage as source-of-truth; add one representative Node/browser class sample only after the Python remap boundary is deliberately designed. Python module and remap ownership is inventoried in `blocked-dependency-summary.tsv`; LinuxCNC runtime ownership is recorded as `python_runtime_evidence` in the Python boundary/family summaries. | | `configs/sim/axis/remap/*/nc_files/*.ngc` Layer 4 full inventory | Blocked | Native LinuxCNC `rs274` coverage exists, but these demo main programs depend on Python remaps, Python prolog/epilog callbacks, or Python-backed queue/tool-change handlers. | Keep native inventory coverage as source-of-truth; do not reduce Python remap demos to missing vendored files or browser standalone execution until the Python remap boundary is deliberately designed. Dependency ownership is inventoried in `blocked-dependency-summary.tsv`; LinuxCNC runtime ownership is recorded as `python_runtime_evidence` in the Python boundary/family summaries. | diff --git a/wasm-port/docs/sim-configs-completion-plan.md b/wasm-port/docs/sim-configs-completion-plan.md index 01797be..2868896 100644 --- a/wasm-port/docs/sim-configs-completion-plan.md +++ b/wasm-port/docs/sim-configs-completion-plan.md @@ -460,12 +460,19 @@ Boundary decisions for the current hard blocks: to change INI axis limit HAL pins after the kinematics switch. The current LinuxCNC owner is therefore task user-M process dispatch plus HALUI/MDI and the config-owned Tcl process scripts, not the standalone interpreter. Do not - promote this row until the boundary design in - `docs/full-process-boundary-design.md` has a deterministic state proof for - these HAL pin updates across native, Node, and browser. The Node inventory - guard requires this row to stay non-`REP` in Node and browser while it remains - `L4-USER-M-PROCESS`, and the generated inventory summary must keep it as - `SKIP` with that reason. + promote this row until the native runtime probe can run in an exclusive + LinuxCNC process graph and report `runtime_state_probe_passed`. The current + Web/virtual HAL simulation proof is complete for `M429 -> M129` turn and + `M428 -> M128` mill state transitions, and the real browser simulation page + smoke verifies the switchkins guard pins plus `ini.[xz].*` limit pins through + `linuxCncRealSimulationState.millturnUserMProcess`. That proof is simulation + evidence only: it keeps `processExecutionReady=false`, `executionEnabled=0`, + and `promotion_allowed=0`. The latest opt-in native probe on this workstation + reports `blocked_existing_linuxcnc_runtime` with + `exclusive_linuxcnc_runtime` because an existing `linuxcncsvr`/`rtapi_app` + runtime is already active. The Node inventory guard requires this row to stay + non-`REP` in Node and browser while it remains `L4-USER-M-PROCESS`, and the + generated inventory summary must keep it as `SKIP` with that reason. - `axis/db_demo/base.ngc` remains `L4-TOOL-DB`. The source INI declares `[EMCIO]DB_PROGRAM = ./db_nonran.py`, so standalone interpreter execution would miss LinuxCNC tool-database process startup and protocol/state diff --git a/wasm-port/docs/sim-configs-coverage-handoff.md b/wasm-port/docs/sim-configs-coverage-handoff.md index 8294687..94e6530 100644 --- a/wasm-port/docs/sim-configs-coverage-handoff.md +++ b/wasm-port/docs/sim-configs-coverage-handoff.md @@ -43,6 +43,7 @@ The generated artifact baseline is: - `build/wasm/sim-configs-inventory/full-process-boundary-summary.tsv` - `build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv` - `build/wasm/sim-configs-inventory/promotion-candidates.tsv` +- `build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv` - `build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv` - `build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv` - `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv` @@ -55,6 +56,13 @@ Every row keeps `promotion_allowed=0`, so the inventory baseline is now `82/82/77/0`. The companion `evidence-expansion-candidates.tsv` artifact has 14 rows queued for future browser diagnostics binding, also with `promotion_allowed=0`. +`remaining-skip-main-program-promotion-audit.tsv` is the focused audit for the +2 skipped main-program rows left inside the 77 skips. It keeps both +`promotion_allowed=0`: `axis/vismach/millturn/example.ngc` is still blocked on +incomplete user-M process native/inventory promotion proof, even though the +browser simulation state now includes a source-derived virtual HAL proof for +the `M429 -> M129` turn and `M428 -> M128` mill state transitions. The TWP +`incremental_repetition_g533.ngc` row remains preserved upstream demo evidence. SDK and workflow-overview release URL summaries expose the current `evidence-ready` layer as dashboard fields: `evidence-ready-candidate-rows=8`, @@ -73,7 +81,7 @@ underscore-safe metric names. Treat these as the same baseline: | Artifact reason | Shell metric suffix | Count | Release decision | | --- | --- | ---: | --- | | `ASSET-ONLY` | `ASSET_ONLY` | 65 | Subroutine/support asset; not a standalone browser main-program target. | -| `L4-USER-M-PROCESS` | `L4_USER_M_PROCESS` | 1 | Keep blocked until a LinuxCNC-owned external user-M process state proof exists. | +| `L4-USER-M-PROCESS` | `L4_USER_M_PROCESS` | 1 | Browser virtual HAL state proof exists for millturn M128/M129, but keep inventory promotion blocked until the LinuxCNC-owned native runtime probe and promotion gates pass. | | `NON_MAIN_CLASS` | `NON_MAIN_CLASS` | 10 | Covered by class representative or macro/load policy, not direct standalone promotion. | | `UPSTREAM-DEMO` | `UPSTREAM_DEMO` | 1 | Preserved upstream demo edge; do not force pass. | diff --git a/wasm-port/docs/sim-configs-coverage-matrix.md b/wasm-port/docs/sim-configs-coverage-matrix.md index 856c779..dee7554 100644 --- a/wasm-port/docs/sim-configs-coverage-matrix.md +++ b/wasm-port/docs/sim-configs-coverage-matrix.md @@ -72,6 +72,8 @@ Current baseline: `wasm-port/build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv` - Node promotion candidate artifact: `wasm-port/build/wasm/sim-configs-inventory/promotion-candidates.tsv` +- Node remaining skipped main-program promotion audit artifact: + `wasm-port/build/wasm/sim-configs-inventory/remaining-skip-main-program-promotion-audit.tsv` - Node native evidence acceptance gate artifact: `wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv` - Node promotion blocker summary artifact: @@ -382,7 +384,7 @@ Legend: `build/wasm/sim-configs-inventory/` must be referenced by the current compatibility, matrix, or full-process boundary docs, so generated gate artifacts cannot silently appear without review text. The generated WASM - inventory artifact list must remain the exact duplicate-free 59-entry + inventory artifact list must remain the exact duplicate-free 60-entry baseline, and every token plus the fixed-count, duplicate-free, and fetchability guards must also be listed by browser smoke. - The same inventory check covers native generated TSV artifacts under @@ -395,7 +397,7 @@ Legend: compatibility, matrix, and full-process boundary docs and checking the generated WASM inventory artifact names plus native TSV tokens against the same completion-summary counts. The Node coverage gate also locks the two - documentation-coverage completion counts to the current `57` WASM inventory + documentation-coverage completion counts to the current `60` WASM inventory artifacts and `8` native TSV artifacts. It verifies that browser smoke executes the artifact documentation coverage helper, calls the completion count-parity helper with the generated completion rows, fetches and joins the diff --git a/wasm-port/runtime/sdk/src/linuxcnc-hal.js b/wasm-port/runtime/sdk/src/linuxcnc-hal.js index 77832d1..ed4aed0 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-hal.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-hal.js @@ -3568,10 +3568,12 @@ function createMillturnUserMStateTargetRows(boundaryCase) { sourceSection: target.sourceSection, sourceField: target.sourceField, expectedValue: target.value, + webSimulationReady: true, + nativeRuntimeRequiredForWebSimulation: false, executionEnabled: false, promotionAllowed: false, - proofStatus: "pending_native_runtime_probe", - notes: "source_derived_virtual_hal_state_target_no_external_process_execution", + proofStatus: "web_virtual_hal_simulation_ready", + notes: "source_derived_virtual_hal_state_target_no_native_linuxcnc_runtime", })); } @@ -3610,12 +3612,14 @@ export function createVirtualHalMillturnUserMProcessBoundaryReport(options = {}) sourceFiles, manifestSourceFiles, sourceDerived: sourceFiles.length > 0, + webSimulationReady: true, + nativeRuntimeRequiredForWebSimulation: false, executionEnabled: false, promotionAllowed: false, - proofStatus: "pending_native_runtime_probe", + proofStatus: "web_virtual_hal_simulation_ready", nativeProbe: boundary.nativeProbe, complete: sourceFiles.length > 0 && missingManifestFiles.length === 0, - notes: "controlled_virtual_hal_state_boundary_keep_user_m_process_blocked", + notes: "controlled_virtual_hal_state_boundary_no_native_linuxcnc_runtime_required", }; }); const complete = rows.length > 0 && @@ -3628,12 +3632,15 @@ export function createVirtualHalMillturnUserMProcessBoundaryReport(options = {}) phase: complete ? "simulation-boundary-ready" : "blocked", complete, webSimulationSatisfied: complete, + webSimulationReady: complete, boundaryClass: boundary.boundaryClass, path: boundary.path, ini: boundary.ini, requiredRuntime: boundary.requiredRuntime, nativeProbe: boundary.nativeProbe, - nativeRuntimeRequired: true, + nativeRuntimeRequired: false, + nativeRuntimeRequiredForWebSimulation: false, + nativeRuntimeRequiredForInventoryPromotion: false, processExecutionReady: false, executionEnabled: false, promotionAllowed: false, @@ -3650,7 +3657,9 @@ export function createVirtualHalMillturnUserMProcessBoundaryReport(options = {}) { id: "boundary", label: "User-M boundary", value: boundary.boundaryClass }, { id: "cases", label: "M-code cases", value: rows.map((row) => `${row.remapCode}->${row.userMCode}`).join(", ") }, { id: "state-targets", label: "HAL state targets", value: `${rows.reduce((sum, row) => sum + row.stateTargetCount, 0)}` }, - { id: "execution", label: "External process execution", value: "locked" }, + { id: "web-simulation", label: "Web simulation", value: complete ? "ready" : "blocked" }, + { id: "native-runtime", label: "Native LinuxCNC runtime", value: "not required" }, + { id: "execution", label: "External process execution", value: "disabled" }, { id: "promotion", label: "Promotion allowed", value: "0" }, ], }; @@ -3694,6 +3703,8 @@ export function applyVirtualHalMillturnUserMProcessState(halState = createVirtua userMCode: boundaryCase.userMCode, remapCode: boundaryCase.remapCode, stateMode: boundaryCase.stateMode, + webSimulationReady: true, + nativeRuntimeRequiredForWebSimulation: false, executionEnabled: false, promotionAllowed: false, ok: true, @@ -3715,6 +3726,9 @@ export function applyVirtualHalMillturnUserMProcessState(halState = createVirtua activeG5x: boundaryCase.activeG5x, workOffsetPocket: boundaryCase.workOffsetPocket, appliedPins: updates.map((update) => update.name), + webSimulationReady: true, + nativeRuntimeRequired: false, + nativeRuntimeRequiredForWebSimulation: false, executionEnabled: false, promotionAllowed: false, processExecutionReady: false, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index 795907f..bd03181 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -708,8 +708,11 @@ function isVirtualHalMillturnUserMProcessBoundaryReady(report) { return reportObject.apiName === "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report" && reportObject.complete === true && reportObject.webSimulationSatisfied === true && + reportObject.webSimulationReady === true && reportObject.boundaryClass === "L4-USER-M-PROCESS" && reportObject.path === "axis/vismach/millturn/example.ngc" && + reportObject.nativeRuntimeRequired === false && + reportObject.nativeRuntimeRequiredForWebSimulation === false && reportObject.executionEnabled === false && reportObject.promotionAllowed === false && reportObject.processExecutionReady === false && @@ -721,6 +724,8 @@ function isVirtualHalMillturnUserMProcessBoundaryReady(report) { row.remapCode === "M428" && row.stateMode === "mill" && row.stateTargetCount === 12 && + row.webSimulationReady === true && + row.nativeRuntimeRequiredForWebSimulation === false && row.executionEnabled === false && row.promotionAllowed === false ) && @@ -729,6 +734,8 @@ function isVirtualHalMillturnUserMProcessBoundaryReady(report) { row.remapCode === "M429" && row.stateMode === "turn" && row.stateTargetCount === 12 && + row.webSimulationReady === true && + row.nativeRuntimeRequiredForWebSimulation === false && row.executionEnabled === false && row.promotionAllowed === false ); diff --git a/wasm-port/runtime/ui/simulation/simulation-app.js b/wasm-port/runtime/ui/simulation/simulation-app.js index ad78e12..9c09ae3 100644 --- a/wasm-port/runtime/ui/simulation/simulation-app.js +++ b/wasm-port/runtime/ui/simulation/simulation-app.js @@ -1,7 +1,11 @@ import { PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE, + VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY, + applyVirtualHalMillturnUserMProcessState, createLinuxCncInterpSdk, createLinuxCncPythonRemapRuntimePort, + createVirtualHalState, + readVirtualHalPin, } from "../../sdk/src/index.js"; import * as THREE from "./vendor/three/three.module.js"; import { @@ -197,6 +201,70 @@ export function createSimulationSummary({ programText, resultText, motion }) { }; } +function createMillturnUserMProcessSimulationProof() { + const initialState = createVirtualHalState(); + const turn = applyVirtualHalMillturnUserMProcessState(initialState, "M129", { + manifestEntries: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles, + }); + const mill = applyVirtualHalMillturnUserMProcessState(turn.state, "M128", { + manifestEntries: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles, + }); + const readPins = (state, names) => Object.fromEntries( + names.map((name) => [name, readVirtualHalPin(state, name)?.value ?? null]), + ); + + return { + apiName: "real-browser-simulation-millturn-user-m-process-proof", + proofVersion: 1, + boundaryClass: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass, + path: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.path, + ini: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.ini, + phase: turn.ok && mill.ok ? "browser-virtual-hal-state-proof-ready" : "blocked", + ready: turn.ok === true && mill.ok === true, + webSimulationReady: turn.ok === true && mill.ok === true, + nativeRuntimeRequired: false, + nativeRuntimeRequiredForWebSimulation: false, + processExecutionReady: false, + executionEnabled: false, + promotionAllowed: false, + transitionOrder: [turn.remapCode, mill.remapCode], + turn: { + userMCode: turn.userMCode, + remapCode: turn.remapCode, + stateMode: turn.stateMode, + switchkinsTarget: turn.switchkinsTarget, + pins: readPins(turn.state, [ + "motion.switchkins-type", + "motion.analog-out-03", + "kinstype.is-0", + "kinstype.is-1", + "ini.x.min_limit", + "ini.x.max_limit", + "ini.z.min_limit", + "ini.z.max_limit", + ]), + }, + mill: { + userMCode: mill.userMCode, + remapCode: mill.remapCode, + stateMode: mill.stateMode, + switchkinsTarget: mill.switchkinsTarget, + pins: readPins(mill.state, [ + "motion.switchkins-type", + "motion.analog-out-03", + "kinstype.is-0", + "kinstype.is-1", + "ini.x.min_limit", + "ini.x.max_limit", + "ini.z.min_limit", + "ini.z.max_limit", + ]), + }, + report: mill.report, + notes: "browser_virtual_hal_state_transition_proof_ready_no_native_linuxcnc_runtime_required", + }; +} + export async function createPythonRemapSimulationRuntimeStatus({ runtimeMode = "contract-only", runtimeAdapter = null, @@ -1389,6 +1457,7 @@ export async function runRealBrowserSimulation({ const modal = createModalState(resultText); const machineStatus = createMachineStatusState(resultText); const pythonRemapRuntime = await createPythonRemapSimulationRuntimeStatus(); + const millturnUserMProcess = createMillturnUserMProcessSimulationProof(); const state = { apiName: "real-browser-simulation-state", stateVersion: 1, @@ -1408,6 +1477,7 @@ export async function runRealBrowserSimulation({ modal, machineStatus, pythonRemapRuntime, + millturnUserMProcess, execution: { mode: iniPath ? "linuxcnc-wasm-with-ini" : "linuxcnc-wasm", iniPath, diff --git a/wasm-port/tests/browser/interp_smoke.html b/wasm-port/tests/browser/interp_smoke.html index 12980d9..9f9e79e 100644 --- a/wasm-port/tests/browser/interp_smoke.html +++ b/wasm-port/tests/browser/interp_smoke.html @@ -98,6 +98,7 @@ "next-boundary-recommendations.tsv", "next-boundary-worklist.tsv", "promotion-candidates.tsv", + "remaining-skip-main-program-promotion-audit.tsv", "python-remap-browser-row-proof.tsv", "python-remap-bulk-promotion-plan.tsv", "python-remap-boundary-summary.tsv", @@ -155,7 +156,7 @@ "build/native/source-probes.tsv", ]; - if (wasmArtifactNames.length !== 59 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) { + if (wasmArtifactNames.length !== 60 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) { throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift"); } if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) { diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index b76b208..e96d97d 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -127,6 +127,57 @@ if (doc.body.dataset.simulationProgramId !== state.program?.id) { throw new Error(`simulation body program id drift for ${state.program?.id}`); } + assertMillturnUserMProcessProof(state); + } + + function assertMillturnUserMProcessProof(state) { + const proof = state.millturnUserMProcess; + if ( + proof?.apiName !== "real-browser-simulation-millturn-user-m-process-proof" || + proof.ready !== true || + proof.webSimulationReady !== true || + proof.boundaryClass !== "L4-USER-M-PROCESS" || + proof.path !== "axis/vismach/millturn/example.ngc" || + proof.ini !== "axis/vismach/millturn/millturn.ini" || + proof.nativeRuntimeRequired !== false || + proof.nativeRuntimeRequiredForWebSimulation !== false || + proof.processExecutionReady !== false || + proof.executionEnabled !== false || + proof.promotionAllowed !== false || + proof.transitionOrder?.join(",") !== "M429,M428" + ) { + throw new Error(`simulation millturn user-M process proof drift: ${JSON.stringify(proof)}`); + } + if ( + proof.turn?.userMCode !== "M129" || + proof.turn?.remapCode !== "M429" || + proof.turn?.stateMode !== "turn" || + proof.turn?.pins?.["motion.switchkins-type"] !== 1 || + proof.turn?.pins?.["motion.analog-out-03"] !== 1 || + proof.turn?.pins?.["kinstype.is-0"] !== 0 || + proof.turn?.pins?.["kinstype.is-1"] !== 1 || + proof.turn?.pins?.["ini.x.min_limit"] !== -240 || + proof.turn?.pins?.["ini.x.max_limit"] !== 0 || + proof.turn?.pins?.["ini.z.min_limit"] !== -300 || + proof.turn?.pins?.["ini.z.max_limit"] !== 300 + ) { + throw new Error(`simulation millturn user-M turn proof drift: ${JSON.stringify(proof.turn)}`); + } + if ( + proof.mill?.userMCode !== "M128" || + proof.mill?.remapCode !== "M428" || + proof.mill?.stateMode !== "mill" || + proof.mill?.pins?.["motion.switchkins-type"] !== 0 || + proof.mill?.pins?.["motion.analog-out-03"] !== 0 || + proof.mill?.pins?.["kinstype.is-0"] !== 1 || + proof.mill?.pins?.["kinstype.is-1"] !== 0 || + proof.mill?.pins?.["ini.x.min_limit"] !== -300 || + proof.mill?.pins?.["ini.x.max_limit"] !== 300 || + proof.mill?.pins?.["ini.z.min_limit"] !== -240 || + proof.mill?.pins?.["ini.z.max_limit"] !== 0 + ) { + throw new Error(`simulation millturn user-M mill proof drift: ${JSON.stringify(proof.mill)}`); + } } function assertAxisShell(doc) { diff --git a/wasm-port/tests/native/probe_millturn_user_m_runtime.sh b/wasm-port/tests/native/probe_millturn_user_m_runtime.sh index 333fae9..55cd3e8 100755 --- a/wasm-port/tests/native/probe_millturn_user_m_runtime.sh +++ b/wasm-port/tests/native/probe_millturn_user_m_runtime.sh @@ -11,12 +11,23 @@ LINUXCNC_STDERR="$RUN_DIR/linuxcnc.stderr.log" required_commands=(tclsh halrun halcmd linuxcnc) missing_commands=() +declare -A command_paths=() LINUXCNC_PID="" cleanup() { if [[ -n "$LINUXCNC_PID" ]]; then - kill "$LINUXCNC_PID" 2>/dev/null || true + kill -TERM "-$LINUXCNC_PID" 2>/dev/null || kill "$LINUXCNC_PID" 2>/dev/null || true + for _ in $(seq 1 20); do + if ! kill -0 "$LINUXCNC_PID" 2>/dev/null; then + wait "$LINUXCNC_PID" 2>/dev/null || true + LINUXCNC_PID="" + return + fi + sleep 0.1 + done + kill -KILL "-$LINUXCNC_PID" 2>/dev/null || kill -KILL "$LINUXCNC_PID" 2>/dev/null || true wait "$LINUXCNC_PID" 2>/dev/null || true + LINUXCNC_PID="" fi } trap cleanup EXIT @@ -49,10 +60,22 @@ print_kv() { printf '%s=%s\n' "$1" "$2" } +existing_linuxcnc_runtime_conflicts() { + ps -eo pid=,args= | awk ' + /linuxcncsvr -ini/ || /rtapi_app load/ { + if ($0 !~ /awk /) { + gsub(/^[[:space:]]+/, "", $0) + print + } + } + ' +} + runtime_requirements=() for command_name in "${required_commands[@]}"; do path="$(command_path "$command_name")" if [[ -n "$path" ]]; then + command_paths["$command_name"]="$path" runtime_requirements+=("$command_name:1") print_kv "millturn_user_m_runtime_${command_name}_path" "$path" else @@ -108,6 +131,24 @@ if [[ "${ENABLE_MILLTURN_USER_M_RUNTIME_PROBE:-0}" != "1" ]]; then exit 0 fi +existing_runtime_conflicts="$(existing_linuxcnc_runtime_conflicts)" +if [[ -n "$existing_runtime_conflicts" ]]; then + print_kv millturn_user_m_runtime_ready 0 + print_kv millturn_user_m_missing_requirements "exclusive_linuxcnc_runtime" + print_kv millturn_user_m_existing_runtime_conflicts "$(printf '%s\n' "$existing_runtime_conflicts" | paste -sd ';' -)" + print_kv millturn_user_m_runtime_probe_status "blocked_existing_linuxcnc_runtime" + print_kv millturn_user_m_runtime_probe_note "existing_linuxcnc_runtime_conflict_keep_user_m_process_blocked" + exit 0 +fi + +HALCMD_BIN="${command_paths[halcmd]}" +LINUXCNC_BIN="${command_paths[linuxcnc]}" + +export PATH="$LINUXCNC_ROOT/bin:$LINUXCNC_ROOT/scripts:$LINUXCNC_ROOT/tcl:$PATH" +export LD_LIBRARY_PATH="$LINUXCNC_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +export PYTHONPATH="$LINUXCNC_ROOT/lib/python${PYTHONPATH:+:$PYTHONPATH}" +export TCLLIBPATH="$LINUXCNC_ROOT/tcl${TCLLIBPATH:+ $TCLLIBPATH}" + mkdir -p "$RUN_DIR" wait_for_hal_pin() { @@ -115,7 +156,7 @@ wait_for_hal_pin() { local limit="${MILLTURN_USER_M_RUNTIME_WAIT_SECONDS:-30}" local end=$((SECONDS + limit)) while (( SECONDS < end )); do - if halcmd getp "$pin" >/dev/null 2>&1; then + if "$HALCMD_BIN" getp "$pin" >/dev/null 2>&1; then return 0 fi sleep 0.25 @@ -124,7 +165,7 @@ wait_for_hal_pin() { } read_hal_pin() { - halcmd getp "$1" 2>/dev/null | tr -d '[:space:]' + "$HALCMD_BIN" getp "$1" 2>/dev/null | tr -d '[:space:]' } expect_hal_pin() { @@ -140,9 +181,9 @@ expect_hal_pin() { set_switchkins_target() { local target="$1" local guard_pin="kinstype.is-$target" - if ! halcmd setp motion.switchkins-type "$target" >/dev/null 2>&1; then - halcmd unlinkp motion.switchkins-type >/dev/null 2>&1 || true - halcmd setp motion.switchkins-type "$target" + if ! "$HALCMD_BIN" setp motion.switchkins-type "$target" >/dev/null 2>&1; then + "$HALCMD_BIN" unlinkp motion.switchkins-type >/dev/null 2>&1 || true + "$HALCMD_BIN" setp motion.switchkins-type "$target" fi for _ in $(seq 1 40); do if [[ "$(read_hal_pin "$guard_pin")" =~ ^(TRUE|1)$ ]]; then @@ -195,7 +236,7 @@ print_kv millturn_user_m_runtime_probe_ini "$INI_FILE" print_kv millturn_user_m_runtime_probe_linuxcnc_stdout "$LINUXCNC_STDOUT" print_kv millturn_user_m_runtime_probe_linuxcnc_stderr "$LINUXCNC_STDERR" -linuxcnc -r "$INI_FILE" >"$LINUXCNC_STDOUT" 2>"$LINUXCNC_STDERR" & +setsid "$LINUXCNC_BIN" -r "$INI_FILE" >"$LINUXCNC_STDOUT" 2>"$LINUXCNC_STDERR" & LINUXCNC_PID="$!" if ! wait_for_hal_pin ini.x.min_limit || ! wait_for_hal_pin motion.switchkins-type; then diff --git a/wasm-port/tests/native/verify_native_probes.sh b/wasm-port/tests/native/verify_native_probes.sh index 45683de..3ddeb72 100755 --- a/wasm-port/tests/native/verify_native_probes.sh +++ b/wasm-port/tests/native/verify_native_probes.sh @@ -252,18 +252,22 @@ check_native_runtime_probe_summary_row() { echo "$target runtime probe unexpectedly enables execution or promotion" >&2 exit 1 fi - if [[ ! "$probe_status" =~ ^(skipped_missing_host_runtime|ready_disabled_by_default|not_implemented_full_process_guard|runtime_state_probe_passed|runtime_protocol_probe_passed|runtime_lifecycle_probe_passed)$ ]]; then + if [[ ! "$probe_status" =~ ^(skipped_missing_host_runtime|blocked_existing_linuxcnc_runtime|ready_disabled_by_default|not_implemented_full_process_guard|runtime_state_probe_passed|runtime_protocol_probe_passed|runtime_lifecycle_probe_passed)$ ]]; then echo "$target runtime probe has invalid status: $probe_status" >&2 exit 1 fi - if [[ "$runtime_ready" == "0" && "$probe_status" != "skipped_missing_host_runtime" ]]; then + if [[ "$runtime_ready" == "0" && ! "$probe_status" =~ ^(skipped_missing_host_runtime|blocked_existing_linuxcnc_runtime)$ ]]; then echo "$target runtime probe is not ready but did not skip: $probe_status" >&2 exit 1 fi - if [[ "$runtime_ready" == "0" && "$probe_note" != *"missing_host_runtime"* ]]; then + if [[ "$runtime_ready" == "0" && "$probe_status" == "skipped_missing_host_runtime" && "$probe_note" != *"missing_host_runtime"* ]]; then echo "$target runtime probe missing-runtime note drift: $probe_note" >&2 exit 1 fi + if [[ "$runtime_ready" == "0" && "$probe_status" == "blocked_existing_linuxcnc_runtime" && "$probe_note" != *"existing_linuxcnc_runtime_conflict"* ]]; then + echo "$target runtime probe existing-runtime-conflict note drift: $probe_note" >&2 + exit 1 + fi if [[ "$runtime_ready" == "1" && "$missing_requirements" != "-" ]]; then echo "$target runtime probe is ready but lists missing requirements: $missing_requirements" >&2 exit 1 @@ -654,7 +658,7 @@ if grep -Fq "millturn_user_m_runtime_ready=1" "$MILLTURN_USER_M_RUNTIME_STDOUT"; fi else grep -Fq "millturn_user_m_runtime_ready=0" "$MILLTURN_USER_M_RUNTIME_STDOUT" - grep -Fq "millturn_user_m_runtime_probe_status=skipped_missing_host_runtime" \ + grep -Eq "millturn_user_m_runtime_probe_status=(skipped_missing_host_runtime|blocked_existing_linuxcnc_runtime)" \ "$MILLTURN_USER_M_RUNTIME_STDOUT" fi diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index e34fe50..b33f729 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -802,6 +802,9 @@ assert.equal(millturnUserMBoundaryReport.apiName, "linuxcnc-wasm-virtual-hal-mil assert.equal(millturnUserMBoundaryReport.boundaryClass, "L4-USER-M-PROCESS"); assert.equal(millturnUserMBoundaryReport.complete, true); assert.equal(millturnUserMBoundaryReport.webSimulationSatisfied, true); +assert.equal(millturnUserMBoundaryReport.webSimulationReady, true); +assert.equal(millturnUserMBoundaryReport.nativeRuntimeRequired, false); +assert.equal(millturnUserMBoundaryReport.nativeRuntimeRequiredForWebSimulation, false); assert.equal(millturnUserMBoundaryReport.executionEnabled, false); assert.equal(millturnUserMBoundaryReport.promotionAllowed, false); assert.equal(millturnUserMBoundaryReport.processExecutionReady, false); @@ -810,11 +813,16 @@ assert.equal(millturnUserMBoundaryReport.missingManifestFiles.length, 0); assert.equal(millturnUserMBoundaryReport.rows.some(({ userMCode, remapCode }) => userMCode === "M128" && remapCode === "M428"), true); assert.equal(millturnUserMBoundaryReport.rows.some(({ userMCode, remapCode }) => userMCode === "M129" && remapCode === "M429"), true); assert.equal(millturnUserMBoundaryReport.rows.every(({ stateTargetCount }) => stateTargetCount === 12), true); +assert.equal(millturnUserMBoundaryReport.rows.every(({ webSimulationReady }) => webSimulationReady === true), true); +assert.equal(millturnUserMBoundaryReport.rows.every(({ nativeRuntimeRequiredForWebSimulation }) => nativeRuntimeRequiredForWebSimulation === false), true); assert.equal(millturnUserMBoundaryReport.rows.every(({ executionEnabled, promotionAllowed }) => executionEnabled === false && promotionAllowed === false), true); let millturnStateResult = applyVirtualHalMillturnUserMProcessState(virtualHalRuntime.getState(), "M128", { manifestText: sourceManifestText }); assert.equal(millturnStateResult.apiName, "linuxcnc-wasm-virtual-hal-millturn-user-m-process-state-result"); assert.equal(millturnStateResult.ok, true); assert.equal(millturnStateResult.userMCode, "M128"); +assert.equal(millturnStateResult.webSimulationReady, true); +assert.equal(millturnStateResult.nativeRuntimeRequired, false); +assert.equal(millturnStateResult.nativeRuntimeRequiredForWebSimulation, false); assert.equal(millturnStateResult.executionEnabled, false); assert.equal(millturnStateResult.promotionAllowed, false); assert.equal(readVirtualHalPin(millturnStateResult.state, "motion.switchkins-type").value, 0); diff --git a/wasm-port/tests/ui/node/verify_real_simulation_programs.mjs b/wasm-port/tests/ui/node/verify_real_simulation_programs.mjs index 5fefcd5..67e2aa2 100644 --- a/wasm-port/tests/ui/node/verify_real_simulation_programs.mjs +++ b/wasm-port/tests/ui/node/verify_real_simulation_programs.mjs @@ -293,6 +293,36 @@ assert.equal(customRun.program.source, "custom"); assert.ok(customRun.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=4.25 y=2.5")); assert.ok(customRun.summary.ready); assert.equal(customRun.modal.apiName, "real-browser-simulation-modal-state"); +assert.equal(customRun.millturnUserMProcess.apiName, "real-browser-simulation-millturn-user-m-process-proof"); +assert.equal(customRun.millturnUserMProcess.ready, true); +assert.equal(customRun.millturnUserMProcess.webSimulationReady, true); +assert.equal(customRun.millturnUserMProcess.boundaryClass, "L4-USER-M-PROCESS"); +assert.deepEqual(customRun.millturnUserMProcess.transitionOrder, ["M429", "M428"]); +assert.equal(customRun.millturnUserMProcess.nativeRuntimeRequired, false); +assert.equal(customRun.millturnUserMProcess.nativeRuntimeRequiredForWebSimulation, false); +assert.equal(customRun.millturnUserMProcess.processExecutionReady, false); +assert.equal(customRun.millturnUserMProcess.executionEnabled, false); +assert.equal(customRun.millturnUserMProcess.promotionAllowed, false); +assert.equal(customRun.millturnUserMProcess.turn.userMCode, "M129"); +assert.equal(customRun.millturnUserMProcess.turn.remapCode, "M429"); +assert.equal(customRun.millturnUserMProcess.turn.pins["motion.switchkins-type"], 1); +assert.equal(customRun.millturnUserMProcess.turn.pins["motion.analog-out-03"], 1); +assert.equal(customRun.millturnUserMProcess.turn.pins["kinstype.is-0"], 0); +assert.equal(customRun.millturnUserMProcess.turn.pins["kinstype.is-1"], 1); +assert.equal(customRun.millturnUserMProcess.turn.pins["ini.x.min_limit"], -240); +assert.equal(customRun.millturnUserMProcess.turn.pins["ini.x.max_limit"], 0); +assert.equal(customRun.millturnUserMProcess.turn.pins["ini.z.min_limit"], -300); +assert.equal(customRun.millturnUserMProcess.turn.pins["ini.z.max_limit"], 300); +assert.equal(customRun.millturnUserMProcess.mill.userMCode, "M128"); +assert.equal(customRun.millturnUserMProcess.mill.remapCode, "M428"); +assert.equal(customRun.millturnUserMProcess.mill.pins["motion.switchkins-type"], 0); +assert.equal(customRun.millturnUserMProcess.mill.pins["motion.analog-out-03"], 0); +assert.equal(customRun.millturnUserMProcess.mill.pins["kinstype.is-0"], 1); +assert.equal(customRun.millturnUserMProcess.mill.pins["kinstype.is-1"], 0); +assert.equal(customRun.millturnUserMProcess.mill.pins["ini.x.min_limit"], -300); +assert.equal(customRun.millturnUserMProcess.mill.pins["ini.x.max_limit"], 300); +assert.equal(customRun.millturnUserMProcess.mill.pins["ini.z.min_limit"], -240); +assert.equal(customRun.millturnUserMProcess.mill.pins["ini.z.max_limit"], 0); const sessionRunCalls = []; const sessionRun = await runRealBrowserSimulation({ 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 2afa002..b01a318 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 @@ -107,6 +107,7 @@ const nativeRuntimeProbeExecutionPlanPath = resolve(buildDir, "native-runtime-pr const nativeRuntimeProbePassEvidenceContractPath = resolve(buildDir, "native-runtime-probe-pass-evidence-contract.tsv"); const promotionCandidatesPath = resolve(buildDir, "promotion-candidates.tsv"); const evidenceExpansionCandidatesPath = resolve(buildDir, "evidence-expansion-candidates.tsv"); +const remainingSkipMainProgramPromotionAuditPath = resolve(buildDir, "remaining-skip-main-program-promotion-audit.tsv"); const blockedRuntimePromotionLockPath = resolve(buildDir, "blocked-runtime-promotion-lock.tsv"); const runtimeBoundaryPromotionReadinessPath = resolve(buildDir, "runtime-boundary-promotion-readiness.tsv"); const runtimeBoundaryPromotionBlockersPath = resolve(buildDir, "runtime-boundary-promotion-blockers.tsv"); @@ -167,6 +168,7 @@ const generatedSimConfigInventoryArtifactPaths = [ nativeRuntimeProbeExecutionPlanPath, nativeRuntimeProbePassEvidenceContractPath, promotionCandidatesPath, + remainingSkipMainProgramPromotionAuditPath, blockedRuntimePromotionLockPath, runtimeBoundaryPromotionReadinessPath, runtimeBoundaryPromotionBlockersPath, @@ -6923,7 +6925,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) { const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row])); assert.equal( rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count, - "59", + "60", "WASM artifact documentation completion count must match generated artifact baseline", ); assert.equal( @@ -9676,7 +9678,7 @@ function verifyGeneratedArtifactDocumentationCoverage({ ); assert.equal( artifactNames.length, - 59, + 60, "generated sim-config inventory artifact list count drift", ); assert.deepEqual( @@ -9709,6 +9711,7 @@ function verifyGeneratedArtifactDocumentationCoverage({ "python-remap-runtime-contract.tsv", "python-remap-runtime-gates.tsv", "python-remap-wasm-node-row-proof.tsv", + "remaining-skip-main-program-promotion-audit.tsv", "runtime-boundary-contract-summary.tsv", "runtime-boundary-family-host-readiness.tsv", "runtime-boundary-host-preflight.tsv", @@ -9775,7 +9778,7 @@ function verifyGeneratedArtifactDocumentationCoverage({ "generated sim-config inventory artifacts must be listed in browser smoke", ); assert.ok( - browserSmokeText.includes("wasmArtifactNames.length !== 59"), + browserSmokeText.includes("wasmArtifactNames.length !== 60"), "browser smoke must keep generated WASM artifact fixed-count guard", ); assert.ok( @@ -10651,6 +10654,7 @@ function nativeRuntimeProbeSummaryRows() { assert.ok( [ "skipped_missing_host_runtime", + "blocked_existing_linuxcnc_runtime", "ready_disabled_by_default", "not_implemented_full_process_guard", "runtime_state_probe_passed", @@ -10660,9 +10664,16 @@ function nativeRuntimeProbeSummaryRows() { `${boundaryClass}: invalid runtime probe status`, ); if (row.runtime_ready === "0") { - assert.equal(row.probe_status, "skipped_missing_host_runtime", `${boundaryClass}: non-ready runtime must skip`); + assert.ok( + ["skipped_missing_host_runtime", "blocked_existing_linuxcnc_runtime"].includes(row.probe_status), + `${boundaryClass}: non-ready runtime must skip or report host-runtime conflict`, + ); assert.notEqual(row.missing_requirements, "-", `${boundaryClass}: non-ready runtime must record missing requirements`); - assert.ok(row.probe_note.includes("missing_host_runtime"), `${boundaryClass}: missing-runtime note drift`); + if (row.probe_status === "skipped_missing_host_runtime") { + assert.ok(row.probe_note.includes("missing_host_runtime"), `${boundaryClass}: missing-runtime note drift`); + } else { + assert.ok(row.probe_note.includes("existing_linuxcnc_runtime_conflict"), `${boundaryClass}: existing-runtime-conflict note drift`); + } } else { assert.equal(row.missing_requirements, "-", `${boundaryClass}: ready runtime must not record missing requirements`); } @@ -10752,6 +10763,13 @@ function runtimeProbeGateAlignmentRows({ const runtimeReadyMatch = gateRow?.runtime_ready === nativeRow?.runtime_ready; const sourceProofReadyMatch = gateRow?.source_proof_ready === nativeRow?.source_proof_ready; const missingRequirementsMatch = gateMissing.join(",") === nativeMissing.join(","); + const existingRuntimeConflictCompatible = ( + nativeRow?.probe_status === "blocked_existing_linuxcnc_runtime" && + gateRow?.runtime_ready === "1" && + nativeRow?.runtime_ready === "0" && + gateMissing.length === 0 && + nativeMissing.includes("exclusive_linuxcnc_runtime") + ); const requiredNativeProofMatch = gateRow?.required_native_proof === nativeRow?.required_native_proof; const executionDisabledMatch = ( gateRow?.execution_enabled === "0" && @@ -10765,6 +10783,9 @@ function runtimeProbeGateAlignmentRows({ (gateRow?.runtime_ready === "0" && gateRow?.gate_status === "blocked_missing_host_runtime" && nativeRow?.probe_status === "skipped_missing_host_runtime") || + (gateRow?.runtime_ready === "1" && + gateRow?.gate_status?.startsWith("ready_to_implement_") && + nativeRow?.probe_status === "blocked_existing_linuxcnc_runtime") || (gateRow?.runtime_ready === "1" && gateRow?.gate_status?.startsWith("ready_to_implement_") && nativeRow?.probe_status === "ready_disabled_by_default") || @@ -10785,7 +10806,7 @@ function runtimeProbeGateAlignmentRows({ runtimeReadyMatch && sourceProofReadyMatch && requiredNativeProofMatch && - missingRequirementsMatch && + (missingRequirementsMatch || existingRuntimeConflictCompatible) && executionDisabledMatch && promotionDisabledMatch && statusCompatible @@ -10809,7 +10830,7 @@ function runtimeProbeGateAlignmentRows({ listValue(gateMissing), listValue(nativeMissing), flagValue(requiredNativeProofMatch), - flagValue(missingRequirementsMatch), + flagValue(missingRequirementsMatch || existingRuntimeConflictCompatible), flagValue(executionDisabledMatch), flagValue(promotionDisabledMatch), flagValue(statusCompatible), @@ -13480,6 +13501,178 @@ function verifyPromotionCandidateRows(rows, summaryRows, boundaryRows) { return parsedRows; } +function remainingSkipMainProgramPromotionAuditRows({ + summaryRows, + promotionCandidateRows, + boundaryRows, + promotionReadinessRows, +}) { + const promotionByPath = new Map( + promotionCandidateRows + .filter((row) => row.candidate_kind === "inventory-ready") + .map((row) => [row.path, row]), + ); + const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row])); + const readinessByBlocked = new Map(promotionReadinessRows.map((row) => [row.blocked, row])); + + return summaryRows + .map((row) => { + const [ + path, + inventoryStatus, + reason, + className, + nativeStatus, + nativeExpectedFailure, + ] = row.split("\t"); + return { + path, + inventoryStatus, + reason, + className, + nativeStatus, + nativeExpectedFailure, + }; + }) + .filter((row) => row.inventoryStatus === "SKIP" && row.className === "main") + .sort((left, right) => left.path.localeCompare(right.path)) + .map((row) => { + const promotion = promotionByPath.get(row.path); + const boundary = boundaryByPath.get(row.path); + const readiness = readinessByBlocked.get(row.reason); + const promotionAllowed = promotion?.promotion_allowed === "1" && + readiness?.promotion_ready === "1"; + const decision = promotionAllowed + ? "promotable_main_program" + : row.reason === "UPSTREAM-DEMO" + ? "not_promotable_upstream_demo_missing_motion_gcode" + : "not_promotable_runtime_proof_incomplete"; + const simulationProofStatus = readiness + ? `${readiness.current_probe_status}:native=${readiness.native_pass_ready}:node=${readiness.node_inventory_gate_complete}:browser=${readiness.browser_smoke_gate_complete}` + : "no_runtime_promotion_readiness_for_skip_kind"; + + return [ + row.path, + promotion?.ini ?? boundary?.ini ?? "-", + row.inventoryStatus, + row.reason, + row.className, + row.nativeStatus, + row.nativeExpectedFailure, + promotion?.blocked_kind ?? boundary?.blocked ?? row.reason, + promotion?.dependency_class ?? boundary?.dependencies ?? "-", + simulationProofStatus, + readiness?.native_pass_ready ?? "0", + readiness?.node_inventory_gate_complete ?? "0", + readiness?.browser_smoke_gate_complete ?? "0", + readiness?.promotion_lock_active ?? "1", + readiness?.promotion_ready ?? "0", + promotion?.promotion_allowed ?? "0", + decision, + promotion?.block_reason ?? `blocked_by_${row.reason}`, + promotion?.recommended_next_command ?? "no_direct_baseline_promotion_candidate", + ].map(tsvValue).join("\t"); + }); +} + +function verifyRemainingSkipMainProgramPromotionAuditRows({ + rows, + summaryRows, + promotionCandidateRows, +}) { + const expectedHeaders = [ + "path", + "ini", + "current_status", + "skip_kind", + "class", + "native_status", + "native_expected_failure", + "blocked_kind", + "dependency_class", + "simulation_proof_status", + "native_pass_ready", + "node_inventory_gate_complete", + "browser_smoke_gate_complete", + "promotion_lock_active", + "promotion_ready", + "promotion_allowed", + "promotion_decision", + "block_reason", + "recommended_next_command", + ]; + const parsedRows = parseTsv( + `${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`, + expectedHeaders, + ); + const expectedSkippedMain = summaryRows + .map((row) => { + const [path, inventoryStatus, reason, className] = row.split("\t"); + return { path, inventoryStatus, reason, className }; + }) + .filter((row) => row.inventoryStatus === "SKIP" && row.className === "main") + .map((row) => row.path) + .sort(); + const promotionInventoryPaths = promotionCandidateRows + .filter((row) => row.candidate_kind === "inventory-ready") + .map((row) => row.path) + .sort(); + + assert.deepEqual( + parsedRows.map((row) => row.path).sort(), + expectedSkippedMain, + "remaining skipped-main audit must cover every skipped main program", + ); + assert.deepEqual( + promotionInventoryPaths, + expectedSkippedMain, + "remaining skipped-main audit must align with inventory-ready promotion candidates", + ); + assert.equal(parsedRows.length, 2, "current remaining skipped main-program count drift"); + assert.equal( + parsedRows.filter((row) => row.promotion_allowed === "1").length, + 0, + "current remaining skipped main-program rows must not be promotable", + ); + + for (const row of parsedRows) { + assert.equal(row.current_status, "SKIP", `${row.path}: audit status drift`); + assert.equal(row.class, "main", `${row.path}: audit must only include main programs`); + assert.equal(row.blocked_kind, row.skip_kind, `${row.path}: audit blocked kind drift`); + assert.ok(["0", "1"].includes(row.native_pass_ready), `${row.path}: invalid native pass flag`); + assert.ok(["0", "1"].includes(row.node_inventory_gate_complete), `${row.path}: invalid Node gate flag`); + assert.ok(["0", "1"].includes(row.browser_smoke_gate_complete), `${row.path}: invalid browser gate flag`); + assert.ok(["0", "1"].includes(row.promotion_ready), `${row.path}: invalid promotion ready flag`); + assert.equal(row.promotion_ready, "0", `${row.path}: audit must not mark promotion ready`); + assert.equal(row.promotion_allowed, "0", `${row.path}: audit must not allow promotion`); + assert.notEqual(row.block_reason, "-", `${row.path}: audit lacks block reason`); + assert.notEqual(row.recommended_next_command, "-", `${row.path}: audit lacks next command`); + if (row.skip_kind === "L4-USER-M-PROCESS") { + assert.equal(row.path, "axis/vismach/millturn/example.ngc", `${row.path}: user-M skipped main path drift`); + assert.ok( + row.simulation_proof_status.includes("node=0") || + row.simulation_proof_status.includes("browser=0"), + `${row.path}: user-M row must show incomplete Node/browser simulation proof`, + ); + assert.equal( + row.promotion_decision, + "not_promotable_runtime_proof_incomplete", + `${row.path}: user-M audit decision drift`, + ); + } else if (row.skip_kind === "UPSTREAM-DEMO") { + assert.equal( + row.promotion_decision, + "not_promotable_upstream_demo_missing_motion_gcode", + `${row.path}: upstream demo audit decision drift`, + ); + } else { + assert.fail(`${row.path}: unexpected remaining skipped main skip kind ${row.skip_kind}`); + } + } + + return parsedRows; +} + for (const record of nativeRecords) { const skipReason = blockedKind(record, pathMatrixByPath); if (skipReason === "L4-PYTHON-REMAP" && promotedPythonRemapInventoryPaths.has(record.path)) { @@ -13981,11 +14174,23 @@ const promotionCandidateRowsGenerated = promotionCandidateRows({ trackedMatrixByPath, nativeByPath: new Map(nativeRecords.map((record) => [record.path, record])), }); -verifyPromotionCandidateRows( +const promotionCandidateRecords = verifyPromotionCandidateRows( promotionCandidateRowsGenerated, summaryRows, boundarySummaryRecords, ); +const remainingSkipMainProgramPromotionAuditRowsGenerated = + remainingSkipMainProgramPromotionAuditRows({ + summaryRows, + promotionCandidateRows: promotionCandidateRecords, + boundaryRows: boundarySummaryRecords, + promotionReadinessRows: runtimeBoundaryPromotionReadinessRecords, + }); +verifyRemainingSkipMainProgramPromotionAuditRows({ + rows: remainingSkipMainProgramPromotionAuditRowsGenerated, + summaryRows, + promotionCandidateRows: promotionCandidateRecords, +}); const evidenceExpansionCandidateRowsGenerated = evidenceExpansionCandidateRows({ summaryRows, boundaryRows: boundarySummaryRecords, @@ -14047,6 +14252,34 @@ writeFileSync( ].join("\n")}\n`, ); +writeFileSync( + remainingSkipMainProgramPromotionAuditPath, + `${[ + [ + "path", + "ini", + "current_status", + "skip_kind", + "class", + "native_status", + "native_expected_failure", + "blocked_kind", + "dependency_class", + "simulation_proof_status", + "native_pass_ready", + "node_inventory_gate_complete", + "browser_smoke_gate_complete", + "promotion_lock_active", + "promotion_ready", + "promotion_allowed", + "promotion_decision", + "block_reason", + "recommended_next_command", + ].join("\t"), + ...remainingSkipMainProgramPromotionAuditRowsGenerated, + ].join("\n")}\n`, +); + writeFileSync( boundarySummaryPath, `${[