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" \