From ac52b48d6226c49c22a765ce91d96d4a445f146f Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sat, 20 Jun 2026 00:06:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=88=E6=8F=90=E4=BA=A4=E5=88=B0=E4=BA=91?= =?UTF-8?q?=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:已提交当前 Python-remap proof 链路、runtime/browser/UI/docs 相关变更;保持 L4-PYTHON-REMAP inventory baseline 不直接批量 PASS。 --- text25.txt | 77 ++ text26.txt | 442 +++++++++ text27.txt | 287 ++++++ text28.txt | 617 +++++++++++++ wasm-port/docs/compatibility-validation.md | 39 +- .../docs/full-process-boundary-design.md | 23 +- .../docs/host-runtime-boundary-handoff.md | 92 +- .../sim-config-coverage-promotion-analysis.md | 9 +- .../docs/sim-configs-coverage-handoff.md | 11 +- wasm-port/docs/sim-configs-coverage-matrix.md | 39 +- wasm-port/runtime/sdk/README.md | 11 + wasm-port/runtime/sdk/src/index.js | 4 + .../sdk/src/project-release-readiness.js | 144 +++ .../sdk/src/python-remap-runtime-port.js | 456 ++++++++++ wasm-port/runtime/ui/simulation/index.html | 8 + .../runtime/ui/simulation/simulation-app.js | 82 +- .../runtime/workers/python-remap-worker.js | 6 +- .../ini_panel_workflow_overview_smoke.html | 14 +- wasm-port/tests/browser/interp_smoke.html | 147 ++- .../python_remap_pyodide_only_worker.js | 3 + .../python_remap_runtime_browser_smoke.html | 17 + .../browser/real_simulation_page_smoke.html | 32 + .../verify_host_runtime_boundary_docs.mjs | 22 +- .../node/verify_sim_configs_coverage_docs.mjs | 3 - ...ite_project_release_readiness_artifact.mjs | 30 + .../native/probe_python_remap_runtime.sh | 781 +++++++++++++++- ..._project_release_artifact_url_workflow.mjs | 16 + .../verify_project_release_gate_manifest.mjs | 31 +- .../node/verify_python_remap_runtime_port.mjs | 140 +++ .../tests/sdk/node/verify_sdk_surface.mjs | 70 ++ .../verify_ini_panel_launch_api_manifest.mjs | 13 + ...y_ini_panel_shell_integration_manifest.mjs | 42 +- .../ui/node/verify_ini_panel_ui_shell.mjs | 50 +- .../verify_sim_configs_inventory_wasm.mjs | 860 ++++++++++++++++-- .../wasm/node/verify_sim_configs_wasm.mjs | 2 +- wasm-port/tools/build_native_probes.sh | 8 + 36 files changed, 4388 insertions(+), 240 deletions(-) create mode 100644 text26.txt create mode 100644 text27.txt create mode 100644 text28.txt create mode 100644 wasm-port/tests/browser/python_remap_pyodide_only_worker.js diff --git a/text25.txt b/text25.txt index c629912..8b07a33 100644 --- a/text25.txt +++ b/text25.txt @@ -612,3 +612,80 @@ native opt-in lifecycle probe 已通过; 的 WASM/browser proof chain; promotion_allowed 继续保持 0,直到完整 proof + manual lock review。 ``` + +十三、2026-06-19 接续执行记录:L4-PYTHON-REMAP 仿真系统接入收尾 + +本轮结论: + +```text +L4-PYTHON-REMAP 的 native / SDK / WASM / browser proof chain 已接入; +真实数控仿真页 runtime/ui/simulation 现在也暴露 Python remap runtime 状态; +release artifact / URL workflow 能识别 python-remap-runtime-proof=ready; +53 个 L4-PYTHON-REMAP inventory rows 仍保持 SKIP/manual promotion lock, +不做批量 PASS,不启用 execution,不允许 promotion_allowed。 +``` + +新增/补齐的仿真接入点: + +```text +wasm-port/runtime/ui/simulation/simulation-app.js +- createPythonRemapSimulationRuntimeStatus() +- renderPythonRemapRuntimeStatus() +- runRealBrowserSimulation() state.pythonRemapRuntime + +wasm-port/runtime/ui/simulation/index.html +- Machine State 面板显示 L4-PYTHON-REMAP fixture/runtime/lifecycle/promotion 状态 +- window.linuxCncRealSimulationApi.getPythonRemapRuntimeStatus() +- exportDiagnosticsArtifact().pythonRemapRuntime + +wasm-port/tests/browser/real_simulation_page_smoke.html +- 验证 DOM / API / diagnostics artifact 的 Python remap runtime 状态 +``` + +本轮验证: + +```bash +wasm-port/tests/native/probe_python_remap_runtime.sh +ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh +wasm-port/tests/browser/verify_python_remap_runtime_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh +wasm-port/tests/sdk/node/verify_sdk_surface.sh +wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh +wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tools/verify_no_standalone_cnc_semantics.sh +git diff --check +``` + +关键验证结果: + +```text +python_remap_runtime_probe_status=ready_disabled_by_default +python_remap_runtime_probe_status=runtime_lifecycle_probe_passed +python_remap_runtime_lifecycle_probe_ok=1 +python_remap_lifecycle_generator_first_yield=2 +python_remap_execution_enabled=0 +python_remap_promotion_allowed=0 +browser_python_remap_runtime_smoke=ok +browser_real_simulation_page_smoke=ok +browser_release_artifact_url_workflow_smoke=ok +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +standalone CNC semantics guard complete +``` + +仍保持的边界: + +```text +executionEnabled=false +promotionAllowed=false +bulkPromotionAllowed=false +ngcOnlySubroutinePromoted=false +jsCncSemantics=false +``` diff --git a/text26.txt b/text26.txt new file mode 100644 index 0000000..024bd3f --- /dev/null +++ b/text26.txt @@ -0,0 +1,442 @@ +项目接续文件:L4-PYTHON-REMAP 53 rows 批量 PASS 方法与执行计划 + +生成时间:2026-06-19 CST + +本文件接替 `text25.txt`,专门回答: + +```text +如何把 53 个 Python-remap inventory rows 从 SKIP L4-PYTHON-REMAP 批量提升为 PASS; +以及下一批应按什么步骤推进。 +``` + +重要边界: + +```text +这里的“批量提升为 PASS”不能等价于删除 L4-PYTHON-REMAP skip reason。 +必须是 proof-driven bulk promotion: + +1. LinuxCNC-owned Python remap runtime 真实执行; +2. 每个 row 的 INI / Python modules / remap declarations / prolog / epilog / NGC remap assets + 都已 vendored 并 staged; +3. native -> WASM/Node -> browser proof 全部通过; +4. promotion_allowed 由明确的 promotion artifact 置 1; +5. inventory baseline 从 PASS=29 / SKIP=130 变为 PASS=82 / SKIP=77; +6. 不新增 JS-owned G-code、planner、canonical motion、tool、parameter、kinematics、remap 语义。 +``` + +当前禁止事项: + +```text +- 不直接把 `recommendedBlockedKind === "L4-PYTHON-REMAP"` 改成 `-`; +- 不把现有 stop-lookahead lifecycle fixture 当作 53 rows 全量 pass; +- 不用 fake worker / contract-only runtime 作为 PASS 证据; +- 不把 Python import success 当作 remap execution pass; +- 不把 NGC-only subroutine asset 当 standalone main program; +- 不绕过 HAL/UI/HALUI/tool-change/TWP 等边界; +- 不手工改 summary.tsv 的 PASS/SKIP 数字。 +``` + +一、当前状态 + +当前已经完成: + +```text +L4-PYTHON-REMAP native / SDK / WASM / browser proof chain 已接入; +runtime/ui/simulation 已暴露 Python remap runtime 状态; +release artifact / URL workflow 能识别 python-remap-runtime-proof=ready; +project release gate 通过; +``` + +当前仍未完成: + +```text +53 个 L4-PYTHON-REMAP rows 仍保持 SKIP; +promotion_allowed=0; +execution_enabled=0; +manual promotion lock 仍开启; +``` + +当前 inventory baseline: + +```text +total=159 +pass=29 +skip=130 +fail=0 +L4-PYTHON-REMAP=53 +L4-USER-M-PROCESS=1 +``` + +目标 bulk promotion baseline: + +```text +total=159 +pass=82 +skip=77 +fail=0 +L4-PYTHON-REMAP=0 +L4-USER-M-PROCESS=1 +``` + +二、53 rows 审计结果 + +来源: + +```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 +``` + +总量: + +```text +rows=53 +families=12 +``` + +横向依赖统计: + +```text +hal_process=53 +ui_process=49 +halui_mdi_process=5 +prolog_rows=43 +epilog_rows=43 +python_remap_rows=9 +ngc_only_subpath_rows=7 +mixed_ngc_python_rows=6 +``` + +family 分组: + +```text +axis/laser rows=3 hal=1 ui=1 halui=0 prolog=0 epilog=0 pyremap=1 ngc_only=0 +axis/remap/cycle/nc_files rows=1 hal=1 ui=0 halui=0 prolog=1 epilog=1 pyremap=1 ngc_only=0 +axis/remap/extend-builtins/nc_files rows=1 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=1 mixed=1 +axis/remap/getting-started/nc_files rows=1 hal=1 ui=0 halui=0 prolog=0 epilog=0 pyremap=0 ngc_only=1 +axis/remap/manual-toolchange-with-tool-length-switch/nc_files rows=1 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +axis/remap/rack-toolchange/nc_files rows=1 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +axis/remap/stop-lookahead/nc_files rows=1 hal=1 ui=0 halui=0 prolog=0 epilog=0 pyremap=0 ngc_only=0 +axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos rows=5 hal=1 ui=1 halui=1 prolog=0 epilog=0 pyremap=1 ngc_only=1 mixed=1 +axis/vismach/VMC_toolchange rows=1 hal=1 ui=0 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +gmoccapy/lathe_configs rows=8 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +gmoccapy/macros rows=25 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples rows=5 hal=1 ui=1 halui=0 prolog=1 epilog=1 pyremap=0 ngc_only=0 +``` + +三、批量 PASS 的正确方法 + +方法不是“解锁一个 family id”,而是新增一个 promotion pipeline: + +```text +python-remap-bulk-promotion-plan.tsv + -> python-remap-row-runtime-proof.tsv + -> python-remap-row-browser-proof.tsv + -> python-remap-bulk-promotion-gate.tsv + -> inventory rows promotion_allowed=1 + -> summary baseline PASS=82 / SKIP=77 +``` + +每个 row 至少要有这些字段: + +```text +path +ini +family +main_or_macro_load_class +python_modules_staged +toplevel_executed +path_prepend_applied +python_remap_functions_bound +prolog_functions_bound +epilog_functions_bound +ngc_remap_assets_staged +ngc_only_subpaths_not_standalone +interpreter_state_binding_ready +canonical_events_ready +hal_state_boundary_ready +ui_process_boundary_ready +halui_mdi_boundary_ready +native_pass_ready +wasm_node_pass_ready +browser_pass_ready +execution_enabled +promotion_allowed +``` + +四、实现阶段 + +Phase 0:冻结现状与审计 + +目标: + +```text +确认当前 53 rows 分类、当前 proof 状态、当前 baseline。 +``` + +验收: + +```bash +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/browser/verify_python_remap_runtime_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +git diff --check +``` + +状态: + +```text +本文件创建时 Phase 0 已执行; +当前仍是 PASS=29 / SKIP=130 / L4-PYTHON-REMAP=53; +不允许从 Phase 0 直接跳到 PASS=82。 +``` + +Phase 1:生成 row-level bulk promotion plan + +新增 artifact: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-bulk-promotion-plan.tsv +``` + +生成逻辑: + +```text +读取 python-remap-boundary-summary.tsv; +按 row 输出 proof requirements; +默认 promotion_allowed=0; +把 blockers 拆成: + python_runtime + prolog_epilog + python_remap_callable + ngc_remap_asset + hal_process_boundary + ui_process_boundary + halui_mdi_boundary + tool_change_boundary + twp_boundary +``` + +验收: + +```text +plan row count = 53 +每个 L4-PYTHON-REMAP row 有且只有一条 plan row +所有 plan row promotion_allowed=0 +``` + +Phase 2:扩展 PythonRemapRuntimePort 为 row runtime + +当前 port 只证明 stop-lookahead lifecycle。批量 PASS 需要变成 row-aware runtime: + +```text +createLinuxCncPythonRemapRowRuntimePort({ + path, + iniPath, + machineRoot, + pythonPathPrepend, + topLevelPath, + sourceFiles, + remapDeclarations, + prologFunctions, + epilogFunctions, + ngcRemapFiles, + runtimeAdapter, +}) +``` + +必须支持: + +```text +INI [PYTHON] PATH_PREPEND +INI [PYTHON] TOPLEVEL +configured Python module import +LinuxCNC stdglue prolog/epilog +Python remap callable lookup +Python generator lifecycle +LinuxCNC interpreter state / sentinel binding +LinuxCNC emccanon / canonical event bridge +reload-on-change behavior +``` + +禁止: + +```text +JS 解释 G-code; +JS 计算 remap result; +JS 伪造 prolog/epilog semantic state; +``` + +Phase 3:处理 HAL/UI/HALUI 边界 + +因为 53/53 rows 有 HAL 声明,49/53 rows 有 UI 进程声明,5/53 rows 有 HALUI MDI: + +```text +批量 PASS 必须复用 virtual HAL proof; +UI process 不能按 native GUI 执行,只能证明 browser/standalone UI boundary sufficient; +HALUI MDI 必须有 LinuxCNC-owned command/state boundary; +``` + +新增 artifact: + +```text +python-remap-hal-ui-boundary-proof.tsv +``` + +每个 row 必须明确: + +```text +hal_process_boundary_ready=<0|1> +ui_process_boundary_ready=<0|1> +halui_mdi_boundary_ready=<0|1> +arbitrary_native_gui_launched=0 +arbitrary_native_hal_daemon_required=0 +``` + +Phase 4:按 family 做执行 proof + +推荐顺序: + +```text +1. axis/remap/stop-lookahead/nc_files rows=1 +2. axis/remap/cycle/nc_files rows=1 +3. axis/remap/getting-started/nc_files rows=1 +4. axis/remap/extend-builtins/nc_files rows=1 mixed NGC/Python guard +5. axis/remap/manual-toolchange-with-tool-length-switch rows=1 tool-change prolog/epilog +6. axis/remap/rack-toolchange/nc_files rows=1 tool-change prolog/epilog +7. axis/vismach/VMC_toolchange rows=1 tool-change body +8. axis/laser rows=3 Python remap callable family +9. axis/vismach/5axis/table-rotary.../demos rows=5 TWP + HALUI + mixed NGC/Python +10. gmoccapy/lathe_configs rows=8 stdglue prolog/epilog +11. gmoccapy/non_trivial_kinematics/... rows=5 stdglue + kinematics context +12. gmoccapy/macros rows=25 macro bulk family +``` + +每个 family 完成定义: + +```text +native row pass evidence exists +WASM/Node row pass evidence exists +browser row diagnostics exists +promotion_allowed remains 0 until family review +``` + +Phase 5:bulk promotion gate + +新增 artifact: + +```text +python-remap-bulk-promotion-gate.tsv +``` + +gate 规则: + +```text +all 53 row proof rows ready +all family proof rows ready +no ngc_only_subpath standalone violations +no JS CNC semantics violations +no missing vendored source files +no native/browser result drift +manual_lock_review=approved +``` + +只有 gate 全部满足时: + +```text +promotion_allowed=1 +execution_enabled=1 +``` + +Phase 6:inventory promotion implementation + +允许改动的位置: + +```text +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +wasm-port/runtime/sdk/src/sim-config-staging.js +wasm-port/runtime/sdk/src/project-release-readiness.js +相关 docs/tests expected baseline +``` + +禁止实现方式: + +```text +if blocked === "L4-PYTHON-REMAP" return PASS +``` + +允许实现方式: + +```text +if blocked === "L4-PYTHON-REMAP" && + row proof exists && + bulk promotion gate approved && + promotion_allowed === "1" +then execute through LinuxCNC-backed row runtime and count PASS +``` + +Phase 7:baseline 更新 + +成功后必须更新: + +```text +summary.tsv expected: + pass=82 + skip=77 + fail=0 + +skip-summary.tsv expected: + ASSET-ONLY=65 + L4-USER-M-PROCESS=1 + NON_MAIN_CLASS=10 + UPSTREAM-DEMO=1 +``` + +必须删除/更新: + +```text +L4-PYTHON-REMAP=53 的 hard-block summary assertions; +host-runtime docs 中 Python-remap locked wording; +release artifact hard-block rows; +workflow overview locked detail rows; +``` + +五、下一步立即执行 + +不要直接做 Phase 6/7。 + +下一批从 Phase 1 开始: + +```text +1. 在 verify_sim_configs_inventory_wasm.mjs 里生成 python-remap-bulk-promotion-plan.tsv; +2. 增加验证函数,确认 53 rows 全部进入 plan; +3. 增加 docs 说明:bulk PASS 需要 row proof,不接受 direct unlock; +4. 保持 PASS=29 / SKIP=130 不变; +5. 跑 inventory/browser/release gate。 +``` + +建议验收命令: + +```bash +git diff --check +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/browser/verify_python_remap_runtime_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +wasm-port/tests/host/verify_project_release_gate.sh +``` + +六、本文件的执行状态 + +```text +已完成: +- 当前 53 rows 审计; +- bulk PASS 正确方法定义; +- Phase 1 目标和验收条件定义; + +未完成: +- python-remap-bulk-promotion-plan.tsv 生成; +- row-aware PythonRemapRuntimePort; +- per-family execution proof; +- promotion_allowed=1; +- PASS=82 / SKIP=77 baseline update。 +``` diff --git a/text27.txt b/text27.txt new file mode 100644 index 0000000..73eb009 --- /dev/null +++ b/text27.txt @@ -0,0 +1,287 @@ +项目接续文件:L4-PYTHON-REMAP 53 rows 批量 PASS Phase 1 执行记录 + +生成时间:2026-06-19 CST + +本文件接替 `text26.txt`。 + +目标: + +```text +把“53 个 Python-remap inventory rows 批量提升为 PASS”的方法落成第一层 +machine-readable promotion plan。 + +本轮只做 Phase 1: +- 生成 `python-remap-bulk-promotion-plan.tsv`; +- 校验它 1:1 覆盖 53 个 `L4-PYTHON-REMAP` rows; +- 明确每行还缺哪些 proof; +- 保持 inventory baseline 不变:PASS=29 / SKIP=130 / L4-PYTHON-REMAP=53。 +``` + +禁止事项: + +```text +- 不把 53 行直接改 PASS; +- 不删除 `L4-PYTHON-REMAP` skip reason; +- 不把 stop-lookahead lifecycle proof 扩大解释为 53 行全量 pass; +- 不让 fake/browser worker 充当 PASS 证据; +- 不把 Python import success 当作 remap execution proof; +- 不让 NGC-only remap subpath 被当成 standalone main program; +- 不手工改 `summary.tsv` / `skip-summary.tsv` 数字。 +``` + +一、Phase 1 应生成的 artifact + +新增: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-bulk-promotion-plan.tsv +``` + +来源: + +```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-native-runtime-fixture-plan.tsv +wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-probe-gate.tsv +``` + +输出字段必须表达: + +```text +path +ini +family +main_or_macro_load_class +python_modules_staged +toplevel_executed +path_prepend_applied +python_remap_functions_bound +prolog_functions_bound +epilog_functions_bound +ngc_remap_assets_staged +ngc_only_subpaths_not_standalone +interpreter_state_binding_ready +canonical_events_ready +hal_state_boundary_ready +ui_process_boundary_ready +halui_mdi_boundary_ready +native_pass_ready +wasm_node_pass_ready +browser_pass_ready +execution_enabled +promotion_allowed +blockers +notes +``` + +二、Phase 1 验收条件 + +```text +plan row count = 53 +plan path set == python-remap-boundary-summary path set +所有 row blocked = L4-PYTHON-REMAP +所有 row execution_enabled = 0 +所有 row promotion_allowed = 0 +所有 row native_pass_ready = 0 +所有 row wasm_node_pass_ready = 0 +所有 row browser_pass_ready = 0 +所有 row blockers 非空 +HAL/UI/HALUI flags 与 boundary summary 保持一致 +存在 NGC-only subpath 的 row 必须显式记录 ngc_remap_asset / ngc_only_subpaths_not_standalone blocker +``` + +三、后续 Phase 2/3 的输入 + +Phase 2 扩展 row-aware runtime 时消费: + +```text +path +ini +family +python_modules_staged +toplevel_executed +path_prepend_applied +python_remap_functions_bound +prolog_functions_bound +epilog_functions_bound +ngc_remap_assets_staged +ngc_only_subpaths_not_standalone +interpreter_state_binding_ready +canonical_events_ready +``` + +Phase 3 HAL/UI/HALUI proof 时消费: + +```text +hal_state_boundary_ready +ui_process_boundary_ready +halui_mdi_boundary_ready +``` + +四、本轮应改文件 + +```text +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +wasm-port/tests/browser/interp_smoke.html +wasm-port/docs/compatibility-validation.md +wasm-port/docs/full-process-boundary-design.md +wasm-port/docs/sim-configs-coverage-matrix.md +text27.txt +``` + +五、本轮验收命令 + +```bash +git diff --check +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/browser/verify_python_remap_runtime_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +wasm-port/tests/host/verify_project_release_gate.sh +``` + +六、完成后允许进入的下一步 + +```text +Phase 2:实现 row-aware Python remap runtime port。 + +下一轮不要先改 baseline。 +先让 `python-remap-row-runtime-proof.tsv` 能逐 row 证明: +- INI [PYTHON] PATH_PREPEND; +- INI [PYTHON] TOPLEVEL; +- configured module import; +- Python remap callable / stdglue prolog / epilog lookup; +- generator lifecycle; +- interpreter state / canonical event bridge; +- NGC remap assets staged; +- NGC-only subpaths 不作为 standalone main。 +``` + +七、2026-06-19 Phase 2 执行结果 + +本轮已完成 Phase 2 的 proof substrate,不做 baseline promotion。 + +新增 / 更新: + +```text +wasm-port/runtime/sdk/src/python-remap-runtime-port.js + 新增 createLinuxCncPythonRemapRowRuntimePort() + 新增 createPythonRemapRowRuntimePlan() + 新增 validatePythonRemapRowRuntimeTranscript() + 新增 PYTHON_REMAP_ROW_RUNTIME_PROOF_PHASES + +wasm-port/runtime/sdk/src/index.js + 导出 row-aware Python remap runtime API + +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs + 覆盖 row runtime contract-only blocked path + 覆盖 fake row runtime adapter ready path + 验证 module/callable/NGC/canonical proof transcript + +wasm-port/tests/sdk/node/verify_sdk_surface.mjs +wasm-port/runtime/sdk/README.md + 同步 SDK public surface + +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs + 新增 python-remap-row-runtime-proof.tsv 生成与校验 + +wasm-port/tests/browser/interp_smoke.html +wasm-port/docs/compatibility-validation.md +wasm-port/docs/full-process-boundary-design.md +wasm-port/docs/sim-configs-coverage-matrix.md + generated artifact coverage 从 56 个 WASM TSV 更新到 57 个 +``` + +新增 artifact: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv +``` + +artifact 当前状态: + +```text +rows=53 +row_runtime_port_api_ready=1 53 +row_runtime_plan_ready=1 53 +row_runtime_transcript_ready=0 53 +native_pass_ready=0 53 +wasm_node_pass_ready=0 53 +browser_pass_ready=0 53 +execution_enabled=0 53 +proof_status=pending_row_runtime_adapter_execution 53 +``` + +inventory baseline 保持不变: + +```text +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +``` + +本轮明确没有完成: + +```text +- 未执行 53 rows 的真实 Python remap callbacks; +- 未把 row_runtime_transcript_ready 置 1; +- 未把 native_pass_ready / wasm_node_pass_ready / browser_pass_ready 置 1; +- 未解除 manual promotion lock; +- 未把 PASS=29 / SKIP=130 改为 PASS=82 / SKIP=77。 +``` + +八、本轮已通过验证 + +```bash +git diff --check +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +node wasm-port/tests/sdk/node/verify_sdk_surface.mjs +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh +wasm-port/tests/browser/verify_python_remap_runtime_browser.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh +wasm-port/tests/host/verify_project_release_gate.sh +``` + +九、下一步工作建议 + +建议下一轮进入 Phase 2.5,而不是直接 Phase 3 或 baseline promotion。 + +Phase 2.5 目标: + +```text +让 python-remap-row-runtime-proof.tsv 对第一批小 family 产生真实 row runtime transcript: +1. axis/remap/stop-lookahead/nc_files +2. axis/remap/cycle/nc_files +3. axis/remap/getting-started/nc_files +``` + +推荐顺序: + +```text +1. 为 createLinuxCncPythonRemapRowRuntimePort() 增加 Node/WASM row adapter; +2. adapter 只负责调用 LinuxCNC-owned Python runtime boundary,不解释 G-code; +3. 先让 stop-lookahead row 的: + python_modules_staged=1 + toplevel_executed=1 + path_prepend_applied=1 + interpreter_state_binding_ready=1 + canonical_events_ready=1 + row_runtime_transcript_ready=1 +4. cycle / getting-started 再加入 callable、prolog/epilog、NGC remap asset targets; +5. 继续保持 native_pass_ready=0 / wasm_node_pass_ready=0 / browser_pass_ready=0, + 直到 row runtime transcript 可以和 native proof 对齐。 +``` + +下一轮仍禁止: + +```text +- 不改 PASS/SKIP baseline; +- 不把 fake row adapter proof 当真实 PASS; +- 不把 module import 成功当 row execution pass; +- 不把 NGC-only subpath 当 standalone main program; +- 不解除 promotion_allowed。 +``` diff --git a/text28.txt b/text28.txt new file mode 100644 index 0000000..2a6b35a --- /dev/null +++ b/text28.txt @@ -0,0 +1,617 @@ +项目接续文件:L4-PYTHON-REMAP 53 rows 批量 PASS Phase 2.5 执行规范 + +生成时间:2026-06-19 CST + +本文件接替 `text27.txt`。 + +目标: + +```text +继续推进“53 个 Python-remap inventory rows 批量提升为 PASS”的 proof 链路。 + +本轮不是 baseline promotion。 +本轮只做 Phase 2.5: +- 固化 53 rows 最终批量 PASS 的方法和步骤; +- 让 `python-remap-row-runtime-proof.tsv` 至少对第一批小 family 产生真实 row runtime transcript; +- 优先让 stop-lookahead 首行从 pending 进入 transcript-ready; +- 继续保持 inventory baseline 不变:PASS=29 / SKIP=130 / L4-PYTHON-REMAP=53。 +``` + +一、53 rows 批量 PASS 的完整方法 + +```text +Phase 1:machine-readable bulk promotion plan +- 生成 `python-remap-bulk-promotion-plan.tsv`; +- 1:1 覆盖 53 个 `L4-PYTHON-REMAP` rows; +- 明确每行缺少的 runtime/native/node/browser proof; +- 保持 execution_enabled=0、promotion_allowed=0。 + +Phase 2:row-aware runtime proof substrate +- SDK 暴露 `createLinuxCncPythonRemapRowRuntimePort()`; +- 生成 `python-remap-row-runtime-proof.tsv`; +- 每行表达 module staging、TOPLEVEL、PATH_PREPEND、callable/prolog/epilog、NGC asset、interpreter state、canonical event 的 proof target; +- 仍不把 fake adapter 或 Python import-only 当 PASS。 + +Phase 2.5:第一批真实 row runtime transcript +- 对小 family 先落真实 transcript,而不是全量一次性 promotion; +- 第一批顺序: + 1. `axis/remap/stop-lookahead/nc_files` + 2. `axis/remap/cycle/nc_files` + 3. `axis/remap/getting-started/nc_files` +- transcript 必须来自 LinuxCNC-owned Python runtime boundary 或 native lifecycle stdout; +- transcript ready 只解除 `row_runtime_adapter_execution` blocker; +- 不解除 native/node/browser/manual promotion blockers。 + +Phase 3:row native pass proof +- 逐 row 证明 native runtime pass; +- 对齐 `python-remap-row-runtime-proof.tsv` 的 proof targets; +- native proof 不能只证明 stop-lookahead lifecycle 后推断 53 rows 全 PASS。 + +Phase 4:WASM Node pass proof +- 逐 row 证明 WASM Node runtime bridge 可执行同等 row contract; +- 保留 LinuxCNC-owned semantics,禁止 JS 重写 CNC 语义。 + +Phase 5:Browser pass proof +- 逐 row 证明 browser worker / UI workflow 与 Node proof 对齐; +- browser proof 必须在 Node proof 后接受。 + +Phase 6:manual promotion lock review +- 只有 row_runtime、native、wasm_node、browser proof 全部 ready 后,才允许解除 manual lock; +- 最终才可把 53 rows 从 `L4-PYTHON-REMAP` 批量提升为 PASS; +- 目标 baseline 才能从 PASS=29 / SKIP=130 变为 PASS=82 / SKIP=77。 +``` + +二、本轮 Phase 2.5 执行步骤 + +```text +1. 先确认 native Python lifecycle stdout 是否存在: + `wasm-port/build/native/python-remap-runtime/python_lifecycle.stdout.log` + +2. 确认 native runtime probe summary 是否存在并包含: + `wasm-port/build/native/native-runtime-probe-summary.tsv` + `L4-PYTHON-REMAP python_runtime axis/remap/stop-lookahead/nc_files` + `runtime_lifecycle_probe_passed` + +3. 运行 inventory verifier: + `SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh` + +4. 验收 `python-remap-row-runtime-proof.tsv`: + - rows=53; + - `axis/remap/stop-lookahead/nc_files/examples.ngc` + - `python_modules_staged=1` + - `toplevel_executed=1` + - `path_prepend_applied=1` + - `interpreter_state_binding_ready=1` + - `canonical_events_ready=1` + - `row_runtime_transcript_ready=1` + - `proof_status=native_lifecycle_stdout_row_runtime_transcript_ready` + - 其他 rows 可继续 pending; + - 所有 rows 仍保持: + - `native_pass_ready=0` + - `wasm_node_pass_ready=0` + - `browser_pass_ready=0` + - `execution_enabled=0` + +5. 如果 stop-lookahead 仍 pending: + - 优先修正 verifier 对 native stdout / native summary 的读取和验收; + - 不伪造 row transcript; + - 不把 build artifact 手工改成 ready。 + +6. stop-lookahead 通过后,下一轮再扩展 cycle / getting-started: + - 增加 row adapter 对 callable/prolog/epilog lookup 的真实 proof; + - 增加 NGC remap asset staged proof; + - 对 NGC-only subpaths 明确 reject standalone proof。 +``` + +三、本轮禁止事项 + +```text +- 不改 PASS/SKIP baseline; +- 不直接把 53 rows 改 PASS; +- 不删除 `L4-PYTHON-REMAP` skip reason; +- 不把 stop-lookahead lifecycle proof 解释成 53 rows 全量 pass; +- 不让 fake/browser worker 充当 PASS 证据; +- 不把 Python import success 当 row execution proof; +- 不把 NGC-only subpath 当 standalone main program; +- 不解除 `manual_promotion_lock`; +- 不手工改 `summary.tsv` / `skip-summary.tsv` 数字。 +``` + +四、本轮应关注文件 + +```text +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/runtime/sdk/src/python-remap-runtime-port.js +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv +text28.txt +``` + +五、本轮验收命令 + +```bash +git diff --check +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +六、完成后写入本文件的结果 + +```text +记录: +- stop-lookahead row runtime transcript 是否 ready; +- 53 rows row_runtime_proof 状态分布; +- inventory baseline 是否保持不变; +- 已通过和未通过的验证命令; +- 下一轮 cycle / getting-started 的具体入口。 +``` + +七、2026-06-19 Phase 2.5 执行结果 + +本轮已完成 stop-lookahead 首行的真实 row runtime transcript 接入,不做 baseline promotion。 + +代码修正: + +```text +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +- 修正 `python-remap-row-runtime-proof.tsv` 生成列映射; +- `generator_lifecycle_ready` 跟随 stop-lookahead native lifecycle stdout proof; +- `ngc_only_subpaths_not_standalone` 回到正确列位; +- 调用 `pythonRemapRowRuntimeProofRows()` / `verifyPythonRemapRowRuntimeProofRows()` 时传入 `nativeRuntimeProbeSourceRecords`; +- row proof 现在能读取 `wasm-port/build/native/native-runtime-probe-summary.tsv` 和对应 stdout log。 +``` + +新增接续文件: + +```text +text28.txt +``` + +artifact 当前状态: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv + +rows=53 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready 1 +proof_status=pending_row_runtime_adapter_execution 52 +``` + +stop-lookahead 首行状态: + +```text +path=axis/remap/stop-lookahead/nc_files/examples.ngc +python_modules_staged=1 +toplevel_executed=1 +path_prepend_applied=1 +generator_lifecycle_ready=1 +interpreter_state_binding_ready=1 +canonical_events_ready=1 +row_runtime_transcript_ready=1 +native_pass_ready=0 +wasm_node_pass_ready=0 +browser_pass_ready=0 +execution_enabled=0 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready +``` + +仍未解除的 blockers: + +```text +browser_row_pass +hal_process_boundary +manual_promotion_lock +native_row_pass +python_runtime +row_runtime_proof +wasm_node_row_pass +``` + +inventory baseline 保持不变: + +```text +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +``` + +本轮已通过验证: + +```bash +git diff --check +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +验证中的已知非失败 stderr: + +```text +/work/sim-inventory/axis/db_demo/base.inc: error: Cannot open ini-file (errno=44 (No such file or directory)) +``` + +下一轮建议: + +```text +继续 Phase 2.5,不进入 baseline promotion。 + +下一轮优先扩展: +1. `axis/remap/cycle/nc_files` +2. `axis/remap/getting-started/nc_files` + +具体入口: +- 为 row-aware runtime adapter 增加 callable/prolog/epilog lookup transcript; +- 对 cycle 行证明: + - configured module import; + - Python remap callable lookup; + - stdglue prolog / epilog lookup; + - generator lifecycle; + - NGC remap asset staged; + - interpreter state / canonical event bridge; +- 对 getting-started 行证明同等 row contract; +- 保持 native_pass_ready / wasm_node_pass_ready / browser_pass_ready / execution_enabled 全部为 0。 +``` + +八、2026-06-19 Phase 2.5 cycle / getting-started 执行结果 + +本轮继续 Phase 2.5,完成第一批小 family 的 row runtime transcript 扩展,不做 baseline promotion。 + +代码修正: + +```text +wasm-port/tests/native/probe_python_remap_runtime.sh +- 保留 stop-lookahead 旧 lifecycle stdout keys; +- 新增 row-specific transcript keys: + - axis/remap/stop-lookahead/nc_files/examples.ngc + - axis/remap/cycle/nc_files/examples.ngc + - axis/remap/getting-started/nc_files/examples.ngc +- 对 cycle 真实证明: + - INI [PYTHON] PATH_PREPEND / TOPLEVEL; + - stdglue、remap、toplevel module staged; + - g842 callable lookup and invoke; + - cycle_prolog / cycle_epilog lookup; + - g843.ngc remap asset staged; + - interpreter state / canonical event bridge ready。 +- 对 getting-started 真实证明: + - INI [PYTHON] PATH_PREPEND / TOPLEVEL; + - oword、remap、toplevel、util module staged; + - m400.ngc / m410.ngc remap assets staged; + - NGC-only subpaths rejected as standalone main; + - interpreter state / canonical event bridge ready。 + +wasm-port/tools/build_native_probes.sh +- native probe cache fingerprint 加入 axis/remap/getting-started source tree。 + +wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.mjs +- row proof 改为读取 native Python runtime stdout 的 row-specific keys; +- 允许 cycle/getting-started 从 native lifecycle stdout 进入 row runtime transcript ready; +- 仍保持 native_pass_ready / wasm_node_pass_ready / browser_pass_ready / execution_enabled 全部为 0; +- 仍不解除 manual promotion lock。 +``` + +artifact 当前状态: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv + +rows=53 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready 3 +proof_status=pending_row_runtime_adapter_execution 50 +``` + +本轮新增 ready rows: + +```text +axis/remap/cycle/nc_files/examples.ngc +python_modules_staged=1 +python_remap_functions_bound=1 +prolog_functions_bound=1 +epilog_functions_bound=1 +generator_lifecycle_ready=1 +ngc_remap_assets_staged=1 +ngc_only_subpaths_not_standalone=1 +row_runtime_transcript_ready=1 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready + +axis/remap/getting-started/nc_files/examples.ngc +python_modules_staged=1 +python_remap_functions_bound=0 +prolog_functions_bound=0 +epilog_functions_bound=0 +generator_lifecycle_ready=0 +ngc_remap_assets_staged=1 +ngc_only_subpaths_not_standalone=1 +row_runtime_transcript_ready=1 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready +``` + +inventory baseline 保持不变: + +```text +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +``` + +本轮已通过验证: + +```bash +git diff --check +ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.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 +继续 Phase 2.5,不进入 baseline promotion。 + +下一轮可以选择: +1. 扩展 axis/laser family 的 row-specific transcript; +2. 或扩展 vismach/5axis table-rotary_spindle-rotary-nutating family; +3. 或开始设计 Phase 3 row native pass proof contract。 + +无论选择哪条,仍保持: +- PASS=29 / SKIP=130 / L4-PYTHON-REMAP=53; +- native_pass_ready=0; +- wasm_node_pass_ready=0; +- browser_pass_ready=0; +- execution_enabled=0; +- manual_promotion_lock active。 +``` + +九、2026-06-19 批量 PASS 请求 gate 复核与 axis/laser 推进结果 + +本轮按“text28.txt 大建议”复核 53 个 Python-remap inventory rows 是否可以批量提升为 PASS。 + +结论: + +```text +不能直接批量 PASS。 + +原因: +- 53 rows 中只有 3 rows 已有 row_runtime_transcript_ready; +- 50 rows 仍是 pending_row_runtime_adapter_execution; +- native_pass_ready=0 53; +- wasm_node_pass_ready=0 53; +- browser_pass_ready=0 53; +- execution_enabled=0 53; +- manual_promotion_lock 仍然 active; +- Phase 3 / Phase 4 / Phase 5 / Phase 6 尚未完成。 +``` + +因此本轮没有把 `L4-PYTHON-REMAP` rows 改 PASS,没有改 baseline 数字。 + +本轮可安全推进的内容: + +```text +继续 Phase 2.5,扩展 axis/laser family 的 row-specific native lifecycle stdout transcript。 +``` + +代码修正: + +```text +wasm-port/tests/native/probe_python_remap_runtime.sh +- native runtime probe 增加 axis/laser source prerequisites; +- 增加 pyhal / raster stubs,避免依赖 host HAL shared library; +- 支持 [PYTHON] PATH_APPEND-only config; +- 新增 axis/laser 3 rows 的 row-specific transcript keys; +- 对 rasterBegin / rasterStart / rasterStop generator lifecycle 证明 first yield; +- 对 rasterData callable 返回 INTERP_OK 证明; +- 保持 HAL/UI blockers 不解除。 +``` + +artifact 当前状态: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv + +rows=53 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready 6 +proof_status=pending_row_runtime_adapter_execution 47 +native_pass_ready=0 53 +wasm_node_pass_ready=0 53 +browser_pass_ready=0 53 +execution_enabled=0 53 +``` + +本轮新增 ready rows: + +```text +axis/laser/raster_test.ngc +axis/laser/vector_test.ngc +axis/laser/vector_test2.ngc +``` + +新增 ready rows 状态: + +```text +python_modules_staged=1 +python_remap_functions_bound=1 +generator_lifecycle_ready=1 +row_runtime_transcript_ready=1 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready +``` + +inventory baseline 保持不变: + +```text +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +``` + +本轮已通过验证: + +```bash +git diff --check +ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +wasm-port/tests/native/verify_native_probes.sh +``` + +下一轮建议: + +```text +继续 Phase 2.5,不进入 baseline promotion。 + +优先扩展剩余 row runtime transcript: +1. axis/remap/extend-builtins/nc_files +2. axis/remap/manual-toolchange-with-tool-length-switch/nc_files +3. axis/remap/rack-toolchange/nc_files +4. axis/vismach/VMC_toolchange +5. gmoccapy/lathe_configs +6. gmoccapy/macros +7. gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples +8. axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos + +只有 53/53 rows 同时满足: +- row_runtime_transcript_ready=1; +- native_pass_ready=1; +- wasm_node_pass_ready=1; +- browser_pass_ready=1; +- execution_enabled=1; +- manual_promotion_lock reviewed/unlocked; + +才允许进入真正 PASS=82 / SKIP=77 的 baseline promotion。 +``` + +十、2026-06-19 批量 PASS 请求 gate 复核与 axis/remap toolchange 推进结果 + +本轮再次按“text28.txt”的 promotion gate 复核 53 个 Python-remap inventory rows 是否可以批量提升为 PASS。 + +结论: + +```text +仍不能直接批量 PASS。 + +原因: +- 53 rows 中只有 9 rows 已有 row_runtime_transcript_ready; +- 44 rows 仍是 pending_row_runtime_adapter_execution; +- native_pass_ready=0 53; +- wasm_node_pass_ready=0 53; +- browser_pass_ready=0 53; +- execution_enabled=0 53; +- manual_promotion_lock 仍然 active; +- Phase 3 / Phase 4 / Phase 5 / Phase 6 尚未完成。 +``` + +因此本轮没有把 `L4-PYTHON-REMAP` rows 改 PASS,没有改 baseline 数字。 + +本轮可安全推进的内容: + +```text +继续 Phase 2.5,扩展 axis/remap toolchange family 的 row-specific native lifecycle stdout transcript。 +``` + +代码修正: + +```text +wasm-port/tests/native/probe_python_remap_runtime.sh +- native runtime probe 增加 extend-builtins、manual-toolchange-with-tool-length-switch、rack-toolchange source prerequisites; +- 对 stdglue remap family 增加 prolog / epilog callable lookup; +- 对 change_epilog generator 生命周期证明 first yield 为 LinuxCNC INTERP_EXECUTE_FINISH; +- 对 extend-builtins 证明 11 个 remap NGC assets staged,并保持 NGC-only subpaths 非 standalone; +- 对 manual/rack toolchange 证明 M6 remap NGC asset staged; +- 保持 HAL/UI/native/node/browser/manual promotion blockers 不解除。 + +wasm-port/tools/build_native_probes.sh +- native probe cache fingerprint 加入 extend-builtins、manual-toolchange-with-tool-length-switch、rack-toolchange source tree。 +``` + +artifact 当前状态: + +```text +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv + +rows=53 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready 9 +proof_status=pending_row_runtime_adapter_execution 44 +native_pass_ready=0 53 +wasm_node_pass_ready=0 53 +browser_pass_ready=0 53 +execution_enabled=0 53 +``` + +本轮新增 ready rows: + +```text +axis/remap/extend-builtins/nc_files/examples.ngc +axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc +axis/remap/rack-toolchange/nc_files/tcdemo.ngc +``` + +新增 ready rows 状态: + +```text +python_modules_staged=1 +prolog_functions_bound=1 +epilog_functions_bound=1 +generator_lifecycle_ready=1 +ngc_remap_assets_staged=1 +row_runtime_transcript_ready=1 +proof_status=native_lifecycle_stdout_row_runtime_transcript_ready +``` + +inventory baseline 保持不变: + +```text +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_skip_L4_PYTHON_REMAP=53 +sim_configs_wasm_node_inventory_unexpected_fail=0 +``` + +本轮已通过验证: + +```bash +git diff --check +ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh +wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +``` + +验证中的已知非失败 stderr: + +```text +/work/sim-inventory/axis/db_demo/base.inc: error: Cannot open ini-file (errno=44 (No such file or directory)) +``` + +下一轮建议: + +```text +继续 Phase 2.5,不进入 baseline promotion。 + +优先扩展剩余 row runtime transcript: +1. axis/vismach/VMC_toolchange +2. gmoccapy/lathe_configs +3. gmoccapy/macros +4. gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples +5. axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos + +只有 53/53 rows 同时满足: +- row_runtime_transcript_ready=1; +- native_pass_ready=1; +- wasm_node_pass_ready=1; +- browser_pass_ready=1; +- execution_enabled=1; +- manual_promotion_lock reviewed/unlocked; + +才允许进入真正 PASS=82 / SKIP=77 的 baseline promotion。 +``` diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 919246c..fffed01 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -200,6 +200,8 @@ wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-probe-gat 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-family-summary.tsv +wasm-port/build/wasm/sim-configs-inventory/python-remap-bulk-promotion-plan.tsv +wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv wasm-port/build/wasm/sim-configs-inventory/boundary-phase-completion-summary.tsv wasm-port/build/wasm/sim-configs-inventory/native-proof-alignment-summary.tsv wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-summary.tsv @@ -311,22 +313,21 @@ command, the current probe status, and keeps `execution_enabled=0` and `promotion_allowed=0`. It does not run probes or relax promotion locks. `runtime-boundary-host-requirement-summary.tsv` normalizes those preflight requirements into one row per host command or source/module prerequisite, so -missing `halcmd`, `halrun`, `linuxcnc`, and `milltask` can be audited by the -blocked families they affect. `runtime-boundary-host-unblock-plan.tsv` -filters that table to unavailable requirements and records the affected -opt-in commands and remaining missing requirements; it is a planning artifact -only. +available and missing requirements can be audited by the blocked families they +affect. `runtime-boundary-host-unblock-plan.tsv` filters that table to +unavailable requirements and records the affected opt-in commands and +remaining missing requirements; it is a planning artifact only. `runtime-boundary-family-host-readiness.tsv` reduces the same host evidence to one row per blocked runtime family, and `runtime-boundary-host-readiness-rollup.tsv` gives the aggregate decision. On -this host the rollup remains `host_blocked_for_all_opt_in_native_probes` with -zero ready opt-in commands. These rows are non-executing and non-promoting. +this host the rollup is `host_ready_for_all_opt_in_native_probes` with three +ready opt-in commands. These rows are non-executing and non-promoting. `runtime-boundary-opt-in-probe-dispatch-plan.tsv` turns each family readiness row into a per-probe dispatch action. `runtime-boundary-opt-in-probe-dispatch-rollup.tsv` -then gives the aggregate dispatch switch; currently every blocked runtime -probe is skipped for missing host requirements. Dispatch permission only means -the guarded native probe may be manually run on a ready host, not that the row -is promoted. +then gives the aggregate dispatch switch; currently blocked runtime probes are +ready for manual opt-in native execution. Dispatch permission only means the +guarded native probe may be manually run on a ready host, not that the row is +promoted. `runtime-boundary-opt-in-probe-skip-evidence-contract.tsv` records why each non-dispatched probe is skipped and whether native pass evidence is required yet. `runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv` gives the @@ -403,10 +404,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 55-entry baseline and the native generated +to remain the exact duplicate-free 57-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 `55` and `8`, respectively. It also +documentation-coverage counts to remain `57` 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 @@ -530,6 +531,18 @@ Python runtime lifecycle fixture, `axis/remap/stop-lookahead/nc_files`, because it exercises Python runtime phases and configured modules without Python callable or NGC-only subpath complexity. It is still a fixture plan only: `proof_status=pending`, `execution_enabled=0`, and `promotion_allowed=0`. +`python-remap-bulk-promotion-plan.tsv` is the row-level Phase 1 promotion +plan for all 53 `L4-PYTHON-REMAP` inventory rows. It records pending module, +toplevel/path, callable, NGC remap asset, interpreter/canonical, HAL/UI/HALUI, +native, WASM/Node, browser, and manual-lock blockers while keeping +`execution_enabled=0` and `promotion_allowed=0`; it is not a PASS baseline +change. +`python-remap-row-runtime-proof.tsv` is the Phase 2 row-runtime proof table. +It records that the row-aware Python-remap runtime port API and per-row proof +target plan exist for all 53 rows, while module staging, TOPLEVEL execution, +PATH_PREPEND application, callable binding, NGC asset staging, +interpreter/canonical bridges, and native/WASM/browser row pass proof remain +pending with `execution_enabled=0`. When explicitly enabled with `ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1`, `probe_python_remap_runtime.sh` follows the fixture `demo.ini` Python path and toplevel declarations, imports the vendored modules, verifies `queuebuster` diff --git a/wasm-port/docs/full-process-boundary-design.md b/wasm-port/docs/full-process-boundary-design.md index 83d6925..68041e3 100644 --- a/wasm-port/docs/full-process-boundary-design.md +++ b/wasm-port/docs/full-process-boundary-design.md @@ -424,6 +424,17 @@ modules but avoids Python callable and NGC-only subpath complexity. The fixture plan is still non-executing: it records the proof target only and keeps `proof_status=pending`, `execution_enabled=0`, and `promotion_allowed=0`. +`python-remap-bulk-promotion-plan.tsv` is the Phase 1 row-level plan for +turning the 53 Python-remap inventory rows into future PASS candidates. It +does not unlock the rows; it records the pending runtime, module/path/toplevel, +callable, NGC remap asset, NGC-only subpath, interpreter/canonical, +HAL/UI/HALUI, native/WASM/browser, and manual-lock blockers while keeping +`execution_enabled=0` and `promotion_allowed=0`. +`python-remap-row-runtime-proof.tsv` is the Phase 2 row-runtime proof table. +It confirms that a row-aware Python-remap runtime port API and per-row proof +target plan exist for all 53 rows, then keeps the actual row runtime transcript +and native/WASM/browser pass proof pending. It is not an inventory promotion +gate and keeps `execution_enabled=0`. `tests/native/probe_python_remap_runtime.sh` is wired into `build_native_probes.sh` as `linuxcnc_python_remap_runtime_probe`. It records the stop-lookahead fixture identity, configured Python modules, required @@ -510,13 +521,13 @@ cat wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-native-evidence- cat wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv ``` -The current host is not ready for any blocked runtime opt-in probe. The -generated rollup reports missing `halcmd`, `halrun`, `linuxcnc`, and -`milltask`, with `host_blocked_for_all_opt_in_native_probes`, -`execution_enabled=0`, and `promotion_allowed=0`. +The current host is ready for manual blocked-runtime opt-in probes. The +generated rollup reports no missing host requirements, with +`host_ready_for_all_opt_in_native_probes`, `execution_enabled=0`, and +`promotion_allowed=0`. -The opt-in commands remain forbidden while their dispatch rows have -`dispatch_allowed=0`: +The opt-in commands remain manual proof gates; dispatch rows may have +`dispatch_allowed=1`, but that does not enable execution or promotion: ```bash ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh diff --git a/wasm-port/docs/host-runtime-boundary-handoff.md b/wasm-port/docs/host-runtime-boundary-handoff.md index 6f421dd..beff87d 100644 --- a/wasm-port/docs/host-runtime-boundary-handoff.md +++ b/wasm-port/docs/host-runtime-boundary-handoff.md @@ -12,14 +12,19 @@ Detailed references: - `docs/sim-configs-coverage-handoff.md` - `tests/wasm/node/verify_sim_configs_inventory_wasm.mjs` -## Current blocked runtime families +## Current runtime boundary families -The current hard blocked families are: +The current runtime-boundary proof families are: - `L4-USER-M-PROCESS` - `L4-TOOL-DB` - `L4-PYTHON-REMAP` +The current Node inventory skip/block summary still has `L4-PYTHON-REMAP=53` +and `L4-USER-M-PROCESS=1`. `L4-TOOL-DB` is no longer a skipped inventory row, +but it remains represented in the runtime-boundary proof ledger with +`promotion_allowed=0`. + Current host readiness is summarized by: - `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv` @@ -32,13 +37,13 @@ Current host readiness is summarized by: Current rollup state: ```text -host_readiness_status=host_blocked_for_all_opt_in_native_probes +host_readiness_status=host_ready_for_all_opt_in_native_probes family_count=3 -host_ready_family_count=0 -host_blocked_family_count=3 -blocked_families=L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS -missing_host_requirements=halcmd,halrun,linuxcnc,milltask -ready_opt_in_command_count=0 +host_ready_family_count=3 +host_blocked_family_count=0 +blocked_families=- +missing_host_requirements=- +ready_opt_in_command_count=3 execution_enabled=0 promotion_allowed=0 ``` @@ -48,10 +53,9 @@ promotion_allowed=0 Each blocked family currently has `promotion_ready=0`, `execution_enabled=0`, and `promotion_allowed=0`. -The current blocker keys are: +For `L4-USER-M-PROCESS` and `L4-TOOL-DB`, the current blocker keys are: ```text -host_runtime_requirements_missing native_runtime_probe_not_passed native_pass_evidence_not_ready node_inventory_gate_not_complete @@ -60,25 +64,46 @@ promotion_lock_active manual_lock_update_required ``` -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 the first Python remap fixture, `axis/remap/stop-lookahead/nc_files`, the +native lifecycle evidence has been accepted and the Node/browser lifecycle +gates are complete: + +```text +current_probe_status=runtime_lifecycle_probe_passed +native_evidence_status=native_pass_evidence_observed +node_inventory_gate_complete=1 +browser_smoke_gate_complete=1 +node_gate_status=node_inventory_promotion_gate_complete +browser_gate_status=browser_smoke_promotion_gate_complete +blocking_reason=promotion_lock_active_manual_review_required +next_unblock_action=manual_lock_review_required +``` + +The Python row still has `promotion_ready=0`, `execution_enabled=0`, and +`promotion_allowed=0`; the remaining blockers are `promotion_lock_active` and +`manual_lock_update_required`. This proof-chain state does not promote any of +the 53 `L4-PYTHON-REMAP` rows. + +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. ## Opt-in probe dispatch The current dispatch rollup is: ```text -dispatch_status=dispatch_blocked_for_all_opt_in_native_probes +dispatch_status=dispatch_allowed_for_all_opt_in_native_probes probe_count=3 -dispatch_allowed_count=0 -dispatch_blocked_count=3 -dispatch_actions=skip_missing_host_requirements +dispatch_allowed_count=3 +dispatch_blocked_count=0 +dispatch_actions=ready_for_manual_opt_in_native_probe execution_enabled=0 promotion_allowed=0 ``` -These commands remain blocked on this host: +These commands are opt-in only and must never run implicitly: ```bash ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh @@ -86,23 +111,37 @@ ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh ``` -Do not run them unless the generated preflight, family readiness, dispatch -plan, and dispatch rollup all permit the specific family. +Only run them deliberately when updating native proof evidence. A passing probe +does not enable execution or promotion by itself. ## Native evidence acceptance +For rows still blocked before native evidence, `runtime-boundary-native-evidence-acceptance-gate.tsv` currently records: -- `current_probe_status=skipped_missing_host_runtime` +- `current_probe_status=ready_disabled_by_default` - `native_evidence_status=pending_until_native_pass` -- `skip_evidence_status=skip_valid_until_host_requirements_available` +- `skip_evidence_status=skip_contract_not_applicable_dispatch_allowed` - `node_gate_status=blocked_until_native_pass_evidence` - `browser_gate_status=blocked_until_node_gate_complete` -- `native_evidence_gate=blocked_until_host_requirements_available` +- `native_evidence_gate=blocked_until_native_pass_evidence` - `evidence_acceptance_allowed=0` -A skipped opt-in probe is not a native pass. It only proves that this host is -correctly blocked until the required LinuxCNC runtime commands are available. +A disabled opt-in probe is not a native pass. It only proves that this host is +not executing the family-specific runtime probe by default. + +For `L4-PYTHON-REMAP`, the same artifact records accepted native lifecycle +evidence for the stop-lookahead fixture: + +- `current_probe_status=runtime_lifecycle_probe_passed` +- `native_evidence_status=native_pass_evidence_observed` +- `skip_evidence_status=native_pass_evidence_observed_skip_contract_closed` +- `node_gate_status=node_inventory_promotion_gate_complete` +- `browser_gate_status=browser_smoke_promotion_gate_complete` +- `native_evidence_gate=native_pass_evidence_accepted` +- `evidence_acceptance_allowed=1` + +This is evidence acceptance only. It does not change `promotion_allowed=0`. ## Release gate @@ -143,4 +182,5 @@ No blocked family may be promoted until all of these are true: deliberately. Until then, `L4-USER-M-PROCESS`, `L4-TOOL-DB`, and `L4-PYTHON-REMAP` remain -blocked, inventory-only runtime families. +locked in the runtime-boundary proof ledger with `promotion_allowed=0`; only +the inventory rows explicitly eligible for Node/WASM execution may run. diff --git a/wasm-port/docs/sim-config-coverage-promotion-analysis.md b/wasm-port/docs/sim-config-coverage-promotion-analysis.md index ce77971..6478949 100644 --- a/wasm-port/docs/sim-config-coverage-promotion-analysis.md +++ b/wasm-port/docs/sim-config-coverage-promotion-analysis.md @@ -16,7 +16,6 @@ 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 -sim_configs_wasm_node_inventory_skip_L4_TOOL_DB=1 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 @@ -24,7 +23,7 @@ sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1 The current generated inventory has no skipped main-program row whose hard block is already empty. That matters: virtual HAL maturity should not be used -as a blanket reason to reduce `skipped=131`. The next useful promotions are +as a blanket reason to reduce the skip baseline. The next useful promotions are case promotions from Node inventory or representative coverage into browser, diagnostics, and release evidence. @@ -399,9 +398,9 @@ carries an inventory `baselineSummary`. The macro/load report requires that summary to match the current generated inventory baseline: ```text -executed=28 -passed=28 -skipped=131 +executed=29 +passed=29 +skipped=130 unexpectedFail=0 ``` diff --git a/wasm-port/docs/sim-configs-coverage-handoff.md b/wasm-port/docs/sim-configs-coverage-handoff.md index 6383f35..4653fae 100644 --- a/wasm-port/docs/sim-configs-coverage-handoff.md +++ b/wasm-port/docs/sim-configs-coverage-handoff.md @@ -29,7 +29,6 @@ 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 -sim_configs_wasm_node_inventory_skip_L4_TOOL_DB=1 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 @@ -51,11 +50,11 @@ The generated artifact baseline is: - `build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv` The current promotion-candidate artifact is evidence accounting only. It has -28 rows total: 8 `evidence-ready` rows with virtual HAL source-derived -browser/release evidence, and 20 `inventory-ready` skipped main-program rows. +27 rows total: 8 `evidence-ready` rows with virtual HAL source-derived +browser/release evidence, and 19 `inventory-ready` skipped main-program rows. Every row keeps `promotion_allowed=0`, so the inventory baseline remains `29/29/130/0`. The companion `evidence-expansion-candidates.tsv` artifact has -13 rows queued for future browser diagnostics binding, also with +14 rows queued for future browser diagnostics binding, also with `promotion_allowed=0`. SDK and workflow-overview release URL summaries expose the current @@ -76,7 +75,6 @@ underscore-safe metric names. Treat these as the same baseline: | --- | --- | ---: | --- | | `ASSET-ONLY` | `ASSET_ONLY` | 65 | Subroutine/support asset; not a standalone browser main-program target. | | `L4-PYTHON-REMAP` | `L4_PYTHON_REMAP` | 53 | Keep inventory-only until a LinuxCNC-owned Python-remap runtime proof exists. | -| `L4-TOOL-DB` | `L4_TOOL_DB` | 1 | Keep blocked until a LinuxCNC-owned tool database process/protocol proof exists. | | `L4-USER-M-PROCESS` | `L4_USER_M_PROCESS` | 1 | Keep blocked until a LinuxCNC-owned external user-M process state proof exists. | | `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. | @@ -99,6 +97,9 @@ Do not promote these runtime families from JavaScript glue alone: - `L4-TOOL-DB` - `L4-USER-M-PROCESS` +`L4-TOOL-DB` is no longer counted in the current skip summary, but it remains +in the runtime-boundary proof ledger with `promotion_allowed=0`. + ## Release gate Use this gate when changing sim-config coverage, promotion state, or inventory diff --git a/wasm-port/docs/sim-configs-coverage-matrix.md b/wasm-port/docs/sim-configs-coverage-matrix.md index 1380b18..7c373cf 100644 --- a/wasm-port/docs/sim-configs-coverage-matrix.md +++ b/wasm-port/docs/sim-configs-coverage-matrix.md @@ -52,6 +52,10 @@ Current baseline: `wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-state-plan.tsv` - Node Python-remap native runtime fixture-plan artifact: `wasm-port/build/wasm/sim-configs-inventory/python-remap-native-runtime-fixture-plan.tsv` +- Node Python-remap bulk promotion plan artifact: + `wasm-port/build/wasm/sim-configs-inventory/python-remap-bulk-promotion-plan.tsv` +- Node Python-remap row runtime proof artifact: + `wasm-port/build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv` - Node runtime-boundary contract summary artifact: `wasm-port/build/wasm/sim-configs-inventory/runtime-boundary-contract-summary.tsv` - Node boundary-phase completion artifact: @@ -235,6 +239,21 @@ Legend: `ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1` proves the `demo.ini` path/toplevel import, `queuebuster` callable lookup, generator return, and first `INTERP_EXECUTE_FINISH` yield. +- `python-remap-bulk-promotion-plan.tsv`: one row per current + `L4-PYTHON-REMAP` inventory row. It records the Phase 1 blockers required + before any bulk promotion, including row runtime proof, Python module and + callable binding, NGC remap asset staging, NGC-only standalone guards, + interpreter/canonical bridges, HAL/UI/HALUI boundaries, native/WASM/browser + row pass proof, and the manual lock. It keeps every row + `execution_enabled=0` and `promotion_allowed=0`, so the current + PASS/SKIP baseline is unchanged. +- `python-remap-row-runtime-proof.tsv`: one row per current + `L4-PYTHON-REMAP` inventory row. It records the Phase 2 row-aware runtime + proof substrate: the SDK row runtime port API and row proof plan are ready, + while module staging, TOPLEVEL execution, PATH_PREPEND application, callable + binding, generator lifecycle, NGC remap staging, NGC-only standalone guards, + interpreter/canonical bridges, and native/WASM/browser row pass proof remain + pending. Every row keeps `execution_enabled=0`. - `runtime-boundary-contract-summary.tsv`: one row per blocked runtime boundary class. It unifies the user-M transition contract, tool DB transaction contract, and Python runtime contract with their native-alignment artifact, @@ -349,7 +368,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 55-entry + inventory artifact list must remain the exact duplicate-free 57-entry baseline, and every token plus the fixed-count, duplicate-free, and fetchability guards must also be listed by browser smoke. - The same inventory check covers native generated TSV artifacts under @@ -362,7 +381,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 `55` WASM inventory + documentation-coverage completion counts to the current `57` 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 @@ -409,23 +428,25 @@ Legend: affected opt-in environments, and exact probe command traceability while remaining non-executing and non-promoting. - `runtime-boundary-host-readiness-rollup.tsv`: the aggregate host-readiness - decision for all blocked runtime families. On this host it records zero - ready families, missing `halcmd`, `halrun`, `linuxcnc`, and `milltask`, and - `host_blocked_for_all_opt_in_native_probes`. + decision for all runtime-boundary proof families. On this host it records + three ready opt-in native probes, no missing host requirements, and + `host_ready_for_all_opt_in_native_probes`. - `runtime-boundary-opt-in-probe-dispatch-plan.tsv`: one dispatch row per guarded native runtime probe. It turns host readiness into a dispatch action and remains non-promoting; a dispatchable row only means the manual opt-in native probe may be attempted on a ready host. - `runtime-boundary-opt-in-probe-dispatch-rollup.tsv`: the aggregate dispatch - switch for blocked runtime probes. Current rows are blocked for missing host - requirements, so no opt-in native probe is dispatchable on this host. + switch for runtime-boundary probes. Current rows are dispatchable for manual + opt-in native probes, while `execution_enabled=0` and `promotion_allowed=0` + remain unchanged. - `runtime-boundary-opt-in-probe-skip-evidence-contract.tsv`: the per-probe skip/evidence contract. It records the skip reason, missing host requirements, current probe status, whether native pass evidence is required now, and keeps every skipped probe non-executing and non-promoting. - `runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv`: the aggregate - evidence decision for skipped opt-in probes. While all probes are skipped for - missing host requirements, no native pass evidence is accepted. + evidence decision for opt-in probes. Dispatchable probes do not require skip + evidence; accepted Python remap lifecycle evidence still does not allow + promotion. ## Summary diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 0451860..c3c6593 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -127,6 +127,10 @@ import { createProjectReleaseReadinessReport, createProjectReleaseReadinessSummaryViewModel, createLinuxCncToolDbProcessPort, + createLinuxCncPythonRemapRowRuntimePort, + createLinuxCncPythonRemapRuntimePort, + createPythonRemapRowRuntimePlan, + validatePythonRemapRowRuntimeTranscript, createToolDbProcessDiagnostics, createToolDbTransactionPlan, createSessionSnapshot, @@ -595,6 +599,13 @@ private module paths: 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. +- `createLinuxCncPythonRemapRowRuntimePort()`, + `createPythonRemapRowRuntimePlan()`, and + `validatePythonRemapRowRuntimeTranscript()` for the row-aware Python remap + runtime proof substrate. The row port accepts an inventory path, INI path, + Python modules, remap/prolog/epilog callables, NGC remap assets, and + NGC-only subpaths, then records the runtime-adapter proof transcript while + keeping 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 diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index 8f10b1f..ebc1c39 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -104,12 +104,16 @@ export { } from "./tool-db-browser-worker-adapter.js"; export { PYTHON_REMAP_LIFECYCLE_PLAN, + PYTHON_REMAP_ROW_RUNTIME_PROOF_PHASES, PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE, + createLinuxCncPythonRemapRowRuntimePort, createLinuxCncPythonRemapRuntimePort, createPythonRemapLifecyclePlan, + createPythonRemapRowRuntimePlan, createPythonRemapRuntimeDiagnostics, validatePythonRemapLifecycleTranscript, + validatePythonRemapRowRuntimeTranscript, } from "./python-remap-runtime-port.js"; export { createLinuxCncPythonRemapBrowserWorkerAdapter, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index 2fba480..5490de5 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -322,6 +322,53 @@ function createToolDbProcessProofSummary(toolDbProcessProof = {}) { }; } +function createPythonRemapRuntimeProofSummary(pythonRemapRuntimeProof = {}) { + const proof = objectOrEmpty(pythonRemapRuntimeProof); + const nativeLifecycleReady = proof.nativeLifecycleReady === true; + const wasmLifecycleReady = proof.wasmLifecycleReady !== false; + const browserLifecycleReady = proof.browserLifecycleReady !== false; + const promotionAllowed = proof.promotionAllowed === true; + const executionEnabled = proof.executionEnabled === true; + const ready = nativeLifecycleReady && + wasmLifecycleReady && + browserLifecycleReady && + promotionAllowed === false && + executionEnabled === false; + + return { + apiName: "python-remap-runtime-proof-summary", + summaryVersion: 1, + phase: ready ? "ready" : "blocked", + ready, + boundaryClass: "L4-PYTHON-REMAP", + fixtureFamily: proof.fixtureFamily ?? "axis/remap/stop-lookahead/nc_files", + fixtureId: proof.fixtureId ?? "stop_lookahead_python_runtime_lifecycle", + iniPath: proof.iniPath ?? "axis/remap/stop-lookahead/demo.ini", + pythonPathPrepend: proof.pythonPathPrepend ?? "python", + topLevelPath: proof.topLevelPath ?? "python/toplevel.py", + callableName: proof.callableName ?? "queuebuster", + runtimeMode: proof.runtimeMode ?? "browser-python-wasm-worker", + nativeProbeStatus: proof.nativeProbeStatus ?? (nativeLifecycleReady ? "runtime_lifecycle_probe_passed" : "not_provided"), + nativeProofArtifactPath: proof.nativeProofArtifactPath ?? "wasm-port/build/native/native-runtime-probe-summary.tsv", + nativeProofStdoutLog: proof.nativeProofStdoutLog ?? null, + nativeLifecycleReady, + wasmLifecycleReady, + browserLifecycleReady, + interpreterStateBindingReady: proof.interpreterStateBindingReady !== false, + generatorLifecycleReady: proof.generatorLifecycleReady !== false, + promotionAllowed, + executionEnabled, + bulkPromotionAllowed: proof.bulkPromotionAllowed === true, + manualLockUpdateRequired: proof.manualLockUpdateRequired !== false, + requiredCommands: [ + "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh", + "wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh", + "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh", + "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_python_remap_runtime_browser.sh", + ], + }; +} + const BLOCKED_RUNTIME_FAMILIES = [ "L4-USER-M-PROCESS", "L4-PYTHON-REMAP", @@ -2069,6 +2116,7 @@ export function createProjectReleaseReadinessReport({ virtualHalSimConfigMacroLoadFixtures = null, virtualHalMotionControllerMatrix = null, toolDbProcessProof = {}, + pythonRemapRuntimeProof = {}, blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES, promotedRuntimeFamilies = [], axisScreenshotArtifacts = [], @@ -2130,6 +2178,8 @@ export function createProjectReleaseReadinessReport({ const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready; const toolDbProcessProofSummary = createToolDbProcessProofSummary(toolDbProcessProof); const toolDbProcessProofReady = toolDbProcessProofSummary.ready; + const pythonRemapRuntimeProofSummary = createPythonRemapRuntimeProofSummary(pythonRemapRuntimeProof); + const pythonRemapRuntimeProofReady = pythonRemapRuntimeProofSummary.ready; const ready = releaseGatePassed && inventoryReady && virtualHalSimConfigSourceCoverageReady && @@ -2137,6 +2187,7 @@ export function createProjectReleaseReadinessReport({ virtualHalSimConfigMacroLoadFixturesReady && virtualHalMotionControllerMatrixReady && toolDbProcessProofReady && + pythonRemapRuntimeProofReady && blockedRuntimeReady && axisScreenshotArtifactsReady; const missing = [ @@ -2147,6 +2198,7 @@ export function createProjectReleaseReadinessReport({ ...(virtualHalSimConfigMacroLoadFixturesReady ? [] : ["virtual-hal-sim-config-macro-load-fixtures"]), ...(virtualHalMotionControllerMatrixReady ? [] : ["virtual-hal-motion-controller-matrix"]), ...(toolDbProcessProofReady ? [] : ["tool-db-process-proof"]), + ...(pythonRemapRuntimeProofReady ? [] : ["python-remap-runtime-proof"]), ...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]), ...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]), ]; @@ -2177,6 +2229,8 @@ export function createProjectReleaseReadinessReport({ virtualHalMotionControllerMatrixReady, toolDbProcessProofSummary, toolDbProcessProofReady, + pythonRemapRuntimeProofSummary, + pythonRemapRuntimeProofReady, blockedRuntimeFamilies: blockedFamilies, promotedRuntimeFamilies: promotedFamilies, promotedBlockedFamilies, @@ -2289,6 +2343,16 @@ export function createProjectReleaseReadinessReport({ label: "Tool DB process proof detail", value: `wasm=${toolDbProcessProofSummary.wasmProtocolReady ? "ready" : "missing"} browser=${toolDbProcessProofSummary.browserProtocolReady ? "ready" : "missing"} opfs=${toolDbProcessProofSummary.opfsPersistenceReady ? "ready" : "missing"} promotion_allowed=${toolDbProcessProofSummary.promotionAllowed ? "1" : "0"}`, }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: pythonRemapRuntimeProofReady ? "ready" : "locked", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: `native=${pythonRemapRuntimeProofSummary.nativeLifecycleReady ? "ready" : "missing"} wasm=${pythonRemapRuntimeProofSummary.wasmLifecycleReady ? "ready" : "missing"} browser=${pythonRemapRuntimeProofSummary.browserLifecycleReady ? "ready" : "missing"} promotion_allowed=${pythonRemapRuntimeProofSummary.promotionAllowed ? "1" : "0"}`, + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -2328,6 +2392,8 @@ export function createProjectReleaseReadinessSummaryViewModel( const virtualHalMotionControllerMatrixReady = report?.virtualHalMotionControllerMatrixReady === true; const toolDbProcessProofSummary = objectOrEmpty(report?.toolDbProcessProofSummary); const toolDbProcessProofReady = report?.toolDbProcessProofReady === true; + const pythonRemapRuntimeProofSummary = objectOrEmpty(report?.pythonRemapRuntimeProofSummary); + const pythonRemapRuntimeProofReady = report?.pythonRemapRuntimeProofReady === true; const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary); const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies); const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies); @@ -2430,6 +2496,18 @@ export function createProjectReleaseReadinessSummaryViewModel( ? `wasm=${toolDbProcessProofSummary.wasmProtocolReady ? "ready" : "missing"} browser=${toolDbProcessProofSummary.browserProtocolReady ? "ready" : "missing"} opfs=${toolDbProcessProofSummary.opfsPersistenceReady ? "ready" : "missing"} promotion_allowed=${toolDbProcessProofSummary.promotionAllowed ? "1" : "0"}` : "missing", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: pythonRemapRuntimeProofReady ? "ready" : "locked", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: pythonRemapRuntimeProofSummary.apiName + ? `native=${pythonRemapRuntimeProofSummary.nativeLifecycleReady ? "ready" : "missing"} wasm=${pythonRemapRuntimeProofSummary.wasmLifecycleReady ? "ready" : "missing"} browser=${pythonRemapRuntimeProofSummary.browserLifecycleReady ? "ready" : "missing"} promotion_allowed=${pythonRemapRuntimeProofSummary.promotionAllowed ? "1" : "0"}` + : "missing", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -2630,6 +2708,18 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel( ? `wasm=${validation.toolDbProcessProofSummary.wasmProtocolReady ? "ready" : "missing"} browser=${validation.toolDbProcessProofSummary.browserProtocolReady ? "ready" : "missing"} opfs=${validation.toolDbProcessProofSummary.opfsPersistenceReady ? "ready" : "missing"} promotion_allowed=${validation.toolDbProcessProofSummary.promotionAllowed ? "1" : "0"}` : "missing", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: validation?.pythonRemapRuntimeProofSummary?.ready === true ? "ready" : "locked", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: validation?.pythonRemapRuntimeProofSummary?.apiName + ? `native=${validation.pythonRemapRuntimeProofSummary.nativeLifecycleReady ? "ready" : "missing"} wasm=${validation.pythonRemapRuntimeProofSummary.wasmLifecycleReady ? "ready" : "missing"} browser=${validation.pythonRemapRuntimeProofSummary.browserLifecycleReady ? "ready" : "missing"} promotion_allowed=${validation.pythonRemapRuntimeProofSummary.promotionAllowed ? "1" : "0"}` + : "missing", + }, { id: "missing", label: "Missing artifact evidence", @@ -3043,6 +3133,8 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel workflow?.validation?.promotionCandidateArtifactSummaryReady === true; const toolDbProcessProofSummary = objectOrEmpty(workflow?.validation?.toolDbProcessProofSummary); const toolDbProcessProofReady = workflow?.validation?.toolDbProcessProofReady === true; + const pythonRemapRuntimeProofSummary = objectOrEmpty(workflow?.validation?.pythonRemapRuntimeProofSummary); + const pythonRemapRuntimeProofReady = workflow?.validation?.pythonRemapRuntimeProofReady === true; const promotionArtifactRows = objectOrEmpty(promotionCandidateArtifactSummary.artifactRows); const hardBlockRuntimeFamilyRows = arrayOrEmpty(promotionCandidateArtifactSummary.hardBlockRuntimeFamilyRows); const hardBlockRuntimeFamilySummaryRows = arrayOrEmpty( @@ -3321,6 +3413,18 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel ? `wasm=${toolDbProcessProofSummary.wasmProtocolReady ? "ready" : "missing"} browser=${toolDbProcessProofSummary.browserProtocolReady ? "ready" : "missing"} opfs=${toolDbProcessProofSummary.opfsPersistenceReady ? "ready" : "missing"} promotion_allowed=${toolDbProcessProofSummary.promotionAllowed ? "1" : "0"}` : "not provided", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: pythonRemapRuntimeProofSummary.ready === true ? "ready" : "locked", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: pythonRemapRuntimeProofSummary.apiName + ? `native=${pythonRemapRuntimeProofSummary.nativeLifecycleReady ? "ready" : "missing"} wasm=${pythonRemapRuntimeProofSummary.wasmLifecycleReady ? "ready" : "missing"} browser=${pythonRemapRuntimeProofSummary.browserLifecycleReady ? "ready" : "missing"} promotion_allowed=${pythonRemapRuntimeProofSummary.promotionAllowed ? "1" : "0"}` + : "not provided", + }, ...arrayOrEmpty(workflow?.validation?.virtualHalPromotionFamilyRows).map((row) => ({ id: `virtual-hal-promotion-family-${row.id}`, label: `Promotion family ${row.label}`, @@ -3523,6 +3627,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { const virtualHalSimConfigMacroLoadFixtures = objectOrEmpty(artifactObject.virtualHalSimConfigMacroLoadFixtures); const virtualHalMotionControllerMatrix = objectOrEmpty(artifactObject.virtualHalMotionControllerMatrix); const toolDbProcessProofSummary = objectOrEmpty(artifactObject.toolDbProcessProofSummary); + const pythonRemapRuntimeProofSummary = objectOrEmpty(artifactObject.pythonRemapRuntimeProofSummary); const gateManifest = objectOrEmpty(artifactObject.gateManifest); const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest); const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel); @@ -3649,6 +3754,31 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { arrayOrEmpty(toolDbProcessProofSummary.requiredCommands).join("\n") === expectedToolDbProcessProofSummary.requiredCommands.join("\n") && artifactObject.toolDbProcessProofReady === true; + const expectedPythonRemapRuntimeProofSummary = + createPythonRemapRuntimeProofSummary(pythonRemapRuntimeProofSummary); + const pythonRemapRuntimeProofReady = + pythonRemapRuntimeProofSummary.apiName === "python-remap-runtime-proof-summary" && + pythonRemapRuntimeProofSummary.summaryVersion === 1 && + pythonRemapRuntimeProofSummary.boundaryClass === "L4-PYTHON-REMAP" && + pythonRemapRuntimeProofSummary.fixtureFamily === "axis/remap/stop-lookahead/nc_files" && + pythonRemapRuntimeProofSummary.fixtureId === "stop_lookahead_python_runtime_lifecycle" && + pythonRemapRuntimeProofSummary.iniPath === "axis/remap/stop-lookahead/demo.ini" && + pythonRemapRuntimeProofSummary.pythonPathPrepend === "python" && + pythonRemapRuntimeProofSummary.topLevelPath === "python/toplevel.py" && + pythonRemapRuntimeProofSummary.callableName === "queuebuster" && + pythonRemapRuntimeProofSummary.nativeProbeStatus === ( + pythonRemapRuntimeProofSummary.nativeLifecycleReady + ? "runtime_lifecycle_probe_passed" + : "not_provided" + ) && + pythonRemapRuntimeProofSummary.nativeProofArtifactPath === "wasm-port/build/native/native-runtime-probe-summary.tsv" && + pythonRemapRuntimeProofSummary.promotionAllowed === false && + pythonRemapRuntimeProofSummary.executionEnabled === false && + pythonRemapRuntimeProofSummary.bulkPromotionAllowed === false && + pythonRemapRuntimeProofSummary.manualLockUpdateRequired === true && + arrayOrEmpty(pythonRemapRuntimeProofSummary.requiredCommands).join("\n") === + expectedPythonRemapRuntimeProofSummary.requiredCommands.join("\n") && + artifactObject.pythonRemapRuntimeProofReady === pythonRemapRuntimeProofSummary.ready; const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts); const blockedRuntimeFamilies = arrayOrEmpty(artifactObject.blockedRuntimeFamilies); const promotedBlockedFamilies = arrayOrEmpty(artifactObject.promotedBlockedFamilies); @@ -3741,6 +3871,8 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { virtualHalMotionControllerMatrixReady, toolDbProcessProofSummary, toolDbProcessProofReady, + pythonRemapRuntimeProofSummary, + pythonRemapRuntimeProofReady, axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady, axisScreenshotArtifactCount: axisScreenshotArtifacts.length, blockedRuntimeFamilies, @@ -3891,6 +4023,18 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { ? `wasm=${toolDbProcessProofSummary.wasmProtocolReady ? "ready" : "missing"} browser=${toolDbProcessProofSummary.browserProtocolReady ? "ready" : "missing"} opfs=${toolDbProcessProofSummary.opfsPersistenceReady ? "ready" : "missing"} promotion_allowed=${toolDbProcessProofSummary.promotionAllowed ? "1" : "0"}` : "missing", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: pythonRemapRuntimeProofSummary.ready === true ? "ready" : "locked", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: pythonRemapRuntimeProofSummary.apiName + ? `native=${pythonRemapRuntimeProofSummary.nativeLifecycleReady ? "ready" : "missing"} wasm=${pythonRemapRuntimeProofSummary.wasmLifecycleReady ? "ready" : "missing"} browser=${pythonRemapRuntimeProofSummary.browserLifecycleReady ? "ready" : "missing"} promotion_allowed=${pythonRemapRuntimeProofSummary.promotionAllowed ? "1" : "0"}` + : "missing", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", diff --git a/wasm-port/runtime/sdk/src/python-remap-runtime-port.js b/wasm-port/runtime/sdk/src/python-remap-runtime-port.js index b908830..e181ed3 100644 --- a/wasm-port/runtime/sdk/src/python-remap-runtime-port.js +++ b/wasm-port/runtime/sdk/src/python-remap-runtime-port.js @@ -27,6 +27,22 @@ export const PYTHON_REMAP_LIFECYCLE_PLAN = [ { phase: "export_diagnostics", method: "exportDiagnostics" }, ]; +export const PYTHON_REMAP_ROW_RUNTIME_PROOF_PHASES = Object.freeze([ + "initialize_python", + "apply_ini_python_path", + "execute_toplevel", + "import_module", + "callable_lookup", + "callable_invoke", + "remap_phase_dispatch", + "generator_finish", + "stage_ngc_remap_asset", + "reject_ngc_only_standalone", + "export_interpreter_state", + "export_canonical_events", + "export_diagnostics", +]); + function requireString(value, label) { if (typeof value !== "string" || value.length === 0) { throw new Error(`${label} must be a non-empty string.`); @@ -41,6 +57,22 @@ function normalizeSourceFiles(sourceFiles = []) { return sourceFiles.map((sourceFile) => requireString(sourceFile, "source file")); } +function normalizeStringList(values = [], label = "value") { + if (values === "-" || values === null || values === undefined) { + return []; + } + if (!Array.isArray(values)) { + throw new Error(`${label} must be an array.`); + } + return values + .filter((value) => value !== "-") + .map((value) => requireString(value, label)); +} + +function callableNameFromSpec(callableSpec) { + return requireString(callableSpec, "callable").split("@")[0]; +} + function stableTextHash(text) { let hash = 0x811c9dc5; for (let index = 0; index < text.length; index += 1) { @@ -70,10 +102,95 @@ function normalizeLifecycleResult(phase, result = {}) { }; } +function defaultRowRuntimePlan({ + sourceFiles, + pythonRemapFunctions, + prologFunctions, + epilogFunctions, + ngcRemapFiles, + ngcOnlySubpaths, +}) { + const callables = [ + ...pythonRemapFunctions, + ...prologFunctions, + ...epilogFunctions, + ]; + return [ + { phase: "initialize_python", method: "initializePython" }, + { phase: "apply_ini_python_path", method: "applyIniPythonPath" }, + { phase: "execute_toplevel", method: "executeTopLevel" }, + ...sourceFiles.map((modulePath) => ({ + phase: "import_module", + method: "importModule", + modulePath, + })), + ...callables.flatMap((callableSpec) => { + const callableName = callableNameFromSpec(callableSpec); + return [ + { + phase: "callable_lookup", + method: "lookupCallable", + callableName, + callableSpec, + }, + { + phase: "callable_invoke", + method: "invokeGenerator", + callableName, + callableSpec, + }, + { + phase: "remap_phase_dispatch", + method: "observeFirstYield", + callableName, + callableSpec, + }, + { + phase: "generator_finish", + method: "finishGenerator", + callableName, + callableSpec, + }, + ]; + }), + ...ngcRemapFiles.map((ngcPath) => ({ + phase: "stage_ngc_remap_asset", + method: "stageNgcRemapAsset", + ngcPath, + })), + ...ngcOnlySubpaths.map((ngcPath) => ({ + phase: "reject_ngc_only_standalone", + method: "rejectNgcOnlyStandalone", + ngcPath, + })), + { phase: "export_interpreter_state", method: "exportInterpreterState" }, + { phase: "export_canonical_events", method: "exportCanonicalEvents" }, + { phase: "export_diagnostics", method: "exportDiagnostics" }, + ]; +} + export function createPythonRemapLifecyclePlan() { return clonePlan(PYTHON_REMAP_LIFECYCLE_PLAN); } +export function createPythonRemapRowRuntimePlan({ + sourceFiles = [], + pythonRemapFunctions = [], + prologFunctions = [], + epilogFunctions = [], + ngcRemapFiles = [], + ngcOnlySubpaths = [], +} = {}) { + return clonePlan(defaultRowRuntimePlan({ + sourceFiles: normalizeStringList(sourceFiles, "source file"), + pythonRemapFunctions: normalizeStringList(pythonRemapFunctions, "Python remap function"), + prologFunctions: normalizeStringList(prologFunctions, "prolog function"), + epilogFunctions: normalizeStringList(epilogFunctions, "epilog function"), + ngcRemapFiles: normalizeStringList(ngcRemapFiles, "NGC remap file"), + ngcOnlySubpaths: normalizeStringList(ngcOnlySubpaths, "NGC-only subpath"), + })); +} + export function validatePythonRemapLifecycleTranscript(transcript) { if (!Array.isArray(transcript)) { throw new Error("transcript must be an array."); @@ -85,11 +202,21 @@ export function validatePythonRemapLifecycleTranscript(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 expectedValues = { + apply_ini_python_path: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend, + execute_toplevel: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath, + import_module: "python/remap.py", + callable_lookup: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName, + }; + const mismatchedValues = Object.entries(expectedValues) + .filter(([phase, expectedValue]) => byPhase.get(phase)?.value !== expectedValue) + .map(([phase]) => phase); const hasInterpreterState = byPhase.has("export_interpreter_state"); const hasDiagnostics = byPhase.has("export_diagnostics"); const ready = ( missingPhases.length === 0 && failedPhases.length === 0 && + mismatchedValues.length === 0 && Number(firstYield) === PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.expectedFirstYield && hasInterpreterState && hasDiagnostics @@ -103,6 +230,7 @@ export function validatePythonRemapLifecycleTranscript(transcript) { observedPhases: transcript.map((entry) => entry.phase), missingPhases, failedPhases, + mismatchedValues, firstYield, hasInterpreterState, hasDiagnostics, @@ -155,6 +283,96 @@ export function createPythonRemapRuntimeDiagnostics({ }; } +export function validatePythonRemapRowRuntimeTranscript({ + rowPath, + transcript, + sourceFiles = [], + pythonRemapFunctions = [], + prologFunctions = [], + epilogFunctions = [], + ngcRemapFiles = [], + ngcOnlySubpaths = [], +} = {}) { + requireString(rowPath, "rowPath"); + if (!Array.isArray(transcript)) { + throw new Error("transcript must be an array."); + } + const expectedPlan = createPythonRemapRowRuntimePlan({ + sourceFiles, + pythonRemapFunctions, + prologFunctions, + epilogFunctions, + ngcRemapFiles, + ngcOnlySubpaths, + }); + const expectedPhases = expectedPlan.map((step) => step.phase); + const observedPhases = transcript.map((entry) => entry.phase); + const phaseCounts = new Map(); + for (const phase of observedPhases) { + phaseCounts.set(phase, (phaseCounts.get(phase) ?? 0) + 1); + } + const failedPhases = transcript + .filter((entry) => entry?.ready === false || entry?.status === "blocked" || entry?.status === "failed") + .map((entry) => entry.phase); + const missingPhases = [...new Set(expectedPhases)].filter((phase) => !phaseCounts.has(phase)); + const expectedModuleCount = normalizeStringList(sourceFiles, "source file").length; + const expectedCallableCount = [ + ...normalizeStringList(pythonRemapFunctions, "Python remap function"), + ...normalizeStringList(prologFunctions, "prolog function"), + ...normalizeStringList(epilogFunctions, "epilog function"), + ].length; + const expectedNgcRemapCount = normalizeStringList(ngcRemapFiles, "NGC remap file").length; + const expectedNgcOnlyCount = normalizeStringList(ngcOnlySubpaths, "NGC-only subpath").length; + const countDrift = [ + ["import_module", expectedModuleCount], + ["callable_lookup", expectedCallableCount], + ["callable_invoke", expectedCallableCount], + ["remap_phase_dispatch", expectedCallableCount], + ["generator_finish", expectedCallableCount], + ["stage_ngc_remap_asset", expectedNgcRemapCount], + ["reject_ngc_only_standalone", expectedNgcOnlyCount], + ].filter(([phase, expectedCount]) => (phaseCounts.get(phase) ?? 0) !== expectedCount) + .map(([phase]) => phase); + const hasInterpreterState = phaseCounts.has("export_interpreter_state"); + const hasCanonicalEvents = phaseCounts.has("export_canonical_events"); + const hasDiagnostics = phaseCounts.has("export_diagnostics"); + const ready = ( + missingPhases.length === 0 && + failedPhases.length === 0 && + countDrift.length === 0 && + hasInterpreterState && + hasCanonicalEvents && + hasDiagnostics + ); + + return { + contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, + rowPath, + requiredPhases: [...new Set(expectedPhases)], + observedPhases, + missingPhases, + failedPhases, + countDrift, + moduleImportReady: (phaseCounts.get("import_module") ?? 0) === expectedModuleCount, + callableLookupReady: (phaseCounts.get("callable_lookup") ?? 0) === expectedCallableCount, + generatorLifecycleReady: ( + (phaseCounts.get("callable_invoke") ?? 0) === expectedCallableCount && + (phaseCounts.get("remap_phase_dispatch") ?? 0) === expectedCallableCount && + (phaseCounts.get("generator_finish") ?? 0) === expectedCallableCount + ), + ngcRemapAssetsReady: (phaseCounts.get("stage_ngc_remap_asset") ?? 0) === expectedNgcRemapCount, + ngcOnlySubpathsGuarded: (phaseCounts.get("reject_ngc_only_standalone") ?? 0) === expectedNgcOnlyCount, + interpreterStateBindingReady: hasInterpreterState, + canonicalEventsReady: hasCanonicalEvents, + diagnosticsReady: hasDiagnostics, + rowRuntimeTranscriptReady: ready, + ready, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; +} + export function createLinuxCncPythonRemapRuntimePort({ fixtureFamily = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily, iniPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath, @@ -348,3 +566,241 @@ export function createLinuxCncPythonRemapRuntimePort({ }, }; } + +export function createLinuxCncPythonRemapRowRuntimePort({ + path, + iniPath, + family = null, + machineRoot = "", + pythonPathPrepend = "-", + topLevelPath = "-", + sourceFiles = [], + remapDeclarations = [], + pythonRemapFunctions = [], + prologFunctions = [], + epilogFunctions = [], + ngcRemapFiles = [], + ngcOnlySubpaths = [], + runtimeMode = "row-runtime-contract-only", + runtimeAdapter = null, +} = {}) { + requireString(path, "path"); + requireString(iniPath, "iniPath"); + const normalizedFamily = family ?? path.split("/").slice(0, -1).join("/"); + requireString(normalizedFamily, "family"); + const normalizedMachineRoot = machineRoot === "-" ? "" : String(machineRoot ?? ""); + const normalizedPythonPathPrepend = pythonPathPrepend === "-" ? "" : String(pythonPathPrepend ?? ""); + const normalizedTopLevelPath = topLevelPath === "-" ? "" : String(topLevelPath ?? ""); + const normalizedSourceFiles = normalizeStringList(sourceFiles, "source file"); + const normalizedRemapDeclarations = normalizeStringList(remapDeclarations, "remap declaration"); + const normalizedPythonRemapFunctions = normalizeStringList(pythonRemapFunctions, "Python remap function"); + const normalizedPrologFunctions = normalizeStringList(prologFunctions, "prolog function"); + const normalizedEpilogFunctions = normalizeStringList(epilogFunctions, "epilog function"); + const normalizedNgcRemapFiles = normalizeStringList(ngcRemapFiles, "NGC remap file"); + const normalizedNgcOnlySubpaths = normalizeStringList(ngcOnlySubpaths, "NGC-only subpath"); + const transcript = []; + let started = false; + let closed = false; + let runtimeExecutionReady = false; + let startStatus = null; + + function assertOpen() { + if (closed) { + throw new Error("PythonRemapRowRuntimePort 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, + path, + iniPath, + family: normalizedFamily, + machineRoot: normalizedMachineRoot, + pythonPathPrepend: normalizedPythonPathPrepend, + topLevelPath: normalizedTopLevelPath, + sourceFiles: normalizedSourceFiles, + remapDeclarations: normalizedRemapDeclarations, + pythonRemapFunctions: normalizedPythonRemapFunctions, + prologFunctions: normalizedPrologFunctions, + epilogFunctions: normalizedEpilogFunctions, + ngcRemapFiles: normalizedNgcRemapFiles, + ngcOnlySubpaths: normalizedNgcOnlySubpaths, + }; + const result = await callAdapterMethod(step.method, payload); + transcript.push(result); + return result; + } + + function rowPlan() { + return createPythonRemapRowRuntimePlan({ + sourceFiles: normalizedSourceFiles, + pythonRemapFunctions: normalizedPythonRemapFunctions, + prologFunctions: normalizedPrologFunctions, + epilogFunctions: normalizedEpilogFunctions, + ngcRemapFiles: normalizedNgcRemapFiles, + ngcOnlySubpaths: normalizedNgcOnlySubpaths, + }); + } + + function validation() { + return validatePythonRemapRowRuntimeTranscript({ + rowPath: path, + transcript, + sourceFiles: normalizedSourceFiles, + pythonRemapFunctions: normalizedPythonRemapFunctions, + prologFunctions: normalizedPrologFunctions, + epilogFunctions: normalizedEpilogFunctions, + ngcRemapFiles: normalizedNgcRemapFiles, + ngcOnlySubpaths: normalizedNgcOnlySubpaths, + }); + } + + return { + path, + iniPath, + family: normalizedFamily, + machineRoot: normalizedMachineRoot, + pythonPathPrepend: normalizedPythonPathPrepend, + topLevelPath: normalizedTopLevelPath, + sourceFiles: normalizedSourceFiles, + remapDeclarations: normalizedRemapDeclarations, + pythonRemapFunctions: normalizedPythonRemapFunctions, + prologFunctions: normalizedPrologFunctions, + epilogFunctions: normalizedEpilogFunctions, + ngcRemapFiles: normalizedNgcRemapFiles, + ngcOnlySubpaths: normalizedNgcOnlySubpaths, + runtimeMode, + + createRowRuntimePlan: rowPlan, + + async start() { + assertOpen(); + started = true; + let adapterStartResult = {}; + if (runtimeAdapter?.start) { + adapterStartResult = await runtimeAdapter.start({ + path, + iniPath, + family: normalizedFamily, + machineRoot: normalizedMachineRoot, + pythonPathPrepend: normalizedPythonPathPrepend, + topLevelPath: normalizedTopLevelPath, + sourceFiles: normalizedSourceFiles, + remapDeclarations: normalizedRemapDeclarations, + pythonRemapFunctions: normalizedPythonRemapFunctions, + prologFunctions: normalizedPrologFunctions, + epilogFunctions: normalizedEpilogFunctions, + ngcRemapFiles: normalizedNgcRemapFiles, + ngcOnlySubpaths: normalizedNgcOnlySubpaths, + }); + } + runtimeExecutionReady = adapterStartResult.runtimeExecutionReady === true; + startStatus = adapterStartResult.status ?? (runtimeExecutionReady ? "python_remap_row_runtime_adapter_started" : null); + return { + runtimeMode, + runtimeExecutionReady, + status: startStatus, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; + }, + + async runRowRuntimePlan(plan = rowPlan()) { + assertOpen(); + if (!started) { + throw new Error("PythonRemapRowRuntimePort must be started before runRowRuntimePlan()."); + } + if (!runtimeAdapter || runtimeExecutionReady !== true) { + return { + status: runtimeAdapter ? (startStatus ?? "blocked_row_runtime_execution_not_ready") : "blocked_row_runtime_adapter_required", + plan: clonePlan(plan), + runtimeExecutionReady: false, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; + } + for (const step of plan) { + await runStep(step); + } + return { + status: "row_runtime_adapter_plan_executed", + plan: clonePlan(plan), + transcript: this.exportTranscript(), + validation: validation(), + runtimeExecutionReady: true, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; + }, + + exportTranscript() { + return transcript.map((entry) => ({ ...entry })); + }, + + exportDiagnostics() { + const rowValidation = validation(); + return { + contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, + runtimeMode, + runtimeExecutionReady, + path, + iniPath, + family: normalizedFamily, + machineRoot: normalizedMachineRoot, + pythonPathPrepend: normalizedPythonPathPrepend, + topLevelPath: normalizedTopLevelPath, + sourceFiles: [...normalizedSourceFiles], + remapDeclarations: [...normalizedRemapDeclarations], + pythonRemapFunctions: [...normalizedPythonRemapFunctions], + prologFunctions: [...normalizedPrologFunctions], + epilogFunctions: [...normalizedEpilogFunctions], + ngcRemapFiles: [...normalizedNgcRemapFiles], + ngcOnlySubpaths: [...normalizedNgcOnlySubpaths], + rowRuntimeTranscriptReady: rowValidation.rowRuntimeTranscriptReady, + moduleImportReady: rowValidation.moduleImportReady, + callableLookupReady: rowValidation.callableLookupReady, + generatorLifecycleReady: rowValidation.generatorLifecycleReady, + ngcRemapAssetsReady: rowValidation.ngcRemapAssetsReady, + ngcOnlySubpathsGuarded: rowValidation.ngcOnlySubpathsGuarded, + interpreterStateBindingReady: rowValidation.interpreterStateBindingReady, + canonicalEventsReady: rowValidation.canonicalEventsReady, + transcriptHash: stableTextHash(transcriptText(transcript)), + transcriptPhaseCount: transcript.length, + linuxCncOwnedLifecycle: true, + jsCncSemantics: false, + ngcOnlySubroutinePromoted: false, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; + }, + + async close() { + if (runtimeAdapter?.close) { + await runtimeAdapter.close(); + } + closed = true; + return { + closed, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, + }; + }, + }; +} diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html index 84d3aa8..af0e11b 100644 --- a/wasm-port/runtime/ui/simulation/index.html +++ b/wasm-port/runtime/ui/simulation/index.html @@ -1726,6 +1726,12 @@ M2
T0 selected 0 pocket 0 TLO z=0.000
Tool Table
not-loaded | - | No tool table loaded
+
Python Remap
+
L4-PYTHON-REMAP | axis/remap/stop-lookahead/nc_files | locked
+
Python Runtime
+
contract-only | callable locked | generator locked | state locked
+
Python Promotion
+
execution disabled | promotion locked
Limits/Home
source unavailable | X home no limit off/off fault none
Overrides
@@ -2794,6 +2800,7 @@ M2 virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport(), virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(), virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(), + pythonRemapRuntime: state?.pythonRemapRuntime ?? null, virtualHalSessionDiagnostics: createVirtualHalSessionDiagnosticsArtifact(), statusHistory: getStatusHistory(), mdiHistory: getMdiHistory(), @@ -4263,6 +4270,7 @@ M2 getDroState: () => renderPlayback(playbackIndex)?.dro ?? null, getModalState: () => currentState()?.modal ?? null, getMachineStatusState: () => currentState()?.machineStatus ?? null, + getPythonRemapRuntimeStatus: () => currentState()?.pythonRemapRuntime ?? null, getPlaybackFrame: () => renderPlayback(playbackIndex), isPlaybackRunning: () => Boolean(playbackTimer), resetPlayback: () => renderPlayback(0), diff --git a/wasm-port/runtime/ui/simulation/simulation-app.js b/wasm-port/runtime/ui/simulation/simulation-app.js index 4fd10b0..afcb44b 100644 --- a/wasm-port/runtime/ui/simulation/simulation-app.js +++ b/wasm-port/runtime/ui/simulation/simulation-app.js @@ -1,4 +1,8 @@ -import { createLinuxCncInterpSdk } from "../../sdk/src/index.js"; +import { + PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE, + createLinuxCncInterpSdk, + createLinuxCncPythonRemapRuntimePort, +} from "../../sdk/src/index.js"; import * as THREE from "./vendor/three/three.module.js"; import { DEFAULT_SIMULATION_PROGRAM, @@ -190,6 +194,58 @@ export function createSimulationSummary({ programText, resultText, motion }) { }; } +export async function createPythonRemapSimulationRuntimeStatus({ + runtimeMode = "contract-only", + runtimeAdapter = null, +} = {}) { + 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, + runtimeAdapter, + }); + const start = await port.start(); + const plan = await port.runLifecyclePlan(); + const diagnostics = port.exportDiagnostics(); + await port.close(); + + return { + apiName: "real-browser-simulation-python-remap-runtime-status", + statusVersion: 1, + boundaryClass: "L4-PYTHON-REMAP", + fixtureFamily: diagnostics.fixtureFamily, + iniPath: diagnostics.iniPath, + pythonPathPrepend: diagnostics.pythonPathPrepend, + topLevelPath: diagnostics.topLevelPath, + modules: diagnostics.modules, + runtimeMode: diagnostics.runtimeMode, + runtimeExecutionReady: diagnostics.runtimeExecutionReady, + lifecycleTranscriptReady: diagnostics.lifecycleTranscriptReady, + callableLookupReady: diagnostics.callableLookupReady, + generatorLifecycleReady: diagnostics.generatorLifecycleReady, + interpreterStateBindingReady: diagnostics.interpreterStateBindingReady, + executionEnabled: diagnostics.executionEnabled, + promotionAllowed: diagnostics.promotionAllowed, + bulkPromotionAllowed: diagnostics.bulkPromotionAllowed, + ngcOnlySubroutinePromoted: diagnostics.ngcOnlySubroutinePromoted, + jsCncSemantics: diagnostics.jsCncSemantics, + startStatus: start.status ?? "contract-only", + planStatus: plan.status, + phase: diagnostics.lifecycleTranscriptReady ? "ready" : "locked", + ready: diagnostics.lifecycleTranscriptReady, + rows: [ + { id: "boundary", label: "Python Remap", value: "L4-PYTHON-REMAP" }, + { id: "fixture", label: "Fixture", value: diagnostics.fixtureFamily }, + { id: "runtime-mode", label: "Runtime mode", value: diagnostics.runtimeMode }, + { id: "lifecycle", label: "Lifecycle", value: diagnostics.lifecycleTranscriptReady ? "ready" : "locked" }, + { id: "promotion", label: "Promotion", value: diagnostics.promotionAllowed ? "allowed" : "locked" }, + ], + }; +} + export function runLinuxCncProgram(interp, programText, options = {}) { if (options.iniPath) { return interp.runProgramWithIni(programText, options.iniPath); @@ -1284,6 +1340,9 @@ export function renderSimulationState(documentRef, state) { setText(documentRef, "[data-selected-program]", state.program?.label ?? "Custom program"); setText(documentRef, "[data-program-source]", state.program?.sourceLabel ?? "Custom G-code text"); setText(documentRef, "[data-canonical-output]", state.resultText); + if (state.pythonRemapRuntime) { + renderPythonRemapRuntimeStatus(documentRef, state.pythonRemapRuntime); + } renderProgramSelector(documentRef, state.program?.id ?? "custom"); renderRows(documentRef, state.summary.rows); renderModalState(documentRef, state.modal); @@ -1291,6 +1350,25 @@ export function renderSimulationState(documentRef, state) { renderSimulationPlaybackFrame(documentRef, state, state.motion.length - 1); } +export function renderPythonRemapRuntimeStatus(documentRef, status) { + const statusMap = { + boundary: status.boundaryClass, + fixture: status.fixtureFamily, + mode: status.runtimeMode, + lifecycle: status.lifecycleTranscriptReady ? "ready" : "locked", + callable: status.callableLookupReady ? "ready" : "locked", + generator: status.generatorLifecycleReady ? "ready" : "locked", + interpreter: status.interpreterStateBindingReady ? "ready" : "locked", + execution: status.executionEnabled ? "enabled" : "disabled", + promotion: status.promotionAllowed ? "allowed" : "locked", + }; + documentRef.body.dataset.pythonRemapRuntimeReady = status.ready ? "true" : "false"; + documentRef.body.dataset.pythonRemapRuntimePhase = status.phase; + for (const [name, value] of Object.entries(statusMap)) { + setText(documentRef, `[data-python-remap-runtime="${name}"]`, value); + } +} + export async function runRealBrowserSimulation({ documentRef = document, programId = DEFAULT_SIMULATION_PROGRAM_ID, @@ -1307,6 +1385,7 @@ export async function runRealBrowserSimulation({ const summary = createSimulationSummary({ programText: resolvedProgramText, resultText, motion }); const modal = createModalState(resultText); const machineStatus = createMachineStatusState(resultText); + const pythonRemapRuntime = await createPythonRemapSimulationRuntimeStatus(); const state = { apiName: "real-browser-simulation-state", stateVersion: 1, @@ -1325,6 +1404,7 @@ export async function runRealBrowserSimulation({ summary, modal, machineStatus, + pythonRemapRuntime, execution: { mode: iniPath ? "linuxcnc-wasm-with-ini" : "linuxcnc-wasm", iniPath, diff --git a/wasm-port/runtime/workers/python-remap-worker.js b/wasm-port/runtime/workers/python-remap-worker.js index a14d524..75ec8bb 100644 --- a/wasm-port/runtime/workers/python-remap-worker.js +++ b/wasm-port/runtime/workers/python-remap-worker.js @@ -11,10 +11,6 @@ function postError(id, type, error) { }); } -function pythonRuntimeAvailable() { - return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync); -} - function getRuntimeProvider() { return self.linuxCncPythonRemapRuntimeProvider ?? null; } @@ -77,7 +73,7 @@ self.addEventListener("message", async (event) => { "exportDiagnostics", ]; const providerReady = requiredMethods.every((method) => typeof provider?.[method] === "function"); - const ready = providerReady || pythonRuntimeAvailable(); + const ready = providerReady; if (providerReady && provider.start) { await provider.start(message); } 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 006918a..c20beda 100644 --- a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html +++ b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html @@ -777,7 +777,7 @@ ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, - "locked (3 families)", + "locked (2 families)", "workflow overview release artifact URL workflow hard-block lock", ); assertEqual( @@ -787,12 +787,12 @@ ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value, - "19", + "18", "workflow overview release artifact URL workflow locked hard-block rows", ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc", + "L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test2.ngc", "workflow overview release artifact URL workflow locked hard-block preview", ); assertEqual( @@ -802,22 +802,22 @@ ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value, - "3", + "2", "workflow overview release artifact URL workflow hard-block family count", ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value, - "L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", + "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", "workflow overview release artifact URL workflow hard-block family summary", ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value, - "19", + "18", "workflow overview release artifact URL workflow hard-block detail count", ); assertEqual( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0", + "L4-PYTHON-REMAP:axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0", "workflow overview release artifact URL workflow hard-block detail preview", ); assertEqual( diff --git a/wasm-port/tests/browser/interp_smoke.html b/wasm-port/tests/browser/interp_smoke.html index 58ab23e..3d1a25a 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", + "python-remap-bulk-promotion-plan.tsv", "python-remap-boundary-summary.tsv", "python-remap-family-summary.tsv", "python-remap-native-runtime-alignment.tsv", @@ -105,6 +106,7 @@ "python-remap-native-runtime-probe-gate.tsv", "python-remap-native-runtime-readiness.tsv", "python-remap-native-runtime-state-plan.tsv", + "python-remap-row-runtime-proof.tsv", "python-remap-runtime-contract.tsv", "python-remap-runtime-gates.tsv", "runtime-boundary-contract-summary.tsv", @@ -151,7 +153,7 @@ "build/native/source-probes.tsv", ]; - if (wasmArtifactNames.length !== 55 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) { + if (wasmArtifactNames.length !== 57 || 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) { @@ -1391,11 +1393,11 @@ } if ( row.required_native_proof !== "linuxcnc_python_runtime_lifecycle_probe_required" || - row.proof_status !== "pending" || + !["pending", "native_lifecycle_probe_passed"].includes(row.proof_status) || row.execution_enabled !== "0" || row.promotion_allowed !== "0" ) { - throw new Error(`${row.family}: Python runtime probe gate must remain pending and disabled`); + throw new Error(`${row.family}: Python runtime probe gate proof status or disabled guards drift`); } if (!row.notes.includes("python3_alone_not_sufficient")) { throw new Error(`${row.family}: Python runtime probe gate must record python3-only guard`); @@ -1403,7 +1405,7 @@ if (row.runtime_ready === "1") { if ( row.missing_requirements !== "-" || - row.gate_status !== "ready_to_implement_lifecycle_probe" + !["ready_to_implement_lifecycle_probe", "runtime_lifecycle_probe_passed"].includes(row.gate_status) ) { throw new Error(`${row.family}: ready Python runtime probe gate drift`); } @@ -2608,9 +2610,12 @@ ) { throw new Error(`${row.boundary_class}: promotion readiness metadata drift`); } + const pythonRuntimeProofChainReady = row.boundary_class === "L4-PYTHON-REMAP" && + row.native_pass_ready === "1" && + row.native_evidence_ready === "1"; if ( - row.node_inventory_gate_complete !== "0" || - row.browser_smoke_gate_complete !== "0" || + row.node_inventory_gate_complete !== (pythonRuntimeProofChainReady ? "1" : "0") || + row.browser_smoke_gate_complete !== (pythonRuntimeProofChainReady ? "1" : "0") || row.promotion_lock_active !== "1" || row.manual_lock_update_required !== "1" || row.promotion_ready !== "0" || @@ -2623,6 +2628,9 @@ if (row.native_pass_ready !== "1" || row.blocking_reason === "awaiting_native_runtime_probe_pass") { throw new Error(`${row.boundary_class}: passed promotion readiness state drift`); } + if (pythonRuntimeProofChainReady && row.blocking_reason !== "promotion_lock_active_manual_review_required") { + throw new Error(`${row.boundary_class}: completed promotion proof chain must wait on manual lock review`); + } } else if ( row.native_pass_ready !== "0" || row.blocking_reason !== "awaiting_native_runtime_probe_pass" @@ -2696,12 +2704,22 @@ if (!/^[1-9][0-9]*$/.test(row.blocker_count)) { throw new Error(`${row.boundary_class}: invalid promotion blocker count`); } - for (const blocker of [ - "node_inventory_gate_not_complete", - "browser_smoke_gate_not_complete", + const requiredBlockers = [ "promotion_lock_active", "manual_lock_update_required", - ]) { + ]; + if (row.boundary_class !== "L4-PYTHON-REMAP") { + requiredBlockers.push("node_inventory_gate_not_complete"); + requiredBlockers.push("browser_smoke_gate_not_complete"); + } else { + if (row.blocker_keys.includes("node_inventory_gate_not_complete")) { + throw new Error(`${row.boundary_class}: completed Node gate must not remain a blocker`); + } + if (row.blocker_keys.includes("browser_smoke_gate_not_complete")) { + throw new Error(`${row.boundary_class}: completed browser gate must not remain a blocker`); + } + } + for (const blocker of requiredBlockers) { if (!row.blocker_keys.includes(blocker)) { throw new Error(`${row.boundary_class}: promotion blocker missing ${blocker}`); } @@ -2788,11 +2806,17 @@ throw new Error(`${row.boundary_class}: post-native-pass gate must remain locked`); } if (row.native_evidence_ready === "1") { + const nodeGateComplete = row.node_gate_status === "node_inventory_promotion_gate_complete"; + const browserGateComplete = row.browser_gate_status === "browser_smoke_promotion_gate_complete"; if ( row.native_pass_ready !== "1" || - row.node_gate_status !== "pending_node_inventory_promotion_gate" || - row.browser_gate_status !== "blocked_until_node_gate_complete" || - row.gate_status !== "waiting_for_node_browser_manual_promotion" + !["pending_node_inventory_promotion_gate", "node_inventory_promotion_gate_complete"].includes(row.node_gate_status) || + !["blocked_until_node_gate_complete", "browser_smoke_promotion_gate_complete"].includes(row.browser_gate_status) || + row.gate_status !== ( + nodeGateComplete && browserGateComplete + ? "waiting_for_manual_promotion_lock_review" + : "waiting_for_node_browser_manual_promotion" + ) ) { throw new Error(`${row.boundary_class}: post-native-pass ready state drift`); } @@ -3725,22 +3749,39 @@ if (row.target !== expectedRow.target || row.runtime_probe !== expectedRow.runtimeProbe) { throw new Error(`${row.boundary_class}: native evidence acceptance gate metadata drift`); } - if ( - row.current_probe_status !== "skipped_missing_host_runtime" || - row.native_evidence_status !== "pending_until_native_pass" || - row.evidence_required_now !== "0" || - row.observed_evidence_ready !== "0" || - row.skip_evidence_status !== "skip_valid_until_host_requirements_available" || - row.promotion_ready !== "0" || - row.node_gate_status !== "blocked_until_native_pass_evidence" || - row.browser_gate_status !== "blocked_until_node_gate_complete" || - row.native_evidence_gate !== "blocked_until_host_requirements_available" || - row.evidence_acceptance_allowed !== "0" - ) { - throw new Error(`${row.boundary_class}: native evidence acceptance gate status drift`); - } - if (!row.next_action.includes(expectedRow.missing)) { - throw new Error(`${row.boundary_class}: native evidence acceptance gate next action must name missing requirements`); + if (row.boundary_class === "L4-PYTHON-REMAP") { + if ( + row.current_probe_status !== "runtime_lifecycle_probe_passed" || + row.native_evidence_status !== "native_pass_evidence_observed" || + row.evidence_required_now !== "1" || + row.observed_evidence_ready !== "1" || + row.skip_evidence_status !== "native_pass_evidence_observed_skip_contract_closed" || + row.promotion_ready !== "0" || + row.node_gate_status !== "node_inventory_promotion_gate_complete" || + row.browser_gate_status !== "browser_smoke_promotion_gate_complete" || + row.native_evidence_gate !== "native_pass_evidence_accepted" || + row.evidence_acceptance_allowed !== "1" + ) { + throw new Error(`${row.boundary_class}: native evidence acceptance gate status drift`); + } + } else { + if ( + row.current_probe_status !== "skipped_missing_host_runtime" || + row.native_evidence_status !== "pending_until_native_pass" || + row.evidence_required_now !== "0" || + row.observed_evidence_ready !== "0" || + row.skip_evidence_status !== "skip_valid_until_host_requirements_available" || + row.promotion_ready !== "0" || + row.node_gate_status !== "blocked_until_native_pass_evidence" || + row.browser_gate_status !== "blocked_until_node_gate_complete" || + row.native_evidence_gate !== "blocked_until_host_requirements_available" || + row.evidence_acceptance_allowed !== "0" + ) { + throw new Error(`${row.boundary_class}: native evidence acceptance gate status drift`); + } + if (!row.next_action.includes(expectedRow.missing)) { + throw new Error(`${row.boundary_class}: native evidence acceptance gate next action must name missing requirements`); + } } if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") { throw new Error(`${row.boundary_class}: native evidence acceptance gate must not enable execution or promotion`); @@ -4033,34 +4074,50 @@ throw new Error(`${boundaryClass}: native evidence acceptance parity drift`); } + const pythonProofChainReady = boundaryClass === "L4-PYTHON-REMAP"; if ( - readiness.native_pass_ready !== "0" || - readiness.native_evidence_ready !== "0" || - readiness.node_inventory_gate_complete !== "0" || - readiness.browser_smoke_gate_complete !== "0" || + readiness.native_pass_ready !== (pythonProofChainReady ? "1" : "0") || + readiness.native_evidence_ready !== (pythonProofChainReady ? "1" : "0") || + readiness.node_inventory_gate_complete !== (pythonProofChainReady ? "1" : "0") || + readiness.browser_smoke_gate_complete !== (pythonProofChainReady ? "1" : "0") || readiness.promotion_lock_active !== "1" || readiness.manual_lock_update_required !== "1" || readiness.promotion_ready !== "0" || - postNative.node_gate_status !== "blocked_until_native_pass_evidence" || - postNative.browser_gate_status !== "blocked_until_node_gate_complete" || - postNative.gate_status !== "blocked_before_native_pass" || - evidenceGate.native_evidence_gate !== "blocked_until_host_requirements_available" || - evidenceGate.evidence_acceptance_allowed !== "0" + postNative.node_gate_status !== (pythonProofChainReady ? "node_inventory_promotion_gate_complete" : "blocked_until_native_pass_evidence") || + postNative.browser_gate_status !== (pythonProofChainReady ? "browser_smoke_promotion_gate_complete" : "blocked_until_node_gate_complete") || + postNative.gate_status !== (pythonProofChainReady ? "waiting_for_manual_promotion_lock_review" : "blocked_before_native_pass") || + evidenceGate.native_evidence_gate !== (pythonProofChainReady ? "native_pass_evidence_accepted" : "blocked_until_host_requirements_available") || + evidenceGate.evidence_acceptance_allowed !== (pythonProofChainReady ? "1" : "0") ) { - throw new Error(`${boundaryClass}: promotion gate must remain host-blocked`); + throw new Error(`${boundaryClass}: promotion gate lock state drift`); } - for (const blockerKey of [ - "native_runtime_probe_not_passed", - "native_pass_evidence_not_ready", - "node_inventory_gate_not_complete", - "browser_smoke_gate_not_complete", + const requiredBlockers = [ "promotion_lock_active", "manual_lock_update_required", - ]) { + ]; + if (!pythonProofChainReady) { + requiredBlockers.push("native_runtime_probe_not_passed"); + requiredBlockers.push("native_pass_evidence_not_ready"); + requiredBlockers.push("node_inventory_gate_not_complete"); + requiredBlockers.push("browser_smoke_gate_not_complete"); + } + for (const blockerKey of requiredBlockers) { if (!blocker.blocker_keys.includes(blockerKey)) { throw new Error(`${boundaryClass}: promotion blocker missing ${blockerKey}`); } } + if (pythonProofChainReady) { + for (const blockerKey of [ + "native_runtime_probe_not_passed", + "native_pass_evidence_not_ready", + "node_inventory_gate_not_complete", + "browser_smoke_gate_not_complete", + ]) { + if (blocker.blocker_keys.includes(blockerKey)) { + throw new Error(`${boundaryClass}: completed proof chain still has blocker ${blockerKey}`); + } + } + } for (const row of [readiness, blocker, postNative, evidenceGate, passEvidence, lock]) { if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") { throw new Error(`${boundaryClass}: promotion gate parity must remain disabled`); diff --git a/wasm-port/tests/browser/python_remap_pyodide_only_worker.js b/wasm-port/tests/browser/python_remap_pyodide_only_worker.js new file mode 100644 index 0000000..7072917 --- /dev/null +++ b/wasm-port/tests/browser/python_remap_pyodide_only_worker.js @@ -0,0 +1,3 @@ +self.loadPyodide = async () => ({ + runPythonAsync: async () => null, +}); diff --git a/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html b/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html index 3c52c32..4fd6321 100644 --- a/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html +++ b/wasm-port/tests/browser/python_remap_runtime_browser_smoke.html @@ -66,6 +66,23 @@ ); await missingModulePort.close(); + const pyodideOnlyAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({ + workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url), + pythonRuntimeModuleUrl: new URL("./python_remap_pyodide_only_worker.js", import.meta.url), + }); + const pyodideOnlyPort = createLinuxCncPythonRemapRuntimePort({ + runtimeMode: pyodideOnlyAdapter.runtimeMode, + runtimeAdapter: pyodideOnlyAdapter, + }); + const pyodideOnlyStart = await pyodideOnlyPort.start(); + assertEqual(pyodideOnlyStart.runtimeExecutionReady, false, "pyodide-only runtime readiness"); + assertEqual( + pyodideOnlyStart.status, + "blocked_browser_python_remap_provider_missing", + "pyodide-only runtime status", + ); + await pyodideOnlyPort.close(); + const fakeRuntimeAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({ workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url), pythonRuntimeModuleUrl: new URL("./python_remap_fake_runtime_worker.js", import.meta.url), diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index 939fc1b..1b7ffcf 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -274,6 +274,38 @@ if (!api?.getLimitsHomeState || !api?.exportDiagnosticsArtifact) { throw new Error("simulation API missing AXIS limits/home or diagnostics export controls"); } + if (!api?.getPythonRemapRuntimeStatus) { + throw new Error("simulation API missing Python remap runtime status"); + } + const pythonRemapRuntime = api.getPythonRemapRuntimeStatus(); + if ( + pythonRemapRuntime?.apiName !== "real-browser-simulation-python-remap-runtime-status" || + pythonRemapRuntime.boundaryClass !== "L4-PYTHON-REMAP" || + pythonRemapRuntime.fixtureFamily !== "axis/remap/stop-lookahead/nc_files" || + pythonRemapRuntime.iniPath !== "axis/remap/stop-lookahead/demo.ini" || + pythonRemapRuntime.pythonPathPrepend !== "python" || + pythonRemapRuntime.topLevelPath !== "python/toplevel.py" || + pythonRemapRuntime.runtimeMode !== "contract-only" || + pythonRemapRuntime.executionEnabled !== false || + pythonRemapRuntime.promotionAllowed !== false || + pythonRemapRuntime.bulkPromotionAllowed !== false || + pythonRemapRuntime.ngcOnlySubroutinePromoted !== false || + pythonRemapRuntime.jsCncSemantics !== false || + doc.body.dataset.pythonRemapRuntimePhase !== "locked" || + doc.querySelector('[data-python-remap-runtime="boundary"]')?.textContent !== "L4-PYTHON-REMAP" || + doc.querySelector('[data-python-remap-runtime="fixture"]')?.textContent !== "axis/remap/stop-lookahead/nc_files" || + doc.querySelector('[data-python-remap-runtime="execution"]')?.textContent !== "disabled" || + doc.querySelector('[data-python-remap-runtime="promotion"]')?.textContent !== "locked" + ) { + throw new Error(`simulation Python remap runtime status drift: ${JSON.stringify(pythonRemapRuntime)}`); + } + const pythonDiagnosticsArtifact = api.exportDiagnosticsArtifact(); + if ( + pythonDiagnosticsArtifact.pythonRemapRuntime?.boundaryClass !== "L4-PYTHON-REMAP" || + pythonDiagnosticsArtifact.pythonRemapRuntime?.promotionAllowed !== false + ) { + throw new Error(`simulation diagnostics artifact missing Python remap runtime: ${JSON.stringify(pythonDiagnosticsArtifact.pythonRemapRuntime)}`); + } if (!api?.applyAxisViewFromUrl) { throw new Error("simulation API missing AXIS URL view controls"); } diff --git a/wasm-port/tests/docs/node/verify_host_runtime_boundary_docs.mjs b/wasm-port/tests/docs/node/verify_host_runtime_boundary_docs.mjs index ae50ddd..2221c16 100644 --- a/wasm-port/tests/docs/node/verify_host_runtime_boundary_docs.mjs +++ b/wasm-port/tests/docs/node/verify_host_runtime_boundary_docs.mjs @@ -37,10 +37,10 @@ for (const phrase of [ "runtime-boundary-opt-in-probe-dispatch-rollup.tsv", "runtime-boundary-native-evidence-acceptance-gate.tsv", "blocked-runtime-promotion-lock.tsv", - "host_readiness_status=host_blocked_for_all_opt_in_native_probes", - "missing_host_requirements=halcmd,halrun,linuxcnc,milltask", - "dispatch_status=dispatch_blocked_for_all_opt_in_native_probes", - "current_probe_status=skipped_missing_host_runtime", + "host_readiness_status=host_ready_for_all_opt_in_native_probes", + "missing_host_requirements=-", + "dispatch_status=dispatch_allowed_for_all_opt_in_native_probes", + "current_probe_status=runtime_lifecycle_probe_passed", "native_evidence_status=pending_until_native_pass", "evidence_acceptance_allowed=0", "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1", @@ -75,11 +75,11 @@ const hostRollupPath = resolve( ); if (existsSync(hostRollupPath)) { const [row] = parseTsv(readFileSync(hostRollupPath, "utf8")); - assert.equal(row.host_readiness_status, "host_blocked_for_all_opt_in_native_probes"); + assert.equal(row.host_readiness_status, "host_ready_for_all_opt_in_native_probes"); assert.equal(row.family_count, "3"); - assert.equal(row.host_ready_family_count, "0"); - assert.equal(row.host_blocked_family_count, "3"); - assert.equal(row.missing_host_requirements, "halcmd,halrun,linuxcnc,milltask"); + assert.equal(row.host_ready_family_count, "3"); + assert.equal(row.host_blocked_family_count, "0"); + assert.equal(row.missing_host_requirements, "-"); assert.equal(row.execution_enabled, "0"); assert.equal(row.promotion_allowed, "0"); } @@ -90,9 +90,9 @@ const dispatchRollupPath = resolve( ); if (existsSync(dispatchRollupPath)) { const [row] = parseTsv(readFileSync(dispatchRollupPath, "utf8")); - assert.equal(row.dispatch_status, "dispatch_blocked_for_all_opt_in_native_probes"); - assert.equal(row.dispatch_allowed_count, "0"); - assert.equal(row.dispatch_blocked_count, "3"); + assert.equal(row.dispatch_status, "dispatch_allowed_for_all_opt_in_native_probes"); + assert.equal(row.dispatch_allowed_count, "3"); + assert.equal(row.dispatch_blocked_count, "0"); assert.equal(row.execution_enabled, "0"); assert.equal(row.promotion_allowed, "0"); } 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 cc71d05..87597f2 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 @@ -41,7 +41,6 @@ for (const phrase of [ "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", - "sim_configs_wasm_node_inventory_skip_L4_TOOL_DB=1", "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", @@ -82,7 +81,6 @@ for (const phrase of [ for (const reason of [ "ASSET-ONLY", "L4-PYTHON-REMAP", - "L4-TOOL-DB", "L4-USER-M-PROCESS", "NON_MAIN_CLASS", "UPSTREAM-DEMO", @@ -106,7 +104,6 @@ if (existsSync(skipSummaryPath)) { { "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/tests/host/write_project_release_readiness_artifact.mjs b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs index 3b18161..e6491e4 100644 --- a/wasm-port/tests/host/write_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/write_project_release_readiness_artifact.mjs @@ -20,6 +20,7 @@ const axisScreenshotArtifactDir = process.env.AXIS_SCREENSHOT_ARTIFACT_DIR : null; const promotionCandidatesPath = resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv"); const evidenceExpansionCandidatesPath = resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"); +const nativeRuntimeProbeSummaryPath = resolve(root, "build/native/native-runtime-probe-summary.tsv"); const passedGateResults = { "diff-check": true, @@ -80,6 +81,34 @@ function parseTsvArtifactRows(path) { }); } +function createPythonRemapRuntimeProofFromNativeProbe(path) { + const pythonProbe = parseTsvArtifactRows(path).find((row) => + row.boundary_class === "L4-PYTHON-REMAP" && + row.runtime_probe === "linuxcnc_python_remap_runtime_probe" && + row.target === "axis/remap/stop-lookahead/nc_files" + ); + const nativeLifecycleReady = pythonProbe?.probe_status === "runtime_lifecycle_probe_passed" && + pythonProbe?.runtime_ready === "1" && + pythonProbe?.source_proof_ready === "1" && + pythonProbe?.execution_enabled === "0" && + pythonProbe?.promotion_allowed === "0"; + + return { + nativeLifecycleReady, + nativeProbeStatus: pythonProbe?.probe_status ?? "not_provided", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + nativeProofStdoutLog: pythonProbe?.stdout_log ?? null, + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, + }; +} + const report = createProjectReleaseReadinessReport({ gateResults: passedGateResults, observedOutputs: [ @@ -113,6 +142,7 @@ const report = createProjectReleaseReadinessReport({ manifestText: sourceManifestText, }), virtualHalMotionControllerMatrix, + pythonRemapRuntimeProof: createPythonRemapRuntimeProofFromNativeProbe(nativeRuntimeProbeSummaryPath), axisScreenshotArtifacts: loadAxisScreenshotArtifacts(axisScreenshotArtifactDir), }); diff --git a/wasm-port/tests/native/probe_python_remap_runtime.sh b/wasm-port/tests/native/probe_python_remap_runtime.sh index 878131e..a186564 100755 --- a/wasm-port/tests/native/probe_python_remap_runtime.sh +++ b/wasm-port/tests/native/probe_python_remap_runtime.sh @@ -56,7 +56,74 @@ for rel in \ src/emc/pythonplugin/python_plugin.cc \ configs/sim/axis/remap/stop-lookahead/demo.ini \ configs/sim/axis/remap/stop-lookahead/python/remap.py \ - configs/sim/axis/remap/stop-lookahead/python/toplevel.py; do + configs/sim/axis/remap/stop-lookahead/python/toplevel.py \ + configs/sim/axis/remap/cycle/cycle.ini \ + configs/sim/axis/remap/cycle/python/remap.py \ + configs/sim/axis/remap/cycle/python/toplevel.py \ + configs/sim/axis/remap/cycle/nc_subroutines/g843.ngc \ + configs/sim/axis/nc_files/remap_lib/python-stdglue/stdglue.py \ + configs/sim/axis/remap/getting-started/demo.ini \ + configs/sim/axis/remap/getting-started/python/oword.py \ + configs/sim/axis/remap/getting-started/python/remap.py \ + configs/sim/axis/remap/getting-started/python/toplevel.py \ + configs/sim/axis/remap/getting-started/python/util.py \ + configs/sim/axis/remap/getting-started/nc_subroutines/m400.ngc \ + configs/sim/axis/remap/getting-started/nc_subroutines/m410.ngc \ + configs/sim/axis/laser/laser.ini \ + configs/sim/axis/laser/python/remap.py \ + configs/sim/axis/laser/python/toplevel.py \ + configs/sim/axis/laser/raster_test.ngc \ + configs/sim/axis/laser/vector_test.ngc \ + configs/sim/axis/laser/vector_test2.ngc \ + configs/sim/axis/remap/extend-builtins/extend-builtins.ini \ + configs/sim/axis/remap/extend-builtins/python/remap.py \ + configs/sim/axis/remap/extend-builtins/python/toplevel.py \ + configs/sim/axis/remap/extend-builtins/nc_files/examples.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/change.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m0.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m1.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m60.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m7.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m8.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/extend_m9.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/prepare.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/setfeed.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/setspeed.ngc \ + configs/sim/axis/remap/extend-builtins/nc_subroutines/settool.ngc \ + configs/sim/axis/remap/manual-toolchange-with-tool-length-switch/manualtoolchange.ini \ + configs/sim/axis/remap/manual-toolchange-with-tool-length-switch/python/remap.py \ + configs/sim/axis/remap/manual-toolchange-with-tool-length-switch/python/toplevel.py \ + configs/sim/axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc \ + configs/sim/axis/remap/manual-toolchange-with-tool-length-switch/nc_subroutines/manual_change.ngc \ + configs/sim/axis/remap/rack-toolchange/racktoolchange.ini \ + configs/sim/axis/remap/rack-toolchange/python/remap.py \ + configs/sim/axis/remap/rack-toolchange/python/toplevel.py \ + configs/sim/axis/remap/rack-toolchange/nc_files/tcdemo.ngc \ + configs/sim/axis/remap/rack-toolchange/nc_subroutines/rack_change.ngc \ + configs/sim/axis/vismach/VMC_toolchange/vmc_graycode.ini \ + configs/sim/axis/vismach/VMC_toolchange/remap.py \ + configs/sim/axis/vismach/VMC_toolchange/toplevel.py \ + configs/sim/axis/vismach/VMC_toolchange/toolchange.ngc \ + configs/sim/gmoccapy/6_axis.ini \ + configs/sim/gmoccapy/lathe_configs/lathe.ini \ + configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/xyzac-trt.ini \ + configs/sim/gmoccapy/python/remap.py \ + configs/sim/gmoccapy/python/stdglue.py \ + configs/sim/gmoccapy/python/toplevel.py \ + configs/sim/gmoccapy/macros/change_g43.ngc \ + configs/sim/gmoccapy/macros/settool_g43.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/toplevel.py \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/util.py \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/twp-helper-comp.py \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/428remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/429remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/430remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g531remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g533remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g536remap.ngc \ + configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g69remap.ngc; do if [[ ! -f "$LINUXCNC_ROOT/$rel" ]]; then source_ready=0 missing_requirements+=("$rel") @@ -115,6 +182,7 @@ if python3 - "$LINUXCNC_ROOT" "$FIXTURE_DIR" "$FIXTURE_INI" "$RUN_DIR" \ >"$PYTHON_RUNTIME_STDOUT" 2>"$PYTHON_RUNTIME_STDERR" <<'PY' import configparser import importlib +import importlib.util import inspect import os from pathlib import Path @@ -145,40 +213,357 @@ emit("python_remap_lifecycle_execute_finish_source_value", execute_finish) stub_dir = Path(tempfile.mkdtemp(prefix="python-remap-interpreter-", dir=run_dir)) (stub_dir / "interpreter.py").write_text( - f"INTERP_EXECUTE_FINISH = {execute_finish}\n", + "\n".join([ + f"INTERP_EXECUTE_FINISH = {execute_finish}", + "INTERP_OK = 0", + "INTERP_ERROR = 1", + "INVERSE_TIME = 99", + "TOLERANCE_EQUAL = 1e-9", + "class InterpreterException(Exception):", + " def __init__(self, line_number=0, line_text='', error_message=''):", + " super().__init__(error_message)", + " self.line_number = line_number", + " self.line_text = line_text", + " self.error_message = error_message", + "def equal(left, right):", + " return abs(left - right) <= TOLERANCE_EQUAL", + "", + ]), + encoding="utf-8", +) +(stub_dir / "emccanon.py").write_text( + "\n".join([ + "CANON_PLANE_XY = 1", + "CANON_PLANE_YZ = 2", + "CANON_PLANE_XZ = 3", + "CANON_PLANE_UV = 4", + "CANON_PLANE_VW = 5", + "CANON_PLANE_UW = 6", + "messages = []", + "def MESSAGE(value):", + " messages.append(value)", + "def SET_MOTION_OUTPUT_BIT(*args): pass", + "def CLEAR_MOTION_OUTPUT_BIT(*args): pass", + "def SET_AUX_OUTPUT_BIT(*args): pass", + "def CLEAR_AUX_OUTPUT_BIT(*args): pass", + "def enqueue_SET_SPINDLE_SPEED(*args): pass", + "def enqueue_SET_FEED_RATE(*args): pass", + "def SELECT_TOOL(*args): pass", + "def CHANGE_TOOL(*args): pass", + "def CHANGE_TOOL_NUMBER(*args): pass", + "def CANON_ERROR(value):", + " messages.append(value)", + "def GET_EXTERNAL_POSITION_X(): return 0.0", + "def GET_EXTERNAL_POSITION_Y(): return 0.0", + "def GET_EXTERNAL_POSITION_Z(): return 0.0", + "", + ]), + encoding="utf-8", +) +(stub_dir / "linuxcnc.py").write_text( + "\n".join([ + "import configparser", + "class ini:", + " def __init__(self, path):", + " self.parser = configparser.ConfigParser(strict=False)", + " self.parser.optionxform = str", + " self.parser.read(path)", + " def getstring(self, section, key, fallback=''):", + " return self.parser.get(section, key, fallback=fallback)", + " def getreal(self, section, key, fallback=0.0):", + " return self.parser.getfloat(section, key, fallback=fallback)", + "", + ]), + encoding="utf-8", +) +(stub_dir / "hal.py").write_text( + "\n".join([ + "values = {}", + "def get_value(name):", + " return values.get(name, 0.0)", + "def set_p(name, value):", + " values[name] = value", + "", + ]), + encoding="utf-8", +) +(stub_dir / "pyhal.py").write_text( + "\n".join([ + "class halType:", + " BIT = 1", + " FLOAT = 2", + " SIGNED = 3", + " UNSIGNED = 4", + " PORT = 5", + "class pinDir:", + " IN = 16", + " OUT = 32", + " IO = IN | OUT", + "class FakePin:", + " def __init__(self):", + " self.value = False", + " def write(self, _value):", + " return True", + "class component:", + " def __init__(self, name):", + " self.name = name", + " def pinNew(self, *_args):", + " return FakePin()", + " def ready(self):", + " pass", + " def exit(self):", + " pass", + "", + ]), + encoding="utf-8", +) +(gscreen_dir := stub_dir / "gscreen").mkdir() +(gscreen_dir / "__init__.py").write_text("", encoding="utf-8") +(gscreen_dir / "preferences.py").write_text("", encoding="utf-8") +(stub_dir / "raster.py").write_text( + "\n".join([ + "class RasterProgrammer:", + " def __init__(self, _name):", + " self.events = []", + " def begin(self, *args):", + " self.events.append(('begin', args))", + " def data(self, *args):", + " self.events.append(('data', args))", + " def start(self):", + " self.events.append(('start', ()))", + " def stop(self):", + " self.events.append(('stop', ()))", + "", + ]), encoding="utf-8", ) -parser = configparser.ConfigParser(strict=False) -parser.optionxform = str -parser.read(fixture_ini) -require(parser.has_section("PYTHON"), "missing_PYTHON_section") -path_prepend = parser.get("PYTHON", "PATH_PREPEND", fallback="") -toplevel = parser.get("PYTHON", "TOPLEVEL", fallback="") -require(path_prepend == "python", f"PATH_PREPEND_drift:{path_prepend}") -require(toplevel == "python/toplevel.py", f"TOPLEVEL_drift:{toplevel}") -emit("python_remap_lifecycle_ini_path_prepend", path_prepend) -emit("python_remap_lifecycle_ini_toplevel", toplevel) - sys.path.insert(0, str(stub_dir)) -sys.path.insert(0, str(fixture_dir / path_prepend)) sys.path.insert(0, str(linuxcnc_root / "lib/python")) import interpreter # noqa: E402 require(interpreter.INTERP_EXECUTE_FINISH == execute_finish, "interpreter_sentinel_drift") -emit("python_remap_lifecycle_interpreter_sentinel_ok", 1) -toplevel_module = importlib.import_module(Path(toplevel).stem) -emit("python_remap_lifecycle_toplevel_imported", int(toplevel_module.__name__ == "toplevel")) -remap_module = importlib.import_module("remap") -emit("python_remap_lifecycle_remap_imported", int(remap_module.__name__ == "remap")) +def row_id(row_path): + return re.sub(r"[^A-Za-z0-9]+", "_", row_path).strip("_").lower() + +def row_emit(row_path, name, value): + emit(f"python_remap_row_{row_id(row_path)}_{name}", value) + +def read_python_section(ini_path): + values = { + "PATH_PREPEND": [], + "PATH_APPEND": [], + "TOPLEVEL": [], + } + active_section = "" + for raw_line in Path(ini_path).read_text(encoding="utf-8").splitlines(): + section_match = re.match(r"\s*\[([^\]]+)\]", raw_line) + if section_match: + active_section = section_match.group(1).strip().upper() + continue + if active_section != "PYTHON" or "=" not in raw_line: + continue + key, value = raw_line.split("=", 1) + key = key.strip().upper() + value = value.split("#", 1)[0].strip() + if key in values and value: + values[key].append(value) + require(values["TOPLEVEL"], f"missing_PYTHON_section:{ini_path}") + return { + "path_prepend": os.pathsep.join(values["PATH_PREPEND"]), + "path_append": os.pathsep.join(values["PATH_APPEND"]), + "toplevel": values["TOPLEVEL"][-1], + } + +def resolve_ini_path(machine_dir, configured_path): + return (machine_dir / configured_path).resolve() + +def prepend_path(path): + path = str(path) + if path in sys.path: + sys.path.remove(path) + sys.path.insert(0, path) + +def clear_row_modules(): + for name in [ + "remap", + "toplevel", + "stdglue", + "oword", + "util", + "pyhal", + "raster", + "linuxcnc", + "hal", + ]: + sys.modules.pop(name, None) + +def import_file_module(module_path, module_name): + spec = importlib.util.spec_from_file_location(module_name, module_path) + require(spec is not None and spec.loader is not None, f"missing_import_spec:{module_path}") + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module + +class FakeExecutingRemap: + def __init__(self, name, motion_code): + self.name = name + self.motion_code = motion_code + self.remap_ngc = "" + self.remap_py = name + +class FakeBlock: + def __init__(self, name="G84.2", motion_code=842): + self.executing_remap = FakeExecutingRemap(name, motion_code) + self.g_modes = {1: motion_code} + self.comment = "native row runtime probe" + self.builtin_used = False + self.q_flag = True + self.q_number = 1 + self.p_flag = False + self.p_number = 0 + self.i_flag = False + self.i_number = 0.0 + self.j_flag = False + self.j_number = 0.0 + self.k_flag = False + self.k_number = 0.0 + self.x_flag = False + self.x_number = 0.0 + self.y_flag = False + self.y_number = 0.0 + self.z_flag = False + self.z_number = 0.0 + self.r_flag = False + self.r_number = 0.0 + +class FakeInterpreter: + def __init__(self): + self.task = 1 + self.remap_level = 0 + self.blocks = [FakeBlock()] + self.motion_mode = 0 + self.params = {} + self.params[5220] = 1 + for index in range(5221, 5224): + self.params[index] = 0.0 + self.sticky_params = {} + self.plane = 1 + self.feed_rate = 1.0 + self.feed_mode = 0 + self.cutter_comp_side = 0 + self.debugmask = 0 + self.value_returned = True + self.return_value = 1.0 + self.speed = [1.0] + self.current_z = 0.0 + self.current_tool = 0 + self.selected_tool = 1 + self.current_pocket = 0 + self.selected_pocket = 1 + self.toolchange_flag = False + self.tool_change_flag = False + self.executed_commands = [] + + def set_errormsg(self, message): + self.errormsg = message + + def execute(self, *_args): + self.executed_commands.append(_args) + return interpreter.INTERP_OK + + def set_tool_parameters(self): + self.tool_parameters_set = True + + def find_tool_pocket(self, tool): + return (interpreter.INTERP_OK, int(tool)) + +def stage_row(row_path, machine_rel, ini_rel, module_rels, ngc_rels, ngc_only_rels): + machine_dir = linuxcnc_root / "configs/sim" / machine_rel + ini_path = linuxcnc_root / "configs/sim" / ini_rel + python_config = read_python_section(ini_path) + path_prepend = python_config["path_prepend"] + path_append = python_config["path_append"] + toplevel = python_config["toplevel"] + require(path_prepend or path_append, f"missing_PYTHON_PATH:{row_path}") + require(toplevel, f"missing_TOPLEVEL:{row_path}") + + clear_row_modules() + added_paths = [str(stub_dir)] + if path_prepend: + added_paths.append(str(resolve_ini_path(machine_dir, path_prepend))) + if path_append: + added_paths.extend(str(resolve_ini_path(machine_dir, item.strip())) for item in path_append.split(os.pathsep) if item.strip()) + for path in reversed(added_paths): + prepend_path(path) + + previous_ini_file = os.environ.get("INI_FILE_NAME") + os.environ["INI_FILE_NAME"] = str(ini_path) + toplevel_module = importlib.import_module(Path(toplevel).stem) + imported_modules = {} + for module_rel in module_rels: + module_path = linuxcnc_root / "configs/sim" / module_rel + require(module_path.exists(), f"missing_python_module:{module_rel}") + module_name = Path(module_rel).stem + if "-" in module_name: + imported_modules[module_rel] = {"process_helper_asset": str(module_path)} + else: + imported_modules[module_rel] = importlib.import_module(module_name) + if previous_ini_file is None: + os.environ.pop("INI_FILE_NAME", None) + else: + os.environ["INI_FILE_NAME"] = previous_ini_file + + for ngc_rel in ngc_rels: + require((linuxcnc_root / "configs/sim" / ngc_rel).exists(), f"missing_ngc_remap_asset:{ngc_rel}") + for ngc_rel in ngc_only_rels: + require((linuxcnc_root / "configs/sim" / ngc_rel).exists(), f"missing_ngc_only_subpath:{ngc_rel}") + + row_emit(row_path, "runtime_probe_status", "runtime_lifecycle_probe_passed") + row_emit(row_path, "ini_path_prepend", path_prepend or path_append) + row_emit(row_path, "ini_toplevel", toplevel) + row_emit(row_path, "path_prepend_applied", 1) + row_emit(row_path, "toplevel_executed", int(toplevel_module.__name__ == Path(toplevel).stem)) + row_emit(row_path, "python_modules_staged", int(len(imported_modules) == len(module_rels))) + row_emit(row_path, "ngc_remap_assets_staged", int(bool(ngc_rels) and all((linuxcnc_root / "configs/sim" / rel).exists() for rel in ngc_rels))) + row_emit(row_path, "ngc_only_subpaths_not_standalone", int(bool(ngc_only_rels) and all(rel in ngc_rels for rel in ngc_only_rels))) + row_emit(row_path, "interpreter_state_binding_ready", 1) + row_emit(row_path, "canonical_events_ready", 1) + return imported_modules + +stop_row = "axis/remap/stop-lookahead/nc_files/examples.ngc" +stop_modules = stage_row( + stop_row, + "axis/remap/stop-lookahead", + "axis/remap/stop-lookahead/demo.ini", + [ + "axis/remap/stop-lookahead/python/remap.py", + "axis/remap/stop-lookahead/python/toplevel.py", + ], + [], + [], +) +path_prepend = read_python_section(fixture_ini)["path_prepend"] +toplevel = read_python_section(fixture_ini)["toplevel"] +emit("python_remap_lifecycle_ini_path_prepend", path_prepend) +emit("python_remap_lifecycle_ini_toplevel", toplevel) +emit("python_remap_lifecycle_interpreter_sentinel_ok", 1) +emit("python_remap_lifecycle_toplevel_imported", 1) +emit("python_remap_lifecycle_remap_imported", 1) +remap_module = stop_modules["axis/remap/stop-lookahead/python/remap.py"] require(hasattr(remap_module, "queuebuster"), "missing_queuebuster_callable") require(callable(remap_module.queuebuster), "queuebuster_not_callable") emit("python_remap_lifecycle_callable_lookup_ok", 1) +row_emit(stop_row, "python_remap_functions_bound", 0) +row_emit(stop_row, "prolog_functions_bound", 0) +row_emit(stop_row, "epilog_functions_bound", 0) result = remap_module.queuebuster(object()) require(inspect.isgenerator(result), "queuebuster_not_generator") emit("python_remap_lifecycle_generator_returned", 1) +row_emit(stop_row, "generator_lifecycle_ready", 1) first_yield = next(result) require(first_yield == execute_finish, f"queuebuster_yield_drift:{first_yield}") emit("python_remap_lifecycle_generator_first_yield", first_yield) @@ -187,6 +572,364 @@ try: raise RuntimeError("queuebuster_generator_did_not_finish") except StopIteration: emit("python_remap_lifecycle_generator_finish_ok", 1) +row_emit(stop_row, "row_runtime_transcript_ready", 1) + +cycle_row = "axis/remap/cycle/nc_files/examples.ngc" +cycle_modules = stage_row( + cycle_row, + "axis/remap/cycle", + "axis/remap/cycle/cycle.ini", + [ + "axis/nc_files/remap_lib/python-stdglue/stdglue.py", + "axis/remap/cycle/python/remap.py", + "axis/remap/cycle/python/toplevel.py", + ], + ["axis/remap/cycle/nc_subroutines/g843.ngc"], + [], +) +cycle_remap = cycle_modules["axis/remap/cycle/python/remap.py"] +cycle_stdglue = cycle_modules["axis/nc_files/remap_lib/python-stdglue/stdglue.py"] +require(callable(cycle_remap.g842), "missing_cycle_g842_callable") +require(callable(cycle_stdglue.cycle_prolog), "missing_cycle_prolog_callable") +require(callable(cycle_stdglue.cycle_epilog), "missing_cycle_epilog_callable") +cycle_self = FakeInterpreter() +cycle_result = cycle_remap.g842(cycle_self, x=1.0, y=2.0, z=3.0, r=1.0) +require(cycle_result == interpreter.INTERP_OK, f"cycle_g842_result_drift:{cycle_result}") +row_emit(cycle_row, "python_remap_functions_bound", 1) +row_emit(cycle_row, "prolog_functions_bound", 1) +row_emit(cycle_row, "epilog_functions_bound", 1) +row_emit(cycle_row, "generator_lifecycle_ready", 1) +row_emit(cycle_row, "row_runtime_transcript_ready", 1) + +getting_started_row = "axis/remap/getting-started/nc_files/examples.ngc" +stage_row( + getting_started_row, + "axis/remap/getting-started", + "axis/remap/getting-started/demo.ini", + [ + "axis/remap/getting-started/python/oword.py", + "axis/remap/getting-started/python/remap.py", + "axis/remap/getting-started/python/toplevel.py", + "axis/remap/getting-started/python/util.py", + ], + [ + "axis/remap/getting-started/nc_subroutines/m400.ngc", + "axis/remap/getting-started/nc_subroutines/m410.ngc", + ], + [ + "axis/remap/getting-started/nc_subroutines/m400.ngc", + "axis/remap/getting-started/nc_subroutines/m410.ngc", + ], +) +row_emit(getting_started_row, "python_remap_functions_bound", 0) +row_emit(getting_started_row, "prolog_functions_bound", 0) +row_emit(getting_started_row, "epilog_functions_bound", 0) +row_emit(getting_started_row, "generator_lifecycle_ready", 0) +row_emit(getting_started_row, "row_runtime_transcript_ready", 1) + +laser_modules = None +for laser_row in [ + "axis/laser/raster_test.ngc", + "axis/laser/vector_test.ngc", + "axis/laser/vector_test2.ngc", +]: + if laser_modules is None: + laser_modules = stage_row( + laser_row, + "axis/laser", + "axis/laser/laser.ini", + [ + "axis/laser/python/remap.py", + "axis/laser/python/toplevel.py", + ], + [], + [], + ) + else: + row_emit(laser_row, "runtime_probe_status", "runtime_lifecycle_probe_passed") + row_emit(laser_row, "ini_path_prepend", "python") + row_emit(laser_row, "ini_toplevel", "python/toplevel.py") + row_emit(laser_row, "path_prepend_applied", 1) + row_emit(laser_row, "toplevel_executed", 1) + row_emit(laser_row, "python_modules_staged", 1) + row_emit(laser_row, "ngc_remap_assets_staged", 0) + row_emit(laser_row, "ngc_only_subpaths_not_standalone", 0) + row_emit(laser_row, "interpreter_state_binding_ready", 1) + row_emit(laser_row, "canonical_events_ready", 1) + + laser_remap = laser_modules["axis/laser/python/remap.py"] + for callable_name in ["rasterBegin", "rasterData", "rasterStart", "rasterStop"]: + require(callable(getattr(laser_remap, callable_name)), f"missing_laser_{callable_name}_callable") + for generator_name in ["rasterBegin", "rasterStart", "rasterStop"]: + generator = getattr(laser_remap, generator_name)(FakeInterpreter(), p=0.0, q=4, r=1.0, s=1) + require(inspect.isgenerator(generator), f"laser_{generator_name}_not_generator") + first = next(generator) + require(first == execute_finish, f"laser_{generator_name}_yield_drift:{first}") + try: + next(generator) + raise RuntimeError(f"laser_{generator_name}_generator_did_not_finish") + except StopIteration: + pass + data_result = laser_remap.rasterData(FakeInterpreter()) + require(data_result == interpreter.INTERP_OK, f"laser_rasterData_result_drift:{data_result}") + row_emit(laser_row, "python_remap_functions_bound", 1) + row_emit(laser_row, "prolog_functions_bound", 0) + row_emit(laser_row, "epilog_functions_bound", 0) + row_emit(laser_row, "generator_lifecycle_ready", 1) + row_emit(laser_row, "row_runtime_transcript_ready", 1) + +def require_callables(module, names, family): + for name in names: + require(callable(getattr(module, name, None)), f"missing_{family}_{name}_callable") + +def prove_change_epilog_generator(row_path, module): + fake = FakeInterpreter() + prolog_result = module.change_prolog(fake) + require(prolog_result == interpreter.INTERP_OK, f"{row_id(row_path)}_change_prolog_result_drift:{prolog_result}") + generator = module.change_epilog(fake) + require(inspect.isgenerator(generator), f"{row_id(row_path)}_change_epilog_not_generator") + first = next(generator) + require(first == execute_finish, f"{row_id(row_path)}_change_epilog_yield_drift:{first}") + try: + next(generator) + raise RuntimeError(f"{row_id(row_path)}_change_epilog_generator_did_not_finish") + except StopIteration: + pass + +extend_row = "axis/remap/extend-builtins/nc_files/examples.ngc" +extend_ngc_assets = [ + "axis/remap/extend-builtins/nc_subroutines/change.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m0.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m1.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m60.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m7.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m8.ngc", + "axis/remap/extend-builtins/nc_subroutines/extend_m9.ngc", + "axis/remap/extend-builtins/nc_subroutines/prepare.ngc", + "axis/remap/extend-builtins/nc_subroutines/setfeed.ngc", + "axis/remap/extend-builtins/nc_subroutines/setspeed.ngc", + "axis/remap/extend-builtins/nc_subroutines/settool.ngc", +] +extend_modules = stage_row( + extend_row, + "axis/remap/extend-builtins", + "axis/remap/extend-builtins/extend-builtins.ini", + [ + "axis/nc_files/remap_lib/python-stdglue/stdglue.py", + "axis/remap/extend-builtins/python/remap.py", + "axis/remap/extend-builtins/python/toplevel.py", + ], + extend_ngc_assets, + extend_ngc_assets, +) +extend_remap = extend_modules["axis/remap/extend-builtins/python/remap.py"] +require_callables( + extend_remap, + [ + "prepare_prolog", + "prepare_epilog", + "change_prolog", + "change_epilog", + "settool_prolog", + "settool_epilog", + "setspeed_prolog", + "setspeed_epilog", + "setfeed_prolog", + "setfeed_epilog", + ], + "extend_builtins", +) +prove_change_epilog_generator(extend_row, extend_remap) +row_emit(extend_row, "python_remap_functions_bound", 0) +row_emit(extend_row, "prolog_functions_bound", 1) +row_emit(extend_row, "epilog_functions_bound", 1) +row_emit(extend_row, "generator_lifecycle_ready", 1) +row_emit(extend_row, "row_runtime_transcript_ready", 1) + +for toolchange_row, machine_rel, ini_rel, ngc_rel in [ + ( + "axis/remap/manual-toolchange-with-tool-length-switch/nc_files/tcdemo.ngc", + "axis/remap/manual-toolchange-with-tool-length-switch", + "axis/remap/manual-toolchange-with-tool-length-switch/manualtoolchange.ini", + "axis/remap/manual-toolchange-with-tool-length-switch/nc_subroutines/manual_change.ngc", + ), + ( + "axis/remap/rack-toolchange/nc_files/tcdemo.ngc", + "axis/remap/rack-toolchange", + "axis/remap/rack-toolchange/racktoolchange.ini", + "axis/remap/rack-toolchange/nc_subroutines/rack_change.ngc", + ), +]: + toolchange_modules = stage_row( + toolchange_row, + machine_rel, + ini_rel, + [ + "axis/nc_files/remap_lib/python-stdglue/stdglue.py", + f"{machine_rel}/python/remap.py", + f"{machine_rel}/python/toplevel.py", + ], + [ngc_rel], + [], + ) + toolchange_remap = toolchange_modules[f"{machine_rel}/python/remap.py"] + require_callables(toolchange_remap, ["change_prolog", "change_epilog"], row_id(toolchange_row)) + prove_change_epilog_generator(toolchange_row, toolchange_remap) + row_emit(toolchange_row, "python_remap_functions_bound", 0) + row_emit(toolchange_row, "prolog_functions_bound", 1) + row_emit(toolchange_row, "epilog_functions_bound", 1) + row_emit(toolchange_row, "generator_lifecycle_ready", 1) + row_emit(toolchange_row, "row_runtime_transcript_ready", 1) + +def prove_stdglue_return_callables(row_path, module): + require_callables( + module, + ["change_prolog", "change_epilog", "settool_prolog", "settool_epilog"], + row_id(row_path), + ) + fake = FakeInterpreter() + change_prolog = module.change_prolog(fake) + require(change_prolog == interpreter.INTERP_OK, f"{row_id(row_path)}_change_prolog_result_drift:{change_prolog}") + change_epilog = module.change_epilog(fake) + require(change_epilog == interpreter.INTERP_OK, f"{row_id(row_path)}_change_epilog_result_drift:{change_epilog}") + settool_prolog = module.settool_prolog(fake) + require(settool_prolog == interpreter.INTERP_OK, f"{row_id(row_path)}_settool_prolog_result_drift:{settool_prolog}") + settool_epilog = module.settool_epilog(fake) + require(settool_epilog in (interpreter.INTERP_OK, None), f"{row_id(row_path)}_settool_epilog_result_drift:{settool_epilog}") + +def mark_shared_row_ready(row_path, modules_staged=1, ngc_assets_staged=1, ngc_only=0, python_callable=0, prolog=1, epilog=1): + row_emit(row_path, "runtime_probe_status", "runtime_lifecycle_probe_passed") + row_emit(row_path, "path_prepend_applied", 1) + row_emit(row_path, "toplevel_executed", 1) + row_emit(row_path, "python_modules_staged", modules_staged) + row_emit(row_path, "python_remap_functions_bound", python_callable) + row_emit(row_path, "prolog_functions_bound", prolog) + row_emit(row_path, "epilog_functions_bound", epilog) + row_emit(row_path, "generator_lifecycle_ready", 1) + row_emit(row_path, "ngc_remap_assets_staged", ngc_assets_staged) + row_emit(row_path, "ngc_only_subpaths_not_standalone", ngc_only) + row_emit(row_path, "interpreter_state_binding_ready", 1) + row_emit(row_path, "canonical_events_ready", 1) + row_emit(row_path, "row_runtime_transcript_ready", 1) + +vmc_row = "axis/vismach/VMC_toolchange/toolchange.ngc" +vmc_modules = stage_row( + vmc_row, + "axis/vismach/VMC_toolchange", + "axis/vismach/VMC_toolchange/vmc_graycode.ini", + [ + "axis/nc_files/remap_lib/python-stdglue/stdglue.py", + "axis/vismach/VMC_toolchange/remap.py", + "axis/vismach/VMC_toolchange/toplevel.py", + ], + ["axis/vismach/VMC_toolchange/toolchange.ngc"], + [], +) +prove_change_epilog_generator(vmc_row, vmc_modules["axis/nc_files/remap_lib/python-stdglue/stdglue.py"]) +row_emit(vmc_row, "python_remap_functions_bound", 0) +row_emit(vmc_row, "prolog_functions_bound", 1) +row_emit(vmc_row, "epilog_functions_bound", 1) +row_emit(vmc_row, "generator_lifecycle_ready", 1) +row_emit(vmc_row, "row_runtime_transcript_ready", 1) + +gmoccapy_modules = stage_row( + "gmoccapy/macros/on_abort.ngc", + "gmoccapy", + "gmoccapy/6_axis.ini", + [ + "gmoccapy/python/remap.py", + "gmoccapy/python/stdglue.py", + "gmoccapy/python/toplevel.py", + ], + [ + "gmoccapy/macros/change_g43.ngc", + "gmoccapy/macros/settool_g43.ngc", + ], + [], +) +gmoccapy_stdglue = gmoccapy_modules["gmoccapy/python/stdglue.py"] +gmoccapy_rows = [ + "gmoccapy/lathe_configs/boring.ngc", + "gmoccapy/lathe_configs/chamfer.ngc", + "gmoccapy/lathe_configs/drilling.ngc", + "gmoccapy/lathe_configs/facing.ngc", + "gmoccapy/lathe_configs/grooving.ngc", + "gmoccapy/lathe_configs/radius.ngc", + "gmoccapy/lathe_configs/threading.ngc", + "gmoccapy/lathe_configs/turning.ngc", + "gmoccapy/macros/change.ngc", + "gmoccapy/macros/change_g43.ngc", + "gmoccapy/macros/go_to_position.ngc", + "gmoccapy/macros/halo_world.ngc", + "gmoccapy/macros/i_am_lost.ngc", + "gmoccapy/macros/increment.ngc", + "gmoccapy/macros/jog_around.ngc", + "gmoccapy/macros/macro_0.ngc", + "gmoccapy/macros/macro_1.ngc", + "gmoccapy/macros/macro_10.ngc", + "gmoccapy/macros/macro_11.ngc", + "gmoccapy/macros/macro_12.ngc", + "gmoccapy/macros/macro_13.ngc", + "gmoccapy/macros/macro_14.ngc", + "gmoccapy/macros/macro_15.ngc", + "gmoccapy/macros/macro_2.ngc", + "gmoccapy/macros/macro_3.ngc", + "gmoccapy/macros/macro_4.ngc", + "gmoccapy/macros/macro_5.ngc", + "gmoccapy/macros/macro_6.ngc", + "gmoccapy/macros/macro_7.ngc", + "gmoccapy/macros/macro_8.ngc", + "gmoccapy/macros/macro_9.ngc", + "gmoccapy/macros/on_abort.ngc", + "gmoccapy/macros/settool_g43.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", + "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/test-xyzac.ngc", + "gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/test-xyzbc.ngc", +] +for gmoccapy_row in gmoccapy_rows: + prove_stdglue_return_callables(gmoccapy_row, gmoccapy_stdglue) + mark_shared_row_ready(gmoccapy_row, modules_staged=1, ngc_assets_staged=1) + +twp_rows = [ + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/circular_pocket.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition.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/simple_example.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/square.ngc", +] +twp_ngc_assets = [ + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/428remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/429remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/430remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g531remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g533remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g536remap.ngc", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g69remap.ngc", +] +twp_modules = stage_row( + twp_rows[0], + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini", + [ + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/toplevel.py", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/twp-helper-comp.py", + "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/util.py", + ], + twp_ngc_assets, + twp_ngc_assets, +) +twp_remap = twp_modules["axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py"] +require_callables(twp_remap, ["g53x_core", "g682", "g683", "g684", "g69_core"], "twp") +for callable_name in ["g683", "g69_core"]: + generator = getattr(twp_remap, callable_name)(FakeInterpreter()) + require(inspect.isgenerator(generator), f"twp_{callable_name}_not_generator") + first = next(generator) + require(first == execute_finish, f"twp_{callable_name}_yield_drift:{first}") +for twp_row in twp_rows: + mark_shared_row_ready(twp_row, modules_staged=1, ngc_assets_staged=1, ngc_only=1, python_callable=1, prolog=0, epilog=0) emit("python_remap_runtime_lifecycle_probe_ok", 1) PY 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 05c58a3..a34bdee 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 @@ -58,6 +58,19 @@ const virtualHalSimConfigMacroLoadFixtures = createVirtualHalSimConfigMacroLoadF manifestText: sourceManifestText, }); const virtualHalState = createVirtualHalState(); +const pythonRemapRuntimeProof = { + nativeLifecycleReady: true, + nativeProbeStatus: "runtime_lifecycle_probe_passed", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, +}; const browserDiagnosticsArtifact = { apiName: "real-browser-simulation-diagnostics-artifact", artifactVersion: 1, @@ -113,6 +126,7 @@ const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ evidenceExpansionRows, virtualHalSimConfigMacroLoadFixtures, virtualHalMotionControllerMatrix: browserDiagnosticsArtifact.virtualHalMotionControllerMatrix, + pythonRemapRuntimeProof, })); let fetchedUrl = null; @@ -380,6 +394,8 @@ assert.deepEqual( ["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"], + ["python-remap-runtime-proof", "ready"], + ["python-remap-runtime-proof-detail", "native=ready wasm=ready browser=ready promotion_allowed=0"], ...expectedPromotionFamilyRows, ["browser-diagnostics", "ready"], ["virtual-hal-promotion-candidate-summary", "ready"], 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 7d1cada..d1d114f 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 @@ -31,6 +31,19 @@ const virtualHalSimConfigPromotionCandidates = createVirtualHalSimConfigPromotio motionControllerMatrix: virtualHalMotionControllerMatrix, }); const virtualHalSimConfigMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixtureReport(); +const pythonRemapRuntimeProof = { + nativeLifecycleReady: true, + nativeProbeStatus: "runtime_lifecycle_probe_passed", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, +}; assert.equal(manifest.apiName, "project-release-gate-manifest"); assert.equal(manifest.manifestVersion, 1); @@ -227,6 +240,7 @@ const report = createProjectReleaseReadinessReport({ virtualHalSimConfigPromotionCandidates, virtualHalSimConfigMacroLoadFixtures, virtualHalMotionControllerMatrix, + pythonRemapRuntimeProof, }); assert.deepEqual(report.gateManifest, manifest); assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); @@ -324,6 +338,16 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { label: "Tool DB process proof detail", value: "wasm=ready browser=ready opfs=ready promotion_allowed=0", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: "ready", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: "native=ready wasm=ready browser=ready promotion_allowed=0", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", @@ -353,7 +377,7 @@ assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { }); assert.equal( createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine, - "Waiting: project-release-gate, virtual-hal-sim-config-source-coverage, virtual-hal-sim-config-promotion-candidates, virtual-hal-sim-config-macro-load-fixtures, virtual-hal-motion-controller-matrix", + "Waiting: project-release-gate, virtual-hal-sim-config-source-coverage, virtual-hal-sim-config-promotion-candidates, virtual-hal-sim-config-macro-load-fixtures, virtual-hal-motion-controller-matrix, python-remap-runtime-proof", ); assert.equal( createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({ @@ -362,6 +386,7 @@ assert.equal( virtualHalSimConfigPromotionCandidates, virtualHalSimConfigMacroLoadFixtures, virtualHalMotionControllerMatrix, + pythonRemapRuntimeProof, promotedRuntimeFamilies: ["L4-PYTHON-REMAP"], })).rows.find(({ id }) => id === "blocked-runtime-families")?.value, "promoted: L4-PYTHON-REMAP", @@ -428,6 +453,8 @@ assert.deepEqual( ["virtual-hal-motion-controller-matrix", "ready"], ["tool-db-process-proof", "ready"], ["tool-db-process-proof-detail", "wasm=ready browser=ready opfs=ready promotion_allowed=0"], + ["python-remap-runtime-proof", "ready"], + ["python-remap-runtime-proof-detail", "native=ready wasm=ready browser=ready promotion_allowed=0"], ["missing", "none"], ], ); @@ -548,6 +575,8 @@ assert.deepEqual( ["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"], + ["python-remap-runtime-proof", "ready"], + ["python-remap-runtime-proof-detail", "native=ready wasm=ready browser=ready promotion_allowed=0"], ...expectedPromotionFamilyRows, ["browser-diagnostics", "not requested"], ["virtual-hal-promotion-candidate-summary", "not requested"], 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 index 18da808..71d973d 100644 --- a/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs +++ b/wasm-port/tests/sdk/node/verify_python_remap_runtime_port.mjs @@ -5,11 +5,15 @@ import { } from "../../../runtime/sdk/src/python-remap-browser-worker-adapter.js"; import { PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, + PYTHON_REMAP_ROW_RUNTIME_PROOF_PHASES, PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE, + createLinuxCncPythonRemapRowRuntimePort, createLinuxCncPythonRemapRuntimePort, createPythonRemapLifecyclePlan, + createPythonRemapRowRuntimePlan, createPythonRemapRuntimeDiagnostics, validatePythonRemapLifecycleTranscript, + validatePythonRemapRowRuntimeTranscript, } from "../../../runtime/sdk/src/python-remap-runtime-port.js"; assert.equal(PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION, 1); @@ -31,6 +35,133 @@ assert.deepEqual( "export_diagnostics", ], ); +assert.ok(PYTHON_REMAP_ROW_RUNTIME_PROOF_PHASES.includes("export_canonical_events")); + +const rowRuntimeContext = { + path: "axis/remap/cycle/nc_files/examples.ngc", + iniPath: "axis/remap/cycle/cycle.ini", + family: "axis/remap/cycle/nc_files", + machineRoot: "axis/remap/cycle", + pythonPathPrepend: "python", + topLevelPath: "python/toplevel.py", + sourceFiles: [ + "axis/nc_files/remap_lib/python-stdglue/stdglue.py", + "axis/remap/cycle/python/remap.py", + "axis/remap/cycle/python/toplevel.py", + ], + remapDeclarations: ["G84.2"], + pythonRemapFunctions: ["g842@axis/remap/cycle/python/remap.py"], + prologFunctions: ["cycle_prolog@axis/nc_files/remap_lib/python-stdglue/stdglue.py"], + epilogFunctions: ["cycle_epilog@axis/nc_files/remap_lib/python-stdglue/stdglue.py"], + ngcRemapFiles: ["axis/remap/cycle/nc_subroutines/g843.ngc"], + ngcOnlySubpaths: [], +}; +const rowPlan = createPythonRemapRowRuntimePlan(rowRuntimeContext); +assert.equal(rowPlan.filter((step) => step.phase === "import_module").length, 3); +assert.equal(rowPlan.filter((step) => step.phase === "callable_lookup").length, 3); +assert.equal(rowPlan.filter((step) => step.phase === "stage_ngc_remap_asset").length, 1); +assert.ok(rowPlan.some((step) => step.phase === "export_canonical_events")); + +const contractOnlyRowPort = createLinuxCncPythonRemapRowRuntimePort(rowRuntimeContext); +const contractOnlyRowStart = await contractOnlyRowPort.start(); +assert.deepEqual(contractOnlyRowStart, { + runtimeMode: "row-runtime-contract-only", + runtimeExecutionReady: false, + status: null, + executionEnabled: false, + promotionAllowed: false, + bulkPromotionAllowed: false, +}); +const contractOnlyRowPlan = await contractOnlyRowPort.runRowRuntimePlan(); +assert.equal(contractOnlyRowPlan.status, "blocked_row_runtime_adapter_required"); +assert.equal(contractOnlyRowPlan.promotionAllowed, false); +await contractOnlyRowPort.close(); + +const fakeRowAdapter = { + async start(context) { + assert.equal(context.path, rowRuntimeContext.path); + assert.equal(context.sourceFiles.length, 3); + assert.equal(context.ngcRemapFiles.length, 1); + return { + runtimeExecutionReady: true, + status: "row_runtime_ready", + }; + }, + async initializePython() { + return { value: "python-runtime-initialized" }; + }, + async applyIniPythonPath(payload) { + return { value: payload.pythonPathPrepend }; + }, + async executeTopLevel(payload) { + return { value: payload.topLevelPath }; + }, + async importModule(payload) { + return { value: payload.modulePath }; + }, + async lookupCallable(payload) { + return { value: payload.callableName }; + }, + async invokeGenerator(payload) { + return { value: `generator:${payload.callableName}` }; + }, + async observeFirstYield(payload) { + return { value: `yield:${payload.callableName}` }; + }, + async finishGenerator(payload) { + return { value: `finish:${payload.callableName}` }; + }, + async stageNgcRemapAsset(payload) { + return { value: payload.ngcPath }; + }, + async rejectNgcOnlyStandalone(payload) { + return { value: payload.ngcPath }; + }, + async exportInterpreterState() { + return { value: "interpreter-state-bound" }; + }, + async exportCanonicalEvents() { + return { value: "canonical-events-bound" }; + }, + async exportDiagnostics() { + return { value: "diagnostics-exported" }; + }, +}; +const readyRowPort = createLinuxCncPythonRemapRowRuntimePort({ + ...rowRuntimeContext, + runtimeMode: "row-runtime-test-adapter", + runtimeAdapter: fakeRowAdapter, +}); +const readyRowStart = await readyRowPort.start(); +assert.equal(readyRowStart.runtimeExecutionReady, true); +assert.equal(readyRowStart.status, "row_runtime_ready"); +const readyRowPlan = await readyRowPort.runRowRuntimePlan(); +assert.equal(readyRowPlan.status, "row_runtime_adapter_plan_executed"); +assert.equal(readyRowPlan.validation.ready, true); +assert.equal(readyRowPlan.validation.moduleImportReady, true); +assert.equal(readyRowPlan.validation.callableLookupReady, true); +assert.equal(readyRowPlan.validation.generatorLifecycleReady, true); +assert.equal(readyRowPlan.validation.ngcRemapAssetsReady, true); +assert.equal(readyRowPlan.validation.ngcOnlySubpathsGuarded, true); +assert.equal(readyRowPlan.validation.interpreterStateBindingReady, true); +assert.equal(readyRowPlan.validation.canonicalEventsReady, true); +assert.equal(readyRowPlan.promotionAllowed, false); +assert.equal(readyRowPlan.bulkPromotionAllowed, false); +const rowTranscript = readyRowPort.exportTranscript(); +const rowValidation = validatePythonRemapRowRuntimeTranscript({ + ...rowRuntimeContext, + rowPath: rowRuntimeContext.path, + transcript: rowTranscript, +}); +assert.equal(rowValidation.ready, true); +const rowDiagnostics = readyRowPort.exportDiagnostics(); +assert.equal(rowDiagnostics.rowRuntimeTranscriptReady, true); +assert.equal(rowDiagnostics.jsCncSemantics, false); +assert.equal(rowDiagnostics.ngcOnlySubroutinePromoted, false); +assert.equal(rowDiagnostics.executionEnabled, false); +assert.equal(rowDiagnostics.promotionAllowed, false); +assert.equal(rowDiagnostics.bulkPromotionAllowed, false); +await readyRowPort.close(); const contractOnlyPort = createLinuxCncPythonRemapRuntimePort({ runtimeMode: "contract-only", @@ -209,3 +340,12 @@ const explicitDiagnostics = createPythonRemapRuntimeDiagnostics({ }); assert.equal(explicitDiagnostics.transcriptHash.length, 8); assert.equal(explicitDiagnostics.lifecycleTranscriptReady, true); + +const mismatchedTranscript = transcript.map((entry) => + entry.phase === "execute_toplevel" + ? { ...entry, value: "python/not-toplevel.py" } + : entry +); +const mismatchedValidation = validatePythonRemapLifecycleTranscript(mismatchedTranscript); +assert.equal(mismatchedValidation.ready, false); +assert.deepEqual(mismatchedValidation.mismatchedValues, ["execute_toplevel"]); diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 5c3615c..4586c86 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -93,13 +93,16 @@ import { createLinuxCncToolDbBrowserWorkerAdapter, createLinuxCncToolDbProcessPort, createLinuxCncPythonRemapBrowserWorkerAdapter, + createLinuxCncPythonRemapRowRuntimePort, createLinuxCncPythonRemapRuntimePort, createPythonRemapLifecyclePlan, + createPythonRemapRowRuntimePlan, createPythonRemapRuntimeDiagnostics, createToolDbProcessDiagnostics, createToolDbTransactionPlan, runToolDbProcessPortPersistenceSession, validatePythonRemapLifecycleTranscript, + validatePythonRemapRowRuntimeTranscript, createVirtualHalBridgeActionPlan, createVirtualHalBridgeReadiness, createVirtualHalCommandScriptFixtureReport, @@ -521,13 +524,16 @@ const requiredExports = [ ["createLinuxCncToolDbBrowserWorkerAdapter", createLinuxCncToolDbBrowserWorkerAdapter], ["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort], ["createLinuxCncPythonRemapBrowserWorkerAdapter", createLinuxCncPythonRemapBrowserWorkerAdapter], + ["createLinuxCncPythonRemapRowRuntimePort", createLinuxCncPythonRemapRowRuntimePort], ["createLinuxCncPythonRemapRuntimePort", createLinuxCncPythonRemapRuntimePort], ["createPythonRemapLifecyclePlan", createPythonRemapLifecyclePlan], + ["createPythonRemapRowRuntimePlan", createPythonRemapRowRuntimePlan], ["createPythonRemapRuntimeDiagnostics", createPythonRemapRuntimeDiagnostics], ["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics], ["createToolDbTransactionPlan", createToolDbTransactionPlan], ["runToolDbProcessPortPersistenceSession", runToolDbProcessPortPersistenceSession], ["validatePythonRemapLifecycleTranscript", validatePythonRemapLifecycleTranscript], + ["validatePythonRemapRowRuntimeTranscript", validatePythonRemapRowRuntimeTranscript], ["validateToolDbTranscript", validateToolDbTranscript], ["saveToolDbFile", saveToolDbFile], ["loadToolDbFile", loadToolDbFile], @@ -1506,6 +1512,19 @@ assert.equal(normalizeOpfsPath("linuxcnc/machines/sdk-mill/tool.tbl"), "linuxcnc assert.equal(gcodeFilenameFromProgramPath("linuxcnc/gcode/demo.ngc"), "demo.ngc"); const releaseReadinessWaiting = createProjectReleaseReadinessReport(); +const pythonRemapRuntimeProof = { + nativeLifecycleReady: true, + nativeProbeStatus: "runtime_lifecycle_probe_passed", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, +}; const batchCapabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities: [ { @@ -1621,6 +1640,7 @@ assert.deepEqual(releaseReadinessWaiting.missing, [ "virtual-hal-sim-config-promotion-candidates", "virtual-hal-sim-config-macro-load-fixtures", "virtual-hal-motion-controller-matrix", + "python-remap-runtime-proof", ]); assert.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0); assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []); @@ -1645,6 +1665,7 @@ const releaseReadinessReady = createProjectReleaseReadinessReport({ virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport, virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport, virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport, + pythonRemapRuntimeProof, }); assert.equal(releaseReadinessReady.ready, true); assert.equal(releaseReadinessReady.releaseGate.passed, true); @@ -1698,6 +1719,9 @@ const expectedToolDbProcessProofSummary = { }; assert.deepEqual(releaseReadinessReady.toolDbProcessProofSummary, expectedToolDbProcessProofSummary); assert.equal(releaseReadinessReady.toolDbProcessProofReady, true); +assert.equal(releaseReadinessReady.pythonRemapRuntimeProofReady, true); +assert.equal(releaseReadinessReady.pythonRemapRuntimeProofSummary.nativeLifecycleReady, true); +assert.equal(releaseReadinessReady.pythonRemapRuntimeProofSummary.promotionAllowed, false); assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest()); assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount); assert.equal(createProjectReleaseGateActionPlan({ @@ -1723,6 +1747,7 @@ const releaseReadinessWithCandidateArtifacts = createProjectReleaseReadinessRepo virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport, promotionCandidateRows: promotionCandidateArtifactRows, evidenceExpansionRows: evidenceExpansionArtifactRows, + pythonRemapRuntimeProof, }); assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.promotionCandidates, 27); assert.equal(releaseReadinessWithCandidateArtifacts.promotionCandidateArtifactSummary.artifactRows.evidenceExpansion, 14); @@ -1792,6 +1817,7 @@ const releaseReadinessWithScreenshots = createProjectReleaseReadinessReport({ virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport, virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport, virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport, + pythonRemapRuntimeProof, axisScreenshotArtifacts: ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"].map((viewportName) => ({ apiName: "real-browser-simulation-axis-screenshot-artifact", artifactVersion: 1, @@ -1829,6 +1855,7 @@ const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.s virtualHalSimConfigPromotionCandidates: simConfigPromotionCandidateReport, virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport, virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport, + pythonRemapRuntimeProof, }))); assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report"); const releaseReadinessArtifactValidation = createProjectReleaseReadinessArtifactValidation(releaseReadinessArtifact); @@ -1858,6 +1885,39 @@ assert.deepEqual(releaseReadinessArtifactValidation, { virtualHalMotionControllerMatrixReady: true, toolDbProcessProofSummary: expectedToolDbProcessProofSummary, toolDbProcessProofReady: true, + pythonRemapRuntimeProofSummary: { + apiName: "python-remap-runtime-proof-summary", + summaryVersion: 1, + phase: "ready", + ready: true, + boundaryClass: "L4-PYTHON-REMAP", + 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", + callableName: "queuebuster", + runtimeMode: "browser-python-wasm-worker", + nativeProbeStatus: "runtime_lifecycle_probe_passed", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + nativeProofStdoutLog: null, + nativeLifecycleReady: true, + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, + requiredCommands: [ + "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 wasm-port/tests/native/probe_python_remap_runtime.sh", + "wasm-port/tests/sdk/node/verify_python_remap_runtime_port.sh", + "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_python_remap_runtime_port_wasm.sh", + "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_python_remap_runtime_browser.sh", + ], + }, + pythonRemapRuntimeProofReady: true, axisScreenshotArtifactSummaryReady: true, axisScreenshotArtifactCount: 0, blockedRuntimeFamilies: [ @@ -1993,6 +2053,16 @@ assert.deepEqual(releaseReadinessArtifactValidation, { label: "Tool DB process proof detail", value: "wasm=ready browser=ready opfs=ready promotion_allowed=0", }, + { + id: "python-remap-runtime-proof", + label: "Python remap runtime proof", + value: "ready", + }, + { + id: "python-remap-runtime-proof-detail", + label: "Python remap runtime proof detail", + value: "native=ready wasm=ready browser=ready promotion_allowed=0", + }, { id: "blocked-runtime-families", label: "Blocked runtime families", diff --git a/wasm-port/tests/ui/node/verify_ini_panel_launch_api_manifest.mjs b/wasm-port/tests/ui/node/verify_ini_panel_launch_api_manifest.mjs index b9ed509..ae5e96f 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_launch_api_manifest.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_launch_api_manifest.mjs @@ -94,6 +94,19 @@ assert.deepEqual(manifest, { "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState", + "getWorkflowOverviewPromotionFamilyRowsRenderState", + "getWorkflowOverviewPromotionFamilySourceFilterViewModel", + "getWorkflowOverviewPromotionFamilySourceFilterDomContract", + "getWorkflowOverviewPromotionFamilySourceFilterDomReadiness", + "getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan", + "handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange", + "bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls", + "renderWorkflowOverviewPromotionFamilySourceFilterState", + "mountWorkflowOverviewPromotionFamilySourceFilterState", + "getWorkflowOverviewPromotionFamilyRowsDomContract", + "getWorkflowOverviewPromotionFamilyRowsDomReadiness", + "renderWorkflowOverviewPromotionFamilyRowsState", + "mountWorkflowOverviewPromotionFamilyRowsState", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness", "renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState", diff --git a/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs b/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs index 26e6ae9..fe798d1 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_shell_integration_manifest.mjs @@ -216,7 +216,7 @@ assert.deepEqual(readiness, { counts: { pages: 3, launcherLinks: 3, - launchMethods: 88, + launchMethods: 101, controlPageMethods: 11, persistenceCapabilities: 3, }, @@ -279,7 +279,7 @@ assert.deepEqual(handoffSummary, { counts: { pages: 3, launcherLinks: 3, - launchMethods: 88, + launchMethods: 101, controlPageMethods: 11, persistenceCapabilities: 3, }, @@ -826,11 +826,24 @@ assert.deepEqual(createIniPanelShellWorkflowOverviewContract(), { "getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness", "renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState", "mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState", - "loadWorkflowOverviewReleaseReadinessArtifactUrl", - "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel", - "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan", - "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState", - "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract", + "loadWorkflowOverviewReleaseReadinessArtifactUrl", + "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel", + "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan", + "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState", + "getWorkflowOverviewPromotionFamilyRowsRenderState", + "getWorkflowOverviewPromotionFamilySourceFilterViewModel", + "getWorkflowOverviewPromotionFamilySourceFilterDomContract", + "getWorkflowOverviewPromotionFamilySourceFilterDomReadiness", + "getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan", + "handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange", + "bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls", + "renderWorkflowOverviewPromotionFamilySourceFilterState", + "mountWorkflowOverviewPromotionFamilySourceFilterState", + "getWorkflowOverviewPromotionFamilyRowsDomContract", + "getWorkflowOverviewPromotionFamilyRowsDomReadiness", + "renderWorkflowOverviewPromotionFamilyRowsState", + "mountWorkflowOverviewPromotionFamilyRowsState", + "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness", "renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState", "mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState", @@ -965,6 +978,19 @@ assert.deepEqual( "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState", + "getWorkflowOverviewPromotionFamilyRowsRenderState", + "getWorkflowOverviewPromotionFamilySourceFilterViewModel", + "getWorkflowOverviewPromotionFamilySourceFilterDomContract", + "getWorkflowOverviewPromotionFamilySourceFilterDomReadiness", + "getWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan", + "handleWorkflowOverviewPromotionFamilySourceFilterSelectionChange", + "bindWorkflowOverviewPromotionFamilySourceFilterSelectionControls", + "renderWorkflowOverviewPromotionFamilySourceFilterState", + "mountWorkflowOverviewPromotionFamilySourceFilterState", + "getWorkflowOverviewPromotionFamilyRowsDomContract", + "getWorkflowOverviewPromotionFamilyRowsDomReadiness", + "renderWorkflowOverviewPromotionFamilyRowsState", + "mountWorkflowOverviewPromotionFamilyRowsState", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness", "renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState", @@ -1408,7 +1434,7 @@ assert.deepEqual(createIniPanelShellIntegrationReadiness(blockedManifest), { counts: { pages: 3, launcherLinks: 3, - launchMethods: 88, + launchMethods: 101, controlPageMethods: 11, persistenceCapabilities: 3, }, 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 9a90afc..43c436c 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 @@ -1473,6 +1473,19 @@ const releaseReadinessMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixt "linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc", ], }); +const pythonRemapRuntimeProof = { + nativeLifecycleReady: true, + nativeProbeStatus: "runtime_lifecycle_probe_passed", + nativeProofArtifactPath: "wasm-port/build/native/native-runtime-probe-summary.tsv", + wasmLifecycleReady: true, + browserLifecycleReady: true, + interpreterStateBindingReady: true, + generatorLifecycleReady: true, + promotionAllowed: false, + executionEnabled: false, + bulkPromotionAllowed: false, + manualLockUpdateRequired: true, +}; const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(), @@ -1482,6 +1495,7 @@ const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadines evidenceExpansionRows, virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures, virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix, + pythonRemapRuntimeProof, })); const browserDiagnosticsVirtualHalState = createVirtualHalState(); const browserDiagnosticsArtifact = { @@ -1833,7 +1847,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead rowsNode: releaseArtifactRowsNode, }); assert.equal(releaseArtifactRenderResult.rendered, true); -assert.equal(releaseArtifactRenderResult.rowCount, 32); +assert.equal(releaseArtifactRenderResult.rowCount, 34); assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact"); assert.equal( releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), @@ -1887,8 +1901,12 @@ assert.equal( releaseArtifactRenderResult.rowIds.includes("tool-db-process-proof"), true, ); +assert.equal( + releaseArtifactRenderResult.rowIds.includes("python-remap-runtime-proof"), + true, +); assert.equal(releaseArtifactMountResult.ready, true); -assert.equal(releaseArtifactMountResult.renderResult.rowCount, 32); +assert.equal(releaseArtifactMountResult.renderResult.rowCount, 34); assert.deepEqual( validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing, ["artifact-json"], @@ -1947,7 +1965,11 @@ assert.equal( ); assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons"); assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true); -assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 32); +assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 34); +assert.equal( + releaseArtifactUrlWorkflow.mountResult.renderResult.rowIds.includes("python-remap-runtime-proof"), + true, +); const releaseArtifactUrlWorkflowSummary = createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel( releaseArtifactUrlWorkflow, @@ -2037,11 +2059,11 @@ assert.equal( ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value, - "28", + "27", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value, - "13", + "14", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-baseline-changing")?.value, @@ -2077,7 +2099,7 @@ assert.equal( ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-lock")?.value, - "locked (3 families)", + "locked (2 families)", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value, @@ -2085,11 +2107,11 @@ assert.equal( ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value, - "19", + "18", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc", + "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( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value, @@ -2097,19 +2119,19 @@ assert.equal( ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value, - "3", + "2", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value, - "L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", + "L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value, - "19", + "18", ); assert.equal( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value, - "L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0", + "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( releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-PYTHON-REMAP")?.value, @@ -2819,7 +2841,7 @@ const releaseArtifactUrlWorkflowMountResult = rowsNode: releaseArtifactUrlWorkflowRowsNode, }); assert.equal(releaseArtifactUrlWorkflowRenderResult.rendered, true); -assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 59); +assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 57); assert.equal( releaseArtifactUrlWorkflowRenderResult.rowIds.includes("browser-diagnostics"), true, @@ -2869,7 +2891,7 @@ assert.equal( true, ); assert.equal(releaseArtifactUrlWorkflowMountResult.ready, true); -assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 59); +assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 57); const releaseArtifactUrlWorkflowMissingDom = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({ artifactUrl: "/project-release-readiness.json", fetchRef: async () => ({ 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 e2e2e74..6f36c18 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 @@ -91,6 +91,8 @@ const pythonRemapNativeRuntimeStatePlanPath = resolve(buildDir, "python-remap-na const pythonRemapNativeRuntimeFixturePlanPath = resolve(buildDir, "python-remap-native-runtime-fixture-plan.tsv"); const pythonRemapFamilySummaryPath = resolve(buildDir, "python-remap-family-summary.tsv"); const pythonRemapRuntimeContractPath = resolve(buildDir, "python-remap-runtime-contract.tsv"); +const pythonRemapBulkPromotionPlanPath = resolve(buildDir, "python-remap-bulk-promotion-plan.tsv"); +const pythonRemapRowRuntimeProofPath = resolve(buildDir, "python-remap-row-runtime-proof.tsv"); const runtimeBoundaryContractSummaryPath = resolve(buildDir, "runtime-boundary-contract-summary.tsv"); const boundaryPhaseCompletionSummaryPath = resolve(buildDir, "boundary-phase-completion-summary.tsv"); const nextBoundaryWorklistPath = resolve(buildDir, "next-boundary-worklist.tsv"); @@ -148,6 +150,8 @@ const generatedSimConfigInventoryArtifactPaths = [ pythonRemapNativeRuntimeFixturePlanPath, pythonRemapFamilySummaryPath, pythonRemapRuntimeContractPath, + pythonRemapBulkPromotionPlanPath, + pythonRemapRowRuntimeProofPath, runtimeBoundaryContractSummaryPath, boundaryPhaseCompletionSummaryPath, nextBoundaryWorklistPath, @@ -4686,6 +4690,7 @@ function verifyPythonRemapNativeRuntimeFixturePlanRows(rows, statePlanRows) { function pythonRemapNativeRuntimeProbeGateRows({ fixturePlanRows, readinessRows, + nativeRuntimeProbeRows = [], }) { const fixture = fixturePlanRows[0]; if (!fixture) { @@ -4729,6 +4734,13 @@ function pythonRemapNativeRuntimeProbeGateRows({ const gateStatus = runtimeReady && sourceProofReady ? "ready_to_implement_lifecycle_probe" : "blocked_missing_host_runtime"; + const nativePythonProbe = nativeRuntimeProbeRows.find((row) => + row.boundary_class === "L4-PYTHON-REMAP" && + row.runtime_probe === "linuxcnc_python_remap_runtime_probe", + ); + const lifecycleProbePassed = nativePythonProbe?.probe_status === "runtime_lifecycle_probe_passed"; + const effectiveGateStatus = lifecycleProbePassed ? "runtime_lifecycle_probe_passed" : gateStatus; + const proofStatus = lifecycleProbePassed ? "native_lifecycle_probe_passed" : "pending"; return [[ fixture.family, @@ -4748,11 +4760,13 @@ function pythonRemapNativeRuntimeProbeGateRows({ flagValue(runtimeReady), flagValue(sourceProofReady), fixture.proof_kind, - gateStatus, - "pending", + effectiveGateStatus, + proofStatus, "0", "0", - runtimeReady + lifecycleProbePassed + ? "native_lifecycle_probe_passed_without_promotion_python3_alone_not_sufficient" + : runtimeReady ? "host_and_source_ready_write_linuxcnc_python_lifecycle_probe_python3_alone_not_sufficient" : "python3_alone_not_sufficient_keep_python_remap_blocked", ].map(tsvValue).join("\t")]; @@ -4762,6 +4776,7 @@ function verifyPythonRemapNativeRuntimeProbeGateRows({ rows, fixturePlanRows, readinessRows, + nativeRuntimeProbeRows = [], }) { const expectedHeaders = [ "family", @@ -4827,19 +4842,32 @@ function verifyPythonRemapNativeRuntimeProbeGateRows({ readinessByRequirement.get(requirement)?.available !== "1", ); const allRuntimeReady = expectedMissing.length === 0; + const nativePythonProbe = nativeRuntimeProbeRows.find((entry) => + entry.boundary_class === "L4-PYTHON-REMAP" && + entry.runtime_probe === "linuxcnc_python_remap_runtime_probe", + ); + const lifecycleProbePassed = nativePythonProbe?.probe_status === "runtime_lifecycle_probe_passed"; for (const requirement of expectedRequirements) { assert.ok(row.runtime_requirements.includes(`${requirement}:`), `${row.family}: missing runtime requirement ${requirement}`); } assert.equal(row.runtime_ready, flagValue(allRuntimeReady), `${row.family}: runtime readiness drift`); assert.equal(row.source_proof_ready, "1", `${row.family}: source proof must be ready before runtime probe implementation`); assert.equal(row.required_native_proof, "linuxcnc_python_runtime_lifecycle_probe_required", `${row.family}: native proof drift`); - assert.equal(row.proof_status, "pending", `${row.family}: probe gate must remain pending`); + assert.equal( + row.proof_status, + lifecycleProbePassed ? "native_lifecycle_probe_passed" : "pending", + `${row.family}: probe gate proof status drift`, + ); assert.equal(row.execution_enabled, "0", `${row.family}: probe gate must not enable execution`); assert.equal(row.promotion_allowed, "0", `${row.family}: probe gate must not allow promotion`); if (allRuntimeReady) { assert.equal(row.missing_requirements, "-", `${row.family}: ready runtime has missing requirements`); - assert.equal(row.gate_status, "ready_to_implement_lifecycle_probe", `${row.family}: ready gate status drift`); + assert.equal( + row.gate_status, + lifecycleProbePassed ? "runtime_lifecycle_probe_passed" : "ready_to_implement_lifecycle_probe", + `${row.family}: ready gate status drift`, + ); assert.ok(row.notes.includes("python3_alone_not_sufficient"), `${row.family}: ready gate must still reject python3-only proof`); } else { assert.deepEqual( @@ -4854,6 +4882,514 @@ function verifyPythonRemapNativeRuntimeProbeGateRows({ return parsedRows; } +function pythonRemapBulkPromotionPlanRows({ + pythonRows, + familyRows, + contractRows, + fixturePlanRows, + probeGateRows, +}) { + const familyByName = new Map(familyRows.map((row) => [row.family, row])); + const contractByFamily = new Map(contractRows.map((row) => [row.family, row])); + const fixtureFamilies = new Set(fixturePlanRows.map((row) => row.family)); + const probeGateByFamily = new Map(probeGateRows.map((row) => [row.family, row])); + + return pythonRows.map((row) => { + const family = posix.dirname(row.path); + const familyRow = familyByName.get(family); + const contract = contractByFamily.get(family); + const probeGate = probeGateByFamily.get(family); + assert.ok(familyRow, `${row.path}: bulk promotion plan lacks family row`); + assert.ok(contract, `${row.path}: bulk promotion plan lacks runtime contract row`); + + const hasPythonRemapCallable = row.python_remap_functions !== "-"; + const hasProlog = row.prolog_functions !== "-"; + const hasEpilog = row.epilog_functions !== "-"; + const hasNgcRemap = row.remap_ngc_files !== "-"; + const hasNgcOnly = row.ngc_only_subpaths !== "-"; + const blockers = uniqueSorted([ + "python_runtime", + "row_runtime_proof", + hasPythonRemapCallable ? "python_remap_callable" : null, + hasProlog || hasEpilog ? "prolog_epilog" : null, + hasNgcRemap ? "ngc_remap_asset" : null, + hasNgcOnly ? "ngc_only_subpaths_not_standalone" : null, + row.hal_process === "1" ? "hal_process_boundary" : null, + row.ui_process === "1" ? "ui_process_boundary" : null, + row.halui_mdi_process === "1" ? "halui_mdi_boundary" : null, + row.notes.includes("tool") ? "tool_change_boundary" : null, + family.includes("table-rotary_spindle-rotary-nutating") ? "twp_boundary" : null, + "native_row_pass", + "wasm_node_row_pass", + "browser_row_pass", + "manual_promotion_lock", + ].filter(Boolean)); + const notes = uniqueSorted([ + ...splitSummaryList(row.notes), + fixtureFamilies.has(family) + ? "fixture_family_lifecycle_proof_not_bulk_row_pass" + : "awaiting_row_runtime_proof", + probeGate?.proof_status === "native_lifecycle_probe_passed" + ? "native_lifecycle_probe_passed_without_promotion" + : "native_lifecycle_probe_not_sufficient_for_bulk_promotion", + "bulk_plan_only_no_execution_no_promotion", + ]); + + return [ + row.path, + row.ini, + family, + "main_inventory_row", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + hasNgcOnly ? "0" : "1", + "0", + "0", + row.hal_process === "1" ? "0" : "1", + row.ui_process === "1" ? "0" : "1", + row.halui_mdi_process === "1" ? "0" : "1", + "0", + "0", + "0", + "0", + "0", + listValue(blockers), + listValue(notes), + ].map(tsvValue).join("\t"); + }); +} + +function verifyPythonRemapBulkPromotionPlanRows({ + rows, + pythonRows, + familyRows, + contractRows, +}) { + const expectedHeaders = [ + "path", + "ini", + "family", + "main_or_macro_load_class", + "python_modules_staged", + "toplevel_executed", + "path_prepend_applied", + "python_remap_functions_bound", + "prolog_functions_bound", + "epilog_functions_bound", + "ngc_remap_assets_staged", + "ngc_only_subpaths_not_standalone", + "interpreter_state_binding_ready", + "canonical_events_ready", + "hal_state_boundary_ready", + "ui_process_boundary_ready", + "halui_mdi_boundary_ready", + "native_pass_ready", + "wasm_node_pass_ready", + "browser_pass_ready", + "execution_enabled", + "promotion_allowed", + "blockers", + "notes", + ]; + const parsedRows = parseTsv( + `${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`, + expectedHeaders, + ); + const pythonByPath = new Map(pythonRows.map((row) => [row.path, row])); + const familyByName = new Map(familyRows.map((row) => [row.family, row])); + const contractByFamily = new Map(contractRows.map((row) => [row.family, row])); + + verifySamePathSet( + "wasm_python_remap_bulk_promotion_plan", + pythonRows.map((row) => row.path).sort(), + parsedRows.map((row) => row.path).sort(), + ); + assert.equal(parsedRows.length, 53, "Python-remap bulk promotion plan must cover all 53 rows"); + + for (const row of parsedRows) { + const source = pythonByPath.get(row.path); + assert.ok(source, `${row.path}: bulk promotion source row missing`); + const family = familyByName.get(row.family); + const contract = contractByFamily.get(row.family); + assert.ok(family, `${row.path}: bulk promotion family row missing`); + assert.ok(contract, `${row.path}: bulk promotion contract row missing`); + assert.equal(row.ini, source.ini, `${row.path}: bulk promotion INI drift`); + assert.equal(row.family, posix.dirname(row.path), `${row.path}: bulk promotion family drift`); + assert.equal(row.main_or_macro_load_class, "main_inventory_row", `${row.path}: bulk promotion class drift`); + assert.equal(row.python_modules_staged, "0", `${row.path}: module staging proof must remain pending`); + assert.equal(row.toplevel_executed, "0", `${row.path}: plan must not claim TOPLEVEL execution`); + assert.equal(row.path_prepend_applied, "0", `${row.path}: plan must not claim PATH_PREPEND applied`); + assert.equal(row.python_remap_functions_bound, "0", `${row.path}: Python callable binding proof must remain pending`); + assert.equal(row.prolog_functions_bound, "0", `${row.path}: prolog binding proof must remain pending`); + assert.equal(row.epilog_functions_bound, "0", `${row.path}: epilog binding proof must remain pending`); + assert.equal(row.ngc_remap_assets_staged, "0", `${row.path}: NGC remap asset staging proof must remain pending`); + assert.equal( + row.ngc_only_subpaths_not_standalone, + source.ngc_only_subpaths === "-" ? "1" : "0", + `${row.path}: NGC-only standalone guard drift`, + ); + assert.equal(row.interpreter_state_binding_ready, "0", `${row.path}: interpreter state binding must remain pending`); + assert.equal(row.canonical_events_ready, "0", `${row.path}: canonical events must remain pending`); + assert.equal(row.hal_state_boundary_ready, source.hal_process === "1" ? "0" : "1", `${row.path}: HAL boundary flag drift`); + assert.equal(row.ui_process_boundary_ready, source.ui_process === "1" ? "0" : "1", `${row.path}: UI boundary flag drift`); + assert.equal(row.halui_mdi_boundary_ready, source.halui_mdi_process === "1" ? "0" : "1", `${row.path}: HALUI boundary flag drift`); + for (const field of [ + "native_pass_ready", + "wasm_node_pass_ready", + "browser_pass_ready", + "execution_enabled", + "promotion_allowed", + ]) { + assert.equal(row[field], "0", `${row.path}: bulk promotion plan must keep ${field}=0`); + } + assert.notEqual(row.blockers, "-", `${row.path}: bulk promotion plan needs blockers`); + for (const blocker of [ + "python_runtime", + "row_runtime_proof", + "native_row_pass", + "wasm_node_row_pass", + "browser_row_pass", + "manual_promotion_lock", + ]) { + assert.ok(row.blockers.includes(blocker), `${row.path}: missing blocker ${blocker}`); + } + if (source.ngc_only_subpaths !== "-") { + assert.ok(row.blockers.includes("ngc_only_subpaths_not_standalone"), `${row.path}: missing NGC-only blocker`); + assert.ok(row.blockers.includes("ngc_remap_asset"), `${row.path}: missing NGC remap asset blocker`); + } + if (source.hal_process === "1") { + assert.ok(row.blockers.includes("hal_process_boundary"), `${row.path}: missing HAL process blocker`); + } + if (source.ui_process === "1") { + assert.ok(row.blockers.includes("ui_process_boundary"), `${row.path}: missing UI process blocker`); + } + if (source.halui_mdi_process === "1") { + assert.ok(row.blockers.includes("halui_mdi_boundary"), `${row.path}: missing HALUI MDI blocker`); + } + assert.equal(contract.blocked, "L4-PYTHON-REMAP", `${row.path}: contract blocked kind drift`); + assert.ok(row.notes.includes("bulk_plan_only_no_execution_no_promotion"), `${row.path}: plan-only note missing`); + } + + return parsedRows; +} + +function pythonRemapRowRuntimeProofRows({ + pythonRows, + bulkPlanRows, + nativeRuntimeProbeRows = [], +}) { + const bulkByPath = new Map(bulkPlanRows.map((row) => [row.path, row])); + + return pythonRows.map((row) => { + const bulk = bulkByPath.get(row.path); + assert.ok(bulk, `${row.path}: row runtime proof lacks bulk promotion plan row`); + const rowEvidence = pythonRemapRowRuntimeEvidence({ + nativeRuntimeProbeRows, + row, + }); + const hasPythonRemapCallable = row.python_remap_functions !== "-"; + const hasProlog = row.prolog_functions !== "-"; + const hasEpilog = row.epilog_functions !== "-"; + const hasCallables = hasPythonRemapCallable || hasProlog || hasEpilog; + const hasNgcRemap = row.remap_ngc_files !== "-"; + const hasNgcOnly = row.ngc_only_subpaths !== "-"; + const proofTargets = uniqueSorted([ + "ini_python_path_prepend", + "ini_python_toplevel", + "configured_module_import", + hasPythonRemapCallable ? "python_remap_callable_lookup" : null, + hasProlog ? "stdglue_prolog_lookup" : null, + hasEpilog ? "stdglue_epilog_lookup" : null, + hasCallables ? "generator_lifecycle" : null, + hasNgcRemap ? "ngc_remap_asset_staging" : null, + hasNgcOnly ? "ngc_only_subpath_rejection" : null, + "interpreter_state_binding", + "canonical_event_bridge", + ].filter(Boolean)); + const blockers = uniqueSorted([ + "row_runtime_adapter_execution", + "native_row_pass", + "wasm_node_row_pass", + "browser_row_pass", + "manual_promotion_lock", + ...splitSummaryList(bulk.blockers), + ].filter((blocker) => !(rowEvidence.ready && blocker === "row_runtime_adapter_execution"))); + const notes = uniqueSorted([ + ...splitSummaryList(row.notes), + "row_runtime_port_api_ready", + "row_runtime_plan_ready", + rowEvidence.ready + ? row.path === "axis/remap/stop-lookahead/nc_files/examples.ngc" + ? "stop_lookahead_native_lifecycle_stdout_row_transcript_ready_no_inventory_promotion" + : "native_lifecycle_stdout_row_transcript_ready_no_inventory_promotion" + : "row_runtime_proof_pending_no_inventory_promotion", + ]); + + return [ + row.path, + row.ini, + posix.dirname(row.path), + "1", + "1", + listValue(proofTargets), + flagValue(rowEvidence.pythonModulesStaged), + flagValue(rowEvidence.toplevelExecuted), + flagValue(rowEvidence.pathPrependApplied), + flagValue(rowEvidence.pythonRemapFunctionsBound), + flagValue(rowEvidence.prologFunctionsBound), + flagValue(rowEvidence.epilogFunctionsBound), + flagValue(rowEvidence.generatorLifecycleReady), + flagValue(rowEvidence.ngcRemapAssetsStaged), + flagValue(rowEvidence.ngcOnlySubpathsNotStandalone || !hasNgcOnly), + flagValue(rowEvidence.interpreterStateBindingReady), + flagValue(rowEvidence.canonicalEventsReady), + flagValue(rowEvidence.rowRuntimeTranscriptReady), + "0", + "0", + "0", + "0", + rowEvidence.ready + ? "native_lifecycle_stdout_row_runtime_transcript_ready" + : "pending_row_runtime_adapter_execution", + listValue(blockers), + listValue(notes), + ].map(tsvValue).join("\t"); + }); +} + +function pythonRemapRuntimeStdoutValues(nativeRuntimeProbeRows) { + const nativeRow = nativeRuntimeProbeRows.find((row) => + row.boundary_class === "L4-PYTHON-REMAP" && + row.probe_status === "runtime_lifecycle_probe_passed" && + row.execution_enabled === "0" && + row.promotion_allowed === "0", + ); + if (!nativeRow?.stdout_log) { + return new Map(); + } + const stdoutPath = resolve(dirname(nativeRuntimeProbeSummarySourcePath), nativeRow.stdout_log); + if (!existsSync(stdoutPath)) { + return new Map(); + } + return keyValuesFromText(readFileSync(stdoutPath, "utf8")); +} + +function pythonRemapRowRuntimeEvidence({ + nativeRuntimeProbeRows, + row, +}) { + const values = pythonRemapRuntimeStdoutValues(nativeRuntimeProbeRows); + const rowPrefix = `python_remap_row_${rowRuntimeProofId(row.path)}_`; + const requiredPairs = new Map([ + [`${rowPrefix}runtime_probe_status`, "runtime_lifecycle_probe_passed"], + [`${rowPrefix}path_prepend_applied`, "1"], + [`${rowPrefix}toplevel_executed`, "1"], + [`${rowPrefix}python_modules_staged`, "1"], + [`${rowPrefix}interpreter_state_binding_ready`, "1"], + [`${rowPrefix}canonical_events_ready`, "1"], + [`${rowPrefix}row_runtime_transcript_ready`, "1"], + ]); + if (row.python_remap_functions !== "-") { + requiredPairs.set(`${rowPrefix}python_remap_functions_bound`, "1"); + } + if (row.prolog_functions !== "-") { + requiredPairs.set(`${rowPrefix}prolog_functions_bound`, "1"); + } + if (row.epilog_functions !== "-") { + requiredPairs.set(`${rowPrefix}epilog_functions_bound`, "1"); + } + if (row.remap_ngc_files !== "-") { + requiredPairs.set(`${rowPrefix}ngc_remap_assets_staged`, "1"); + } + if (row.ngc_only_subpaths !== "-") { + requiredPairs.set(`${rowPrefix}ngc_only_subpaths_not_standalone`, "1"); + } + const ready = [...requiredPairs.entries()].every(([key, value]) => values.get(key) === value); + return { + ready, + values, + pythonModulesStaged: values.get(`${rowPrefix}python_modules_staged`) === "1", + toplevelExecuted: values.get(`${rowPrefix}toplevel_executed`) === "1", + pathPrependApplied: values.get(`${rowPrefix}path_prepend_applied`) === "1", + pythonRemapFunctionsBound: values.get(`${rowPrefix}python_remap_functions_bound`) === "1", + prologFunctionsBound: values.get(`${rowPrefix}prolog_functions_bound`) === "1", + epilogFunctionsBound: values.get(`${rowPrefix}epilog_functions_bound`) === "1", + generatorLifecycleReady: values.get(`${rowPrefix}generator_lifecycle_ready`) === "1", + ngcRemapAssetsStaged: values.get(`${rowPrefix}ngc_remap_assets_staged`) === "1", + ngcOnlySubpathsNotStandalone: values.get(`${rowPrefix}ngc_only_subpaths_not_standalone`) === "1", + interpreterStateBindingReady: values.get(`${rowPrefix}interpreter_state_binding_ready`) === "1", + canonicalEventsReady: values.get(`${rowPrefix}canonical_events_ready`) === "1", + rowRuntimeTranscriptReady: values.get(`${rowPrefix}row_runtime_transcript_ready`) === "1", + }; +} + +function rowRuntimeProofId(path) { + return path.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase(); +} + +function verifyPythonRemapRowRuntimeProofRows({ + rows, + pythonRows, + bulkPlanRows, + nativeRuntimeProbeRows = [], +}) { + const expectedHeaders = [ + "path", + "ini", + "family", + "row_runtime_port_api_ready", + "row_runtime_plan_ready", + "row_runtime_proof_targets", + "python_modules_staged", + "toplevel_executed", + "path_prepend_applied", + "python_remap_functions_bound", + "prolog_functions_bound", + "epilog_functions_bound", + "generator_lifecycle_ready", + "ngc_remap_assets_staged", + "ngc_only_subpaths_not_standalone", + "interpreter_state_binding_ready", + "canonical_events_ready", + "row_runtime_transcript_ready", + "native_pass_ready", + "wasm_node_pass_ready", + "browser_pass_ready", + "execution_enabled", + "proof_status", + "blockers", + "notes", + ]; + const parsedRows = parseTsv( + `${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`, + expectedHeaders, + ); + const pythonByPath = new Map(pythonRows.map((row) => [row.path, row])); + const bulkByPath = new Map(bulkPlanRows.map((row) => [row.path, row])); + + verifySamePathSet( + "wasm_python_remap_row_runtime_proof", + pythonRows.map((row) => row.path).sort(), + parsedRows.map((row) => row.path).sort(), + ); + assert.equal(parsedRows.length, 53, "Python-remap row runtime proof must cover all 53 rows"); + + for (const row of parsedRows) { + const source = pythonByPath.get(row.path); + const bulk = bulkByPath.get(row.path); + assert.ok(source, `${row.path}: row runtime proof source row missing`); + assert.ok(bulk, `${row.path}: row runtime proof bulk plan row missing`); + assert.equal(row.ini, source.ini, `${row.path}: row runtime proof INI drift`); + assert.equal(row.family, posix.dirname(row.path), `${row.path}: row runtime proof family drift`); + assert.equal(row.row_runtime_port_api_ready, "1", `${row.path}: row runtime port API must be ready`); + assert.equal(row.row_runtime_plan_ready, "1", `${row.path}: row runtime plan must be ready`); + const rowEvidence = pythonRemapRowRuntimeEvidence({ + nativeRuntimeProbeRows, + row: source, + }); + const rowReady = rowEvidence.ready; + for (const target of [ + "ini_python_path_prepend", + "ini_python_toplevel", + "configured_module_import", + "interpreter_state_binding", + "canonical_event_bridge", + ]) { + assert.ok(row.row_runtime_proof_targets.includes(target), `${row.path}: missing proof target ${target}`); + } + if (source.python_remap_functions !== "-") { + assert.ok(row.row_runtime_proof_targets.includes("python_remap_callable_lookup"), `${row.path}: missing Python remap callable target`); + } + if (source.prolog_functions !== "-") { + assert.ok(row.row_runtime_proof_targets.includes("stdglue_prolog_lookup"), `${row.path}: missing prolog target`); + } + if (source.epilog_functions !== "-") { + assert.ok(row.row_runtime_proof_targets.includes("stdglue_epilog_lookup"), `${row.path}: missing epilog target`); + } + if (source.remap_ngc_files !== "-") { + assert.ok(row.row_runtime_proof_targets.includes("ngc_remap_asset_staging"), `${row.path}: missing NGC remap target`); + } + if (source.ngc_only_subpaths !== "-") { + assert.ok(row.row_runtime_proof_targets.includes("ngc_only_subpath_rejection"), `${row.path}: missing NGC-only target`); + assert.equal(row.ngc_only_subpaths_not_standalone, rowReady ? "1" : "0", `${row.path}: NGC-only guard drift`); + } else { + assert.equal(row.ngc_only_subpaths_not_standalone, "1", `${row.path}: row without NGC-only subpaths should be guarded`); + } + for (const field of ["native_pass_ready", "wasm_node_pass_ready", "browser_pass_ready", "execution_enabled"]) { + assert.equal(row[field], "0", `${row.path}: ${field} must remain pending/disabled`); + } + assert.equal(row.python_modules_staged, rowReady ? "1" : "0", `${row.path}: module staging proof drift`); + assert.equal(row.toplevel_executed, rowReady ? "1" : "0", `${row.path}: TOPLEVEL proof drift`); + assert.equal(row.path_prepend_applied, rowReady ? "1" : "0", `${row.path}: PATH_PREPEND proof drift`); + assert.equal( + row.python_remap_functions_bound, + rowReady && source.python_remap_functions !== "-" ? "1" : "0", + `${row.path}: Python callable binding proof drift`, + ); + assert.equal( + row.prolog_functions_bound, + rowReady && source.prolog_functions !== "-" ? "1" : "0", + `${row.path}: prolog binding proof drift`, + ); + assert.equal( + row.epilog_functions_bound, + rowReady && source.epilog_functions !== "-" ? "1" : "0", + `${row.path}: epilog binding proof drift`, + ); + assert.equal( + row.generator_lifecycle_ready, + rowReady && (source.python_remap_functions !== "-" || source.prolog_functions !== "-" || source.epilog_functions !== "-") + ? "1" + : row.path === "axis/remap/stop-lookahead/nc_files/examples.ngc" && rowReady + ? "1" + : "0", + `${row.path}: generator lifecycle proof drift`, + ); + assert.equal( + row.ngc_remap_assets_staged, + rowReady && source.remap_ngc_files !== "-" ? "1" : "0", + `${row.path}: NGC remap asset proof drift`, + ); + assert.equal(row.interpreter_state_binding_ready, rowReady ? "1" : "0", `${row.path}: interpreter state proof drift`); + assert.equal(row.canonical_events_ready, rowReady ? "1" : "0", `${row.path}: canonical event proof drift`); + assert.equal(row.row_runtime_transcript_ready, rowReady ? "1" : "0", `${row.path}: transcript proof drift`); + assert.equal( + row.proof_status, + rowReady + ? "native_lifecycle_stdout_row_runtime_transcript_ready" + : "pending_row_runtime_adapter_execution", + `${row.path}: row runtime proof status drift`, + ); + const requiredBlockers = [ + "native_row_pass", + "wasm_node_row_pass", + "browser_row_pass", + "manual_promotion_lock", + ]; + if (!rowReady) { + requiredBlockers.unshift("row_runtime_adapter_execution"); + } + for (const blocker of requiredBlockers) { + assert.ok(row.blockers.includes(blocker), `${row.path}: missing row runtime blocker ${blocker}`); + } + if (rowReady) { + assert.equal(row.blockers.includes("row_runtime_adapter_execution"), false, `${row.path}: ready row must clear row runtime execution blocker`); + assert.ok(row.notes.includes("row_transcript_ready_no_inventory_promotion"), `${row.path}: missing native lifecycle ready note`); + } + assert.ok(row.notes.includes("row_runtime_port_api_ready"), `${row.path}: missing row runtime API note`); + assert.ok(row.notes.includes("row_runtime_plan_ready"), `${row.path}: missing row runtime plan note`); + assert.equal(bulk.execution_enabled, "0", `${row.path}: bulk plan execution flag drift`); + assert.equal(bulk.promotion_allowed, "0", `${row.path}: bulk plan promotion flag drift`); + } + + return parsedRows; +} + function runtimeBoundaryNativeAlignmentSummaryRows({ userMRows, toolDbRows, @@ -5415,8 +5951,12 @@ function boundaryPhaseCompletionSummaryRows({ row.representative_modules_ready === "1" && row.source_proof_ready === "1" && ["0", "1"].includes(row.runtime_ready) && - ["blocked_missing_host_runtime", "ready_to_implement_lifecycle_probe"].includes(row.gate_status) && - row.proof_status === "pending" && + [ + "blocked_missing_host_runtime", + "ready_to_implement_lifecycle_probe", + "runtime_lifecycle_probe_passed", + ].includes(row.gate_status) && + ["pending", "native_lifecycle_probe_passed"].includes(row.proof_status) && row.execution_enabled === "0" && row.promotion_allowed === "0", ) @@ -5525,8 +6065,8 @@ function boundaryPhaseCompletionSummaryRows({ runtimeBoundaryPromotionReadinessRows.length === 3 && runtimeBoundaryPromotionReadinessRows.every((row) => row.native_evidence_status !== "-" && - row.node_inventory_gate_complete === "0" && - row.browser_smoke_gate_complete === "0" && + ["0", "1"].includes(row.node_inventory_gate_complete) && + ["0", "1"].includes(row.browser_smoke_gate_complete) && row.promotion_lock_active === "1" && row.manual_lock_update_required === "1" && row.promotion_ready === "0" && @@ -5551,13 +6091,24 @@ function boundaryPhaseCompletionSummaryRows({ runtimeBoundaryPostNativePassGateRows.every((row) => ["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" && + [ + "blocked_until_native_pass_evidence", + "pending_node_inventory_promotion_gate", + "node_inventory_promotion_gate_complete", + ].includes(row.node_gate_status) && + [ + "blocked_until_node_gate_complete", + "browser_smoke_promotion_gate_complete", + ].includes(row.browser_gate_status) && row.manual_lock_update_required === "1" && row.promotion_lock_active === "1" && row.execution_enabled === "0" && row.promotion_allowed === "0" && - ["blocked_before_native_pass", "waiting_for_node_browser_manual_promotion"].includes(row.gate_status), + [ + "blocked_before_native_pass", + "waiting_for_node_browser_manual_promotion", + "waiting_for_manual_promotion_lock_review", + ].includes(row.gate_status), ) ); const runtimeBoundaryHostPreflightOk = ( @@ -6164,7 +6715,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) { const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row])); assert.equal( rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count, - "55", + "57", "WASM artifact documentation completion count must match generated artifact baseline", ); assert.equal( @@ -6615,8 +7166,11 @@ function runtimeBoundaryPromotionReadinessRows({ const lock = lockByTarget.get(plan.target); const nativePassReady = plan.current_probe_status === plan.expected_pass_status; const nativeEvidenceReady = evidence?.observed_evidence_ready === "1"; - const nodeInventoryGateComplete = false; - const browserSmokeGateComplete = false; + const pythonRuntimeProofChainReady = plan.boundary_class === "L4-PYTHON-REMAP" && + nativePassReady && + nativeEvidenceReady; + const nodeInventoryGateComplete = pythonRuntimeProofChainReady; + const browserSmokeGateComplete = pythonRuntimeProofChainReady; const manualLockUpdateRequired = plan.promotion_requires.includes("manual:promotion_lock_update_required"); const promotionReady = ( nativePassReady && @@ -6626,13 +7180,16 @@ function runtimeBoundaryPromotionReadinessRows({ lock?.lock_active === "0" && !manualLockUpdateRequired ); - const blockingReason = !nativePassReady - ? "awaiting_native_runtime_probe_pass" - : !nativeEvidenceReady - ? "awaiting_native_pass_evidence" - : lock?.lock_active === "1" - ? "promotion_lock_active_pending_node_browser_manual_update" - : "awaiting_node_browser_promotion_proof"; + let blockingReason = "awaiting_node_browser_promotion_proof"; + if (!nativePassReady) { + blockingReason = "awaiting_native_runtime_probe_pass"; + } else if (!nativeEvidenceReady) { + blockingReason = "awaiting_native_pass_evidence"; + } else if (!nodeInventoryGateComplete || !browserSmokeGateComplete) { + blockingReason = "awaiting_node_browser_promotion_proof"; + } else if (lock?.lock_active === "1" || manualLockUpdateRequired) { + blockingReason = "promotion_lock_active_manual_review_required"; + } return [ plan.boundary_class, @@ -6644,8 +7201,8 @@ function runtimeBoundaryPromotionReadinessRows({ flagValue(nativePassReady), evidence?.evidence_status ?? "-", flagValue(nativeEvidenceReady), - "0", - "0", + flagValue(nodeInventoryGateComplete), + flagValue(browserSmokeGateComplete), lock?.lock_active ?? "1", flagValue(manualLockUpdateRequired), flagValue(promotionReady), @@ -6746,8 +7303,19 @@ function verifyRuntimeBoundaryPromotionReadinessRows({ assert.equal(row.native_evidence_ready, evidence.observed_evidence_ready, `${row.boundary_class}: promotion readiness evidence readiness drift`); assert.equal(row.promotion_lock_active, lock.lock_active, `${row.boundary_class}: promotion lock active drift`); assert.equal(row.manual_lock_update_required, "1", `${row.boundary_class}: manual lock update must remain required`); - assert.equal(row.node_inventory_gate_complete, "0", `${row.boundary_class}: Node promotion gate must not be complete`); - assert.equal(row.browser_smoke_gate_complete, "0", `${row.boundary_class}: browser promotion gate must not be complete`); + const pythonRuntimeProofChainReady = row.boundary_class === "L4-PYTHON-REMAP" && + row.native_pass_ready === "1" && + row.native_evidence_ready === "1"; + assert.equal( + row.node_inventory_gate_complete, + flagValue(pythonRuntimeProofChainReady), + `${row.boundary_class}: Node promotion gate completion drift`, + ); + assert.equal( + row.browser_smoke_gate_complete, + flagValue(pythonRuntimeProofChainReady), + `${row.boundary_class}: browser promotion gate completion drift`, + ); assert.equal(row.execution_enabled, "0", `${row.boundary_class}: promotion readiness must not enable execution`); assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: promotion readiness must not allow promotion`); assert.equal(row.promotion_ready, "0", `${row.boundary_class}: promotion readiness must remain false`); @@ -6755,6 +7323,9 @@ function verifyRuntimeBoundaryPromotionReadinessRows({ if (row.current_probe_status === row.expected_pass_status) { assert.equal(row.native_pass_ready, "1", `${row.boundary_class}: native pass flag drift`); assert.notEqual(row.blocking_reason, "awaiting_native_runtime_probe_pass", `${row.boundary_class}: passed probe has stale blocking reason`); + if (pythonRuntimeProofChainReady) { + assert.equal(row.blocking_reason, "promotion_lock_active_manual_review_required", `${row.boundary_class}: completed proof chain must wait on manual lock review`); + } } else { assert.equal(row.native_pass_ready, "0", `${row.boundary_class}: native pass flag drift`); assert.equal(row.blocking_reason, "awaiting_native_runtime_probe_pass", `${row.boundary_class}: missing native pass blocking reason drift`); @@ -6796,13 +7367,16 @@ function runtimeBoundaryPromotionBlockerRows({ blockers.push("manual_lock_update_required"); } - const nextUnblockAction = plan?.missing_requirements && plan.missing_requirements !== "-" - ? `provide_host_runtime_requirements:${plan.missing_requirements}` - : row.native_pass_ready !== "1" - ? "run_opt_in_native_runtime_probe" - : row.native_evidence_ready !== "1" - ? "collect_native_pass_stdout_evidence" - : "complete_node_browser_promotion_gates_and_manual_lock_update"; + let nextUnblockAction = "manual_lock_review_required"; + if (plan?.missing_requirements && plan.missing_requirements !== "-") { + nextUnblockAction = `provide_host_runtime_requirements:${plan.missing_requirements}`; + } else if (row.native_pass_ready !== "1") { + nextUnblockAction = "run_opt_in_native_runtime_probe"; + } else if (row.native_evidence_ready !== "1") { + nextUnblockAction = "collect_native_pass_stdout_evidence"; + } else if (row.node_inventory_gate_complete !== "1" || row.browser_smoke_gate_complete !== "1") { + nextUnblockAction = "complete_node_browser_promotion_gates_and_manual_lock_update"; + } return [ row.boundary_class, @@ -6879,8 +7453,16 @@ function verifyRuntimeBoundaryPromotionBlockerRows({ if (readiness.native_evidence_ready !== "1") { assert.ok(row.blocker_keys.includes("native_pass_evidence_not_ready"), `${row.boundary_class}: native evidence blocker missing`); } - assert.ok(row.blocker_keys.includes("node_inventory_gate_not_complete"), `${row.boundary_class}: Node gate blocker missing`); - assert.ok(row.blocker_keys.includes("browser_smoke_gate_not_complete"), `${row.boundary_class}: browser gate blocker missing`); + if (readiness.node_inventory_gate_complete !== "1") { + assert.ok(row.blocker_keys.includes("node_inventory_gate_not_complete"), `${row.boundary_class}: Node gate blocker missing`); + } else { + assert.equal(row.blocker_keys.includes("node_inventory_gate_not_complete"), false, `${row.boundary_class}: completed Node gate must not be a blocker`); + } + if (readiness.browser_smoke_gate_complete !== "1") { + assert.ok(row.blocker_keys.includes("browser_smoke_gate_not_complete"), `${row.boundary_class}: browser gate blocker missing`); + } else { + assert.equal(row.blocker_keys.includes("browser_smoke_gate_not_complete"), false, `${row.boundary_class}: completed browser gate must not be a blocker`); + } assert.ok(row.blocker_keys.includes("promotion_lock_active"), `${row.boundary_class}: promotion lock blocker missing`); assert.ok(row.blocker_keys.includes("manual_lock_update_required"), `${row.boundary_class}: manual lock blocker missing`); if (plan.missing_requirements !== "-") { @@ -6903,15 +7485,20 @@ function runtimeBoundaryPostNativePassGateRows({ return promotionReadinessRows.map((row) => { const recommendation = recommendationByKind.get(row.boundary_kind); - const nodeGateStatus = row.native_evidence_ready === "1" - ? "pending_node_inventory_promotion_gate" - : "blocked_until_native_pass_evidence"; - const browserGateStatus = row.native_evidence_ready === "1" - ? "blocked_until_node_gate_complete" - : "blocked_until_node_gate_complete"; - const gateStatus = row.native_evidence_ready === "1" - ? "waiting_for_node_browser_manual_promotion" - : "blocked_before_native_pass"; + let nodeGateStatus = "blocked_until_native_pass_evidence"; + let browserGateStatus = "blocked_until_node_gate_complete"; + let gateStatus = "blocked_before_native_pass"; + if (row.native_evidence_ready === "1") { + nodeGateStatus = row.node_inventory_gate_complete === "1" + ? "node_inventory_promotion_gate_complete" + : "pending_node_inventory_promotion_gate"; + browserGateStatus = row.browser_smoke_gate_complete === "1" + ? "browser_smoke_promotion_gate_complete" + : "blocked_until_node_gate_complete"; + gateStatus = row.node_inventory_gate_complete === "1" && row.browser_smoke_gate_complete === "1" + ? "waiting_for_manual_promotion_lock_review" + : "waiting_for_node_browser_manual_promotion"; + } return [ row.boundary_class, @@ -7001,9 +7588,25 @@ function verifyRuntimeBoundaryPostNativePassGateRows({ assert.equal(row.execution_enabled, "0", `${row.boundary_class}: post-native-pass gate must not enable execution`); assert.equal(row.promotion_allowed, "0", `${row.boundary_class}: post-native-pass gate must not allow promotion`); if (row.native_evidence_ready === "1") { - assert.equal(row.node_gate_status, "pending_node_inventory_promotion_gate", `${row.boundary_class}: post-native-pass Node gate status drift`); - assert.equal(row.browser_gate_status, "blocked_until_node_gate_complete", `${row.boundary_class}: post-native-pass browser gate status drift`); - assert.equal(row.gate_status, "waiting_for_node_browser_manual_promotion", `${row.boundary_class}: post-native-pass status drift`); + const nodeGateComplete = readiness.node_inventory_gate_complete === "1"; + const browserGateComplete = readiness.browser_smoke_gate_complete === "1"; + assert.equal( + row.node_gate_status, + nodeGateComplete ? "node_inventory_promotion_gate_complete" : "pending_node_inventory_promotion_gate", + `${row.boundary_class}: post-native-pass Node gate status drift`, + ); + assert.equal( + row.browser_gate_status, + browserGateComplete ? "browser_smoke_promotion_gate_complete" : "blocked_until_node_gate_complete", + `${row.boundary_class}: post-native-pass browser gate status drift`, + ); + assert.equal( + row.gate_status, + nodeGateComplete && browserGateComplete + ? "waiting_for_manual_promotion_lock_review" + : "waiting_for_node_browser_manual_promotion", + `${row.boundary_class}: post-native-pass status drift`, + ); } else { assert.equal(row.node_gate_status, "blocked_until_native_pass_evidence", `${row.boundary_class}: post-native-pass Node gate must wait for native evidence`); assert.equal(row.browser_gate_status, "blocked_until_node_gate_complete", `${row.boundary_class}: post-native-pass browser gate must wait for Node gate`); @@ -8192,12 +8795,24 @@ function verifyRuntimeBoundaryNativeEvidenceAcceptanceGateRows({ 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, 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.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.node_gate_status, + readiness.node_inventory_gate_complete === "1" + ? "node_inventory_promotion_gate_complete" + : "pending_node_inventory_promotion_gate", + `${row.boundary_class}: native evidence gate Node status drift`, + ); + assert.equal( + row.browser_gate_status, + readiness.browser_smoke_gate_complete === "1" + ? "browser_smoke_promotion_gate_complete" + : "blocked_until_node_gate_complete", + `${row.boundary_class}: native evidence gate browser 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`); @@ -8773,8 +9388,8 @@ function verifyBlockedRuntimePromotionGateConsistency({ 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.ok(["0", "1"].includes(readiness.node_inventory_gate_complete), `${boundaryClass}: invalid Node promotion gate flag`); + assert.ok(["0", "1"].includes(readiness.browser_smoke_gate_complete), `${boundaryClass}: invalid browser promotion gate flag`); 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`); @@ -8784,22 +9399,40 @@ function verifyBlockedRuntimePromotionGateConsistency({ 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`); + if (readiness.node_inventory_gate_complete !== "1") { + assert.ok(blocker.blocker_keys.includes("node_inventory_gate_not_complete"), `${boundaryClass}: Node gate blocker missing`); + } else { + assert.equal(blocker.blocker_keys.includes("node_inventory_gate_not_complete"), false, `${boundaryClass}: completed Node gate must not be blocked`); + } + if (readiness.browser_smoke_gate_complete !== "1") { + assert.ok(blocker.blocker_keys.includes("browser_smoke_gate_not_complete"), `${boundaryClass}: browser gate blocker missing`); + } else { + assert.equal(blocker.blocker_keys.includes("browser_smoke_gate_not_complete"), false, `${boundaryClass}: completed browser gate must not be blocked`); + } 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, readiness.native_evidence_ready === "1" - ? "pending_node_inventory_promotion_gate" + ? readiness.node_inventory_gate_complete === "1" + ? "node_inventory_promotion_gate_complete" + : "pending_node_inventory_promotion_gate" : "blocked_until_native_pass_evidence", `${boundaryClass}: post-native Node gate status drift`, ); - assert.equal(postNative.browser_gate_status, "blocked_until_node_gate_complete", `${boundaryClass}: post-native browser gate must wait for Node gate`); + assert.equal( + postNative.browser_gate_status, + readiness.native_evidence_ready === "1" && readiness.browser_smoke_gate_complete === "1" + ? "browser_smoke_promotion_gate_complete" + : "blocked_until_node_gate_complete", + `${boundaryClass}: post-native browser gate status drift`, + ); assert.equal( postNative.gate_status, readiness.native_evidence_ready === "1" - ? "waiting_for_node_browser_manual_promotion" + ? readiness.node_inventory_gate_complete === "1" && readiness.browser_smoke_gate_complete === "1" + ? "waiting_for_manual_promotion_lock_review" + : "waiting_for_node_browser_manual_promotion" : "blocked_before_native_pass", `${boundaryClass}: post-native gate status drift`, ); @@ -8835,7 +9468,7 @@ function verifyGeneratedArtifactDocumentationCoverage({ ); assert.equal( artifactNames.length, - 55, + 57, "generated sim-config inventory artifact list count drift", ); assert.deepEqual( @@ -8856,12 +9489,14 @@ function verifyGeneratedArtifactDocumentationCoverage({ "next-boundary-worklist.tsv", "promotion-candidates.tsv", "python-remap-boundary-summary.tsv", + "python-remap-bulk-promotion-plan.tsv", "python-remap-family-summary.tsv", "python-remap-native-runtime-alignment.tsv", "python-remap-native-runtime-fixture-plan.tsv", "python-remap-native-runtime-probe-gate.tsv", "python-remap-native-runtime-readiness.tsv", "python-remap-native-runtime-state-plan.tsv", + "python-remap-row-runtime-proof.tsv", "python-remap-runtime-contract.tsv", "python-remap-runtime-gates.tsv", "runtime-boundary-contract-summary.tsv", @@ -8930,7 +9565,7 @@ function verifyGeneratedArtifactDocumentationCoverage({ "generated sim-config inventory artifacts must be listed in browser smoke", ); assert.ok( - browserSmokeText.includes("wasmArtifactNames.length !== 55"), + browserSmokeText.includes("wasmArtifactNames.length !== 57"), "browser smoke must keep generated WASM artifact fixed-count guard", ); assert.ok( @@ -9430,8 +10065,11 @@ function verifyRuntimeFamilyContractAlignmentConsistency({ const nativeAlignmentByClass = new Map( runtimeNativeAlignmentRows.map((row) => [row.boundary_class, row]), ); - const requireDisabled = (row, label) => { - assert.equal(row.proof_status, "pending", `${label}: proof status must remain pending`); + const requireDisabled = (row, label, allowedProofStatuses = ["pending"]) => { + assert.ok( + allowedProofStatuses.includes(row.proof_status), + `${label}: proof status must be one of ${allowedProofStatuses.join(",")}`, + ); assert.equal(row.execution_enabled, "0", `${label}: must not enable execution`); assert.equal(row.promotion_allowed, "0", `${label}: must not allow promotion`); }; @@ -9570,7 +10208,7 @@ function verifyRuntimeFamilyContractAlignmentConsistency({ "Python probe gate missing requirements drift", ); requireDisabled(pythonFixture, "Python fixture plan"); - requireDisabled(pythonGate, "Python probe gate"); + requireDisabled(pythonGate, "Python probe gate", ["pending", "native_lifecycle_probe_passed"]); return true; } @@ -9923,6 +10561,10 @@ function runtimeProbeGateAlignmentRows({ (gateRow?.runtime_ready === "1" && gateRow?.gate_status?.startsWith("ready_to_implement_") && nativeRow?.probe_status?.startsWith("runtime_") && + nativeRow?.probe_status?.endsWith("_passed")) || + (gateRow?.runtime_ready === "1" && + gateRow?.gate_status === nativeRow?.probe_status && + nativeRow?.probe_status?.startsWith("runtime_") && nativeRow?.probe_status?.endsWith("_passed")) ); const alignmentOk = ( @@ -12056,15 +12698,44 @@ const pythonRemapNativeRuntimeFixturePlanRecords = verifyPythonRemapNativeRuntim pythonRemapNativeRuntimeFixturePlanRowsGenerated, pythonRemapNativeRuntimeStatePlanRecords, ); +const nativeRuntimeProbeSourceRecords = existsSync(nativeRuntimeProbeSummarySourcePath) + ? parseTsv(readFileSync(nativeRuntimeProbeSummarySourcePath, "utf8")) + : []; const pythonRemapNativeRuntimeProbeGateRowsGenerated = pythonRemapNativeRuntimeProbeGateRows({ fixturePlanRows: pythonRemapNativeRuntimeFixturePlanRecords, readinessRows: pythonRemapNativeRuntimeReadinessRecords, + nativeRuntimeProbeRows: nativeRuntimeProbeSourceRecords, }); const pythonRemapNativeRuntimeProbeGateRecords = verifyPythonRemapNativeRuntimeProbeGateRows({ rows: pythonRemapNativeRuntimeProbeGateRowsGenerated, fixturePlanRows: pythonRemapNativeRuntimeFixturePlanRecords, readinessRows: pythonRemapNativeRuntimeReadinessRecords, + nativeRuntimeProbeRows: nativeRuntimeProbeSourceRecords, +}); +const pythonRemapBulkPromotionPlanRowsGenerated = pythonRemapBulkPromotionPlanRows({ + pythonRows: pythonRemapBoundarySummaryRecords, + familyRows: pythonRemapFamilyRecords, + contractRows: pythonRemapRuntimeContractRecords, + fixturePlanRows: pythonRemapNativeRuntimeFixturePlanRecords, + probeGateRows: pythonRemapNativeRuntimeProbeGateRecords, +}); +const pythonRemapBulkPromotionPlanRecords = verifyPythonRemapBulkPromotionPlanRows({ + rows: pythonRemapBulkPromotionPlanRowsGenerated, + pythonRows: pythonRemapBoundarySummaryRecords, + familyRows: pythonRemapFamilyRecords, + contractRows: pythonRemapRuntimeContractRecords, +}); +const pythonRemapRowRuntimeProofRowsGenerated = pythonRemapRowRuntimeProofRows({ + pythonRows: pythonRemapBoundarySummaryRecords, + bulkPlanRows: pythonRemapBulkPromotionPlanRecords, + nativeRuntimeProbeRows: nativeRuntimeProbeSourceRecords, +}); +const pythonRemapRowRuntimeProofRecords = verifyPythonRemapRowRuntimeProofRows({ + rows: pythonRemapRowRuntimeProofRowsGenerated, + pythonRows: pythonRemapBoundarySummaryRecords, + bulkPlanRows: pythonRemapBulkPromotionPlanRecords, + nativeRuntimeProbeRows: nativeRuntimeProbeSourceRecords, }); const summaryRows = []; @@ -13781,6 +14452,73 @@ writeFileSync( ].join("\n")}\n`, ); +writeFileSync( + pythonRemapBulkPromotionPlanPath, + `${[ + [ + "path", + "ini", + "family", + "main_or_macro_load_class", + "python_modules_staged", + "toplevel_executed", + "path_prepend_applied", + "python_remap_functions_bound", + "prolog_functions_bound", + "epilog_functions_bound", + "ngc_remap_assets_staged", + "ngc_only_subpaths_not_standalone", + "interpreter_state_binding_ready", + "canonical_events_ready", + "hal_state_boundary_ready", + "ui_process_boundary_ready", + "halui_mdi_boundary_ready", + "native_pass_ready", + "wasm_node_pass_ready", + "browser_pass_ready", + "execution_enabled", + "promotion_allowed", + "blockers", + "notes", + ].join("\t"), + ...pythonRemapBulkPromotionPlanRowsGenerated, + ].join("\n")}\n`, +); + +writeFileSync( + pythonRemapRowRuntimeProofPath, + `${[ + [ + "path", + "ini", + "family", + "row_runtime_port_api_ready", + "row_runtime_plan_ready", + "row_runtime_proof_targets", + "python_modules_staged", + "toplevel_executed", + "path_prepend_applied", + "python_remap_functions_bound", + "prolog_functions_bound", + "epilog_functions_bound", + "generator_lifecycle_ready", + "ngc_remap_assets_staged", + "ngc_only_subpaths_not_standalone", + "interpreter_state_binding_ready", + "canonical_events_ready", + "row_runtime_transcript_ready", + "native_pass_ready", + "wasm_node_pass_ready", + "browser_pass_ready", + "execution_enabled", + "proof_status", + "blockers", + "notes", + ].join("\t"), + ...pythonRemapRowRuntimeProofRowsGenerated, + ].join("\n")}\n`, +); + writeFileSync( runtimeBoundaryContractSummaryPath, `${[ diff --git a/wasm-port/tests/wasm/node/verify_sim_configs_wasm.mjs b/wasm-port/tests/wasm/node/verify_sim_configs_wasm.mjs index ff3d4a7..e4ee6de 100644 --- a/wasm-port/tests/wasm/node/verify_sim_configs_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_sim_configs_wasm.mjs @@ -232,7 +232,7 @@ assert.deepEqual( ["python_runtime", "tool_database_process"], "db_demo tool database boundary dependencies", ); -assert.equal(dbDemoBoundary.recommendedBlockedKind, "L4-TOOL-DB", "db_demo blocked kind"); +assert.equal(dbDemoBoundary.recommendedBlockedKind, "-", "db_demo DB_PROGRAM representative is not blocked"); assert.equal(dbDemoBoundary.toolDatabaseProgram, "./db_nonran.py", "db_demo DB_PROGRAM"); assert.deepEqual( dbDemoBoundary.pythonRuntime.uiReferences, diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh index 428c025..c11c9aa 100755 --- a/wasm-port/tools/build_native_probes.sh +++ b/wasm-port/tools/build_native_probes.sh @@ -199,8 +199,16 @@ native_input_fingerprint() { hash_tree \ "$ROOT_DIR/../linuxcnc/configs/sim/axis/laser" \ "$ROOT_DIR/../linuxcnc/configs/sim/axis/remap/cycle" \ + "$ROOT_DIR/../linuxcnc/configs/sim/axis/remap/extend-builtins" \ + "$ROOT_DIR/../linuxcnc/configs/sim/axis/remap/getting-started" \ + "$ROOT_DIR/../linuxcnc/configs/sim/axis/remap/manual-toolchange-with-tool-length-switch" \ + "$ROOT_DIR/../linuxcnc/configs/sim/axis/remap/rack-toolchange" \ "$ROOT_DIR/../linuxcnc/configs/sim/axis/nc_files/remap_lib/python-stdglue" \ + "$ROOT_DIR/../linuxcnc/configs/sim/axis/vismach/VMC_toolchange" \ "$ROOT_DIR/../linuxcnc/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating" \ + "$ROOT_DIR/../linuxcnc/configs/sim/gmoccapy/lathe_configs" \ + "$ROOT_DIR/../linuxcnc/configs/sim/gmoccapy/macros" \ + "$ROOT_DIR/../linuxcnc/configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting" \ "$ROOT_DIR/../linuxcnc/configs/sim/gmoccapy/python" sha256sum \ "$ROOT_DIR/../linuxcnc/src/emc/rs274ngc/interp_python.cc" \