建立ToolDbProcessPort合约层
结论:已新增ToolDbProcessPort合约、OPFS tool DB flat-file/transcript store和Node/WASM gates,baseline保持28/28/131/0且promotion继续锁定。
This commit is contained in:
@@ -61,6 +61,37 @@ export function toolTablePath(machineId, filename = "tool.tbl") {
|
||||
);
|
||||
}
|
||||
|
||||
export function toolDbRootPath(machineId) {
|
||||
return opfsPath(
|
||||
ROOT,
|
||||
"machines",
|
||||
assertSegment(machineId, "machine id"),
|
||||
"tool-db",
|
||||
);
|
||||
}
|
||||
|
||||
export function toolDbFilePath(machineId, filename = "db_nonran_file") {
|
||||
return opfsPath(
|
||||
ROOT,
|
||||
"machines",
|
||||
assertSegment(machineId, "machine id"),
|
||||
"tool-db",
|
||||
assertSegment(filename, "tool DB filename"),
|
||||
);
|
||||
}
|
||||
|
||||
export function toolDbTranscriptPath(machineId, runId, filename = "transcript.json") {
|
||||
return opfsPath(
|
||||
ROOT,
|
||||
"machines",
|
||||
assertSegment(machineId, "machine id"),
|
||||
"tool-db",
|
||||
"transcripts",
|
||||
assertSegment(runId, "tool DB run id"),
|
||||
assertSegment(filename, "tool DB transcript filename"),
|
||||
);
|
||||
}
|
||||
|
||||
export function parameterFilePath(machineId, filename = "linuxcnc.var") {
|
||||
return opfsPath(
|
||||
ROOT,
|
||||
|
||||
78
wasm-port/runtime/opfs/tool-db-store.js
Normal file
78
wasm-port/runtime/opfs/tool-db-store.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { loadTextFile, saveTextFile } from "./file-service.js";
|
||||
import { toolDbFilePath, toolDbTranscriptPath } from "./path-model.js";
|
||||
|
||||
function stableTextHash(text) {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
hash ^= text.charCodeAt(index);
|
||||
hash = Math.imul(hash, 0x01000193) >>> 0;
|
||||
}
|
||||
return hash.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
function displayOpfsPath(path) {
|
||||
return path.startsWith("linuxcnc/") ? path.slice("linuxcnc".length) : `/${path}`;
|
||||
}
|
||||
|
||||
export function createToolDbStorePaths(machineId, {
|
||||
dbFilename = "db_nonran_file",
|
||||
runId = "run-1",
|
||||
transcriptFilename = "transcript.json",
|
||||
} = {}) {
|
||||
return {
|
||||
dbFile: toolDbFilePath(machineId, dbFilename),
|
||||
transcript: toolDbTranscriptPath(machineId, runId, transcriptFilename),
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveToolDbFile(machineId, text, options = {}) {
|
||||
const path = options.path ?? toolDbFilePath(machineId, options.filename);
|
||||
await saveTextFile(path, text, options.storage);
|
||||
return {
|
||||
opfsPath: path,
|
||||
displayPath: displayOpfsPath(path),
|
||||
dbFileHash: stableTextHash(text),
|
||||
savedText: text,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadToolDbFile(machineId, options = {}) {
|
||||
const path = options.path ?? toolDbFilePath(machineId, options.filename);
|
||||
const text = await loadTextFile(path, options.storage);
|
||||
return {
|
||||
opfsPath: path,
|
||||
displayPath: displayOpfsPath(path),
|
||||
dbFileHash: stableTextHash(text),
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveToolDbTranscript(machineId, runId, transcript, options = {}) {
|
||||
if (!Array.isArray(transcript)) {
|
||||
throw new Error("transcript must be an array.");
|
||||
}
|
||||
const path = options.path ?? toolDbTranscriptPath(machineId, runId, options.filename);
|
||||
const text = JSON.stringify({
|
||||
schema: "linuxcnc.toolDbProcessTranscript.v1",
|
||||
transcript,
|
||||
}, null, 2);
|
||||
await saveTextFile(path, text, options.storage);
|
||||
return {
|
||||
opfsPath: path,
|
||||
displayPath: displayOpfsPath(path),
|
||||
transcriptHash: stableTextHash(text),
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadToolDbTranscript(machineId, runId, options = {}) {
|
||||
const path = options.path ?? toolDbTranscriptPath(machineId, runId, options.filename);
|
||||
const text = await loadTextFile(path, options.storage);
|
||||
const payload = JSON.parse(text);
|
||||
return {
|
||||
opfsPath: path,
|
||||
displayPath: displayOpfsPath(path),
|
||||
transcriptHash: stableTextHash(text),
|
||||
payload,
|
||||
};
|
||||
}
|
||||
@@ -126,6 +126,9 @@ import {
|
||||
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
createProjectReleaseReadinessReport,
|
||||
createProjectReleaseReadinessSummaryViewModel,
|
||||
createLinuxCncToolDbProcessPort,
|
||||
createToolDbProcessDiagnostics,
|
||||
createToolDbTransactionPlan,
|
||||
createSessionSnapshot,
|
||||
defaultMachinePaths,
|
||||
gcodeFilenameFromProgramPath,
|
||||
@@ -143,6 +146,8 @@ import {
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
loadMachineToolTableFromOpfs,
|
||||
loadToolDbFile,
|
||||
loadToolDbTranscript,
|
||||
loadTextFile,
|
||||
machineFilePaths,
|
||||
machineIniPath,
|
||||
@@ -183,9 +188,15 @@ import {
|
||||
saveMachineSessionSnapshot,
|
||||
saveMachineTextFiles,
|
||||
saveSessionSnapshot,
|
||||
saveToolDbFile,
|
||||
saveToolDbTranscript,
|
||||
saveTextFile,
|
||||
sessionSnapshotPath,
|
||||
toolDbFilePath,
|
||||
toolDbRootPath,
|
||||
toolDbTranscriptPath,
|
||||
toolTablePath,
|
||||
validateToolDbTranscript,
|
||||
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
|
||||
validateSessionSnapshot,
|
||||
} from "./src/index.js";
|
||||
@@ -534,6 +545,7 @@ private module paths:
|
||||
- `getOpfsRoot()`, `saveTextFile()`, and `loadTextFile()` for browser OPFS text
|
||||
persistence.
|
||||
- `machineIniPath()`, `toolTablePath()`, `parameterFilePath()`,
|
||||
`toolDbRootPath()`, `toolDbFilePath()`, `toolDbTranscriptPath()`,
|
||||
`gcodeProgramPath()`, `sessionSnapshotPath()`, `defaultMachinePaths()`, and
|
||||
`normalizeOpfsPath()` for stable OPFS path construction.
|
||||
- `createMachineSessionSnapshotPayload()`, `createSessionSnapshot()`,
|
||||
@@ -552,6 +564,14 @@ private module paths:
|
||||
- `restoreMachineParametersFromOpfs()`, `loadMachineToolTableFromOpfs()`, and
|
||||
`loadMachineSessionFromOpfs()` for loading OPFS machine state into the
|
||||
LinuxCNC-backed interpreter SDK.
|
||||
- `createLinuxCncToolDbProcessPort()`, `createToolDbTransactionPlan()`,
|
||||
`validateToolDbTranscript()`, and `createToolDbProcessDiagnostics()` for the
|
||||
LinuxCNC-owned tool DB process port contract. Contract-only mode does not run
|
||||
`DB_PROGRAM`, does not parse `.tbl` as a fallback, and keeps execution and
|
||||
promotion disabled.
|
||||
- `saveToolDbFile()`, `loadToolDbFile()`, `saveToolDbTranscript()`, and
|
||||
`loadToolDbTranscript()` for OPFS persistence of the DB flat file and protocol
|
||||
transcript artifacts.
|
||||
|
||||
These helpers only move and validate host files. Parameter and tool-table
|
||||
effects still come from the LinuxCNC-backed interpreter SDK methods they call.
|
||||
|
||||
@@ -91,6 +91,14 @@ export {
|
||||
planIniFileContextStaging,
|
||||
planSimConfigStaging,
|
||||
} from "./sim-config-staging.js";
|
||||
export {
|
||||
TOOL_DB_PROCESS_PORT_CONTRACT_VERSION,
|
||||
TOOL_DB_TRANSACTION_PLAN,
|
||||
createLinuxCncToolDbProcessPort,
|
||||
createToolDbProcessDiagnostics,
|
||||
createToolDbTransactionPlan,
|
||||
validateToolDbTranscript,
|
||||
} from "./tool-db-process-port.js";
|
||||
|
||||
export {
|
||||
defaultMachinePaths,
|
||||
@@ -103,6 +111,9 @@ export {
|
||||
previewCachePath,
|
||||
sessionSnapshotPath,
|
||||
splitOpfsPath,
|
||||
toolDbFilePath,
|
||||
toolDbRootPath,
|
||||
toolDbTranscriptPath,
|
||||
toolTablePath,
|
||||
} from "../../opfs/path-model.js";
|
||||
export {
|
||||
@@ -138,6 +149,13 @@ export {
|
||||
loadMachineToolTableFromOpfs,
|
||||
saveMachineToolTableToOpfs,
|
||||
} from "../../opfs/linuxcnc-tool-table-bridge.js";
|
||||
export {
|
||||
createToolDbStorePaths,
|
||||
loadToolDbFile,
|
||||
loadToolDbTranscript,
|
||||
saveToolDbFile,
|
||||
saveToolDbTranscript,
|
||||
} from "../../opfs/tool-db-store.js";
|
||||
export { loadMachineSessionFromOpfs } from "../../opfs/linuxcnc-machine-session-bridge.js";
|
||||
export { readMachineSessionReadiness } from "../../opfs/machine-session-readiness.js";
|
||||
|
||||
|
||||
241
wasm-port/runtime/sdk/src/tool-db-process-port.js
Normal file
241
wasm-port/runtime/sdk/src/tool-db-process-port.js
Normal file
@@ -0,0 +1,241 @@
|
||||
const TOOL_DB_PROTOCOL_VERSION = "v2.1";
|
||||
|
||||
export const TOOL_DB_PROCESS_PORT_CONTRACT_VERSION = 1;
|
||||
|
||||
export const TOOL_DB_TRANSACTION_PLAN = [
|
||||
{
|
||||
phase: "startup_handshake",
|
||||
direction: "read",
|
||||
expect: TOOL_DB_PROTOCOL_VERSION,
|
||||
requiresFini: false,
|
||||
},
|
||||
{
|
||||
phase: "initial_get_all",
|
||||
direction: "write",
|
||||
line: "g",
|
||||
expect: "T10..T19",
|
||||
requiresFini: true,
|
||||
},
|
||||
{
|
||||
phase: "tool_offset_notify",
|
||||
direction: "write",
|
||||
line: "p t11 p111 d0.33 z0.11",
|
||||
expect: "FINI",
|
||||
requiresFini: true,
|
||||
},
|
||||
{
|
||||
phase: "spindle_load_notify",
|
||||
direction: "write",
|
||||
line: "l t14 p0",
|
||||
expect: "T14 P0 after query",
|
||||
requiresFini: true,
|
||||
},
|
||||
{
|
||||
phase: "spindle_unload_notify",
|
||||
direction: "write",
|
||||
line: "u t0 p0",
|
||||
expect: "T14 P114 after query",
|
||||
requiresFini: true,
|
||||
},
|
||||
];
|
||||
|
||||
function requireString(value, label) {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
throw new Error(`${label} must be a non-empty string.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeSourceFiles(sourceFiles = []) {
|
||||
if (!Array.isArray(sourceFiles)) {
|
||||
throw new Error("sourceFiles must be an array.");
|
||||
}
|
||||
return sourceFiles.map((sourceFile) => requireString(sourceFile, "source file"));
|
||||
}
|
||||
|
||||
function stableTextHash(text) {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
hash ^= text.charCodeAt(index);
|
||||
hash = Math.imul(hash, 0x01000193) >>> 0;
|
||||
}
|
||||
return hash.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
function transcriptText(transcript) {
|
||||
return transcript
|
||||
.map((entry) => `${entry.direction}:${entry.line}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function createToolDbTransactionPlan() {
|
||||
return TOOL_DB_TRANSACTION_PLAN.map((step) => ({ ...step }));
|
||||
}
|
||||
|
||||
export function createToolDbProcessDiagnostics({
|
||||
dbProgramPath,
|
||||
opfsDbPath = "-",
|
||||
transcript = [],
|
||||
dbFileText = "",
|
||||
startupToolCount = 0,
|
||||
mutationCount = 0,
|
||||
runtimeMode = "contract-only",
|
||||
runtimeExecutionReady = false,
|
||||
} = {}) {
|
||||
requireString(dbProgramPath, "dbProgramPath");
|
||||
const transcriptHash = stableTextHash(transcriptText(transcript));
|
||||
const dbFileHash = stableTextHash(dbFileText);
|
||||
|
||||
return {
|
||||
contractVersion: TOOL_DB_PROCESS_PORT_CONTRACT_VERSION,
|
||||
dbProgramPath,
|
||||
opfsDbPath,
|
||||
runtimeMode,
|
||||
runtimeExecutionReady: runtimeExecutionReady === true,
|
||||
protocolTranscriptReady: transcript.length > 0,
|
||||
opfsPersistenceReady: dbFileText.length > 0,
|
||||
transcriptHash,
|
||||
dbFileHash,
|
||||
startupToolCount,
|
||||
mutationCount,
|
||||
tblFallbackSufficient: false,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function validateToolDbTranscript(transcript) {
|
||||
if (!Array.isArray(transcript)) {
|
||||
throw new Error("transcript must be an array.");
|
||||
}
|
||||
const lines = transcript.map((entry) => entry?.line ?? "");
|
||||
const hasVersion = lines.includes(TOOL_DB_PROTOCOL_VERSION);
|
||||
const hasGetAll = lines.includes("g");
|
||||
const hasFini = lines.some((line) => line.startsWith("FINI"));
|
||||
const hasPut = lines.includes("p t11 p111 d0.33 z0.11");
|
||||
const hasLoad = lines.includes("l t14 p0");
|
||||
const hasUnload = lines.includes("u t0 p0");
|
||||
|
||||
return {
|
||||
protocolVersion: TOOL_DB_PROTOCOL_VERSION,
|
||||
hasVersion,
|
||||
hasGetAll,
|
||||
hasFini,
|
||||
hasPut,
|
||||
hasLoad,
|
||||
hasUnload,
|
||||
ready: hasVersion && hasGetAll && hasFini && hasPut && hasLoad && hasUnload,
|
||||
};
|
||||
}
|
||||
|
||||
export function createLinuxCncToolDbProcessPort({
|
||||
dbProgramPath,
|
||||
sourceFiles = [],
|
||||
opfsRoot = null,
|
||||
runtimeMode = "contract-only",
|
||||
runtimeAdapter = null,
|
||||
} = {}) {
|
||||
requireString(dbProgramPath, "dbProgramPath");
|
||||
const normalizedSourceFiles = normalizeSourceFiles(sourceFiles);
|
||||
const transcript = [];
|
||||
let started = false;
|
||||
let closed = false;
|
||||
|
||||
function assertOpen() {
|
||||
if (closed) {
|
||||
throw new Error("ToolDbProcessPort is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dbProgramPath,
|
||||
sourceFiles: normalizedSourceFiles,
|
||||
opfsRoot,
|
||||
runtimeMode,
|
||||
|
||||
async start() {
|
||||
assertOpen();
|
||||
started = true;
|
||||
if (runtimeAdapter?.start) {
|
||||
await runtimeAdapter.start();
|
||||
}
|
||||
return {
|
||||
runtimeMode,
|
||||
runtimeExecutionReady: Boolean(runtimeAdapter),
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
};
|
||||
},
|
||||
|
||||
async writeLine(line) {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("ToolDbProcessPort must be started before writeLine().");
|
||||
}
|
||||
const normalizedLine = requireString(line, "line");
|
||||
transcript.push({ direction: "write", line: normalizedLine });
|
||||
if (runtimeAdapter?.writeLine) {
|
||||
await runtimeAdapter.writeLine(normalizedLine);
|
||||
}
|
||||
},
|
||||
|
||||
async readLine() {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("ToolDbProcessPort must be started before readLine().");
|
||||
}
|
||||
if (!runtimeAdapter?.readLine) {
|
||||
throw new Error("ToolDbProcessPort has no runtime adapter; contract-only mode cannot read DB_PROGRAM output.");
|
||||
}
|
||||
const line = await runtimeAdapter.readLine();
|
||||
transcript.push({ direction: "read", line });
|
||||
return line;
|
||||
},
|
||||
|
||||
async runTransactionPlan(plan = createToolDbTransactionPlan()) {
|
||||
assertOpen();
|
||||
if (!runtimeAdapter) {
|
||||
return {
|
||||
status: "blocked_runtime_adapter_required",
|
||||
plan,
|
||||
runtimeExecutionReady: false,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
tblFallbackSufficient: false,
|
||||
};
|
||||
}
|
||||
for (const step of plan) {
|
||||
if (step.direction === "write") {
|
||||
await this.writeLine(step.line);
|
||||
} else {
|
||||
await this.readLine();
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: "runtime_adapter_transaction_plan_executed",
|
||||
plan,
|
||||
transcript: this.exportTranscript(),
|
||||
runtimeExecutionReady: true,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
tblFallbackSufficient: false,
|
||||
};
|
||||
},
|
||||
|
||||
exportTranscript() {
|
||||
return transcript.map((entry) => ({ ...entry }));
|
||||
},
|
||||
|
||||
close() {
|
||||
if (runtimeAdapter?.close) {
|
||||
runtimeAdapter.close();
|
||||
}
|
||||
closed = true;
|
||||
return {
|
||||
closed,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user