建立ToolDbProcessPort合约层
结论:已新增ToolDbProcessPort合约、OPFS tool DB flat-file/transcript store和Node/WASM gates,baseline保持28/28/131/0且promotion继续锁定。
This commit is contained in:
@@ -13,6 +13,9 @@ import {
|
||||
parameterFilePath,
|
||||
previewCachePath,
|
||||
sessionSnapshotPath,
|
||||
toolDbFilePath,
|
||||
toolDbRootPath,
|
||||
toolDbTranscriptPath,
|
||||
toolTablePath,
|
||||
} from "../../../runtime/opfs/path-model.js";
|
||||
import {
|
||||
@@ -205,6 +208,12 @@ assert.equal(await getOpfsRoot(storage), root);
|
||||
assert.equal(normalizeOpfsPath("linuxcnc/machines/xyzab.ini"), "linuxcnc/machines/xyzab.ini");
|
||||
assert.equal(machineIniPath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/machine.ini");
|
||||
assert.equal(toolTablePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool.tbl");
|
||||
assert.equal(toolDbRootPath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool-db");
|
||||
assert.equal(toolDbFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool-db/db_nonran_file");
|
||||
assert.equal(
|
||||
toolDbTranscriptPath("xyzab-tdr", "run-1"),
|
||||
"linuxcnc/machines/xyzab-tdr/tool-db/transcripts/run-1/transcript.json",
|
||||
);
|
||||
assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxcnc.var");
|
||||
assertGcodeProgramStoragePath("fixture.ngc", gcodeProgramPath("fixture.ngc"));
|
||||
assertGcodeProgramStoragePath(
|
||||
|
||||
110
wasm-port/tests/opfs/node/verify_tool_db_store.mjs
Normal file
110
wasm-port/tests/opfs/node/verify_tool_db_store.mjs
Normal file
@@ -0,0 +1,110 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createToolDbStorePaths,
|
||||
loadToolDbFile,
|
||||
loadToolDbTranscript,
|
||||
saveToolDbFile,
|
||||
saveToolDbTranscript,
|
||||
} from "../../../runtime/opfs/tool-db-store.js";
|
||||
import {
|
||||
toolDbFilePath,
|
||||
toolDbRootPath,
|
||||
toolDbTranscriptPath,
|
||||
} from "../../../runtime/opfs/path-model.js";
|
||||
|
||||
class MockFileHandle {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.textValue = "";
|
||||
}
|
||||
|
||||
async createWritable() {
|
||||
return {
|
||||
write: async (text) => {
|
||||
this.textValue = String(text);
|
||||
},
|
||||
close: async () => {},
|
||||
};
|
||||
}
|
||||
|
||||
async getFile() {
|
||||
return {
|
||||
text: async () => this.textValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MockDirectoryHandle {
|
||||
constructor(name = "") {
|
||||
this.name = name;
|
||||
this.dirs = new Map();
|
||||
this.files = new Map();
|
||||
}
|
||||
|
||||
async getDirectoryHandle(name, options = {}) {
|
||||
if (!this.dirs.has(name)) {
|
||||
if (!options.create) {
|
||||
throw new Error(`missing directory: ${name}`);
|
||||
}
|
||||
this.dirs.set(name, new MockDirectoryHandle(name));
|
||||
}
|
||||
return this.dirs.get(name);
|
||||
}
|
||||
|
||||
async getFileHandle(name, options = {}) {
|
||||
if (!this.files.has(name)) {
|
||||
if (!options.create) {
|
||||
throw new Error(`missing file: ${name}`);
|
||||
}
|
||||
this.files.set(name, new MockFileHandle(name));
|
||||
}
|
||||
return this.files.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
const root = new MockDirectoryHandle();
|
||||
const storage = {
|
||||
getDirectory: async () => root,
|
||||
};
|
||||
|
||||
assert.equal(toolDbRootPath("db-demo"), "linuxcnc/machines/db-demo/tool-db");
|
||||
assert.equal(toolDbFilePath("db-demo"), "linuxcnc/machines/db-demo/tool-db/db_nonran_file");
|
||||
assert.equal(
|
||||
toolDbTranscriptPath("db-demo", "run-42"),
|
||||
"linuxcnc/machines/db-demo/tool-db/transcripts/run-42/transcript.json",
|
||||
);
|
||||
assert.deepEqual(createToolDbStorePaths("db-demo", { runId: "run-42" }), {
|
||||
dbFile: "linuxcnc/machines/db-demo/tool-db/db_nonran_file",
|
||||
transcript: "linuxcnc/machines/db-demo/tool-db/transcripts/run-42/transcript.json",
|
||||
});
|
||||
|
||||
const dbText = "T11 P111 D0.33 Z0.11\nT14 P114 D0.14 Z0.14\n";
|
||||
const savedDb = await saveToolDbFile("db-demo", dbText, { storage });
|
||||
assert.equal(savedDb.opfsPath, "linuxcnc/machines/db-demo/tool-db/db_nonran_file");
|
||||
assert.equal(savedDb.displayPath, "/machines/db-demo/tool-db/db_nonran_file");
|
||||
assert.match(savedDb.dbFileHash, /^[0-9a-f]{8}$/);
|
||||
|
||||
const loadedDb = await loadToolDbFile("db-demo", { storage });
|
||||
assert.equal(loadedDb.text, dbText);
|
||||
assert.equal(loadedDb.dbFileHash, savedDb.dbFileHash);
|
||||
|
||||
const transcript = [
|
||||
{ direction: "read", line: "v2.1" },
|
||||
{ direction: "write", line: "g" },
|
||||
{ direction: "read", line: "FINI (get_cmd)" },
|
||||
];
|
||||
const savedTranscript = await saveToolDbTranscript("db-demo", "run-42", transcript, { storage });
|
||||
assert.equal(
|
||||
savedTranscript.opfsPath,
|
||||
"linuxcnc/machines/db-demo/tool-db/transcripts/run-42/transcript.json",
|
||||
);
|
||||
assert.equal(savedTranscript.displayPath, "/machines/db-demo/tool-db/transcripts/run-42/transcript.json");
|
||||
assert.match(savedTranscript.transcriptHash, /^[0-9a-f]{8}$/);
|
||||
|
||||
const loadedTranscript = await loadToolDbTranscript("db-demo", "run-42", { storage });
|
||||
assert.equal(loadedTranscript.payload.schema, "linuxcnc.toolDbProcessTranscript.v1");
|
||||
assert.deepEqual(loadedTranscript.payload.transcript, transcript);
|
||||
assert.equal(loadedTranscript.transcriptHash, savedTranscript.transcriptHash);
|
||||
|
||||
console.log("tool_db_store_opfs=ok");
|
||||
6
wasm-port/tests/opfs/node/verify_tool_db_store.sh
Normal file
6
wasm-port/tests/opfs/node/verify_tool_db_store.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/opfs/node/verify_tool_db_store.mjs"
|
||||
@@ -90,6 +90,9 @@ import {
|
||||
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
createProjectReleaseReadinessReport,
|
||||
createProjectReleaseReadinessSummaryViewModel,
|
||||
createLinuxCncToolDbProcessPort,
|
||||
createToolDbProcessDiagnostics,
|
||||
createToolDbTransactionPlan,
|
||||
createVirtualHalBridgeActionPlan,
|
||||
createVirtualHalBridgeReadiness,
|
||||
createVirtualHalCommandScriptFixtureReport,
|
||||
@@ -153,6 +156,8 @@ import {
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
loadMachineToolTableFromOpfs,
|
||||
loadToolDbFile,
|
||||
loadToolDbTranscript,
|
||||
loadTextFile,
|
||||
machineFilePaths,
|
||||
machineIniPath,
|
||||
@@ -172,7 +177,13 @@ import {
|
||||
saveSessionSnapshot,
|
||||
saveTextFile,
|
||||
sessionSnapshotPath,
|
||||
saveToolDbFile,
|
||||
saveToolDbTranscript,
|
||||
toolDbFilePath,
|
||||
toolDbRootPath,
|
||||
toolDbTranscriptPath,
|
||||
toolTablePath,
|
||||
validateToolDbTranscript,
|
||||
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
|
||||
validateSessionSnapshot,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
@@ -490,6 +501,9 @@ const requiredExports = [
|
||||
["loadTextFile", loadTextFile],
|
||||
["machineIniPath", machineIniPath],
|
||||
["toolTablePath", toolTablePath],
|
||||
["toolDbRootPath", toolDbRootPath],
|
||||
["toolDbFilePath", toolDbFilePath],
|
||||
["toolDbTranscriptPath", toolDbTranscriptPath],
|
||||
["parameterFilePath", parameterFilePath],
|
||||
["gcodeProgramPath", gcodeProgramPath],
|
||||
["sessionSnapshotPath", sessionSnapshotPath],
|
||||
@@ -507,6 +521,14 @@ const requiredExports = [
|
||||
["saveMachineTextFiles", saveMachineTextFiles],
|
||||
["loadMachineTextFiles", loadMachineTextFiles],
|
||||
["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath],
|
||||
["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort],
|
||||
["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics],
|
||||
["createToolDbTransactionPlan", createToolDbTransactionPlan],
|
||||
["validateToolDbTranscript", validateToolDbTranscript],
|
||||
["saveToolDbFile", saveToolDbFile],
|
||||
["loadToolDbFile", loadToolDbFile],
|
||||
["saveToolDbTranscript", saveToolDbTranscript],
|
||||
["loadToolDbTranscript", loadToolDbTranscript],
|
||||
["restoreMachineParametersFromOpfs", restoreMachineParametersFromOpfs],
|
||||
["loadMachineToolTableFromOpfs", loadMachineToolTableFromOpfs],
|
||||
["loadMachineSessionFromOpfs", loadMachineSessionFromOpfs],
|
||||
|
||||
95
wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs
Normal file
95
wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
TOOL_DB_PROCESS_PORT_CONTRACT_VERSION,
|
||||
TOOL_DB_TRANSACTION_PLAN,
|
||||
createLinuxCncToolDbProcessPort,
|
||||
createToolDbProcessDiagnostics,
|
||||
createToolDbTransactionPlan,
|
||||
validateToolDbTranscript,
|
||||
} from "../../../runtime/sdk/src/tool-db-process-port.js";
|
||||
|
||||
assert.equal(TOOL_DB_PROCESS_PORT_CONTRACT_VERSION, 1);
|
||||
assert.deepEqual(
|
||||
createToolDbTransactionPlan().map((step) => step.phase),
|
||||
[
|
||||
"startup_handshake",
|
||||
"initial_get_all",
|
||||
"tool_offset_notify",
|
||||
"spindle_load_notify",
|
||||
"spindle_unload_notify",
|
||||
],
|
||||
);
|
||||
assert.equal(TOOL_DB_TRANSACTION_PLAN[0].expect, "v2.1");
|
||||
assert.equal(TOOL_DB_TRANSACTION_PLAN.every((step) => step.requiresFini || step.phase === "startup_handshake"), true);
|
||||
|
||||
const port = createLinuxCncToolDbProcessPort({
|
||||
dbProgramPath: "./db_nonran.py",
|
||||
sourceFiles: [
|
||||
"configs/sim/axis/db_demo/db_nonran.py",
|
||||
"configs/sim/axis/db_demo/db.py",
|
||||
"lib/python/tooldb.py",
|
||||
],
|
||||
runtimeMode: "contract-only",
|
||||
});
|
||||
|
||||
const startResult = await port.start();
|
||||
assert.deepEqual(startResult, {
|
||||
runtimeMode: "contract-only",
|
||||
runtimeExecutionReady: false,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
});
|
||||
|
||||
const blockedPlan = await port.runTransactionPlan();
|
||||
assert.equal(blockedPlan.status, "blocked_runtime_adapter_required");
|
||||
assert.equal(blockedPlan.runtimeExecutionReady, false);
|
||||
assert.equal(blockedPlan.executionEnabled, false);
|
||||
assert.equal(blockedPlan.promotionAllowed, false);
|
||||
assert.equal(blockedPlan.tblFallbackSufficient, false);
|
||||
|
||||
const transcript = [
|
||||
{ direction: "read", line: "v2.1" },
|
||||
{ direction: "write", line: "g" },
|
||||
{ direction: "read", line: "T10 P110 D0.10 Z0.10" },
|
||||
{ direction: "read", line: "FINI (get_cmd)" },
|
||||
{ direction: "write", line: "p t11 p111 d0.33 z0.11" },
|
||||
{ direction: "read", line: "FINI (update recvd) t11 p111 d0.33 z0.11" },
|
||||
{ direction: "write", line: "l t14 p0" },
|
||||
{ direction: "read", line: "FINI (update recvd) t14 p0" },
|
||||
{ direction: "write", line: "u t0 p0" },
|
||||
{ direction: "read", line: "FINI (update recvd) t0 p0" },
|
||||
];
|
||||
const validation = validateToolDbTranscript(transcript);
|
||||
assert.equal(validation.ready, true);
|
||||
assert.equal(validation.hasVersion, true);
|
||||
assert.equal(validation.hasGetAll, true);
|
||||
assert.equal(validation.hasFini, true);
|
||||
assert.equal(validation.hasPut, true);
|
||||
assert.equal(validation.hasLoad, true);
|
||||
assert.equal(validation.hasUnload, true);
|
||||
|
||||
const diagnostics = createToolDbProcessDiagnostics({
|
||||
dbProgramPath: "./db_nonran.py",
|
||||
opfsDbPath: "/machines/db-demo/tool-db/db_nonran_file",
|
||||
transcript,
|
||||
dbFileText: "T11 P111 D0.33 Z0.11\nT14 P114 D0.14 Z0.14\n",
|
||||
startupToolCount: 10,
|
||||
mutationCount: 3,
|
||||
runtimeMode: "wasm-tool-db-protocol-worker",
|
||||
});
|
||||
assert.equal(diagnostics.dbProgramPath, "./db_nonran.py");
|
||||
assert.equal(diagnostics.protocolTranscriptReady, true);
|
||||
assert.equal(diagnostics.opfsPersistenceReady, true);
|
||||
assert.equal(diagnostics.startupToolCount, 10);
|
||||
assert.equal(diagnostics.mutationCount, 3);
|
||||
assert.equal(diagnostics.tblFallbackSufficient, false);
|
||||
assert.equal(diagnostics.runtimeExecutionReady, false);
|
||||
assert.equal(diagnostics.executionEnabled, false);
|
||||
assert.equal(diagnostics.promotionAllowed, false);
|
||||
assert.match(diagnostics.transcriptHash, /^[0-9a-f]{8}$/);
|
||||
assert.match(diagnostics.dbFileHash, /^[0-9a-f]{8}$/);
|
||||
|
||||
assert.equal(port.close().promotionAllowed, false);
|
||||
|
||||
console.log("tool_db_process_port_sdk=ok");
|
||||
6
wasm-port/tests/sdk/node/verify_tool_db_process_port.sh
Normal file
6
wasm-port/tests/sdk/node/verify_tool_db_process_port.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/sdk/node/verify_tool_db_process_port.mjs"
|
||||
@@ -0,0 +1,69 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import {
|
||||
createLinuxCncToolDbProcessPort,
|
||||
createToolDbProcessDiagnostics,
|
||||
createToolDbTransactionPlan,
|
||||
validateToolDbTranscript,
|
||||
} from "../../../runtime/sdk/src/tool-db-process-port.js";
|
||||
import {
|
||||
createToolDbStorePaths,
|
||||
} from "../../../runtime/opfs/tool-db-store.js";
|
||||
|
||||
const iniText = readFileSync(
|
||||
resolve("linuxcnc/configs/sim/axis/db_demo/db_nonran.ini"),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(iniText, /DB_PROGRAM\s*=\s*\.\/db_nonran\.py/);
|
||||
|
||||
const plan = createToolDbTransactionPlan();
|
||||
assert.equal(plan.length, 5);
|
||||
assert.equal(plan[0].expect, "v2.1");
|
||||
assert.equal(plan.some((step) => step.line === "g"), true);
|
||||
assert.equal(plan.some((step) => step.line === "p t11 p111 d0.33 z0.11"), true);
|
||||
assert.equal(plan.some((step) => step.line === "l t14 p0"), true);
|
||||
assert.equal(plan.some((step) => step.line === "u t0 p0"), true);
|
||||
|
||||
const port = createLinuxCncToolDbProcessPort({
|
||||
dbProgramPath: "./db_nonran.py",
|
||||
sourceFiles: [
|
||||
"configs/sim/axis/db_demo/db_nonran.py",
|
||||
"configs/sim/axis/db_demo/db.py",
|
||||
"lib/python/tooldb.py",
|
||||
],
|
||||
runtimeMode: "contract-only",
|
||||
});
|
||||
assert.equal((await port.start()).runtimeExecutionReady, false);
|
||||
assert.equal((await port.runTransactionPlan()).status, "blocked_runtime_adapter_required");
|
||||
|
||||
const transcript = [
|
||||
{ direction: "read", line: "v2.1" },
|
||||
{ direction: "write", line: "g" },
|
||||
{ direction: "read", line: "T10 P110 D0.10 Z0.10" },
|
||||
{ direction: "read", line: "FINI (get_cmd)" },
|
||||
{ direction: "write", line: "p t11 p111 d0.33 z0.11" },
|
||||
{ direction: "write", line: "l t14 p0" },
|
||||
{ direction: "write", line: "u t0 p0" },
|
||||
];
|
||||
assert.equal(validateToolDbTranscript(transcript).ready, true);
|
||||
|
||||
const paths = createToolDbStorePaths("db-demo", { runId: "run-1" });
|
||||
assert.equal(paths.dbFile, "linuxcnc/machines/db-demo/tool-db/db_nonran_file");
|
||||
assert.equal(paths.transcript, "linuxcnc/machines/db-demo/tool-db/transcripts/run-1/transcript.json");
|
||||
|
||||
const diagnostics = createToolDbProcessDiagnostics({
|
||||
dbProgramPath: "./db_nonran.py",
|
||||
opfsDbPath: "/machines/db-demo/tool-db/db_nonran_file",
|
||||
transcript,
|
||||
dbFileText: "T11 P111 D0.33 Z0.11\n",
|
||||
startupToolCount: 10,
|
||||
mutationCount: 3,
|
||||
});
|
||||
assert.equal(diagnostics.tblFallbackSufficient, false);
|
||||
assert.equal(diagnostics.runtimeExecutionReady, false);
|
||||
assert.equal(diagnostics.executionEnabled, false);
|
||||
assert.equal(diagnostics.promotionAllowed, false);
|
||||
|
||||
console.log("tool_db_process_port_wasm=ok");
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
node "$ROOT_DIR/tests/wasm/node/verify_tool_db_process_port_wasm.mjs"
|
||||
Reference in New Issue
Block a user