Enforce LinuxCNC WASM XYZAC command authority

This commit is contained in:
2026-08-28 13:04:47 +08:00
parent 1a7995fa9b
commit 78d884bc67
43 changed files with 567 additions and 186 deletions

View File

@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
import { AxisGateError, AxisOperationController } from "../app/axis-operation-controller.mjs";
import { AxisOperationController } from "../app/axis-operation-controller.mjs";
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs";
@@ -50,19 +50,20 @@ assert.deepEqual(clear.payload.before, clear.payload.after);
assert.equal(send("PROGRAM_RUN", "program-run").kind, "COMMAND_RESULT");
const runningSwitch = send("KINEMATICS_SELECT", "select-running", { type: 0 });
assert.equal(runningSwitch.kind, "ERROR");
assert.equal(runningSwitch.payload.gate_reason, "xyzac_interp_not_idle");
assert.equal(runningSwitch.payload.gate_reason, "xyzac_interp_running");
send("PROGRAM_ABORT", "program-abort");
const sent = [];
const fakeClient = { send: async (kind, payload, operationId) => { sent.push({ kind, payload, operationId }); return { kind: "COMMAND_RESULT", payload: { status: "verified" } }; } };
const controller = new AxisOperationController(fakeClient);
const disabledState = { task: { state: "READY", mode: "MANUAL", interp_state: "IDLE" }, xyzac: { coordinates: "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true }, gates: { switchkinsXyzac: { action: "switchkinsXyzac", enabled: false, reason: "worker_gate_disabled" } } };
await assert.rejects(controller.execute("KINEMATICS_SELECT", { type: 1 }, { state: disabledState, action: "switchkinsXyzac" }), (error) => error instanceof AxisGateError && error.code === "worker_gate_disabled");
assert.equal(sent.length, 0);
const workerBlocked = await controller.execute("KINEMATICS_SELECT", { type: 1 }, { state: disabledState, action: "switchkinsXyzac" });
assert.equal(workerBlocked.response.kind, "COMMAND_RESULT");
assert.equal(sent.length, 1);
const enabledState = { ...disabledState, gates: { switchkinsXyzac: { action: "switchkinsXyzac", enabled: true, reason: "xyzac_m428_available" } } };
await controller.execute("KINEMATICS_SELECT", { type: 1 }, { state: enabledState, action: "switchkinsXyzac" });
assert.equal(sent.length, 1);
assert.deepEqual(controller.exportTrace().map((item) => item.phase), ["blocked", "pending", "result"]);
assert.equal(sent.length, 2);
assert.deepEqual(controller.exportTrace().map((item) => item.phase), ["pending", "result", "pending", "result"]);
const appSource = await readFile(new URL("../react/src/App.tsx", import.meta.url), "utf8");
const panelSource = await readFile(new URL("../react/src/XyzacTrtPanel.tsx", import.meta.url), "utf8");