推进 OPFS 机器会话快照保存入口

This commit is contained in:
2026-06-14 15:03:33 +08:00
parent 2bff22e0d9
commit a829984300
4 changed files with 147 additions and 0 deletions

View File

@@ -70,6 +70,22 @@ remap 等语义必须来自 LinuxCNC upstream 或 vendored LinuxCNC C/C++/配置
native、Node、browser gate 都按顺序通过; native、Node、browser gate 都按顺序通过;
- browser 不能依赖目录枚举。 - browser 不能依赖目录枚举。
4. 加快实质性推进铁律。
核心移植已稳定后,暂停性能测试和纯 test glue 微整理,后续默认优先推进能被用户或
上层 API 直接使用的实质性能力:
- 优先级从高到低为OPFS/session persistence、SDK/API surface、browser/UI workflow、
sim config coverage promotion、host/runtime boundary proof、文档化可执行工作流
- 每批应尽量产出一个可调用 helper、可复用 API、可验证用户流程、可执行 gate、
可提升覆盖面的 runtime boundary proof或者一份直接指导下一步实现的设计/清单;
- 允许继续做测试胶水,但只能作为支撑实质能力的验证,不再把“局部常量化/断言整理”
作为主要目标;
- 禁止启动性能测试,除非已有明确功能路径和用户流程需要性能基线;
- 禁止为了“推进”而扩大 CNC 语义边界G-code、tool、parameter、planner 等语义仍必须
由 LinuxCNC-owned runtime 提供;
- 每批仍必须小步、可验证、可回滚,并按 `text10.txt` 记录结论和下一步建议。
三、当前完成情况摘要 三、当前完成情况摘要
1. 当前工作树状态。 1. 当前工作树状态。
@@ -336,3 +352,66 @@ host_wasm_opfs_browser_smokes=ok
继续小批量 browser test glue guard。优先扫描 machine text store path assertion groups 继续小批量 browser test glue guard。优先扫描 machine text store path assertion groups
或剩余 OPFS readback path groups 是否还能进一步局部常量化或复用 existing helpers 或剩余 OPFS readback path groups 是否还能进一步局部常量化或复用 existing helpers
只改测试胶水不改变输入、OPFS path、WASM path 字符串或保存/读取行为。 只改测试胶水不改变输入、OPFS path、WASM path 字符串或保存/读取行为。
三、2026-06-14 继续执行记录OPFS machine session snapshot save helper
本轮先将“加快实质性推进”规则写入 `text10.txt` 的当前项目纪律铁律中,并按该规则
继续推进 OPFS/session persistence 的实际 API 入口,而不是继续纯 test glue 微整理。
完成内容:
- 在“当前项目纪律”中新增“加快实质性推进铁律”;
- 在 `wasm-port/runtime/opfs/snapshot-store.js` 新增
`saveMachineSessionSnapshot(sessionId, machineId, options)`
- 该 helper 组合既有 `createMachineSessionSnapshotPayload(...)` 与
`saveSessionSnapshot(...)`,让上层能用一个入口生成并保存机器会话快照;
- 支持沿用既有 `storage`、snapshot `filename`、`createdAt`、`metadata` 以及
`iniFilename`、`parameterFilename`、`toolTableFilename`、`gcodeFilename`、
`iniOpfsPath`、`parameterOpfsPath`、`toolTableOpfsPath`、`gcodeOpfsPath`
选项;
- 在 Node OPFS smoke 中验证组合 helper 的自定义 snapshot 文件名、G-code path、
metadata 与 load round trip
- 在 browser INI panel smoke 中验证浏览器运行时可直接调用组合 helper并能加载回
parameter/tool-table/G-code path 与 metadata
- 未改变 session snapshot envelope 格式、版本、path model、file-service 行为或既有
save/load API
- 未改变 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
```
下一步建议:
继续沿 OPFS/session persistence 的实质能力推进。优先新增
`loadMachineSessionSnapshot(sessionId, machineId, options)`,在读取 generic snapshot
后校验 `payload.machineId` 与机器文件 path 结构,并补 Node/browser smoke如果该入口
已足够清晰,再把 save/load machine session snapshot 接到 INI panel 的实际会话保存/恢复
workflow。仍只处理 OPFS/filesystem/browser boundary不引入或重写任何 CNC 语义。

View File

@@ -89,6 +89,12 @@ export async function saveSessionSnapshot(sessionId, payload, options = {}) {
return snapshot; return snapshot;
} }
export async function saveMachineSessionSnapshot(sessionId, machineId, options = {}) {
assertPlainObject(options, "machine session snapshot save options");
const payload = createMachineSessionSnapshotPayload(machineId, options);
return saveSessionSnapshot(sessionId, payload, options);
}
export async function loadSessionSnapshot(sessionId, options = {}) { export async function loadSessionSnapshot(sessionId, options = {}) {
const path = sessionSnapshotPath(sessionId, options.filename); const path = sessionSnapshotPath(sessionId, options.filename);
const text = await loadTextFile(path, options.storage); const text = await loadTextFile(path, options.storage);

View File

@@ -17,6 +17,7 @@
import { import {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
loadSessionSnapshot, loadSessionSnapshot,
saveMachineSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
} from "../../runtime/opfs/snapshot-store.js"; } from "../../runtime/opfs/snapshot-store.js";
import { import {
@@ -204,6 +205,42 @@ TOOL_TABLE = browser-tool.tbl
customSnapshot.metadata.source, customSnapshot.metadata.source,
"custom snapshot metadata", "custom snapshot metadata",
); );
const machineSessionSnapshot = await saveMachineSessionSnapshot(
"browser-machine-session",
"browser-smoke",
{
filename: "browser-machine-snapshot.json",
parameterFilename: "browser-linuxcnc.var",
toolTableFilename: "browser-tool.tbl",
gcodeFilename: "browser-smoke.ngc",
createdAt: "2026-06-08T00:00:00.000Z",
metadata: { source: "browser-machine-session" },
},
);
const loadedMachineSessionSnapshot = await loadSessionSnapshot(
"browser-machine-session",
{ filename: "browser-machine-snapshot.json" },
);
assertEqual(
machineSessionSnapshot.payload.files.parameters,
"linuxcnc/machines/browser-smoke/browser-linuxcnc.var",
"machine session snapshot parameter file",
);
assertEqual(
machineSessionSnapshot.payload.files.toolTable,
"linuxcnc/machines/browser-smoke/browser-tool.tbl",
"machine session snapshot tool table",
);
assertEqual(
loadedMachineSessionSnapshot.payload.files.gcode,
"linuxcnc/gcode/browser-smoke.ngc",
"machine session snapshot G-code",
);
assertEqual(
loadedMachineSessionSnapshot.metadata.source,
"browser-machine-session",
"machine session snapshot metadata",
);
await assertRejects( await assertRejects(
"browser snapshot invalid filename save", "browser snapshot invalid filename save",
() => saveSessionSnapshot("browser-session", {}, { () => saveSessionSnapshot("browser-session", {}, {

View File

@@ -19,6 +19,7 @@ import {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createSessionSnapshot, createSessionSnapshot,
loadSessionSnapshot, loadSessionSnapshot,
saveMachineSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
validateSessionSnapshot, validateSessionSnapshot,
} from "../../../runtime/opfs/snapshot-store.js"; } from "../../../runtime/opfs/snapshot-store.js";
@@ -325,6 +326,30 @@ assert.deepEqual(
}), }),
customSnapshot, customSnapshot,
); );
const machineSessionSnapshot = await saveMachineSessionSnapshot(
"machine-session-1",
"xyzab-tdr",
{
storage,
filename: "machine-session-snapshot.json",
gcodeFilename: "fixture.ngc",
createdAt: "2026-06-08T00:00:00.000Z",
metadata: { source: "node-machine-session" },
},
);
assertSessionSnapshotStoragePath(
"machine-session-1",
sessionSnapshotPath("machine-session-1", "machine-session-snapshot.json"),
"machine-session-snapshot.json",
);
assert.deepEqual(machineSessionSnapshot.payload, snapshotPayload);
assert.deepEqual(
await loadSessionSnapshot("machine-session-1", {
storage,
filename: "machine-session-snapshot.json",
}),
machineSessionSnapshot,
);
await saveMachineTextFiles("xyzab-tdr", { await saveMachineTextFiles("xyzab-tdr", {
ini: "[EMC]\nMACHINE = xyzab-tdr\n", ini: "[EMC]\nMACHINE = xyzab-tdr\n",