接续L4-TOOL-DB浏览器Worker边界
结论:浏览器 Tool DB Worker 传输层已接入并明确阻塞缺失的 Python/WASM runtime,未冒充 DB_PROGRAM 执行就绪。
This commit is contained in:
@@ -573,6 +573,11 @@ private module paths:
|
|||||||
process port, persisting its transcript and DB flat file through the OPFS tool
|
process port, persisting its transcript and DB flat file through the OPFS tool
|
||||||
DB store, and returning diagnostics. The helper is browser-safe glue; runtime
|
DB store, and returning diagnostics. The helper is browser-safe glue; runtime
|
||||||
semantics still come from the supplied port adapter.
|
semantics still come from the supplied port adapter.
|
||||||
|
- `createLinuxCncToolDbBrowserWorkerAdapter()` for the browser Worker transport
|
||||||
|
boundary. The bundled worker reports
|
||||||
|
`blocked_browser_python_wasm_runtime_missing` until a real Python/WASM runtime
|
||||||
|
is installed; the Worker shell alone must not mark `DB_PROGRAM` execution
|
||||||
|
ready.
|
||||||
- `createLinuxCncToolDbNodeRuntimeAdapter()` in
|
- `createLinuxCncToolDbNodeRuntimeAdapter()` in
|
||||||
`runtime/sdk/src/tool-db-node-runtime-adapter.js` for Node/native verification
|
`runtime/sdk/src/tool-db-node-runtime-adapter.js` for Node/native verification
|
||||||
of the real LinuxCNC `DB_PROGRAM` child process. This adapter is intentionally
|
of the real LinuxCNC `DB_PROGRAM` child process. This adapter is intentionally
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ export {
|
|||||||
createToolDbTransactionPlan,
|
createToolDbTransactionPlan,
|
||||||
validateToolDbTranscript,
|
validateToolDbTranscript,
|
||||||
} from "./tool-db-process-port.js";
|
} from "./tool-db-process-port.js";
|
||||||
|
export {
|
||||||
|
createLinuxCncToolDbBrowserWorkerAdapter,
|
||||||
|
} from "./tool-db-browser-worker-adapter.js";
|
||||||
export {
|
export {
|
||||||
runToolDbProcessPortPersistenceSession,
|
runToolDbProcessPortPersistenceSession,
|
||||||
} from "./tool-db-runtime-session.js";
|
} from "./tool-db-runtime-session.js";
|
||||||
|
|||||||
105
wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js
Normal file
105
wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
function requireWorkerUrl(value) {
|
||||||
|
if (typeof value === "string" && value.length > 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value instanceof URL) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new Error("workerUrl must be a non-empty string or URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRequestId() {
|
||||||
|
return `tool-db-worker-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLinuxCncToolDbBrowserWorkerAdapter({
|
||||||
|
workerUrl,
|
||||||
|
workerFactory = (url) => new Worker(url, { type: "module" }),
|
||||||
|
timeoutMs = 5000,
|
||||||
|
} = {}) {
|
||||||
|
const normalizedWorkerUrl = requireWorkerUrl(workerUrl);
|
||||||
|
let worker = null;
|
||||||
|
const pending = new Map();
|
||||||
|
let runtimeExecutionReady = false;
|
||||||
|
let startStatus = "blocked_browser_python_wasm_runtime_missing";
|
||||||
|
|
||||||
|
function rejectPending(error) {
|
||||||
|
for (const { reject, timer } of pending.values()) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
pending.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(type, payload = {}) {
|
||||||
|
if (!worker) {
|
||||||
|
return Promise.reject(new Error("Tool DB browser worker is not started."));
|
||||||
|
}
|
||||||
|
const id = makeRequestId();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
pending.delete(id);
|
||||||
|
reject(new Error(`timeout waiting for Tool DB browser worker ${type}.`));
|
||||||
|
}, timeoutMs);
|
||||||
|
pending.set(id, { resolve, reject, timer });
|
||||||
|
worker.postMessage({ id, type, ...payload });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessage(event) {
|
||||||
|
const message = event.data ?? {};
|
||||||
|
const waiter = pending.get(message.id);
|
||||||
|
if (!waiter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pending.delete(message.id);
|
||||||
|
clearTimeout(waiter.timer);
|
||||||
|
if (message.ok === false) {
|
||||||
|
waiter.reject(new Error(message.error ?? `Tool DB browser worker ${message.type ?? "request"} failed.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
waiter.resolve(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
runtimeMode: "browser-python-wasm-worker",
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
worker = workerFactory(normalizedWorkerUrl);
|
||||||
|
worker.addEventListener("message", handleMessage);
|
||||||
|
worker.addEventListener("error", (event) => {
|
||||||
|
rejectPending(new Error(event.message ?? "Tool DB browser worker error."));
|
||||||
|
});
|
||||||
|
const result = await request("start");
|
||||||
|
runtimeExecutionReady = result.runtimeExecutionReady === true;
|
||||||
|
startStatus = result.status ?? (runtimeExecutionReady
|
||||||
|
? "browser_python_wasm_runtime_ready"
|
||||||
|
: "blocked_browser_python_wasm_runtime_missing");
|
||||||
|
return {
|
||||||
|
status: startStatus,
|
||||||
|
runtimeExecutionReady,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async writeLine(line) {
|
||||||
|
if (runtimeExecutionReady !== true) {
|
||||||
|
throw new Error(`${startStatus}: browser Tool DB runtime is not execution-ready.`);
|
||||||
|
}
|
||||||
|
await request("writeLine", { line });
|
||||||
|
},
|
||||||
|
|
||||||
|
async readLine() {
|
||||||
|
if (runtimeExecutionReady !== true) {
|
||||||
|
throw new Error(`${startStatus}: browser Tool DB runtime is not execution-ready.`);
|
||||||
|
}
|
||||||
|
const result = await request("readLine");
|
||||||
|
return result.line;
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
rejectPending(new Error("Tool DB browser worker closed."));
|
||||||
|
worker?.terminate();
|
||||||
|
worker = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -103,6 +103,10 @@ export function createLinuxCncToolDbNodeRuntimeAdapter({
|
|||||||
failWaiters(new Error(`DB_PROGRAM exited with code ${code}.`));
|
failWaiters(new Error(`DB_PROGRAM exited with code ${code}.`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
status: "node_linuxcnc_db_program_started",
|
||||||
|
runtimeExecutionReady: true,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async writeLine(line) {
|
async writeLine(line) {
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ export function createLinuxCncToolDbProcessPort({
|
|||||||
const transcript = [];
|
const transcript = [];
|
||||||
let started = false;
|
let started = false;
|
||||||
let closed = false;
|
let closed = false;
|
||||||
|
let runtimeExecutionReady = false;
|
||||||
|
let startStatus = null;
|
||||||
|
|
||||||
function assertOpen() {
|
function assertOpen() {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
@@ -156,12 +158,17 @@ export function createLinuxCncToolDbProcessPort({
|
|||||||
async start() {
|
async start() {
|
||||||
assertOpen();
|
assertOpen();
|
||||||
started = true;
|
started = true;
|
||||||
|
let adapterStartResult = {};
|
||||||
if (runtimeAdapter?.start) {
|
if (runtimeAdapter?.start) {
|
||||||
await runtimeAdapter.start();
|
adapterStartResult = await runtimeAdapter.start();
|
||||||
}
|
}
|
||||||
|
runtimeExecutionReady = adapterStartResult.runtimeExecutionReady === true ||
|
||||||
|
(Boolean(runtimeAdapter) && adapterStartResult.runtimeExecutionReady !== false && !adapterStartResult.status);
|
||||||
|
startStatus = adapterStartResult.status ?? (runtimeExecutionReady ? "runtime_adapter_started" : null);
|
||||||
return {
|
return {
|
||||||
runtimeMode,
|
runtimeMode,
|
||||||
runtimeExecutionReady: Boolean(runtimeAdapter),
|
runtimeExecutionReady,
|
||||||
|
status: startStatus,
|
||||||
executionEnabled: false,
|
executionEnabled: false,
|
||||||
promotionAllowed: false,
|
promotionAllowed: false,
|
||||||
};
|
};
|
||||||
@@ -194,9 +201,9 @@ export function createLinuxCncToolDbProcessPort({
|
|||||||
|
|
||||||
async runTransactionPlan(plan = createToolDbTransactionPlan()) {
|
async runTransactionPlan(plan = createToolDbTransactionPlan()) {
|
||||||
assertOpen();
|
assertOpen();
|
||||||
if (!runtimeAdapter) {
|
if (!runtimeAdapter || runtimeExecutionReady !== true) {
|
||||||
return {
|
return {
|
||||||
status: "blocked_runtime_adapter_required",
|
status: runtimeAdapter ? (startStatus ?? "blocked_runtime_execution_not_ready") : "blocked_runtime_adapter_required",
|
||||||
plan,
|
plan,
|
||||||
runtimeExecutionReady: false,
|
runtimeExecutionReady: false,
|
||||||
executionEnabled: false,
|
executionEnabled: false,
|
||||||
|
|||||||
39
wasm-port/runtime/workers/tool-db-python-worker.js
Normal file
39
wasm-port/runtime/workers/tool-db-python-worker.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
function postReply(id, type, payload) {
|
||||||
|
self.postMessage({ id, type, ok: true, ...payload });
|
||||||
|
}
|
||||||
|
|
||||||
|
function postError(id, type, error) {
|
||||||
|
self.postMessage({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
ok: false,
|
||||||
|
error: error?.message ?? String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pythonRuntimeAvailable() {
|
||||||
|
return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener("message", async (event) => {
|
||||||
|
const message = event.data ?? {};
|
||||||
|
const { id, type } = message;
|
||||||
|
try {
|
||||||
|
if (type === "start") {
|
||||||
|
const ready = pythonRuntimeAvailable();
|
||||||
|
postReply(id, type, {
|
||||||
|
runtimeMode: "browser-python-wasm-worker",
|
||||||
|
runtimeExecutionReady: ready,
|
||||||
|
status: ready
|
||||||
|
? "browser_python_wasm_runtime_ready"
|
||||||
|
: "blocked_browser_python_wasm_runtime_missing",
|
||||||
|
executionEnabled: false,
|
||||||
|
promotionAllowed: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error("blocked_browser_python_wasm_runtime_missing");
|
||||||
|
} catch (error) {
|
||||||
|
postError(id, type, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import {
|
import {
|
||||||
createLinuxCncToolDbProcessPort,
|
createLinuxCncToolDbProcessPort,
|
||||||
|
createLinuxCncToolDbBrowserWorkerAdapter,
|
||||||
createToolDbProcessDiagnostics,
|
createToolDbProcessDiagnostics,
|
||||||
createToolDbStorePaths,
|
createToolDbStorePaths,
|
||||||
loadToolDbFile,
|
loadToolDbFile,
|
||||||
@@ -58,6 +59,32 @@
|
|||||||
assertEqual(planResult.status, "blocked_runtime_adapter_required", "contract-only plan status");
|
assertEqual(planResult.status, "blocked_runtime_adapter_required", "contract-only plan status");
|
||||||
assertEqual(planResult.tblFallbackSufficient, false, "tbl fallback guard");
|
assertEqual(planResult.tblFallbackSufficient, false, "tbl fallback guard");
|
||||||
|
|
||||||
|
const workerAdapter = createLinuxCncToolDbBrowserWorkerAdapter({
|
||||||
|
workerUrl: new URL("../../runtime/workers/tool-db-python-worker.js", import.meta.url),
|
||||||
|
});
|
||||||
|
const workerPort = 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: workerAdapter.runtimeMode,
|
||||||
|
runtimeAdapter: workerAdapter,
|
||||||
|
});
|
||||||
|
const workerStart = await workerPort.start();
|
||||||
|
assertEqual(workerStart.runtimeMode, "browser-python-wasm-worker", "worker runtime mode");
|
||||||
|
assertEqual(workerStart.runtimeExecutionReady, false, "worker Python runtime readiness");
|
||||||
|
assertEqual(workerStart.status, "blocked_browser_python_wasm_runtime_missing", "worker Python runtime status");
|
||||||
|
const workerPlanResult = await workerPort.runTransactionPlan();
|
||||||
|
assertEqual(
|
||||||
|
workerPlanResult.status,
|
||||||
|
"blocked_browser_python_wasm_runtime_missing",
|
||||||
|
"worker plan blocked without Python/WASM runtime",
|
||||||
|
);
|
||||||
|
assertEqual(workerPlanResult.promotionAllowed, false, "worker promotion guard");
|
||||||
|
await workerPort.close();
|
||||||
|
|
||||||
const transcript = [
|
const transcript = [
|
||||||
{ direction: "read", line: "v2.1" },
|
{ direction: "read", line: "v2.1" },
|
||||||
{ direction: "write", line: "g" },
|
{ direction: "write", line: "g" },
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ import {
|
|||||||
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
||||||
createProjectReleaseReadinessReport,
|
createProjectReleaseReadinessReport,
|
||||||
createProjectReleaseReadinessSummaryViewModel,
|
createProjectReleaseReadinessSummaryViewModel,
|
||||||
|
createLinuxCncToolDbBrowserWorkerAdapter,
|
||||||
createLinuxCncToolDbProcessPort,
|
createLinuxCncToolDbProcessPort,
|
||||||
createToolDbProcessDiagnostics,
|
createToolDbProcessDiagnostics,
|
||||||
createToolDbTransactionPlan,
|
createToolDbTransactionPlan,
|
||||||
@@ -522,6 +523,7 @@ const requiredExports = [
|
|||||||
["saveMachineTextFiles", saveMachineTextFiles],
|
["saveMachineTextFiles", saveMachineTextFiles],
|
||||||
["loadMachineTextFiles", loadMachineTextFiles],
|
["loadMachineTextFiles", loadMachineTextFiles],
|
||||||
["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath],
|
["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath],
|
||||||
|
["createLinuxCncToolDbBrowserWorkerAdapter", createLinuxCncToolDbBrowserWorkerAdapter],
|
||||||
["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort],
|
["createLinuxCncToolDbProcessPort", createLinuxCncToolDbProcessPort],
|
||||||
["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics],
|
["createToolDbProcessDiagnostics", createToolDbProcessDiagnostics],
|
||||||
["createToolDbTransactionPlan", createToolDbTransactionPlan],
|
["createToolDbTransactionPlan", createToolDbTransactionPlan],
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const port = createLinuxCncToolDbProcessPort({
|
|||||||
const startResult = await port.start();
|
const startResult = await port.start();
|
||||||
assert.equal(startResult.runtimeMode, "node-linuxcnc-db-program");
|
assert.equal(startResult.runtimeMode, "node-linuxcnc-db-program");
|
||||||
assert.equal(startResult.runtimeExecutionReady, true);
|
assert.equal(startResult.runtimeExecutionReady, true);
|
||||||
|
assert.equal(startResult.status, "node_linuxcnc_db_program_started");
|
||||||
assert.equal(startResult.executionEnabled, false);
|
assert.equal(startResult.executionEnabled, false);
|
||||||
assert.equal(startResult.promotionAllowed, false);
|
assert.equal(startResult.promotionAllowed, false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createLinuxCncToolDbBrowserWorkerAdapter,
|
||||||
|
} from "../../../runtime/sdk/src/tool-db-browser-worker-adapter.js";
|
||||||
import {
|
import {
|
||||||
TOOL_DB_PROCESS_PORT_CONTRACT_VERSION,
|
TOOL_DB_PROCESS_PORT_CONTRACT_VERSION,
|
||||||
TOOL_DB_TRANSACTION_PLAN,
|
TOOL_DB_TRANSACTION_PLAN,
|
||||||
@@ -37,6 +40,7 @@ const startResult = await port.start();
|
|||||||
assert.deepEqual(startResult, {
|
assert.deepEqual(startResult, {
|
||||||
runtimeMode: "contract-only",
|
runtimeMode: "contract-only",
|
||||||
runtimeExecutionReady: false,
|
runtimeExecutionReady: false,
|
||||||
|
status: null,
|
||||||
executionEnabled: false,
|
executionEnabled: false,
|
||||||
promotionAllowed: false,
|
promotionAllowed: false,
|
||||||
});
|
});
|
||||||
@@ -48,6 +52,53 @@ assert.equal(blockedPlan.executionEnabled, false);
|
|||||||
assert.equal(blockedPlan.promotionAllowed, false);
|
assert.equal(blockedPlan.promotionAllowed, false);
|
||||||
assert.equal(blockedPlan.tblFallbackSufficient, false);
|
assert.equal(blockedPlan.tblFallbackSufficient, false);
|
||||||
|
|
||||||
|
class FakeBlockedWorker {
|
||||||
|
constructor() {
|
||||||
|
this.listeners = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(type, listener) {
|
||||||
|
this.listeners.set(type, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage(message) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
this.listeners.get("message")?.({
|
||||||
|
data: {
|
||||||
|
id: message.id,
|
||||||
|
type: message.type,
|
||||||
|
ok: true,
|
||||||
|
runtimeExecutionReady: false,
|
||||||
|
status: "blocked_browser_python_wasm_runtime_missing",
|
||||||
|
executionEnabled: false,
|
||||||
|
promotionAllowed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const browserWorkerAdapter = createLinuxCncToolDbBrowserWorkerAdapter({
|
||||||
|
workerUrl: "./tool-db-python-worker.js",
|
||||||
|
workerFactory: () => new FakeBlockedWorker(),
|
||||||
|
});
|
||||||
|
const browserWorkerPort = createLinuxCncToolDbProcessPort({
|
||||||
|
dbProgramPath: "./db_nonran.py",
|
||||||
|
runtimeMode: browserWorkerAdapter.runtimeMode,
|
||||||
|
runtimeAdapter: browserWorkerAdapter,
|
||||||
|
});
|
||||||
|
const browserWorkerStart = await browserWorkerPort.start();
|
||||||
|
assert.equal(browserWorkerStart.runtimeMode, "browser-python-wasm-worker");
|
||||||
|
assert.equal(browserWorkerStart.runtimeExecutionReady, false);
|
||||||
|
assert.equal(browserWorkerStart.status, "blocked_browser_python_wasm_runtime_missing");
|
||||||
|
const browserWorkerBlockedPlan = await browserWorkerPort.runTransactionPlan();
|
||||||
|
assert.equal(browserWorkerBlockedPlan.status, "blocked_browser_python_wasm_runtime_missing");
|
||||||
|
assert.equal(browserWorkerBlockedPlan.runtimeExecutionReady, false);
|
||||||
|
assert.equal(browserWorkerBlockedPlan.promotionAllowed, false);
|
||||||
|
await browserWorkerPort.close();
|
||||||
|
|
||||||
const transcript = [
|
const transcript = [
|
||||||
{ direction: "read", line: "v2.1" },
|
{ direction: "read", line: "v2.1" },
|
||||||
{ direction: "write", line: "g" },
|
{ direction: "write", line: "g" },
|
||||||
|
|||||||
Reference in New Issue
Block a user