diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 159e9c8..0126e1d 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -105,7 +105,9 @@ OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the host boundary copies text into and out of the WASM filesystem without defining parameter semantics, and that INI-derived machine file names are still rejected by the OPFS path model when they contain traversal or nested path -segments. +segments. The same Node smoke also validates that explicit session file-name +options take precedence over INI-derived parameter and tool-table file names, +keeping host override policy outside CNC semantics. The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs Chromium headless against a test page that imports the JS SDK, loads the INI @@ -125,7 +127,10 @@ storage plus the interpreter SDK to restore and save a LinuxCNC parameter file through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`, to load/save LinuxCNC tool tables through vendored `tooldata_common.cc`, and to reject invalid INI-derived parameter/tool-table file names through the OPFS -path model after the names are parsed by the LinuxCNC-backed INI WASM SDK. +path model after the names are parsed by the LinuxCNC-backed INI WASM SDK. It +also verifies that explicit session parameter/tool-table file-name options +override INI-derived names before OPFS text is copied into the LinuxCNC-backed +WASM filesystem. The aggregate host smoke script builds the INI and interpreter-core WASM artifacts once, then runs the Node WASM smokes, the Node OPFS mock smoke, and @@ -262,9 +267,10 @@ groups INI, parameter, and tool-table loading, the random-toolchanger flag derived from vendored LinuxCNC INI boolean parsing, INI-derived `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` file names mapped to OPFS machine files, OPFS path-model rejection of invalid INI-derived file names, -and a browser interpreter smoke that uses the same session bridge with real -LinuxCNC INI WASM parsing before saving OPFS-backed parameter and tool-table -text through vendored LinuxCNC file APIs. +explicit host session file-name overrides taking precedence over INI-derived +names, and a browser interpreter smoke that uses the same session bridge with +real LinuxCNC INI WASM parsing before saving OPFS-backed parameter and +tool-table text through vendored LinuxCNC file APIs. Full browser coverage, full SDK coverage, and richer machine-state validation remain future work. diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index 2cfbb3b..db18a01 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -44,7 +44,7 @@ semantic rewrites: | Kinematics component lifecycle | Kinematics modules are initialized through LinuxCNC module entry points where native runtime probes exist, while HAL component init/ready/exit, HAL pin allocation, and RTAPI module metadata are handled by standalone shims. | | Go math C/C++ linkage | `genserkins` runtime probing compiles vendored `gomath.c` through a narrow C++ wrapper so LinuxCNC `genserfuncs.c` can link to the upstream Go math symbols without editing vendored source. | | Switchkins iterative forward | `genhexkins` runtime probing follows LinuxCNC switchkins iterative-forward behavior, including the first-call warmup path before asserting roundtrip convergence. | -| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence, `runtime/opfs/linuxcnc-parameter-bridge.js` only copies parameter files between OPFS text storage and the LinuxCNC-backed WASM parameter-file ABI, `runtime/opfs/linuxcnc-tool-table-bridge.js` only copies tool tables between OPFS text storage and the LinuxCNC-backed WASM tool-table ABI, and `runtime/opfs/linuxcnc-machine-session-bridge.js` groups INI, parameter, and tool-table loading while using the LinuxCNC-backed INI SDK for `[EMCIO]RANDOM_TOOLCHANGER`, `[RS274NGC]PARAMETER_FILE`, and `[EMCIO]TOOL_TABLE` when available; path validation remains owned by the OPFS path model, including rejection of traversal or nested path segments from INI-derived file names. | +| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence, `runtime/opfs/linuxcnc-parameter-bridge.js` only copies parameter files between OPFS text storage and the LinuxCNC-backed WASM parameter-file ABI, `runtime/opfs/linuxcnc-tool-table-bridge.js` only copies tool tables between OPFS text storage and the LinuxCNC-backed WASM tool-table ABI, and `runtime/opfs/linuxcnc-machine-session-bridge.js` groups INI, parameter, and tool-table loading while using the LinuxCNC-backed INI SDK for `[EMCIO]RANDOM_TOOLCHANGER`, `[RS274NGC]PARAMETER_FILE`, and `[EMCIO]TOOL_TABLE` when available; explicit host session file-name options take precedence over INI-derived file names, and path validation remains owned by the OPFS path model, including rejection of traversal or nested path segments from INI-derived file names. | ## Enforced Non-Drift Rules @@ -69,9 +69,9 @@ semantic rewrites: - OPFS validation covers a Node mock of the file-service adapter, the host-side path model, generic session snapshot storage, pure-text machine file and G-code storage, parameter/tool-table bridge copying into the - interpreter SDK filesystem, grouped machine-session loading, invalid - INI-derived machine file names, and a Chromium localhost round trip for those - persistence paths. + interpreter SDK filesystem, grouped machine-session loading, explicit + session file-name overrides, invalid INI-derived machine file names, and a + Chromium localhost round trip for those persistence paths. - Host-side smoke validation is aggregated by `tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks run from one command. diff --git a/wasm-port/docs/source-reuse-map.md b/wasm-port/docs/source-reuse-map.md index 9354db3..a3771ab 100644 --- a/wasm-port/docs/source-reuse-map.md +++ b/wasm-port/docs/source-reuse-map.md @@ -54,7 +54,7 @@ Current validation is intentionally mechanical: | Dependency | LinuxCNC files that expose it | Standalone treatment | | --- | --- | --- | -| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, `tooldata_common.cc`, parameter file paths, tool table paths | Allowed in native probes; the interpreter WASM C ABI validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` and tool-table load/save by calling vendored `tooldata_load()` and `tooldata_save()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, pure-text machine-file persistence in `runtime/opfs/machine-file-store.js`, OPFS-to-WASM parameter-file copying in `runtime/opfs/linuxcnc-parameter-bridge.js`, OPFS-to-WASM tool-table copying in `runtime/opfs/linuxcnc-tool-table-bridge.js`, and grouped INI/parameter/tool-table session loading in `runtime/opfs/linuxcnc-machine-session-bridge.js`, including INI-derived `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` OPFS filename selection through the LinuxCNC-backed INI SDK; OPFS path validation rejects traversal and nested segments before host storage access | +| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, `tooldata_common.cc`, parameter file paths, tool table paths | Allowed in native probes; the interpreter WASM C ABI validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` and tool-table load/save by calling vendored `tooldata_load()` and `tooldata_save()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, pure-text machine-file persistence in `runtime/opfs/machine-file-store.js`, OPFS-to-WASM parameter-file copying in `runtime/opfs/linuxcnc-parameter-bridge.js`, OPFS-to-WASM tool-table copying in `runtime/opfs/linuxcnc-tool-table-bridge.js`, and grouped INI/parameter/tool-table session loading in `runtime/opfs/linuxcnc-machine-session-bridge.js`, including INI-derived `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` OPFS filename selection through the LinuxCNC-backed INI SDK; explicit host session file-name options take precedence over INI-derived names, and OPFS path validation rejects traversal and nested segments before host storage access | | RTAPI | `rtapi_*.h`, TP, posemath, motion headers | Minimal standalone shim in `runtime/core/shims/rtapi.h` | | NML transport | `emc.hh`, motion/NML type headers | Transport is not ported; only the status/type edges needed by vendored compute code are exposed through standalone shims and probes | | HAL runtime | named parameter lookup, kinematics component lifecycle, and runtime status edges | Standalone HAL adapter under `runtime/core/linuxcnc_wrap/` | @@ -95,7 +95,9 @@ Current validation is intentionally mechanical: tool-table loading into one host-side session load boundary and can derive the random-toolchanger tooldata mode from `[EMCIO]RANDOM_TOOLCHANGER` plus parameter/tool-table OPFS file names from `[RS274NGC]PARAMETER_FILE` and - `[EMCIO]TOOL_TABLE` through the LinuxCNC-backed INI SDK, with invalid - traversal or nested file names rejected by `runtime/opfs/path-model.js`. - Full machine-state restoration remains future work. + `[EMCIO]TOOL_TABLE` through the LinuxCNC-backed INI SDK. Explicit host + session file-name options take precedence over those INI-derived names, with + invalid traversal or nested file names rejected by + `runtime/opfs/path-model.js`. Full machine-state restoration remains future + work. - Native LinuxCNC GUI code remains out of scope for implementation. diff --git a/wasm-port/tests/browser/interp_smoke.html b/wasm-port/tests/browser/interp_smoke.html index ff27744..30e6db8 100644 --- a/wasm-port/tests/browser/interp_smoke.html +++ b/wasm-port/tests/browser/interp_smoke.html @@ -412,6 +412,82 @@ ].join("\n"), ); + await saveMachineTextFiles("browser-ini-override-session", { + ini: [ + "[EMC]", + "MACHINE = browser-ini-override-session", + "", + "[RS274NGC]", + "PARAMETER_FILE = browser-ignored.var", + "", + "[EMCIO]", + "TOOL_TABLE = browser-ignored-tool.tbl", + "", + ].join("\n"), + }); + await saveTextFile( + "linuxcnc/machines/browser-ini-override-session/browser-explicit.var", + [ + "5161 41.25", + "5162 82.5", + "5220 1", + "5221 6.5", + "5399 101", + "", + ].join("\n"), + ); + await saveTextFile( + "linuxcnc/machines/browser-ini-override-session/browser-explicit-tool.tbl", + "T9 P9 Z5.5 D0.625 ;browser explicit tool\n", + ); + const loadedIniOverrideSession = await loadMachineSessionFromOpfs( + interp, + "browser-ini-override-session", + { + iniSdk: ini, + iniWasmPath: "/work/browser-ini-override-session.ini", + parameterFilename: "browser-explicit.var", + parameterWasmPath: "/work/browser-explicit.var", + toolTableFilename: "browser-explicit-tool.tbl", + toolTableWasmPath: "/work/browser-explicit-tool.tbl", + }, + ); + if ( + loadedIniOverrideSession.parameters.opfsPath !== + "linuxcnc/machines/browser-ini-override-session/browser-explicit.var" + ) { + throw new Error(`unexpected explicit parameter path: ${loadedIniOverrideSession.parameters.opfsPath}`); + } + if ( + loadedIniOverrideSession.toolTable.opfsPath !== + "linuxcnc/machines/browser-ini-override-session/browser-explicit-tool.tbl" + ) { + throw new Error(`unexpected explicit tool path: ${loadedIniOverrideSession.toolTable.opfsPath}`); + } + verifyExpectedOutput( + "opfs_load_explicit_parameter_file", + loadedIniOverrideSession.parameters.result, + [ + "restore_parameters=0", + "parameter_5161=41.25", + "parameter_5162=82.5", + "parameter_5221=6.5", + "parameter_5399=101", + ].join("\n"), + ); + verifyExpectedOutput( + "opfs_load_explicit_tool_table", + loadedIniOverrideSession.toolTable.result, + [ + "tooldata_load=0", + "tool_1.toolno=9", + "tool_1.pocketno=9", + "tool_1.z=5.5", + "tool_1.diameter=0.625", + "tool_1.comment=browser explicit tool", + ].join("\n"), + ); + await saveMachineTextFiles("browser-invalid-param-file-session", { ini: [ "[EMC]", diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index 5713a4e..8617bc3 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -209,6 +209,13 @@ const bridgeIniSdk = { ) { return "custom.var"; } + if ( + path === "/work/ini-override-session.ini" && + section === "RS274NGC" && + tag === "PARAMETER_FILE" + ) { + return "ignored.var"; + } if ( path === "/work/invalid-ini-file-session.ini" && section === "RS274NGC" && @@ -223,6 +230,13 @@ const bridgeIniSdk = { ) { return "custom-tool.tbl"; } + if ( + path === "/work/ini-override-session.ini" && + section === "EMCIO" && + tag === "TOOL_TABLE" + ) { + return "ignored-tool.tbl"; + } if ( path === "/work/invalid-tool-file-session.ini" && section === "EMCIO" && @@ -430,6 +444,53 @@ assert.equal( "tooldata_load=0\nload_tool_path=/work/ini-file-session-tool.tbl\nrandom_toolchanger=0\n", ); +await saveMachineTextFiles("ini-override-session", { + ini: [ + "[EMC]", + "MACHINE = ini-override-session", + "", + "[RS274NGC]", + "PARAMETER_FILE = ignored.var", + "", + "[EMCIO]", + "TOOL_TABLE = ignored-tool.tbl", + "", + ].join("\n"), +}, { storage }); +await saveTextFile( + "linuxcnc/machines/ini-override-session/explicit.var", + "5161 11.0\n5162 22.0\n", + storage, +); +await saveTextFile( + "linuxcnc/machines/ini-override-session/explicit-tool.tbl", + "T3 P3 Z1.75 D0.375\n", + storage, +); +const loadedIniOverrideSession = await loadMachineSessionFromOpfs( + bridgeInterp, + "ini-override-session", + { + storage, + iniSdk: bridgeIniSdk, + iniWasmPath: "/work/ini-override-session.ini", + parameterFilename: "explicit.var", + parameterWasmPath: "/work/ini-override-session.var", + toolTableFilename: "explicit-tool.tbl", + toolTableWasmPath: "/work/ini-override-session-tool.tbl", + }, +); +assert.equal( + loadedIniOverrideSession.parameters.opfsPath, + "linuxcnc/machines/ini-override-session/explicit.var", +); +assert.equal( + loadedIniOverrideSession.toolTable.opfsPath, + "linuxcnc/machines/ini-override-session/explicit-tool.tbl", +); +assert.equal(bridgeFiles.get("/work/ini-override-session.var"), "5161 11.0\n5162 22.0\n"); +assert.equal(bridgeFiles.get("/work/ini-override-session-tool.tbl"), "T3 P3 Z1.75 D0.375\n"); + await saveMachineTextFiles("invalid-ini-file-session", { ini: [ "[EMC]",