diff --git a/text10.txt b/text10.txt index 4b03c0d..0ec5d6a 100644 --- a/text10.txt +++ b/text10.txt @@ -415,3 +415,66 @@ host_wasm_opfs_browser_smokes=ok 后校验 `payload.machineId` 与机器文件 path 结构,并补 Node/browser smoke;如果该入口 已足够清晰,再把 save/load machine session snapshot 接到 INI panel 的实际会话保存/恢复 workflow。仍只处理 OPFS/filesystem/browser boundary,不引入或重写任何 CNC 语义。 + +四、2026-06-14 继续执行记录:OPFS machine session snapshot load helper + +本轮继续按“加快实质性推进铁律”推进 OPFS/session persistence 的实际 API 入口, +补齐 machine session snapshot 的读取校验侧能力。 + +完成内容: + +- 在 `wasm-port/runtime/opfs/snapshot-store.js` 新增 + `loadMachineSessionSnapshot(sessionId, machineId, options)`; +- 该 helper 复用既有 `loadSessionSnapshot(...)` envelope 校验,然后校验 + `payload.machineId` 与调用方传入的 `machineId` 一致; +- 新增内部 machine snapshot payload 校验,要求 `payload.files` 为 plain object, + 且 `ini`、`parameters`、`toolTable` 为合法 OPFS path,`gcode` 如存在也必须是合法 + OPFS path; +- 校验只覆盖 host-side snapshot/path boundary,不解释 G-code、parameter、tool-table + 内容,不扩大 CNC 语义边界; +- 在 Node OPFS smoke 中覆盖 machine snapshot load round trip、machine id mismatch + reject,以及非法 OPFS path reject; +- 在 browser INI panel smoke 中覆盖浏览器运行时的 checked machine snapshot load 和 + wrong-machine reject; +- 未改变 generic session snapshot envelope、path model、file-service、save/load API + 兼容性或已有 storage 行为; +- 未改变 OPFS bridge behavior、SDK planner、interpreter、INI、tool、parameter 或 + LinuxCNC-owned runtime semantics。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/opfs/node/verify_file_service.sh +SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh +wasm-port/tools/verify_vendor_sync.sh +wasm-port/tools/verify_no_standalone_cnc_semantics.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh +SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh +wasm-port/tests/host/verify_host_smokes.sh +``` + +关键输出: + +```text +opfs_file_service_node_smoke=ok +browser_ini_opfs_smoke=ok +vendor sync up to date +standalone CNC semantics guard complete +interp_wasm_node_smoke=ok +sim_configs_wasm_node_inventory_executed=28 +sim_configs_wasm_node_inventory_passed=28 +sim_configs_wasm_node_inventory_skipped=131 +sim_configs_wasm_node_inventory_unexpected_fail=0 +browser_interp_smoke=ok +host_wasm_opfs_browser_smokes=ok +``` + +下一步建议: + +继续把 machine session snapshot save/load helper 接到更明确的用户流程。优先扫描 +`wasm-port/runtime/ui/ini-panel/app.js` 和 browser smoke,增加一个最小会话保存/恢复 +workflow:保存当前 INI、parameter/tool-table path 和可选 G-code snapshot,再通过 +`loadMachineSessionSnapshot(...)` 恢复出待加载文件清单。范围仍限定 OPFS/filesystem/UI +workflow,不解析或重写任何 LinuxCNC-owned CNC 语义。 diff --git a/wasm-port/runtime/opfs/snapshot-store.js b/wasm-port/runtime/opfs/snapshot-store.js index 0b5b120..6e0309e 100644 --- a/wasm-port/runtime/opfs/snapshot-store.js +++ b/wasm-port/runtime/opfs/snapshot-store.js @@ -27,6 +27,27 @@ function optionalOpfsPath(value, label) { return normalizeOpfsPath(value); } +function requiredSnapshotFilePath(files, key, label) { + const value = files[key]; + if (typeof value !== "string") { + throw new Error(`${label} must be a string.`); + } + return normalizeOpfsPath(value); +} + +function validateMachineSessionSnapshotPayload(payload, machineId) { + assertPlainObject(payload, "machine session snapshot payload"); + if (payload.machineId !== machineId) { + throw new Error(`Machine session snapshot id mismatch: ${payload.machineId}`); + } + assertPlainObject(payload.files, "machine session snapshot files"); + requiredSnapshotFilePath(payload.files, "ini", "machine session INI OPFS path"); + requiredSnapshotFilePath(payload.files, "parameters", "machine session parameter OPFS path"); + requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path"); + optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path"); + return payload; +} + export function createMachineSessionSnapshotPayload(machineId, options = {}) { assertPlainObject(options, "machine session snapshot options"); const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ?? @@ -95,6 +116,12 @@ export async function saveMachineSessionSnapshot(sessionId, machineId, options = return saveSessionSnapshot(sessionId, payload, options); } +export async function loadMachineSessionSnapshot(sessionId, machineId, options = {}) { + const snapshot = await loadSessionSnapshot(sessionId, options); + validateMachineSessionSnapshotPayload(snapshot.payload, machineId); + return snapshot; +} + export async function loadSessionSnapshot(sessionId, options = {}) { const path = sessionSnapshotPath(sessionId, options.filename); const text = await loadTextFile(path, options.storage); diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index b9c4adc..a2bc884 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -16,6 +16,7 @@ } from "../../runtime/opfs/path-model.js"; import { createMachineSessionSnapshotPayload, + loadMachineSessionSnapshot, loadSessionSnapshot, saveMachineSessionSnapshot, saveSessionSnapshot, @@ -221,6 +222,11 @@ TOOL_TABLE = browser-tool.tbl "browser-machine-session", { filename: "browser-machine-snapshot.json" }, ); + const loadedCheckedMachineSessionSnapshot = await loadMachineSessionSnapshot( + "browser-machine-session", + "browser-smoke", + { filename: "browser-machine-snapshot.json" }, + ); assertEqual( machineSessionSnapshot.payload.files.parameters, "linuxcnc/machines/browser-smoke/browser-linuxcnc.var", @@ -241,6 +247,20 @@ TOOL_TABLE = browser-tool.tbl "browser-machine-session", "machine session snapshot metadata", ); + assertEqual( + loadedCheckedMachineSessionSnapshot.payload.machineId, + "browser-smoke", + "checked machine session snapshot id", + ); + await assertRejects( + "browser machine snapshot wrong machine", + () => loadMachineSessionSnapshot( + "browser-machine-session", + "other-browser-machine", + { filename: "browser-machine-snapshot.json" }, + ), + /Machine session snapshot id mismatch/, + ); await assertRejects( "browser snapshot invalid filename save", () => saveSessionSnapshot("browser-session", {}, { diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index fc83558..0d3fcd1 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -18,6 +18,7 @@ import { import { createMachineSessionSnapshotPayload, createSessionSnapshot, + loadMachineSessionSnapshot, loadSessionSnapshot, saveMachineSessionSnapshot, saveSessionSnapshot, @@ -350,6 +351,40 @@ assert.deepEqual( }), machineSessionSnapshot, ); +assert.deepEqual( + await loadMachineSessionSnapshot("machine-session-1", "xyzab-tdr", { + storage, + filename: "machine-session-snapshot.json", + }), + machineSessionSnapshot, +); +await assert.rejects( + () => loadMachineSessionSnapshot("machine-session-1", "other-machine", { + storage, + filename: "machine-session-snapshot.json", + }), + /Machine session snapshot id mismatch/, +); +await saveTextFile( + sessionSnapshotPath("machine-session-bad-files"), + JSON.stringify({ + ...machineSessionSnapshot, + sessionId: "machine-session-bad-files", + payload: { + machineId: "xyzab-tdr", + files: { + ini: "linuxcnc/machines/xyzab-tdr/machine.ini", + parameters: "../linuxcnc.var", + toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", + }, + }, + }), + storage, +); +await assert.rejects( + () => loadMachineSessionSnapshot("machine-session-bad-files", "xyzab-tdr", { storage }), + /Invalid OPFS path/, +); await saveMachineTextFiles("xyzab-tdr", { ini: "[EMC]\nMACHINE = xyzab-tdr\n",