新增 OPFS session readiness SDK helper
This commit is contained in:
@@ -42,8 +42,8 @@ The project is complete for the current scope when all of the following are true
|
||||
| Sim config inventory | Stable with skips | `executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`; `docs/sim-configs-coverage-handoff.md` records the release-gate baseline. |
|
||||
| Native nc_files baseline | Stable | Last recorded Layer 1: `total 107`, `pass 101`, `expected_fail 6`, `unexpected_fail 0`. |
|
||||
| Native sim configs baseline | Stable | Last recorded Layer 2: `total 159`, `pass 151`, `expected_fail 8`, `unexpected_fail 0`. |
|
||||
| OPFS/session persistence | Stable for current scope | Browser smoke passes, SDK re-exports OPFS/session helpers, and `docs/opfs-session-persistence.md` defines the project-level release gate. |
|
||||
| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session, sim-config staging, and INI panel shell/workflow overview helpers with a Node surface smoke. |
|
||||
| OPFS/session persistence | Stable for current scope | Browser smoke passes, SDK re-exports OPFS/session helpers including `readMachineSessionReadiness()`, and `docs/opfs-session-persistence.md` defines the project-level release gate. |
|
||||
| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session readiness/load helpers, sim-config staging, and INI panel shell/workflow overview helpers with a Node surface smoke. |
|
||||
| Browser/UI workflow | Stable for current scope | Workflow overview embedding mount DOM closure is complete and documented for external shells. |
|
||||
| Workflow overview embedding | Stable for current scope | Report/display/render, DOM contract/readiness/renderer, mount result/display/render, mount DOM contract/readiness/renderer/wrapper exist. |
|
||||
| Host/runtime boundary proof | Stable blocked state | Host smoke passes; runtime families remain blocked and `docs/host-runtime-boundary-handoff.md` records host readiness, promotion blockers, dispatch, and evidence gates. |
|
||||
|
||||
66
text14.txt
66
text14.txt
@@ -1638,3 +1638,69 @@ project_release_gate=ok
|
||||
若本批验证通过并提交,后续可以继续做低风险可维护性推进:为 project release gate 增加短文档索引或
|
||||
CI 入口说明;或者在真实 LinuxCNC host runtime 可用前,保持 blocked runtime family 不 promotion,
|
||||
只维护 guard、docs 和 workflow/API 边界。
|
||||
|
||||
二十、2026-06-16 继续执行记录:OPFS machine session readiness SDK helper
|
||||
|
||||
本轮按“功能实质性推进为主”继续推进,新增可直接复用的 OPFS/session readiness helper。该 helper
|
||||
允许外部 SDK/UI 调用方在真正加载 machine session 到 WASM 前,结构化检查 persisted INI、参数文件、
|
||||
工具表、可选 G-code 和可选 session snapshot 是否存在且 snapshot envelope 有效。
|
||||
|
||||
完成内容:
|
||||
|
||||
- 新增 `wasm-port/runtime/opfs/machine-session-readiness.js`;
|
||||
- 新增 `readMachineSessionReadiness(machineId, options)`;
|
||||
- helper 返回:
|
||||
- `machineId`;
|
||||
- `ready`;
|
||||
- `phase`;
|
||||
- `paths`;
|
||||
- `checks`;
|
||||
- `snapshotCheck`;
|
||||
- `snapshot`;
|
||||
- `missing`;
|
||||
- `errors`;
|
||||
- 支持默认 machine file paths;
|
||||
- 支持显式 `iniOpfsPath`、`parameterOpfsPath`、`toolTableOpfsPath`;
|
||||
- 支持 `gcodeFilename`、`gcodeOpfsPath` 和 `gcodeRequired`;
|
||||
- 支持 `sessionId` 与 `snapshotFilename`,并复用现有 `loadMachineSessionSnapshot()` 做 snapshot
|
||||
envelope/machine id 校验;
|
||||
- `wasm-port/runtime/sdk/src/index.js` re-export `readMachineSessionReadiness()`;
|
||||
- `wasm-port/tests/opfs/node/verify_file_service.mjs` 覆盖 ready session、missing session、
|
||||
snapshot check、G-code check 与 invalid OPFS path;
|
||||
- `wasm-port/tests/sdk/node/verify_sdk_surface.mjs` 覆盖 SDK export;
|
||||
- `wasm-port/runtime/sdk/README.md` 记录新 SDK helper;
|
||||
- `wasm-port/docs/opfs-session-persistence.md` 记录 readiness workflow;
|
||||
- `wasm-port/tests/docs/node/verify_opfs_session_docs.mjs` 覆盖 docs drift;
|
||||
- `PROJECT_COMPLETION_TRACKER.md` 同步 OPFS/session 与 SDK/API surface 状态;
|
||||
- 未新增控制按钮;
|
||||
- 未执行 action-plan;
|
||||
- 未解析 G-code;
|
||||
- 未解释 canonical events;
|
||||
- 未新增 JS CNC 语义;
|
||||
- 未运行 opt-in runtime probes;
|
||||
- 未 promotion blocked runtime family;
|
||||
- 未改变 LinuxCNC interpreter、tool、parameter、planner、kinematics、remap 或 LinuxCNC-owned
|
||||
runtime semantics。
|
||||
|
||||
已通过的定向验证:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
wasm-port/tests/opfs/node/verify_file_service.sh
|
||||
wasm-port/tests/sdk/node/verify_sdk_surface.sh
|
||||
wasm-port/tests/docs/node/verify_opfs_session_docs.sh
|
||||
```
|
||||
|
||||
关键输出:
|
||||
|
||||
```text
|
||||
opfs_file_service_node_smoke=ok
|
||||
sdk_surface_node_smoke=ok
|
||||
opfs_session_docs_node_smoke=ok
|
||||
```
|
||||
|
||||
下一步工作计划:
|
||||
|
||||
继续以实质能力推进为主。下一批建议把 `readMachineSessionReadiness()` 接入 INI panel/session
|
||||
workflow 或 external shell handoff view-model,让 UI 在加载 session 前显示结构化 ready/blocked
|
||||
原因;仍保持只读,不新增控制按钮,不解析 G-code,不扩大 LinuxCNC 语义边界。
|
||||
|
||||
@@ -31,6 +31,7 @@ Session snapshot helpers:
|
||||
- `loadSessionSnapshot()`
|
||||
- `loadMachineSessionSnapshot()`
|
||||
- `validateSessionSnapshot()`
|
||||
- `readMachineSessionReadiness()`
|
||||
|
||||
Machine file and program helpers:
|
||||
|
||||
@@ -74,6 +75,11 @@ The browser/UI persistence workflow is host-side only:
|
||||
`restoreParameters()`, `saveParameters()`, `loadToolTable()`, and
|
||||
`saveToolTable()`.
|
||||
|
||||
External callers can run `readMachineSessionReadiness()` before step 4 to get a
|
||||
structured `ready`/`blocked` report for persisted INI, parameter, tool-table,
|
||||
optional G-code, and optional session snapshot files. This helper only checks
|
||||
host file availability and snapshot envelope validity.
|
||||
|
||||
This workflow stores host files and stages them into Emscripten FS. It does not
|
||||
implement G-code, tool-table, parameter-file, planner, remap, or kinematics
|
||||
semantics in JavaScript.
|
||||
@@ -154,6 +160,8 @@ ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_rem
|
||||
- Invalid OPFS paths are rejected before storage access.
|
||||
- Invalid snapshot format, version, session id, payload, or machine id is
|
||||
rejected by snapshot validation helpers.
|
||||
- `readMachineSessionReadiness()` reports missing files or invalid snapshots as
|
||||
structured blocked checks before callers load a session into WASM.
|
||||
- Missing INI, parameter, or tool-table OPFS files remain host persistence
|
||||
errors. JavaScript does not synthesize LinuxCNC machine state.
|
||||
- Tool, parameter, remap, planner, and kinematics behavior remains owned by
|
||||
|
||||
103
wasm-port/runtime/opfs/machine-session-readiness.js
Normal file
103
wasm-port/runtime/opfs/machine-session-readiness.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { loadTextFile } from "./file-service.js";
|
||||
import {
|
||||
gcodeProgramPath,
|
||||
machineIniPath,
|
||||
normalizeOpfsPath,
|
||||
parameterFilePath,
|
||||
sessionSnapshotPath,
|
||||
toolTablePath,
|
||||
} from "./path-model.js";
|
||||
import { loadMachineSessionSnapshot } from "./snapshot-store.js";
|
||||
|
||||
function checkResult(name, path, ok, error = null) {
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
ok,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
async function checkTextFile(name, path, storage) {
|
||||
try {
|
||||
const text = await loadTextFile(path, storage);
|
||||
return {
|
||||
...checkResult(name, path, true),
|
||||
bytes: text.length,
|
||||
};
|
||||
} catch (error) {
|
||||
return checkResult(name, path, false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveReadinessPaths(machineId, options = {}) {
|
||||
const paths = {
|
||||
ini: options.iniOpfsPath ?? machineIniPath(machineId, options.iniFilename),
|
||||
parameters:
|
||||
options.parameterOpfsPath ?? parameterFilePath(machineId, options.parameterFilename),
|
||||
toolTable:
|
||||
options.toolTableOpfsPath ?? toolTablePath(machineId, options.toolTableFilename),
|
||||
};
|
||||
|
||||
if (options.gcodeOpfsPath !== undefined) {
|
||||
paths.gcode = normalizeOpfsPath(options.gcodeOpfsPath);
|
||||
} else if (options.gcodeFilename !== undefined) {
|
||||
paths.gcode = gcodeProgramPath(options.gcodeFilename);
|
||||
}
|
||||
|
||||
if (options.sessionId !== undefined) {
|
||||
paths.snapshot = sessionSnapshotPath(options.sessionId, options.snapshotFilename);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function summarizeReadiness(checks, snapshotResult) {
|
||||
const failed = checks.filter((check) => !check.ok);
|
||||
const snapshotFailed = snapshotResult && !snapshotResult.ok ? [snapshotResult] : [];
|
||||
return [...failed, ...snapshotFailed];
|
||||
}
|
||||
|
||||
export async function readMachineSessionReadiness(machineId, options = {}) {
|
||||
const paths = resolveReadinessPaths(machineId, options);
|
||||
const checks = [
|
||||
await checkTextFile("ini", paths.ini, options.storage),
|
||||
await checkTextFile("parameters", paths.parameters, options.storage),
|
||||
await checkTextFile("toolTable", paths.toolTable, options.storage),
|
||||
];
|
||||
|
||||
if (options.gcodeRequired === true || paths.gcode !== undefined) {
|
||||
checks.push(await checkTextFile("gcode", paths.gcode, options.storage));
|
||||
}
|
||||
|
||||
let snapshot = null;
|
||||
let snapshotCheck = null;
|
||||
if (options.sessionId !== undefined) {
|
||||
try {
|
||||
snapshot = await loadMachineSessionSnapshot(options.sessionId, machineId, {
|
||||
storage: options.storage,
|
||||
filename: options.snapshotFilename,
|
||||
});
|
||||
snapshotCheck = checkResult("snapshot", paths.snapshot, true);
|
||||
} catch (error) {
|
||||
snapshotCheck = checkResult("snapshot", paths.snapshot, false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const failures = summarizeReadiness(checks, snapshotCheck);
|
||||
return {
|
||||
machineId,
|
||||
ready: failures.length === 0,
|
||||
phase: failures.length === 0 ? "ready" : "blocked",
|
||||
paths,
|
||||
checks,
|
||||
snapshotCheck,
|
||||
snapshot,
|
||||
missing: failures.map((failure) => failure.name),
|
||||
errors: failures.map((failure) => ({
|
||||
name: failure.name,
|
||||
path: failure.path,
|
||||
error: failure.error,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
parameterFilePath,
|
||||
planIniFileContextStaging,
|
||||
planSimConfigStaging,
|
||||
readMachineSessionReadiness,
|
||||
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
|
||||
restoreMachineParametersFromOpfs,
|
||||
saveMachineSessionSnapshot,
|
||||
@@ -171,6 +172,9 @@ private module paths:
|
||||
snapshot persistence.
|
||||
- `machineFilePaths()`, `saveMachineTextFiles()`, `loadMachineTextFiles()`, and
|
||||
`gcodeFilenameFromProgramPath()` for machine-file and G-code file helpers.
|
||||
- `readMachineSessionReadiness()` for checking persisted INI, parameter,
|
||||
tool-table, optional G-code, and optional session snapshot files before
|
||||
loading a session.
|
||||
- `restoreMachineParametersFromOpfs()`, `loadMachineToolTableFromOpfs()`, and
|
||||
`loadMachineSessionFromOpfs()` for loading OPFS machine state into the
|
||||
LinuxCNC-backed interpreter SDK.
|
||||
|
||||
@@ -51,6 +51,7 @@ export {
|
||||
saveMachineToolTableToOpfs,
|
||||
} from "../../opfs/linuxcnc-tool-table-bridge.js";
|
||||
export { loadMachineSessionFromOpfs } from "../../opfs/linuxcnc-machine-session-bridge.js";
|
||||
export { readMachineSessionReadiness } from "../../opfs/machine-session-readiness.js";
|
||||
|
||||
export {
|
||||
createIniPanelLaunchApiManifest,
|
||||
|
||||
@@ -17,6 +17,7 @@ for (const phrase of [
|
||||
"OPFS/session persistence and release gate",
|
||||
"runtime/sdk/src/index.js",
|
||||
"createMachineSessionSnapshotPayload",
|
||||
"readMachineSessionReadiness",
|
||||
"loadMachineSessionFromOpfs",
|
||||
"restoreMachineParametersFromOpfs",
|
||||
"loadMachineToolTableFromOpfs",
|
||||
@@ -33,6 +34,7 @@ for (const phrase of [
|
||||
|
||||
assert.match(readmeText, /docs\/opfs-session-persistence\.md/);
|
||||
assert.match(sdkReadmeText, /OPFS\/session persistence exports/);
|
||||
assert.match(sdkReadmeText, /readMachineSessionReadiness/);
|
||||
assert.match(hostSmokeText, /verify_sdk_surface\.sh/);
|
||||
assert.match(hostSmokeText, /verify_file_service\.sh/);
|
||||
assert.match(browserSmokeText, /browser_ini_opfs_smoke=ok/);
|
||||
|
||||
@@ -43,6 +43,9 @@ import {
|
||||
import {
|
||||
loadMachineSessionFromOpfs,
|
||||
} from "../../../runtime/opfs/linuxcnc-machine-session-bridge.js";
|
||||
import {
|
||||
readMachineSessionReadiness,
|
||||
} from "../../../runtime/opfs/machine-session-readiness.js";
|
||||
|
||||
class MockFileHandle {
|
||||
constructor(name) {
|
||||
@@ -420,6 +423,54 @@ assertGcodeProgramStoragePath(
|
||||
);
|
||||
assert.equal(await loadGcodeProgram("custom-fixture.ngc", { storage }), "G1 X1 F10\nM2\n");
|
||||
|
||||
const readyMachineSession = await readMachineSessionReadiness("xyzab-tdr", {
|
||||
storage,
|
||||
sessionId: "machine-session-1",
|
||||
snapshotFilename: "machine-session-snapshot.json",
|
||||
gcodeFilename: "fixture.ngc",
|
||||
});
|
||||
assert.equal(readyMachineSession.ready, true);
|
||||
assert.equal(readyMachineSession.phase, "ready");
|
||||
assert.deepEqual(readyMachineSession.missing, []);
|
||||
assert.deepEqual(readyMachineSession.errors, []);
|
||||
assert.deepEqual(
|
||||
readyMachineSession.checks.map((check) => [check.name, check.ok, check.path]),
|
||||
[
|
||||
["ini", true, "linuxcnc/machines/xyzab-tdr/machine.ini"],
|
||||
["parameters", true, "linuxcnc/machines/xyzab-tdr/linuxcnc.var"],
|
||||
["toolTable", true, "linuxcnc/machines/xyzab-tdr/tool.tbl"],
|
||||
["gcode", true, "linuxcnc/gcode/fixture.ngc"],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(readyMachineSession.snapshotCheck, {
|
||||
name: "snapshot",
|
||||
path: "linuxcnc/sessions/machine-session-1/machine-session-snapshot.json",
|
||||
ok: true,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(readyMachineSession.snapshot.sessionId, "machine-session-1");
|
||||
|
||||
const missingMachineSession = await readMachineSessionReadiness("missing-machine", {
|
||||
storage,
|
||||
sessionId: "missing-session",
|
||||
gcodeRequired: true,
|
||||
gcodeFilename: "missing.ngc",
|
||||
});
|
||||
assert.equal(missingMachineSession.ready, false);
|
||||
assert.equal(missingMachineSession.phase, "blocked");
|
||||
assert.deepEqual(missingMachineSession.missing, [
|
||||
"ini",
|
||||
"parameters",
|
||||
"toolTable",
|
||||
"gcode",
|
||||
"snapshot",
|
||||
]);
|
||||
assert.deepEqual(
|
||||
missingMachineSession.errors.map((error) => error.name),
|
||||
["ini", "parameters", "toolTable", "gcode", "snapshot"],
|
||||
);
|
||||
assert.match(missingMachineSession.errors[0].error, /missing directory|missing file/);
|
||||
|
||||
const bridgeFiles = new Map();
|
||||
const bridgeInterp = {
|
||||
writeTextFile(path, text) {
|
||||
@@ -897,6 +948,13 @@ await assert.rejects(
|
||||
() => loadGcodeProgram("../escape.ngc", { storage }),
|
||||
/Invalid G-code filename/,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => readMachineSessionReadiness("xyzab-tdr", {
|
||||
storage,
|
||||
gcodeOpfsPath: "../escape.ngc",
|
||||
}),
|
||||
/Invalid OPFS path/,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => saveSessionSnapshot("session-1", {}, {
|
||||
storage,
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
parameterFilePath,
|
||||
planIniFileContextStaging,
|
||||
planSimConfigStaging,
|
||||
readMachineSessionReadiness,
|
||||
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
|
||||
restoreMachineParametersFromOpfs,
|
||||
saveMachineSessionSnapshot,
|
||||
@@ -76,6 +77,7 @@ const requiredExports = [
|
||||
["restoreMachineParametersFromOpfs", restoreMachineParametersFromOpfs],
|
||||
["loadMachineToolTableFromOpfs", loadMachineToolTableFromOpfs],
|
||||
["loadMachineSessionFromOpfs", loadMachineSessionFromOpfs],
|
||||
["readMachineSessionReadiness", readMachineSessionReadiness],
|
||||
["createIniPanelLaunchApiManifest", createIniPanelLaunchApiManifest],
|
||||
["isSupportedIniPanelLaunchApiManifest", isSupportedIniPanelLaunchApiManifest],
|
||||
["createIniPanelShellViewModel", createIniPanelShellViewModel],
|
||||
|
||||
Reference in New Issue
Block a user