结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
4100 lines
254 KiB
HTML
4100 lines
254 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>LinuxCNC Real Simulation Page Smoke</title>
|
|
</head>
|
|
<body>
|
|
<pre id="status">running</pre>
|
|
<pre id="axis-diagnostics-artifact" hidden>{}</pre>
|
|
<script type="module">
|
|
import {
|
|
gcodeProgramPath,
|
|
machineFilePaths,
|
|
saveGcodeProgram,
|
|
saveMachineSessionSnapshot,
|
|
saveMachineTextFiles,
|
|
} from "../../runtime/sdk/src/index.js";
|
|
|
|
const status = document.getElementById("status");
|
|
|
|
async function loadFrame(src) {
|
|
const frame = document.createElement("iframe");
|
|
frame.src = src;
|
|
frame.width = "1280";
|
|
frame.height = "800";
|
|
document.body.appendChild(frame);
|
|
await new Promise((resolve, reject) => {
|
|
frame.addEventListener("load", resolve, { once: true });
|
|
frame.addEventListener("error", reject, { once: true });
|
|
});
|
|
return frame;
|
|
}
|
|
|
|
async function waitForState(frame) {
|
|
for (let i = 0; i < 200; i += 1) {
|
|
const state = frame.contentWindow?.linuxCncRealSimulationState;
|
|
if (state?.summary?.ready) {
|
|
return state;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
}
|
|
throw new Error("real simulation page did not expose ready state");
|
|
}
|
|
|
|
const HANDOFF_BASELINE = "82/82/77/0";
|
|
|
|
function assertEqual(actual, expected, context) {
|
|
if (actual !== expected) {
|
|
throw new Error(`${context}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
}
|
|
}
|
|
|
|
function assertDatasetValues(doc, expectedValues, context) {
|
|
for (const [key, expected] of Object.entries(expectedValues)) {
|
|
assertEqual(doc.body.dataset[key], expected, `${context} dataset.${key}`);
|
|
}
|
|
}
|
|
|
|
function assertDomText(doc, expectedValues, context) {
|
|
for (const [selector, expected] of Object.entries(expectedValues)) {
|
|
assertEqual(doc.querySelector(selector)?.textContent, expected, `${context} ${selector}`);
|
|
}
|
|
}
|
|
|
|
function assertRenderedState(doc, state) {
|
|
const polyline = doc.querySelector("[data-toolpath-polyline]");
|
|
const threeCanvas = doc.querySelector("[data-toolpath-three]");
|
|
const rows = [...doc.querySelectorAll("[data-motion-row]")];
|
|
const programRows = [...doc.querySelectorAll("[data-program-line]")];
|
|
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
|
|
|
if (doc.body.dataset.simulationReady !== "true") {
|
|
throw new Error(`simulation body readiness flag not true for ${state.program?.id}`);
|
|
}
|
|
if (state.apiName !== "real-browser-simulation-state") {
|
|
throw new Error("simulation state API name drift");
|
|
}
|
|
if (state.summary.motionEventCount < 2) {
|
|
throw new Error(`simulation motion event count too low for ${state.program?.id}`);
|
|
}
|
|
if (!canonicalOutput.includes("canon_event=")) {
|
|
throw new Error(`simulation canonical output missing events for ${state.program?.id}`);
|
|
}
|
|
if (!polyline?.getAttribute("points")) {
|
|
throw new Error(`simulation toolpath polyline missing points for ${state.program?.id}`);
|
|
}
|
|
if (rows.length !== state.motion.length) {
|
|
throw new Error(`simulation motion table row count drift for ${state.program?.id}`);
|
|
}
|
|
if (programRows.length !== state.summary.programLineCount) {
|
|
throw new Error(`simulation program row count drift for ${state.program?.id}`);
|
|
}
|
|
if (!doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox")) {
|
|
throw new Error(`simulation SVG viewBox missing for ${state.program?.id}`);
|
|
}
|
|
if (
|
|
threeCanvas?.dataset.threeReady !== "true" ||
|
|
threeCanvas?.dataset.threeRevision !== "183" ||
|
|
Number(threeCanvas?.dataset.threePathPoints ?? 0) !== state.motion.length ||
|
|
Number(threeCanvas?.dataset.threeGridLines ?? 0) < 4 ||
|
|
Number(threeCanvas?.dataset.threeAxisLines ?? 0) !== 3 ||
|
|
threeCanvas?.dataset.threeAxisLabels !== "3" ||
|
|
threeCanvas?.dataset.threeOrientationTriadObjects !== "6" ||
|
|
threeCanvas?.dataset.threeWorkPlanes !== "1" ||
|
|
threeCanvas?.dataset.threeEnvelopeEdges !== "12" ||
|
|
Number(threeCanvas?.dataset.threeSceneObjects ?? 0) < 20 ||
|
|
Number(threeCanvas?.dataset.threeToolMarkerObjects ?? 0) < 3 ||
|
|
Number(threeCanvas?.dataset.threeScaleBarObjects ?? 0) !== 4 ||
|
|
!threeCanvas?.dataset.threeViewBox ||
|
|
!threeCanvas?.dataset.threeMachineEnvelope ||
|
|
!threeCanvas?.dataset.threeToolhead ||
|
|
!threeCanvas?.dataset.threeToolGeometry ||
|
|
!threeCanvas?.dataset.threeScaleBar ||
|
|
!threeCanvas?.dataset.threeOrientationTriad ||
|
|
!threeCanvas?.dataset.threeCanvasSize
|
|
) {
|
|
throw new Error(`simulation Three.js preview missing rendered path for ${state.program?.id}`);
|
|
}
|
|
const toolhead = JSON.parse(threeCanvas.dataset.threeToolhead);
|
|
const envelope = JSON.parse(threeCanvas.dataset.threeMachineEnvelope);
|
|
const toolGeometry = JSON.parse(threeCanvas.dataset.threeToolGeometry);
|
|
const scaleBar = JSON.parse(threeCanvas.dataset.threeScaleBar);
|
|
const orientationTriad = JSON.parse(threeCanvas.dataset.threeOrientationTriad);
|
|
if (
|
|
!Number.isFinite(toolhead.x) ||
|
|
!Number.isFinite(toolhead.y) ||
|
|
!Number.isFinite(envelope.minZ) ||
|
|
envelope.maxZ <= envelope.minZ ||
|
|
!Number.isFinite(toolGeometry.bodyLength) ||
|
|
toolGeometry.bodyLength <= 0 ||
|
|
!Number.isFinite(scaleBar.length) ||
|
|
scaleBar.length <= 0 ||
|
|
!Number.isFinite(orientationTriad.length) ||
|
|
orientationTriad.length <= 0
|
|
) {
|
|
throw new Error(`simulation Three.js preview missing structured toolhead/envelope state for ${state.program?.id}`);
|
|
}
|
|
const gl = threeCanvas.getContext("webgl2") || threeCanvas.getContext("webgl");
|
|
if (!gl) {
|
|
throw new Error("simulation Three.js preview missing WebGL context");
|
|
}
|
|
const pixel = new Uint8Array(4);
|
|
gl.readPixels(Math.floor(threeCanvas.width / 2), Math.floor(threeCanvas.height / 2), 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
|
if (pixel[0] === 0 && pixel[1] === 0 && pixel[2] === 0 && pixel[3] === 0) {
|
|
throw new Error("simulation Three.js preview canvas pixel check was blank");
|
|
}
|
|
if (doc.body.dataset.simulationProgramId !== state.program?.id) {
|
|
throw new Error(`simulation body program id drift for ${state.program?.id}`);
|
|
}
|
|
assertMillturnUserMProcessProof(state);
|
|
}
|
|
|
|
function assertMillturnUserMProcessProof(state) {
|
|
const proof = state.millturnUserMProcess;
|
|
if (
|
|
proof?.apiName !== "real-browser-simulation-millturn-user-m-process-proof" ||
|
|
proof.ready !== true ||
|
|
proof.webSimulationReady !== true ||
|
|
proof.boundaryClass !== "L4-USER-M-PROCESS" ||
|
|
proof.path !== "axis/vismach/millturn/example.ngc" ||
|
|
proof.ini !== "axis/vismach/millturn/millturn.ini" ||
|
|
proof.nativeRuntimeRequired !== false ||
|
|
proof.nativeRuntimeRequiredForWebSimulation !== false ||
|
|
proof.processExecutionReady !== false ||
|
|
proof.executionEnabled !== false ||
|
|
proof.promotionAllowed !== false ||
|
|
proof.transitionOrder?.join(",") !== "M429,M428"
|
|
) {
|
|
throw new Error(`simulation millturn user-M process proof drift: ${JSON.stringify(proof)}`);
|
|
}
|
|
if (
|
|
proof.turn?.userMCode !== "M129" ||
|
|
proof.turn?.remapCode !== "M429" ||
|
|
proof.turn?.stateMode !== "turn" ||
|
|
proof.turn?.pins?.["motion.switchkins-type"] !== 1 ||
|
|
proof.turn?.pins?.["motion.analog-out-03"] !== 1 ||
|
|
proof.turn?.pins?.["kinstype.is-0"] !== 0 ||
|
|
proof.turn?.pins?.["kinstype.is-1"] !== 1 ||
|
|
proof.turn?.pins?.["ini.x.min_limit"] !== -240 ||
|
|
proof.turn?.pins?.["ini.x.max_limit"] !== 0 ||
|
|
proof.turn?.pins?.["ini.z.min_limit"] !== -300 ||
|
|
proof.turn?.pins?.["ini.z.max_limit"] !== 300
|
|
) {
|
|
throw new Error(`simulation millturn user-M turn proof drift: ${JSON.stringify(proof.turn)}`);
|
|
}
|
|
if (
|
|
proof.mill?.userMCode !== "M128" ||
|
|
proof.mill?.remapCode !== "M428" ||
|
|
proof.mill?.stateMode !== "mill" ||
|
|
proof.mill?.pins?.["motion.switchkins-type"] !== 0 ||
|
|
proof.mill?.pins?.["motion.analog-out-03"] !== 0 ||
|
|
proof.mill?.pins?.["kinstype.is-0"] !== 1 ||
|
|
proof.mill?.pins?.["kinstype.is-1"] !== 0 ||
|
|
proof.mill?.pins?.["ini.x.min_limit"] !== -300 ||
|
|
proof.mill?.pins?.["ini.x.max_limit"] !== 300 ||
|
|
proof.mill?.pins?.["ini.z.min_limit"] !== -240 ||
|
|
proof.mill?.pins?.["ini.z.max_limit"] !== 0
|
|
) {
|
|
throw new Error(`simulation millturn user-M mill proof drift: ${JSON.stringify(proof.mill)}`);
|
|
}
|
|
}
|
|
|
|
function assertAxisShell(doc) {
|
|
for (const region of [
|
|
"titlebar",
|
|
"menubar",
|
|
"toolbar",
|
|
"toolbar-file-controls",
|
|
"workspace",
|
|
"manual-mdi",
|
|
"mdi-history",
|
|
"preview-dro",
|
|
"preview-controls",
|
|
"preview-stage",
|
|
"preview-hud",
|
|
"preview-legend",
|
|
"active-codes",
|
|
"gcode-pane",
|
|
"program-editor",
|
|
"recent-programs",
|
|
"machine-state",
|
|
"diagnostics",
|
|
"tool-table",
|
|
"limits-home",
|
|
"status-history",
|
|
"statusbar",
|
|
]) {
|
|
if (!doc.querySelector(`[data-axis-shell="${region}"]`)) {
|
|
throw new Error(`AXIS-style shell missing ${region}`);
|
|
}
|
|
}
|
|
for (const tab of ["manual", "mdi", "preview", "dro"]) {
|
|
if (!doc.querySelector(`[data-axis-tab="${tab}"]`)) {
|
|
throw new Error(`AXIS-style shell missing ${tab} tab`);
|
|
}
|
|
if (!doc.querySelector(`[data-axis-panel="${tab}"]`)) {
|
|
throw new Error(`AXIS-style shell missing ${tab} panel`);
|
|
}
|
|
}
|
|
const menuText = doc.querySelector('[data-axis-shell="menubar"]')?.textContent ?? "";
|
|
for (const label of ["File", "Machine", "View", "Help"]) {
|
|
if (!menuText.includes(label)) {
|
|
throw new Error(`AXIS-style menu missing ${label}`);
|
|
}
|
|
}
|
|
const statusText = doc.querySelector('[data-axis-shell="statusbar"]')?.textContent ?? "";
|
|
if (
|
|
!statusText.includes("ESTOP") ||
|
|
!statusText.includes("Tool") ||
|
|
!statusText.includes("Program") ||
|
|
!statusText.includes("Line") ||
|
|
!statusText.includes("Position: Relative Actual") ||
|
|
!statusText.includes("Preview") ||
|
|
!statusText.includes("Action")
|
|
) {
|
|
throw new Error(`AXIS-style statusbar drift: ${statusText}`);
|
|
}
|
|
}
|
|
|
|
function assertExternalShellCopyExportVerification(doc, verification, phase, digest) {
|
|
const expectedState = `${phase}-copy-export-verified`;
|
|
const expectedText = `${expectedState}; copy=match; export=match; summary=match; digest=match`;
|
|
if (
|
|
verification.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-badge-copy-export-verification" ||
|
|
verification.phase !== phase ||
|
|
verification.ready !== true ||
|
|
verification.statusText !== "verified" ||
|
|
verification.verificationState !== expectedState ||
|
|
verification.verificationText !== expectedText ||
|
|
verification.copyMatches !== true ||
|
|
verification.exportMatches !== true ||
|
|
verification.summaryMatches !== true ||
|
|
verification.digestMatches !== true ||
|
|
verification.digest !== digest ||
|
|
verification.baseline !== HANDOFF_BASELINE
|
|
) {
|
|
throw new Error(`AXIS-style handoff shell copy/export verification drift: ${JSON.stringify(verification)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationPhase: phase,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationReady: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationStatus: "verified",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationState: expectedState,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationText: expectedText,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationCopyMatches: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationExportMatches: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationSummaryMatches: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationDigestMatches: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationDigest: digest,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportVerificationBaseline: HANDOFF_BASELINE,
|
|
}, "external shell badge copy/export verification");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-badge-copy-export-verification"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="verification-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="verification-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="copy-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="export-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="summary-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="digest-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-verification-value="digest"]': digest,
|
|
}, "external shell badge copy/export verification");
|
|
}
|
|
|
|
function assertExternalShellBundle(
|
|
doc,
|
|
bundle,
|
|
{ phase, action, preflight, shellBadgeText, copyText, exportText, verificationText, digest },
|
|
) {
|
|
const expectedState = `${phase}-external-shell-bundle-verified`;
|
|
const expectedText = [
|
|
expectedState,
|
|
`action=${action}`,
|
|
`preflight=${preflight}`,
|
|
`shell=${shellBadgeText}`,
|
|
`copy=${copyText}`,
|
|
`export=${exportText}`,
|
|
`verification=${verificationText}`,
|
|
"baseline=82/82/77/0",
|
|
`digest=${digest}`,
|
|
].join("; ");
|
|
if (
|
|
bundle.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-bundle" ||
|
|
bundle.phase !== phase ||
|
|
bundle.action !== action ||
|
|
bundle.preflight !== preflight ||
|
|
bundle.ready !== true ||
|
|
bundle.bundleState !== expectedState ||
|
|
bundle.bundleText !== expectedText ||
|
|
bundle.shellBadgeText !== shellBadgeText ||
|
|
bundle.copyText !== copyText ||
|
|
bundle.exportText !== exportText ||
|
|
bundle.verificationText !== verificationText ||
|
|
bundle.baseline !== HANDOFF_BASELINE ||
|
|
bundle.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell bundle drift: ${JSON.stringify(bundle)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellBundlePhase: phase,
|
|
evidenceSessionHandoffExternalShellBundleReady: "true",
|
|
evidenceSessionHandoffExternalShellBundleAction: action,
|
|
evidenceSessionHandoffExternalShellBundlePreflight: preflight,
|
|
evidenceSessionHandoffExternalShellBundleState: expectedState,
|
|
evidenceSessionHandoffExternalShellBundleText: expectedText,
|
|
evidenceSessionHandoffExternalShellBundleShellBadgeText: shellBadgeText,
|
|
evidenceSessionHandoffExternalShellBundleCopyText: copyText,
|
|
evidenceSessionHandoffExternalShellBundleExportText: exportText,
|
|
evidenceSessionHandoffExternalShellBundleVerificationText: verificationText,
|
|
evidenceSessionHandoffExternalShellBundleBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellBundleDigest: digest,
|
|
}, "external shell bundle");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-bundle"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="bundle-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="bundle-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="shell-badge-text"]': shellBadgeText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="copy-text"]': copyText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="export-text"]': exportText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="verification-text"]': verificationText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-value="digest"]': digest,
|
|
}, "external shell bundle");
|
|
}
|
|
|
|
function assertExternalShellBundleBadge(doc, badge, phase, digest) {
|
|
const expectedState = `${phase}-bundle-verified`;
|
|
const expectedText = `${expectedState} 1/1 ${digest}`;
|
|
if (
|
|
badge.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-bundle-badge" ||
|
|
badge.phase !== phase ||
|
|
badge.ready !== true ||
|
|
badge.statusText !== "verified" ||
|
|
badge.readyCount !== 1 ||
|
|
badge.totalCount !== 1 ||
|
|
badge.bundleBadgeState !== expectedState ||
|
|
badge.bundleBadgeText !== expectedText ||
|
|
badge.statusbarBundleBadgeText !== expectedText ||
|
|
badge.statusbarBundleBadgeState !== expectedState ||
|
|
badge.digest !== digest ||
|
|
badge.baseline !== HANDOFF_BASELINE
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell bundle badge drift: ${JSON.stringify(badge)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellBundleBadgePhase: phase,
|
|
evidenceSessionHandoffExternalShellBundleBadgeReady: "true",
|
|
evidenceSessionHandoffExternalShellBundleBadgeStatus: "verified",
|
|
evidenceSessionHandoffExternalShellBundleBadgeState: expectedState,
|
|
evidenceSessionHandoffExternalShellBundleBadgeText: expectedText,
|
|
evidenceSessionHandoffExternalShellBundleBadgeReadyCount: "1",
|
|
evidenceSessionHandoffExternalShellBundleBadgeTotalCount: "1",
|
|
evidenceSessionHandoffExternalShellBundleBadgeDigest: digest,
|
|
evidenceSessionHandoffExternalShellBundleBadgeBaseline: HANDOFF_BASELINE,
|
|
}, "external shell bundle badge");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-bundle-badge"]': expectedText,
|
|
'[data-statusbar="handoff-bundle-badge"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-badge-value="bundle-badge-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-bundle-badge-value="bundle-badge-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-bundle-badge-value="ready-count"]': "1/1",
|
|
'[data-evidence-session-handoff-external-shell-bundle-badge-value="digest"]': digest,
|
|
}, "external shell bundle badge");
|
|
}
|
|
|
|
function assertExternalShellReceipt(
|
|
doc,
|
|
receipt,
|
|
{ phase, action, preflight, bundleBadgeText, bundleState, digest },
|
|
) {
|
|
const expectedState = `${phase}-external-shell-receipt-verified`;
|
|
const expectedText = [
|
|
expectedState,
|
|
`bundleBadge=${bundleBadgeText}`,
|
|
`bundleState=${bundleState}`,
|
|
`action=${action}`,
|
|
`preflight=${preflight}`,
|
|
"baseline=82/82/77/0",
|
|
`digest=${digest}`,
|
|
].join("; ");
|
|
if (
|
|
receipt.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt" ||
|
|
receipt.phase !== phase ||
|
|
receipt.ready !== true ||
|
|
receipt.receiptState !== expectedState ||
|
|
receipt.receiptText !== expectedText ||
|
|
receipt.statusbarReceiptText !== expectedText ||
|
|
receipt.statusbarReceiptState !== expectedState ||
|
|
receipt.bundleBadgeText !== bundleBadgeText ||
|
|
receipt.bundleState !== bundleState ||
|
|
receipt.action !== action ||
|
|
receipt.preflight !== preflight ||
|
|
receipt.baseline !== HANDOFF_BASELINE ||
|
|
receipt.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt drift: ${JSON.stringify(receipt)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptPhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptBundleBadgeText: bundleBadgeText,
|
|
evidenceSessionHandoffExternalShellReceiptBundleState: bundleState,
|
|
evidenceSessionHandoffExternalShellReceiptAction: action,
|
|
evidenceSessionHandoffExternalShellReceiptPreflight: preflight,
|
|
evidenceSessionHandoffExternalShellReceiptBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptDigest: digest,
|
|
}, "external shell receipt");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt"]': expectedText,
|
|
'[data-statusbar="handoff-shell-receipt"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="receipt-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="receipt-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="bundle-badge-text"]': bundleBadgeText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="bundle-state"]': bundleState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="action"]': action,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="preflight"]': preflight,
|
|
'[data-evidence-session-handoff-external-shell-receipt-value="digest"]': digest,
|
|
}, "external shell receipt");
|
|
}
|
|
|
|
function assertExternalShellReceiptVerification(
|
|
doc,
|
|
verification,
|
|
{ phase, receiptText, receiptState, bundleBadgeText, bundleBadgeState, bundleState, digest },
|
|
) {
|
|
const expectedState = `${phase}-external-shell-receipt-verified`;
|
|
const expectedText = [
|
|
expectedState,
|
|
"receipt=match",
|
|
"bundleBadge=match",
|
|
"bundleState=match",
|
|
"digest=match",
|
|
].join("; ");
|
|
if (
|
|
verification.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-verification" ||
|
|
verification.phase !== phase ||
|
|
verification.ready !== true ||
|
|
verification.statusText !== "verified" ||
|
|
verification.verificationState !== expectedState ||
|
|
verification.verificationText !== expectedText ||
|
|
verification.receiptMatches !== true ||
|
|
verification.bundleBadgeMatches !== true ||
|
|
verification.bundleMatches !== true ||
|
|
verification.digestMatches !== true ||
|
|
verification.receiptText !== receiptText ||
|
|
verification.receiptState !== receiptState ||
|
|
verification.bundleBadgeText !== bundleBadgeText ||
|
|
verification.bundleBadgeState !== bundleBadgeState ||
|
|
verification.bundleState !== bundleState ||
|
|
verification.baseline !== HANDOFF_BASELINE ||
|
|
verification.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt verification drift: ${JSON.stringify(verification)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptVerificationPhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationReceiptMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBundleBadgeMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBundleStateMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationDigestMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationReceiptText: receiptText,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationReceiptState: receiptState,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBundleBadgeText: bundleBadgeText,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBundleBadgeState: bundleBadgeState,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBundleState: bundleState,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationDigest: digest,
|
|
}, "external shell receipt verification");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-verification"]': expectedText,
|
|
'[data-statusbar="handoff-shell-receipt-verification"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="verification-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="verification-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="receipt-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="bundle-badge-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="bundle-state-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="digest-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-value="digest"]': digest,
|
|
}, "external shell receipt verification");
|
|
}
|
|
|
|
function assertExternalShellReceiptVerificationBadge(doc, badge, phase, digest) {
|
|
const expectedState = `${phase}-external-shell-receipt-verification-verified`;
|
|
const expectedText = `${expectedState} 4/4 ${digest}`;
|
|
if (
|
|
badge.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-verification-badge" ||
|
|
badge.phase !== phase ||
|
|
badge.ready !== true ||
|
|
badge.statusText !== "verified" ||
|
|
badge.readyCount !== 4 ||
|
|
badge.totalCount !== 4 ||
|
|
badge.badgeState !== expectedState ||
|
|
badge.badgeText !== expectedText ||
|
|
badge.statusbarBadgeText !== expectedText ||
|
|
badge.statusbarBadgeState !== expectedState ||
|
|
badge.baseline !== HANDOFF_BASELINE ||
|
|
badge.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt verification badge drift: ${JSON.stringify(badge)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgePhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeReadyCount: "4",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeTotalCount: "4",
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptVerificationBadgeDigest: digest,
|
|
axisStatusbarHandoffShellReceiptVerificationBadgeText: expectedText,
|
|
axisStatusbarHandoffShellReceiptVerificationBadgeState: expectedState,
|
|
axisStatusbarHandoffShellReceiptVerificationBadgeDigest: digest,
|
|
}, "external shell receipt verification badge");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-verification-badge"]': expectedText,
|
|
'[data-statusbar="handoff-shell-receipt-verification-badge"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-badge-value="badge-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-badge-value="badge-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-badge-value="ready-count"]': "4/4",
|
|
'[data-evidence-session-handoff-external-shell-receipt-verification-badge-value="digest"]': digest,
|
|
}, "external shell receipt verification badge");
|
|
}
|
|
|
|
function assertExternalShellReceiptAuditSnapshot(
|
|
doc,
|
|
snapshot,
|
|
{ phase, receiptText, verificationText, badgeText, badgeState, digest },
|
|
) {
|
|
const expectedState = `${phase}-external-shell-receipt-audit-verified`;
|
|
const expectedText = [
|
|
expectedState,
|
|
`receipt=${receiptText}`,
|
|
`verification=${verificationText}`,
|
|
`badge=${badgeText}`,
|
|
`badgeState=${badgeState}`,
|
|
"baseline=82/82/77/0",
|
|
`digest=${digest}`,
|
|
].join("; ");
|
|
if (
|
|
snapshot.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-audit-snapshot" ||
|
|
snapshot.phase !== phase ||
|
|
snapshot.ready !== true ||
|
|
snapshot.statusText !== "verified" ||
|
|
snapshot.auditState !== expectedState ||
|
|
snapshot.auditText !== expectedText ||
|
|
snapshot.statusbarAuditText !== expectedText ||
|
|
snapshot.statusbarAuditState !== expectedState ||
|
|
snapshot.receiptText !== receiptText ||
|
|
snapshot.verificationText !== verificationText ||
|
|
snapshot.badgeText !== badgeText ||
|
|
snapshot.badgeState !== badgeState ||
|
|
snapshot.baseline !== HANDOFF_BASELINE ||
|
|
snapshot.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt audit snapshot drift: ${JSON.stringify(snapshot)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotPhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotReceiptText: receiptText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotVerificationText: verificationText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotBadgeText: badgeText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotBadgeState: badgeState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptAuditSnapshotDigest: digest,
|
|
axisStatusbarHandoffShellReceiptAuditSnapshotText: expectedText,
|
|
axisStatusbarHandoffShellReceiptAuditSnapshotState: expectedState,
|
|
axisStatusbarHandoffShellReceiptAuditSnapshotDigest: digest,
|
|
}, "external shell receipt audit snapshot");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-audit-snapshot"]': expectedText,
|
|
'[data-statusbar="handoff-shell-receipt-audit-snapshot"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="audit-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="audit-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="receipt-text"]': receiptText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="verification-text"]': verificationText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="badge-text"]': badgeText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="badge-state"]': badgeState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-snapshot-value="digest"]': digest,
|
|
}, "external shell receipt audit snapshot");
|
|
}
|
|
|
|
function assertExternalShellReceiptAuditCopyExport(
|
|
doc,
|
|
viewModel,
|
|
{ phase, auditState, auditText, digest },
|
|
) {
|
|
const expectedExportText = JSON.stringify({
|
|
phase,
|
|
auditState,
|
|
auditText,
|
|
receiptText: viewModel.snapshot?.receiptText,
|
|
verificationText: viewModel.snapshot?.verificationText,
|
|
badgeText: viewModel.snapshot?.badgeText,
|
|
badgeState: viewModel.snapshot?.badgeState,
|
|
baseline: "82/82/77/0",
|
|
digest,
|
|
});
|
|
const expectedSummaryText = `${phase} | ${digest} | ${auditState}`;
|
|
if (
|
|
viewModel.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-audit-copy-export" ||
|
|
viewModel.phase !== phase ||
|
|
viewModel.ready !== true ||
|
|
viewModel.auditState !== auditState ||
|
|
viewModel.auditText !== auditText ||
|
|
viewModel.copyText !== auditText ||
|
|
viewModel.exportText !== expectedExportText ||
|
|
viewModel.summaryText !== expectedSummaryText ||
|
|
viewModel.baseline !== HANDOFF_BASELINE ||
|
|
viewModel.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt audit copy/export drift: ${JSON.stringify(viewModel)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportPhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportState: auditState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportText: auditText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportExportText: expectedExportText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportSummary: expectedSummaryText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportDigest: digest,
|
|
}, "external shell receipt audit copy/export");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-audit-copy-export"]': expectedSummaryText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-value="copy-text"]': auditText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-value="export-text"]': expectedExportText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-value="summary-text"]': expectedSummaryText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-value="digest"]': digest,
|
|
}, "external shell receipt audit copy/export");
|
|
}
|
|
|
|
function assertExternalShellReceiptAuditCopyExportVerification(doc, verification, phase, digest) {
|
|
const expectedState = `${phase}-receipt-audit-copy-export-verified`;
|
|
const expectedText = `${expectedState}; copy=match; export=match; summary=match; digest=match`;
|
|
if (
|
|
verification.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification" ||
|
|
verification.phase !== phase ||
|
|
verification.ready !== true ||
|
|
verification.statusText !== "verified" ||
|
|
verification.verificationState !== expectedState ||
|
|
verification.verificationText !== expectedText ||
|
|
verification.copyMatches !== true ||
|
|
verification.exportMatches !== true ||
|
|
verification.summaryMatches !== true ||
|
|
verification.digestMatches !== true ||
|
|
verification.baseline !== HANDOFF_BASELINE ||
|
|
verification.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt audit copy/export verification drift: ${JSON.stringify(verification)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationPhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationCopyMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationExportMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationSummaryMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationDigestMatches: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationDigest: digest,
|
|
}, "external shell receipt audit copy/export verification");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-audit-copy-export-verification"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="verification-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="verification-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="copy-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="export-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="summary-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="digest-matches"]': "match",
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-value="digest"]': digest,
|
|
}, "external shell receipt audit copy/export verification");
|
|
}
|
|
|
|
function assertExternalShellReceiptAuditCopyExportVerificationBadge(doc, badge, phase, digest) {
|
|
const expectedState = `${phase}-receipt-audit-copy-export-verification-verified`;
|
|
const expectedText = `${expectedState} 4/4 ${digest}`;
|
|
if (
|
|
badge.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-badge" ||
|
|
badge.phase !== phase ||
|
|
badge.ready !== true ||
|
|
badge.statusText !== "verified" ||
|
|
badge.readyCount !== 4 ||
|
|
badge.totalCount !== 4 ||
|
|
badge.badgeState !== expectedState ||
|
|
badge.badgeText !== expectedText ||
|
|
badge.baseline !== HANDOFF_BASELINE ||
|
|
badge.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt audit copy/export verification badge drift: ${JSON.stringify(badge)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgePhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeReadyCount: "4",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeTotalCount: "4",
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadgeDigest: digest,
|
|
}, "external shell receipt audit copy/export verification badge");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-audit-copy-export-verification-badge"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-badge-value="badge-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-badge-value="badge-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-badge-value="ready-count"]': "4/4",
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-copy-export-verification-badge-value="digest"]': digest,
|
|
}, "external shell receipt audit copy/export verification badge");
|
|
}
|
|
|
|
function assertExternalShellReceiptAuditBundle(
|
|
doc,
|
|
bundle,
|
|
{ phase, auditText, copyText, exportText, verificationText, badgeText, badgeState, digest },
|
|
) {
|
|
const expectedState = `${phase}-receipt-audit-bundle-verified`;
|
|
const expectedText = [
|
|
expectedState,
|
|
`audit=${auditText}`,
|
|
`copy=${copyText}`,
|
|
`export=${exportText}`,
|
|
`verification=${verificationText}`,
|
|
`badge=${badgeText}`,
|
|
`badgeState=${badgeState}`,
|
|
"baseline=82/82/77/0",
|
|
`digest=${digest}`,
|
|
].join("; ");
|
|
if (
|
|
bundle.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-receipt-audit-bundle" ||
|
|
bundle.phase !== phase ||
|
|
bundle.ready !== true ||
|
|
bundle.statusText !== "verified" ||
|
|
bundle.bundleState !== expectedState ||
|
|
bundle.bundleText !== expectedText ||
|
|
bundle.auditText !== auditText ||
|
|
bundle.copyText !== copyText ||
|
|
bundle.exportText !== exportText ||
|
|
bundle.verificationText !== verificationText ||
|
|
bundle.badgeText !== badgeText ||
|
|
bundle.badgeState !== badgeState ||
|
|
bundle.baseline !== HANDOFF_BASELINE ||
|
|
bundle.digest !== digest
|
|
) {
|
|
throw new Error(`AXIS-style handoff external shell receipt audit bundle drift: ${JSON.stringify(bundle)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundlePhase: phase,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleReady: "true",
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleStatus: "verified",
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleState: expectedState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleText: expectedText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleAuditText: auditText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleCopyText: copyText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleExportText: exportText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleVerificationText: verificationText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleBadgeText: badgeText,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleBadgeState: badgeState,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellReceiptAuditBundleDigest: digest,
|
|
}, "external shell receipt audit bundle");
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-external-shell-receipt-audit-bundle"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="bundle-text"]': expectedText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="bundle-state"]': expectedState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="audit-text"]': auditText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="copy-text"]': copyText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="export-text"]': exportText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="verification-text"]': verificationText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="badge-text"]': badgeText,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="badge-state"]': badgeState,
|
|
'[data-evidence-session-handoff-external-shell-receipt-audit-bundle-value="digest"]': digest,
|
|
}, "external shell receipt audit bundle");
|
|
}
|
|
|
|
function assertLegacyHandoffReviewStatusbarDom(
|
|
doc,
|
|
{
|
|
phase,
|
|
action,
|
|
preflight,
|
|
reviewNoteText,
|
|
reviewPacket,
|
|
reviewPacketCopyExport,
|
|
reviewPacketCopyText,
|
|
reviewPacketVerification,
|
|
reviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary,
|
|
statusbarReceiptText,
|
|
statusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText,
|
|
externalShellBadgeText,
|
|
externalShellBadgeExportText,
|
|
externalShellBadgeSummary,
|
|
},
|
|
) {
|
|
const digest = reviewPacket.digest;
|
|
const reviewBadgeState = `${phase}-verified`;
|
|
const receiptBadgeState = `${phase}-receipt-verified`;
|
|
const shellBadgeState = `${phase}-shell-verified`;
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-review-note"]': reviewNoteText,
|
|
'[data-evidence-session-handoff-review-note-value="copy-text"]': reviewNoteText,
|
|
'[data-diagnostics="handoff-review-packet"]': reviewPacket.summaryText,
|
|
'[data-diagnostics="handoff-review-packet-copy-export"]': reviewPacketCopyExport.summaryText,
|
|
'[data-diagnostics="handoff-review-packet-verification"]': reviewPacketVerification.summaryText,
|
|
'[data-diagnostics="handoff-review-packet-verification-badge"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-diagnostics="handoff-statusbar-snapshot"]': statusbarSnapshotSummary,
|
|
'[data-diagnostics="handoff-statusbar-receipt"]': statusbarReceiptText,
|
|
'[data-diagnostics="handoff-statusbar-receipt-verification"]': statusbarReceiptVerificationSummary,
|
|
'[data-diagnostics="handoff-statusbar-receipt-badge"]': statusbarReceiptBadgeText,
|
|
'[data-diagnostics="handoff-external-shell-badge-snapshot"]': externalShellBadgeText,
|
|
'[data-diagnostics="handoff-external-shell-badge-copy-export"]': externalShellBadgeSummary,
|
|
'[data-evidence-session-handoff-review-packet-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-review-packet-value="json"]': reviewPacket.packetJson,
|
|
'[data-evidence-session-handoff-review-packet-copy-export-value="copy-text"]': reviewPacketCopyText,
|
|
'[data-evidence-session-handoff-review-packet-copy-export-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-review-packet-copy-export-value="json"]': reviewPacket.packetJson,
|
|
'[data-evidence-session-handoff-review-packet-verification-value="digest-matches"]': "match",
|
|
'[data-evidence-session-handoff-review-packet-verification-value="json-parse-ready"]': "parse-ready",
|
|
'[data-evidence-session-handoff-review-packet-verification-value="review-note-matches"]': "match",
|
|
'[data-evidence-session-handoff-review-packet-verification-value="preflight-matches"]': "match",
|
|
'[data-evidence-session-handoff-review-packet-verification-badge-value="badge-text"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-evidence-session-handoff-review-packet-verification-badge-value="badge-state"]': reviewBadgeState,
|
|
'[data-evidence-session-handoff-review-packet-verification-badge-value="ready-count"]': "4/4",
|
|
'[data-evidence-session-handoff-review-packet-verification-badge-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="phase"]': phase,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="action"]': action,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="preflight"]': preflight,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="packet-badge-text"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="packet-badge-state"]': reviewBadgeState,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-statusbar-snapshot-value="baseline"]': HANDOFF_BASELINE,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="receipt-text"]': statusbarReceiptText,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="phase"]': phase,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="action"]': action,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="preflight"]': preflight,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="badge"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-statusbar-receipt-value="baseline"]': HANDOFF_BASELINE,
|
|
'[data-evidence-session-handoff-statusbar-receipt-verification-value="receipt-matches"]': "match",
|
|
'[data-evidence-session-handoff-statusbar-receipt-verification-value="digest-matches"]': "match",
|
|
'[data-evidence-session-handoff-statusbar-receipt-verification-value="badge-matches"]': "match",
|
|
'[data-evidence-session-handoff-statusbar-receipt-badge-value="receipt-badge-text"]': statusbarReceiptBadgeText,
|
|
'[data-evidence-session-handoff-statusbar-receipt-badge-value="receipt-badge-state"]': receiptBadgeState,
|
|
'[data-evidence-session-handoff-statusbar-receipt-badge-value="ready-count"]': "3/3",
|
|
'[data-evidence-session-handoff-statusbar-receipt-badge-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-external-shell-badge-snapshot-value="shell-badge-text"]': externalShellBadgeText,
|
|
'[data-evidence-session-handoff-external-shell-badge-snapshot-value="shell-badge-state"]': shellBadgeState,
|
|
'[data-evidence-session-handoff-external-shell-badge-snapshot-value="packet-badge-text"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-evidence-session-handoff-external-shell-badge-snapshot-value="receipt-badge-text"]': statusbarReceiptBadgeText,
|
|
'[data-evidence-session-handoff-external-shell-badge-snapshot-value="digest"]': digest,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-value="copy-text"]': externalShellBadgeText,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-value="export-text"]': externalShellBadgeExportText,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-value="summary-text"]': externalShellBadgeSummary,
|
|
'[data-evidence-session-handoff-external-shell-badge-copy-export-value="digest"]': digest,
|
|
'[data-statusbar="handoff-packet-badge"]': reviewPacketVerificationBadge.badgeText,
|
|
'[data-statusbar="handoff-receipt-badge"]': statusbarReceiptBadgeText,
|
|
'[data-statusbar="handoff-shell-badge"]': externalShellBadgeText,
|
|
'[data-statusbar="handoff-action"]': action,
|
|
'[data-statusbar="handoff-action-detail"]': preflight,
|
|
}, `legacy handoff review/statusbar DOM ${phase}`);
|
|
}
|
|
|
|
function assertLegacyHandoffReviewStatusbarDataset(
|
|
doc,
|
|
{
|
|
phase,
|
|
action,
|
|
preflight,
|
|
reviewNoteText,
|
|
reviewPacket,
|
|
reviewPacketCopyExport,
|
|
reviewPacketCopyText,
|
|
reviewPacketVerification,
|
|
reviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary,
|
|
statusbarReceiptText,
|
|
statusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText,
|
|
externalShellBadgeText,
|
|
externalShellBadgeExportText,
|
|
externalShellBadgeSummary,
|
|
},
|
|
) {
|
|
const digest = reviewPacket.digest;
|
|
const reviewBadgeState = `${phase}-verified`;
|
|
const receiptBadgeState = `${phase}-receipt-verified`;
|
|
const shellBadgeState = `${phase}-shell-verified`;
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffReviewPacketPhase: phase,
|
|
evidenceSessionHandoffReviewPacketAction: action,
|
|
evidenceSessionHandoffReviewPacketPreflight: preflight,
|
|
evidenceSessionHandoffReviewPacketBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffReviewPacketLatest: reviewPacket.latestHistory,
|
|
evidenceSessionHandoffReviewPacketDigest: digest,
|
|
evidenceSessionHandoffReviewPacketJson: reviewPacket.packetJson,
|
|
evidenceSessionHandoffReviewPacketSummary: reviewPacket.summaryText,
|
|
evidenceSessionHandoffReviewPacketCopyExportPhase: phase,
|
|
evidenceSessionHandoffReviewPacketCopyExportReady: String(reviewPacketCopyExport.ready),
|
|
evidenceSessionHandoffReviewPacketCopyExportAction: action,
|
|
evidenceSessionHandoffReviewPacketCopyExportPreflight: preflight,
|
|
evidenceSessionHandoffReviewPacketCopyExportBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffReviewPacketCopyExportDigest: digest,
|
|
evidenceSessionHandoffReviewPacketCopyExportJson: reviewPacket.packetJson,
|
|
evidenceSessionHandoffReviewPacketCopyExportReviewNote: reviewNoteText,
|
|
evidenceSessionHandoffReviewPacketCopyExportText: reviewPacketCopyText,
|
|
evidenceSessionHandoffReviewPacketCopyExportSummary: reviewPacketCopyExport.summaryText,
|
|
evidenceSessionHandoffReviewPacketVerificationPhase: phase,
|
|
evidenceSessionHandoffReviewPacketVerificationReady: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationStatus: "verified",
|
|
evidenceSessionHandoffReviewPacketVerificationDigestMatches: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationJsonParseReady: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationReviewNoteMatches: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationPreflightMatches: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationDigest: digest,
|
|
evidenceSessionHandoffReviewPacketVerificationSummary: reviewPacketVerification.summaryText,
|
|
evidenceSessionHandoffReviewPacketVerificationBadgePhase: phase,
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeReady: "true",
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeStatus: "verified",
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeState: reviewBadgeState,
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeText: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeReadyCount: "4",
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeTotalCount: "4",
|
|
evidenceSessionHandoffReviewPacketVerificationBadgeDigest: digest,
|
|
evidenceSessionHandoffStatusbarBadgeText: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffStatusbarBadgeState: reviewBadgeState,
|
|
evidenceSessionHandoffStatusbarBadgeDigest: digest,
|
|
evidenceSessionHandoffStatusbarSnapshotPhase: phase,
|
|
evidenceSessionHandoffStatusbarSnapshotAction: action,
|
|
evidenceSessionHandoffStatusbarSnapshotPreflight: preflight,
|
|
evidenceSessionHandoffStatusbarSnapshotPacketBadgeText: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffStatusbarSnapshotPacketBadgeState: reviewBadgeState,
|
|
evidenceSessionHandoffStatusbarSnapshotDigest: digest,
|
|
evidenceSessionHandoffStatusbarSnapshotBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffStatusbarSnapshotReady: String(phase === "ready"),
|
|
evidenceSessionHandoffStatusbarSnapshotSummary: statusbarSnapshotSummary,
|
|
evidenceSessionHandoffStatusbarReceiptPhase: phase,
|
|
evidenceSessionHandoffStatusbarReceiptAction: action,
|
|
evidenceSessionHandoffStatusbarReceiptPreflight: preflight,
|
|
evidenceSessionHandoffStatusbarReceiptBadge: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffStatusbarReceiptPacketBadgeState: reviewBadgeState,
|
|
evidenceSessionHandoffStatusbarReceiptDigest: digest,
|
|
evidenceSessionHandoffStatusbarReceiptBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffStatusbarReceiptReady: String(phase === "ready"),
|
|
evidenceSessionHandoffStatusbarReceiptText: statusbarReceiptText,
|
|
evidenceSessionHandoffStatusbarReceiptVerificationPhase: phase,
|
|
evidenceSessionHandoffStatusbarReceiptVerificationReady: "true",
|
|
evidenceSessionHandoffStatusbarReceiptVerificationStatus: "verified",
|
|
evidenceSessionHandoffStatusbarReceiptVerificationReceiptMatches: "true",
|
|
evidenceSessionHandoffStatusbarReceiptVerificationDigestMatches: "true",
|
|
evidenceSessionHandoffStatusbarReceiptVerificationBadgeMatches: "true",
|
|
evidenceSessionHandoffStatusbarReceiptVerificationDigest: digest,
|
|
evidenceSessionHandoffStatusbarReceiptVerificationBadge: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffStatusbarReceiptVerificationBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffStatusbarReceiptVerificationSummary: statusbarReceiptVerificationSummary,
|
|
evidenceSessionHandoffStatusbarReceiptBadgePhase: phase,
|
|
evidenceSessionHandoffStatusbarReceiptBadgeReady: "true",
|
|
evidenceSessionHandoffStatusbarReceiptBadgeStatus: "verified",
|
|
evidenceSessionHandoffStatusbarReceiptBadgeState: receiptBadgeState,
|
|
evidenceSessionHandoffStatusbarReceiptBadgeText: statusbarReceiptBadgeText,
|
|
evidenceSessionHandoffStatusbarReceiptBadgeReadyCount: "3",
|
|
evidenceSessionHandoffStatusbarReceiptBadgeTotalCount: "3",
|
|
evidenceSessionHandoffStatusbarReceiptBadgeDigest: digest,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotPhase: phase,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotAction: action,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotPreflight: preflight,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotPacketBadgeText: reviewPacketVerificationBadge.badgeText,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotReceiptBadgeText: statusbarReceiptBadgeText,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotBaseline: HANDOFF_BASELINE,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotDigest: digest,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotReady: "true",
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotState: shellBadgeState,
|
|
evidenceSessionHandoffExternalShellBadgeSnapshotText: externalShellBadgeText,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportPhase: phase,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportReady: "true",
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportAction: action,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportPreflight: preflight,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportState: shellBadgeState,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportText: externalShellBadgeText,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportExportText: externalShellBadgeExportText,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportSummary: externalShellBadgeSummary,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportDigest: digest,
|
|
evidenceSessionHandoffExternalShellBadgeCopyExportBaseline: HANDOFF_BASELINE,
|
|
}, `legacy handoff review/statusbar dataset ${phase}`);
|
|
}
|
|
|
|
function assertLegacyHandoffSummaryCompatibility(
|
|
doc,
|
|
handoff,
|
|
runSummary,
|
|
{
|
|
phase,
|
|
ready,
|
|
detail,
|
|
evidence,
|
|
halSession,
|
|
savedDiagnostics,
|
|
machineSession,
|
|
baseline,
|
|
},
|
|
) {
|
|
if (
|
|
handoff.apiName !== "real-browser-simulation-evidence-session-handoff-summary" ||
|
|
handoff.phase !== phase ||
|
|
handoff.ready !== ready ||
|
|
handoff.fields.evidence !== evidence ||
|
|
handoff.fields.virtualHalSession !== halSession ||
|
|
handoff.fields.savedDiagnostics !== savedDiagnostics ||
|
|
handoff.fields.machineSession !== machineSession ||
|
|
handoff.fields.baseline !== baseline ||
|
|
runSummary?.handoffPhase !== phase ||
|
|
runSummary?.handoffDetail !== detail
|
|
) {
|
|
throw new Error(`AXIS-style handoff summary compatibility drift: ${JSON.stringify(handoff)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffReady: String(ready),
|
|
evidenceSessionHandoffEvidence: evidence,
|
|
evidenceSessionHandoffHalSession: halSession,
|
|
evidenceSessionHandoffSavedDiagnostics: savedDiagnostics,
|
|
evidenceSessionHandoffMachineSession: machineSession,
|
|
evidenceSessionHandoffBaseline: baseline,
|
|
evidenceSessionHandoffControlLabel: phase,
|
|
evidenceSessionHandoffControlDetail: detail,
|
|
evidenceSessionHandoffStatusbar: phase,
|
|
evidenceSessionHandoffRunSummaryPhase: phase,
|
|
evidenceSessionHandoffRunSummaryDetail: detail,
|
|
evidenceSessionHandoffDiagnosticsPhase: phase,
|
|
evidenceSessionHandoffDiagnosticsDetail: detail,
|
|
}, `legacy handoff summary dataset ${phase}`);
|
|
if (
|
|
doc.querySelector("[data-evidence-session-handoff-control]")?.dataset.state !== phase ||
|
|
doc.querySelector("[data-evidence-session-handoff-control-label]")?.textContent !== phase ||
|
|
doc.querySelector("[data-evidence-session-handoff-control-detail]")?.textContent !== detail ||
|
|
doc.querySelector('[data-statusbar="handoff"]')?.textContent !== phase ||
|
|
doc.querySelector('[data-run-summary="handoff-phase"]')?.textContent !== phase ||
|
|
doc.querySelector('[data-run-summary="handoff-detail"]')?.textContent !== detail ||
|
|
doc.querySelector('[data-diagnostics="handoff-phase"]')?.textContent !== phase ||
|
|
doc.querySelector('[data-diagnostics="handoff-detail"]')?.textContent !== detail ||
|
|
doc.querySelector('[data-evidence-session-handoff-value="evidence"]')?.textContent !== evidence ||
|
|
doc.querySelector('[data-evidence-session-handoff-value="hal-session"]')?.textContent !== halSession ||
|
|
doc.querySelector('[data-evidence-session-handoff-value="saved-diagnostics"]')?.textContent !== savedDiagnostics ||
|
|
doc.querySelector('[data-evidence-session-handoff-value="machine-session"]')?.textContent !== machineSession ||
|
|
doc.querySelector('[data-evidence-session-handoff-value="baseline"]')?.textContent !== baseline
|
|
) {
|
|
throw new Error(`AXIS-style handoff summary DOM compatibility drift: ${JSON.stringify(handoff)}`);
|
|
}
|
|
}
|
|
|
|
function assertLegacyHandoffOperatorSnapshotCompatibility(
|
|
doc,
|
|
snapshot,
|
|
{ phase, ready, detail, baseline, historyLatestKind, historyLatestMessage },
|
|
) {
|
|
const historyLatestText = historyLatestMessage ? `${historyLatestKind}: ${historyLatestMessage}` : "-";
|
|
if (
|
|
snapshot.apiName !== "real-browser-simulation-evidence-session-handoff-operator-snapshot" ||
|
|
snapshot.phase !== phase ||
|
|
snapshot.ready !== ready ||
|
|
snapshot.detail !== detail ||
|
|
snapshot.statusbar !== phase ||
|
|
snapshot.runSummary?.phase !== phase ||
|
|
snapshot.runSummary?.detail !== detail ||
|
|
snapshot.diagnostics?.phase !== phase ||
|
|
snapshot.diagnostics?.detail !== detail ||
|
|
snapshot.baseline !== baseline ||
|
|
(historyLatestMessage
|
|
? snapshot.historyLatest?.kind !== historyLatestKind || snapshot.historyLatest?.message !== historyLatestMessage
|
|
: snapshot.historyLatest !== null)
|
|
) {
|
|
throw new Error(`AXIS-style handoff operator snapshot compatibility drift: ${JSON.stringify(snapshot)}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffOperatorSnapshotPhase: phase,
|
|
evidenceSessionHandoffOperatorSnapshotDetail: detail,
|
|
evidenceSessionHandoffOperatorSnapshotHistoryLatest: historyLatestText,
|
|
evidenceSessionHandoffOperatorSnapshotBaseline: baseline,
|
|
}, `legacy handoff operator snapshot dataset ${phase}`);
|
|
assertDomText(doc, {
|
|
'[data-evidence-session-handoff-operator-snapshot-value="phase"]': phase,
|
|
'[data-evidence-session-handoff-operator-snapshot-value="detail"]': detail,
|
|
'[data-evidence-session-handoff-operator-snapshot-value="history-latest"]': historyLatestText,
|
|
'[data-evidence-session-handoff-operator-snapshot-value="baseline"]': baseline,
|
|
}, `legacy handoff operator snapshot DOM ${phase}`);
|
|
}
|
|
|
|
function assertLegacyHandoffActionPlanCompatibility(
|
|
doc,
|
|
actionPlan,
|
|
compactStatus,
|
|
{ phase, ready, action, baseline, detail, historyLatest },
|
|
) {
|
|
const compactStatusText = `${phase} | ${action} | ${historyLatest}`;
|
|
if (
|
|
actionPlan.apiName !== "real-browser-simulation-evidence-session-handoff-operator-action-plan" ||
|
|
actionPlan.phase !== phase ||
|
|
actionPlan.ready !== ready ||
|
|
actionPlan.nextActions?.join(", ") !== action ||
|
|
actionPlan.baseline !== baseline ||
|
|
actionPlan.snapshot?.ready !== ready ||
|
|
compactStatus.apiName !== "real-browser-simulation-evidence-session-handoff-operator-compact-status" ||
|
|
compactStatus.phase !== phase ||
|
|
compactStatus.action !== action ||
|
|
compactStatus.baseline !== baseline ||
|
|
compactStatus.detail !== detail ||
|
|
compactStatus.historyLatest !== historyLatest
|
|
) {
|
|
throw new Error(`AXIS-style handoff action plan compatibility drift: ${JSON.stringify({ actionPlan, compactStatus })}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffOperatorActionPlanPhase: phase,
|
|
evidenceSessionHandoffOperatorActionPlanNextActions: action,
|
|
evidenceSessionHandoffOperatorActionPlanBaseline: baseline,
|
|
evidenceSessionHandoffOperatorCompactStatusPhase: phase,
|
|
evidenceSessionHandoffOperatorCompactStatusAction: action,
|
|
evidenceSessionHandoffOperatorCompactStatusHistoryLatest: historyLatest,
|
|
evidenceSessionHandoffOperatorCompactStatusText: compactStatusText,
|
|
evidenceSessionHandoffStatusbarAction: action,
|
|
}, `legacy handoff action-plan dataset ${phase}`);
|
|
assertDomText(doc, {
|
|
'[data-diagnostics="handoff-compact-status"]': compactStatusText,
|
|
'[data-evidence-session-handoff-operator-action-plan-value="phase"]': phase,
|
|
'[data-evidence-session-handoff-operator-action-plan-value="next-actions"]': action,
|
|
'[data-evidence-session-handoff-operator-action-plan-value="baseline"]': baseline,
|
|
}, `legacy handoff action-plan DOM ${phase}`);
|
|
}
|
|
|
|
function assertLegacyHandoffPreflightAndHistoryCompatibility(
|
|
doc,
|
|
preflight,
|
|
reviewNote,
|
|
statusHistory,
|
|
{
|
|
phase,
|
|
ready,
|
|
readyCount,
|
|
totalCount,
|
|
action,
|
|
detail,
|
|
halSessionValue,
|
|
diagnosticsValue,
|
|
baseline,
|
|
historyLatest,
|
|
},
|
|
) {
|
|
if (
|
|
preflight.apiName !== "real-browser-simulation-evidence-session-handoff-preflight-checklist" ||
|
|
preflight.phase !== phase ||
|
|
preflight.ready !== ready ||
|
|
preflight.readyCount !== readyCount ||
|
|
preflight.totalCount !== totalCount ||
|
|
preflight.summaryText !== `${readyCount}/${totalCount} ready` ||
|
|
preflight.actionDetail !== `${readyCount}/${totalCount} ready` ||
|
|
preflight.baseline !== baseline ||
|
|
reviewNote.apiName !== "real-browser-simulation-evidence-session-handoff-review-note" ||
|
|
reviewNote.phase !== phase ||
|
|
reviewNote.action !== action ||
|
|
reviewNote.preflight !== `${readyCount}/${totalCount} ready` ||
|
|
reviewNote.baseline !== baseline ||
|
|
reviewNote.latestHistory !== historyLatest ||
|
|
reviewNote.noteText !== `phase=${phase}; action=${action}; preflight=${readyCount}/${totalCount} ready; baseline=${baseline}; latest=${historyLatest}`
|
|
) {
|
|
throw new Error(`AXIS-style handoff preflight/history compatibility drift: ${JSON.stringify({ preflight, reviewNote })}`);
|
|
}
|
|
assertDatasetValues(doc, {
|
|
evidenceSessionHandoffPreflightPhase: phase,
|
|
evidenceSessionHandoffPreflightReady: String(ready),
|
|
evidenceSessionHandoffPreflightReadyCount: String(readyCount),
|
|
evidenceSessionHandoffPreflightTotalCount: String(totalCount),
|
|
evidenceSessionHandoffPreflightSummary: `${readyCount}/${totalCount} ready`,
|
|
evidenceSessionHandoffPreflightEvidenceReady: "ready",
|
|
evidenceSessionHandoffPreflightHalSessionReady: ready ? "ready" : "blocked",
|
|
evidenceSessionHandoffPreflightDiagnosticsReady: ready ? "ready" : "blocked",
|
|
evidenceSessionHandoffPreflightBaselineLocked: "ready",
|
|
evidenceSessionHandoffReviewNotePhase: phase,
|
|
evidenceSessionHandoffReviewNoteAction: action,
|
|
evidenceSessionHandoffReviewNotePreflight: `${readyCount}/${totalCount} ready`,
|
|
evidenceSessionHandoffReviewNoteBaseline: baseline,
|
|
evidenceSessionHandoffReviewNoteLatest: historyLatest,
|
|
evidenceSessionHandoffReviewNoteText: reviewNote.noteText,
|
|
evidenceSessionHandoffStatusbarActionDetail: `${readyCount}/${totalCount} ready`,
|
|
}, `legacy handoff preflight/history dataset ${phase}`);
|
|
if (
|
|
doc.querySelector('[data-diagnostics="handoff-preflight"]')?.textContent !== `${readyCount}/${totalCount} ready` ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-row="hal-session-ready"]')?.dataset.ready !== String(ready) ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-value="hal-session-ready"]')?.textContent !== `${ready ? "ready" : "blocked"}: ${halSessionValue}` ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-row="diagnostics-ready"]')?.dataset.ready !== String(ready) ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-value="diagnostics-ready"]')?.textContent !== `${ready ? "ready" : "blocked"}: ${diagnosticsValue}` ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-row="baseline-locked"]')?.dataset.ready !== "true" ||
|
|
doc.querySelector('[data-evidence-session-handoff-preflight-value="baseline-locked"]')?.textContent !== `ready: ${baseline}` ||
|
|
(historyLatest !== "-" &&
|
|
!statusHistory.some(({ kind, message }) => kind === "handoff" && message === historyLatest.replace(/^handoff: /, ""))) ||
|
|
(historyLatest !== "-" && !doc.querySelector("[data-status-history]")?.textContent.includes(historyLatest))
|
|
) {
|
|
throw new Error(`AXIS-style handoff preflight/history DOM compatibility drift: ${JSON.stringify({ preflight, reviewNote })}`);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
|
|
const doc = frame.contentDocument;
|
|
const state = await waitForState(frame);
|
|
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
|
const api = frame.contentWindow?.linuxCncRealSimulationApi;
|
|
|
|
assertAxisShell(doc);
|
|
assertRenderedState(doc, state);
|
|
if (!canonicalOutput.includes("canon_event=STRAIGHT_FEED line=2 x=1 y=0 z=0")) {
|
|
throw new Error("simulation canonical output missing LinuxCNC feed event");
|
|
}
|
|
if (!doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points")?.includes("1,0")) {
|
|
throw new Error("simulation toolpath polyline missing expected point");
|
|
}
|
|
if (doc.querySelector('[data-axis="x"]')?.textContent !== "0.000") {
|
|
throw new Error("simulation final X axis drift");
|
|
}
|
|
if (doc.querySelector('[data-axis="y"]')?.textContent !== "0.000") {
|
|
throw new Error("simulation final Y axis drift");
|
|
}
|
|
|
|
if (!api?.getPrograms || !api?.runProgramById || !api?.reloadCurrentProgram) {
|
|
throw new Error("simulation API missing program controls");
|
|
}
|
|
if (!api?.getDroState || !api?.getModalState) {
|
|
throw new Error("simulation API missing AXIS DRO/modal controls");
|
|
}
|
|
if (!api?.fitPreview || !api?.resetPreview || !api?.zoomPreview || !api?.panPreview || !api?.getPreviewState || !api?.setPreviewViewMode || !api?.getPreviewLayerState || !api?.setPreviewLayer || !api?.getPreviewCursorState) {
|
|
throw new Error("simulation API missing AXIS preview controls");
|
|
}
|
|
if (!api?.getOpfsProgramState || !api?.saveProgramToOpfs || !api?.loadProgramFromOpfs) {
|
|
throw new Error("simulation API missing AXIS OPFS program persistence controls");
|
|
}
|
|
if (!api?.getMachineReadiness || !api?.checkMachineSessionReadiness) {
|
|
throw new Error("simulation API missing AXIS machine/session readiness controls");
|
|
}
|
|
if (!api?.getMachineStatusState) {
|
|
throw new Error("simulation API missing AXIS machine status controls");
|
|
}
|
|
if (
|
|
!api?.getVirtualHalState ||
|
|
!api?.applyVirtualHalAction ||
|
|
!api?.getVirtualHalDroState ||
|
|
!api?.getVirtualHalLimitsHomeState ||
|
|
!api?.getVirtualHalMachineStatusState ||
|
|
!api?.getVirtualHalPinRegistry ||
|
|
!api?.readVirtualHalPin ||
|
|
!api?.writeVirtualHalPin ||
|
|
!api?.applyVirtualHalPinUpdates ||
|
|
!api?.getVirtualHalIntegrityReport ||
|
|
!api?.executeVirtualHalcmd ||
|
|
!api?.executeVirtualHalCommand ||
|
|
!api?.stepVirtualHalMotionController ||
|
|
!api?.getVirtualHalSimulationRuntimeReport ||
|
|
!api?.getVirtualHalSimulationReplacementReport ||
|
|
!api?.getVirtualHalMillturnUserMProcessBoundaryReport ||
|
|
!api?.getMillturnUserMProcessState ||
|
|
!api?.applyMillturnUserMProcessState ||
|
|
!api?.getVirtualHalSourceComplianceReport ||
|
|
!api?.getVirtualHalSimConfigSourceCoverageReport ||
|
|
!api?.getVirtualHalSimConfigPromotionCandidateReport ||
|
|
!api?.getVirtualHalPromotionCandidateSummary ||
|
|
!api?.getVirtualHalPromotionFamilyRows ||
|
|
!api?.getVirtualHalEvidenceExpansionFamilyDrilldown ||
|
|
!api?.getEvidenceStatusStripSummary ||
|
|
!api?.getEvidenceStatusCopyExportViewModel ||
|
|
!api?.getEvidenceSessionHandoffSummary ||
|
|
!api?.getEvidenceSessionHandoffOperatorSnapshot ||
|
|
!api?.getEvidenceSessionHandoffOperatorActionPlan ||
|
|
!api?.getEvidenceSessionHandoffOperatorCompactStatus ||
|
|
!api?.getEvidenceSessionHandoffPreflightChecklist ||
|
|
!api?.getEvidenceSessionHandoffReviewNote ||
|
|
!api?.getEvidenceSessionHandoffReviewPacket ||
|
|
!api?.getEvidenceSessionHandoffReviewPacketCopyExport ||
|
|
!api?.getEvidenceSessionHandoffReviewPacketVerification ||
|
|
!api?.getEvidenceSessionHandoffReviewPacketVerificationBadge ||
|
|
!api?.getEvidenceSessionHandoffStatusbarSnapshot ||
|
|
!api?.getEvidenceSessionHandoffStatusbarReceipt ||
|
|
!api?.getEvidenceSessionHandoffStatusbarReceiptVerification ||
|
|
!api?.getEvidenceSessionHandoffStatusbarReceiptBadge ||
|
|
!api?.getEvidenceSessionHandoffExternalShellBadgeSnapshot ||
|
|
!api?.getEvidenceSessionHandoffExternalShellBadgeCopyExport ||
|
|
!api?.getEvidenceSessionHandoffExternalShellBadgeCopyExportVerification ||
|
|
!api?.getEvidenceSessionHandoffExternalShellBundle ||
|
|
!api?.getEvidenceSessionHandoffExternalShellBundleBadge ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceipt ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptVerification ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptVerificationBadge ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptAuditSnapshot ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExport ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerification ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadge ||
|
|
!api?.getEvidenceSessionHandoffExternalShellReceiptAuditBundle ||
|
|
!api?.getVirtualHalSimConfigMacroLoadFixtureReport ||
|
|
!api?.getVirtualHalCommandScriptFixtureReport ||
|
|
!api?.getVirtualHalMotionControllerMatrixReport ||
|
|
!api?.getVirtualRealtimeHalRuntimeReport
|
|
) {
|
|
throw new Error("simulation API missing browser virtual HAL controls");
|
|
}
|
|
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
|
throw new Error("simulation API missing AXIS machine/session load controls");
|
|
}
|
|
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot || !api?.exportVirtualHalSessionDiagnosticsArtifact) {
|
|
throw new Error("simulation API missing virtual HAL session persistence controls");
|
|
}
|
|
if (!api?.getRunMode || !api?.getRunControlState || !api?.setUseLoadedSession || !api?.getRunSummary) {
|
|
throw new Error("simulation API missing AXIS run-mode controls");
|
|
}
|
|
if (!api?.getAxisStatusbarState) {
|
|
throw new Error("simulation API missing AXIS statusbar controls");
|
|
}
|
|
if (!api?.getDiagnosticsState || !api?.handleAxisShortcut) {
|
|
throw new Error("simulation API missing AXIS diagnostics/shortcut controls");
|
|
}
|
|
if (!api?.getStatusHistory || !api?.getToolTableSummary) {
|
|
throw new Error("simulation API missing AXIS status history/tool table summary controls");
|
|
}
|
|
if (!api?.getLimitsHomeState || !api?.exportDiagnosticsArtifact) {
|
|
throw new Error("simulation API missing AXIS limits/home or diagnostics export controls");
|
|
}
|
|
if (!api?.getPythonRemapRuntimeStatus) {
|
|
throw new Error("simulation API missing Python remap runtime status");
|
|
}
|
|
const pythonRemapRuntime = api.getPythonRemapRuntimeStatus();
|
|
if (
|
|
pythonRemapRuntime?.apiName !== "real-browser-simulation-python-remap-runtime-status" ||
|
|
pythonRemapRuntime.boundaryClass !== "L4-PYTHON-REMAP" ||
|
|
pythonRemapRuntime.fixtureFamily !== "axis/remap/stop-lookahead/nc_files" ||
|
|
pythonRemapRuntime.iniPath !== "axis/remap/stop-lookahead/demo.ini" ||
|
|
pythonRemapRuntime.pythonPathPrepend !== "python" ||
|
|
pythonRemapRuntime.topLevelPath !== "python/toplevel.py" ||
|
|
pythonRemapRuntime.runtimeMode !== "contract-only" ||
|
|
pythonRemapRuntime.executionEnabled !== false ||
|
|
pythonRemapRuntime.promotionAllowed !== false ||
|
|
pythonRemapRuntime.bulkPromotionAllowed !== false ||
|
|
pythonRemapRuntime.ngcOnlySubroutinePromoted !== false ||
|
|
pythonRemapRuntime.jsCncSemantics !== false ||
|
|
doc.body.dataset.pythonRemapRuntimePhase !== "locked" ||
|
|
doc.querySelector('[data-python-remap-runtime="boundary"]')?.textContent !== "L4-PYTHON-REMAP" ||
|
|
doc.querySelector('[data-python-remap-runtime="fixture"]')?.textContent !== "axis/remap/stop-lookahead/nc_files" ||
|
|
doc.querySelector('[data-python-remap-runtime="execution"]')?.textContent !== "disabled" ||
|
|
doc.querySelector('[data-python-remap-runtime="promotion"]')?.textContent !== "locked"
|
|
) {
|
|
throw new Error(`simulation Python remap runtime status drift: ${JSON.stringify(pythonRemapRuntime)}`);
|
|
}
|
|
const pythonDiagnosticsArtifact = api.exportDiagnosticsArtifact();
|
|
if (
|
|
pythonDiagnosticsArtifact.pythonRemapRuntime?.boundaryClass !== "L4-PYTHON-REMAP" ||
|
|
pythonDiagnosticsArtifact.pythonRemapRuntime?.promotionAllowed !== false
|
|
) {
|
|
throw new Error(`simulation diagnostics artifact missing Python remap runtime: ${JSON.stringify(pythonDiagnosticsArtifact.pythonRemapRuntime)}`);
|
|
}
|
|
if (!api?.applyAxisViewFromUrl) {
|
|
throw new Error("simulation API missing AXIS URL view controls");
|
|
}
|
|
if (!api?.runProgramText || !api?.loadProgramText || !api?.loadProgramFile || !api?.runMdiProgramText) {
|
|
throw new Error("simulation API missing real G-code loading controls");
|
|
}
|
|
if (!api?.getMdiHistory || !api?.clearMdiHistory) {
|
|
throw new Error("simulation API missing AXIS MDI history controls");
|
|
}
|
|
if (!api?.getRecentPrograms || !api?.clearRecentPrograms) {
|
|
throw new Error("simulation API missing AXIS recent program controls");
|
|
}
|
|
if (!api?.getProgramText || !api?.setProgramText || !api?.runEditorProgramText) {
|
|
throw new Error("simulation API missing editor text controls");
|
|
}
|
|
if (!api?.resetPlayback || !api?.stepPlayback || !api?.finishPlayback) {
|
|
throw new Error("simulation API missing playback controls");
|
|
}
|
|
if (!api?.isPlaybackRunning) {
|
|
throw new Error("simulation API missing playback running state");
|
|
}
|
|
const programs = api.getPrograms();
|
|
if (programs.length < 5) {
|
|
throw new Error("simulation test program inventory too small");
|
|
}
|
|
const initialRecentPrograms = api.getRecentPrograms();
|
|
if (
|
|
initialRecentPrograms.length !== 1 ||
|
|
initialRecentPrograms[0].source !== "builtin" ||
|
|
doc.body.dataset.recentProgramCount !== "1" ||
|
|
!doc.querySelector("[data-recent-program-list]")?.textContent.includes(initialRecentPrograms[0].label)
|
|
) {
|
|
throw new Error(`simulation initial built-in run should populate recent programs: ${JSON.stringify(initialRecentPrograms)}`);
|
|
}
|
|
api.clearRecentPrograms();
|
|
if (api.getRecentPrograms().length !== 0 || doc.body.dataset.recentProgramCount !== "0" || !doc.querySelector("[data-recent-program-list]")?.textContent.includes("No recent programs")) {
|
|
throw new Error(`simulation recent programs clear did not reset API/DOM state: ${JSON.stringify(api.getRecentPrograms())}`);
|
|
}
|
|
if (!doc.querySelector("[data-open-program]") || !doc.querySelector("[data-open-program-file]")) {
|
|
throw new Error("simulation page missing open program controls");
|
|
}
|
|
if (!doc.querySelector("[data-program-editor]") || !doc.querySelector("[data-run-editor-program]")) {
|
|
throw new Error("simulation page missing editable G-code controls");
|
|
}
|
|
if (!doc.querySelector("[data-recent-program-list]") || !doc.querySelector("[data-clear-recent-programs]")) {
|
|
throw new Error("simulation page missing recent program controls");
|
|
}
|
|
if (!doc.querySelector("[data-opfs-program-filename]") || !doc.querySelector("[data-save-program-opfs]") || !doc.querySelector("[data-load-program-opfs]")) {
|
|
throw new Error("simulation page missing OPFS program persistence controls");
|
|
}
|
|
if (
|
|
!doc.querySelector('[data-axis-shell="machine-session"]') ||
|
|
!doc.querySelector("[data-check-machine-session]") ||
|
|
!doc.querySelector("[data-load-machine-session]") ||
|
|
!doc.querySelector("[data-save-virtual-hal-session]") ||
|
|
!doc.querySelector("[data-restore-virtual-hal-session]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-control]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-operator-action-plan]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-preflight-checklist]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-review-note]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-review-packet]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-review-packet-copy-export]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-review-packet-verification]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-review-packet-verification-badge]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-statusbar-snapshot]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-statusbar-receipt]") ||
|
|
!doc.querySelector("[data-evidence-session-handoff-statusbar-receipt-verification]") ||
|
|
!doc.querySelector("[data-use-loaded-session]")
|
|
) {
|
|
throw new Error("simulation page missing machine/session readiness controls");
|
|
}
|
|
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("browser virtual HAL active")) {
|
|
throw new Error("simulation page should explicitly mark browser virtual HAL machine controls");
|
|
}
|
|
if (!doc.querySelector("[data-mdi-program-text]") || !doc.querySelector("[data-load-mdi-program]") || !doc.querySelector("[data-run-mdi-program]") || !doc.querySelector("[data-mdi-history-list]") || !doc.querySelector("[data-clear-mdi-history]")) {
|
|
throw new Error("simulation page missing MDI program text controls");
|
|
}
|
|
if (api.getRunMode().mode !== "standalone" || doc.body.dataset.runMode !== "standalone" || !doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Standalone")) {
|
|
throw new Error("simulation initial run mode should be standalone");
|
|
}
|
|
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="execution-mode"]')?.textContent !== "linuxcnc-wasm") {
|
|
throw new Error("simulation initial run summary should report standalone LinuxCNC WASM execution");
|
|
}
|
|
const initialRunControl = api.getRunControlState();
|
|
if (
|
|
initialRunControl.phase !== "fallback" ||
|
|
doc.body.dataset.runControlPhase !== "fallback" ||
|
|
!doc.querySelector("[data-run-control-state]")?.textContent.includes("no loaded machine session")
|
|
) {
|
|
throw new Error(`simulation initial run control should explain standalone fallback: ${JSON.stringify(initialRunControl)}`);
|
|
}
|
|
const initialMachineStatus = api.getMachineStatusState();
|
|
if (
|
|
initialMachineStatus?.source !== "linuxcnc-canonical-events" ||
|
|
initialMachineStatus.spindle.state !== "off" ||
|
|
doc.querySelector('[data-machine-status="spindle-state"]')?.textContent !== "off"
|
|
) {
|
|
throw new Error(`simulation initial machine status should expose AXIS default state: ${JSON.stringify(initialMachineStatus)}`);
|
|
}
|
|
const modalState = api.getModalState();
|
|
if (modalState?.source !== "linuxcnc-update-tag" || !modalState.raw?.includes("canon_event=UPDATE_TAG")) {
|
|
throw new Error(`simulation modal state should come from LinuxCNC UPDATE_TAG: ${JSON.stringify(modalState)}`);
|
|
}
|
|
const modalText = doc.querySelector("[data-modal-rows]")?.textContent ?? "";
|
|
if (!modalText.includes("Plane: plane=170") || !modalText.includes("Origin: origin=530")) {
|
|
throw new Error(`simulation modal display missing LinuxCNC fields: ${modalText}`);
|
|
}
|
|
const diagnosticsState = api.getDiagnosticsState();
|
|
if (
|
|
diagnosticsState.lastEvent === "pending" ||
|
|
diagnosticsState.lastError !== "none" ||
|
|
!doc.querySelector('[data-diagnostics="last-event"]')?.textContent.includes("canon_event=")
|
|
) {
|
|
throw new Error(`simulation diagnostics should expose last LinuxCNC event: ${JSON.stringify(diagnosticsState)}`);
|
|
}
|
|
const initialHistory = api.getStatusHistory();
|
|
if (!initialHistory.some(({ kind }) => kind === "run") || !doc.querySelector("[data-status-history]")?.textContent.includes("run:")) {
|
|
throw new Error(`simulation status history should record initial run: ${JSON.stringify(initialHistory)}`);
|
|
}
|
|
const limitsHome = api.getLimitsHomeState();
|
|
if (
|
|
limitsHome.source !== "browser-virtual-hal" ||
|
|
limitsHome.axes.x.homed !== "no" ||
|
|
doc.querySelector('[data-limits-home-row="x"]')?.children.length !== 5 ||
|
|
!doc.querySelector("[data-limits-home-summary]")?.textContent.includes("source browser-virtual-hal")
|
|
) {
|
|
throw new Error(`simulation limits/home state should expose browser virtual HAL rows: ${JSON.stringify(limitsHome)}`);
|
|
}
|
|
const virtualHalInitial = api.getVirtualHalState();
|
|
if (
|
|
virtualHalInitial.source !== "browser-virtual-hal" ||
|
|
virtualHalInitial.component !== "axisui" ||
|
|
virtualHalInitial.task.state !== "ESTOP" ||
|
|
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.x") ||
|
|
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.increment")
|
|
) {
|
|
throw new Error(`simulation virtual HAL initial state drift: ${JSON.stringify(virtualHalInitial)}`);
|
|
}
|
|
const initialVirtualHalRegistry = api.getVirtualHalPinRegistry();
|
|
if (
|
|
initialVirtualHalRegistry.apiName !== "linuxcnc-wasm-virtual-hal-pin-registry" ||
|
|
!initialVirtualHalRegistry.byName["halui.machine.on"]?.writable ||
|
|
!initialVirtualHalRegistry.byName["motion.tooloffset.z"]?.writable ||
|
|
!api.getVirtualHalIntegrityReport().complete
|
|
) {
|
|
throw new Error(`simulation virtual HAL registry/integrity drift: ${JSON.stringify(initialVirtualHalRegistry)}`);
|
|
}
|
|
let diagnosticsArtifact = api.exportDiagnosticsArtifact();
|
|
const millturnUserMBoundary = api.getVirtualHalMillturnUserMProcessBoundaryReport();
|
|
if (
|
|
millturnUserMBoundary.apiName !== "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report" ||
|
|
millturnUserMBoundary.boundaryClass !== "L4-USER-M-PROCESS" ||
|
|
millturnUserMBoundary.complete !== true ||
|
|
millturnUserMBoundary.webSimulationSatisfied !== true ||
|
|
millturnUserMBoundary.executionEnabled !== false ||
|
|
millturnUserMBoundary.promotionAllowed !== false ||
|
|
millturnUserMBoundary.processExecutionReady !== false ||
|
|
millturnUserMBoundary.rows.length !== 2 ||
|
|
!millturnUserMBoundary.rows.some(({ userMCode, remapCode, stateTargetCount }) => userMCode === "M128" && remapCode === "M428" && stateTargetCount === 12) ||
|
|
!millturnUserMBoundary.rows.some(({ userMCode, remapCode, stateTargetCount }) => userMCode === "M129" && remapCode === "M429" && stateTargetCount === 12) ||
|
|
doc.body.dataset.userMProcessPhase !== "ready" ||
|
|
doc.querySelector('[data-user-m-process="boundary"]')?.textContent !== "L4-USER-M-PROCESS" ||
|
|
doc.querySelector('[data-user-m-process="path"]')?.textContent !== "axis/vismach/millturn/example.ngc" ||
|
|
doc.querySelector('[data-user-m-process="execution"]')?.textContent !== "disabled" ||
|
|
doc.querySelector('[data-user-m-process="promotion"]')?.textContent !== "locked"
|
|
) {
|
|
throw new Error(`simulation millturn user-M boundary drift: ${JSON.stringify(millturnUserMBoundary)}`);
|
|
}
|
|
const millturnM128 = api.applyMillturnUserMProcessState("M128");
|
|
if (
|
|
millturnM128.apiName !== "linuxcnc-wasm-virtual-hal-millturn-user-m-process-state-result" ||
|
|
millturnM128.userMCode !== "M128" ||
|
|
millturnM128.remapCode !== "M428" ||
|
|
millturnM128.stateMode !== "mill" ||
|
|
api.readVirtualHalPin("motion.switchkins-type")?.value !== 0 ||
|
|
api.readVirtualHalPin("ini.x.min_limit")?.value !== -300 ||
|
|
api.readVirtualHalPin("ini.z.max_limit")?.value !== 0 ||
|
|
doc.body.dataset.userMProcessPhase !== "applied" ||
|
|
doc.body.dataset.userMProcessActiveCode !== "M128" ||
|
|
doc.body.dataset.userMProcessActiveMode !== "mill" ||
|
|
doc.querySelector('[data-user-m-process="active-code"]')?.textContent !== "M128" ||
|
|
doc.querySelector('[data-user-m-process="active-remap"]')?.textContent !== "M428" ||
|
|
doc.querySelector('[data-user-m-process="active-mode"]')?.textContent !== "mill" ||
|
|
doc.querySelector('[data-user-m-process="switchkins"]')?.textContent !== "0"
|
|
) {
|
|
throw new Error(`simulation M128 user-M state did not apply: ${JSON.stringify(millturnM128)}`);
|
|
}
|
|
const millturnM129 = api.applyMillturnUserMProcessState("M129");
|
|
if (
|
|
millturnM129.userMCode !== "M129" ||
|
|
millturnM129.remapCode !== "M429" ||
|
|
millturnM129.stateMode !== "turn" ||
|
|
api.readVirtualHalPin("motion.switchkins-type")?.value !== 1 ||
|
|
api.readVirtualHalPin("motion.analog-out-03")?.value !== 1 ||
|
|
api.readVirtualHalPin("kinstype.is-0")?.value !== 0 ||
|
|
api.readVirtualHalPin("kinstype.is-1")?.value !== 1 ||
|
|
api.readVirtualHalPin("ini.x.max_limit")?.value !== 0 ||
|
|
api.readVirtualHalPin("ini.z.max_limit")?.value !== 300 ||
|
|
api.getMillturnUserMProcessState().activeMode !== "turn" ||
|
|
doc.querySelector('[data-user-m-process="active-code"]')?.textContent !== "M129" ||
|
|
doc.querySelector('[data-user-m-process="active-remap"]')?.textContent !== "M429" ||
|
|
doc.querySelector('[data-user-m-process="active-mode"]')?.textContent !== "turn" ||
|
|
doc.querySelector('[data-user-m-process="switchkins"]')?.textContent !== "1"
|
|
) {
|
|
throw new Error(`simulation M129 user-M state did not apply: ${JSON.stringify(millturnM129)}`);
|
|
}
|
|
diagnosticsArtifact = api.exportDiagnosticsArtifact();
|
|
const promotionCandidateSummary = api.getVirtualHalPromotionCandidateSummary();
|
|
const expectedPromotionCandidates = [
|
|
[
|
|
"qtdragon-multi-joint-on-abort",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
|
],
|
|
[
|
|
"qtdragon-xyz-on-abort",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/qtdragon_inch.ini",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc",
|
|
],
|
|
[
|
|
"qtdragon-xyz45-on-abort",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini",
|
|
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/on_abort.ngc",
|
|
],
|
|
[
|
|
"qtdragon-hd-xyz-on-abort",
|
|
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/qtdragon_hd_vertical.ini",
|
|
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc",
|
|
],
|
|
[
|
|
"qtdragon-hd-z-compensation-on-abort",
|
|
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini",
|
|
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/on_abort.ngc",
|
|
],
|
|
[
|
|
"qtvcp-screens-qtdragon-on-abort",
|
|
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/qtdragon_mpg.ini",
|
|
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc",
|
|
],
|
|
[
|
|
"puma-seam-weld",
|
|
"linuxcnc/configs/sim/axis/vismach/puma/puma.ini",
|
|
"linuxcnc/configs/sim/axis/vismach/puma/puma_seam_weld.ngc",
|
|
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc",
|
|
],
|
|
[
|
|
"rose-engine-rcone-demo",
|
|
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
|
|
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
|
|
],
|
|
];
|
|
const expectedMacroLoadFixtures = [
|
|
[
|
|
"rose-engine-rcone-macro-load",
|
|
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
|
|
false,
|
|
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
|
|
"rcone.ngc",
|
|
34,
|
|
"linuxcnc/configs/sim/axis/rose_engine/rose_engine.ini",
|
|
"linuxcnc/configs/sim/axis/rose_engine/rcone.ngc",
|
|
"linuxcnc/configs/sim/axis/rose_engine/rcone_demo.ngc",
|
|
],
|
|
[
|
|
"external-offsets-queuebuster-macro-load",
|
|
"linuxcnc/configs/sim/axis/external_offsets/queuebuster.ngc",
|
|
true,
|
|
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
|
|
"queuebuster.ngc",
|
|
40,
|
|
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
|
|
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
|
|
],
|
|
];
|
|
const expectedBlockedMacroLoadFixtures = [
|
|
[
|
|
"silverdragon-tool-sensor-python-ui-boundary",
|
|
"linuxcnc/configs/sim/gscreen/silverdragon/macros/tool_sensor.ngc",
|
|
"PYTHON-UI-PROCESS",
|
|
],
|
|
[
|
|
"gmoccapy-on-abort-python-remap-boundary",
|
|
"linuxcnc/configs/sim/gmoccapy/macros/on_abort.ngc",
|
|
"L4-PYTHON-REMAP",
|
|
],
|
|
];
|
|
const expectedMacroLoadAuditCandidates = [
|
|
[
|
|
"gscreen-industrial-lathe-wear-toolchange-audit",
|
|
"linuxcnc/configs/sim/gscreen/industrial_lathe_wear/toolchange.ngc",
|
|
"blocked-missing-owning-ini",
|
|
],
|
|
[
|
|
"qtvcp-industrial-lathe-wear-toolchange-audit",
|
|
"linuxcnc/configs/sim/qtvcp_screens/industrial_lathe_wear/toolchange.ngc",
|
|
"blocked-missing-owning-ini",
|
|
],
|
|
];
|
|
const initialEvidenceSessionHandoff = api.getEvidenceSessionHandoffSummary();
|
|
const initialHandoffSnapshot = api.getEvidenceSessionHandoffOperatorSnapshot();
|
|
const initialHandoffActionPlan = api.getEvidenceSessionHandoffOperatorActionPlan();
|
|
const initialHandoffCompactStatus = api.getEvidenceSessionHandoffOperatorCompactStatus();
|
|
const initialHandoffPreflight = api.getEvidenceSessionHandoffPreflightChecklist();
|
|
const initialHandoffReviewNote = api.getEvidenceSessionHandoffReviewNote();
|
|
const initialHandoffReviewPacket = api.getEvidenceSessionHandoffReviewPacket();
|
|
const initialHandoffReviewPacketCopyExport = api.getEvidenceSessionHandoffReviewPacketCopyExport();
|
|
const initialHandoffReviewPacketVerification = api.getEvidenceSessionHandoffReviewPacketVerification();
|
|
const initialHandoffReviewPacketVerificationBadge = api.getEvidenceSessionHandoffReviewPacketVerificationBadge();
|
|
const initialHandoffStatusbarSnapshot = api.getEvidenceSessionHandoffStatusbarSnapshot();
|
|
const initialHandoffStatusbarReceipt = api.getEvidenceSessionHandoffStatusbarReceipt();
|
|
const initialHandoffStatusbarReceiptVerification = api.getEvidenceSessionHandoffStatusbarReceiptVerification();
|
|
const initialHandoffStatusbarReceiptBadge = api.getEvidenceSessionHandoffStatusbarReceiptBadge();
|
|
const initialHandoffExternalShellBadgeSnapshot = api.getEvidenceSessionHandoffExternalShellBadgeSnapshot();
|
|
const initialHandoffExternalShellBadgeCopyExport = api.getEvidenceSessionHandoffExternalShellBadgeCopyExport();
|
|
const initialHandoffExternalShellBadgeCopyExportVerification = api.getEvidenceSessionHandoffExternalShellBadgeCopyExportVerification();
|
|
const initialHandoffExternalShellBundle = api.getEvidenceSessionHandoffExternalShellBundle();
|
|
const initialHandoffExternalShellBundleBadge = api.getEvidenceSessionHandoffExternalShellBundleBadge();
|
|
const initialHandoffExternalShellReceipt = api.getEvidenceSessionHandoffExternalShellReceipt();
|
|
const initialHandoffExternalShellReceiptVerification = api.getEvidenceSessionHandoffExternalShellReceiptVerification();
|
|
const initialHandoffExternalShellReceiptVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptVerificationBadge();
|
|
const initialHandoffExternalShellReceiptAuditSnapshot = api.getEvidenceSessionHandoffExternalShellReceiptAuditSnapshot();
|
|
const initialHandoffExternalShellReceiptAuditCopyExport = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExport();
|
|
const initialHandoffExternalShellReceiptAuditCopyExportVerification = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerification();
|
|
const initialHandoffExternalShellReceiptAuditCopyExportVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadge();
|
|
const initialHandoffExternalShellReceiptAuditBundle = api.getEvidenceSessionHandoffExternalShellReceiptAuditBundle();
|
|
const initialHandoffReviewPacketCopyText = `phase=blocked; action=save HAL session; preflight=2/4 ready; digest=${initialHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const initialHandoffStatusbarSnapshotSummary = `blocked | save HAL session | 2/4 ready | ${initialHandoffReviewPacketVerificationBadge.badgeText} | 82/82/77/0`;
|
|
const initialHandoffStatusbarReceiptText = `phase=blocked; action=save HAL session; preflight=2/4 ready; badge=${initialHandoffReviewPacketVerificationBadge.badgeText}; digest=${initialHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const initialHandoffStatusbarReceiptVerificationSummary = "blocked | verified | receipt=match digest=match badge=match";
|
|
const initialHandoffStatusbarReceiptBadgeText = `blocked-receipt-verified 3/3 ${initialHandoffReviewPacket.digest}`;
|
|
const initialHandoffExternalShellBadgeText = `blocked-shell-verified; action=save HAL session; preflight=2/4 ready; packet=${initialHandoffReviewPacketVerificationBadge.badgeText}; receipt=${initialHandoffStatusbarReceiptBadgeText}; baseline=82/82/77/0; digest=${initialHandoffReviewPacket.digest}`;
|
|
const initialHandoffExternalShellBadgeExportText = JSON.stringify({
|
|
phase: "blocked",
|
|
action: "save HAL session",
|
|
preflight: "2/4 ready",
|
|
shellBadgeState: "blocked-shell-verified",
|
|
shellBadgeText: initialHandoffExternalShellBadgeText,
|
|
packetBadgeText: initialHandoffReviewPacketVerificationBadge.badgeText,
|
|
receiptBadgeText: initialHandoffStatusbarReceiptBadgeText,
|
|
baseline: "82/82/77/0",
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
const initialHandoffExternalShellBadgeSummary = `blocked | ${initialHandoffReviewPacket.digest} | blocked-shell-verified`;
|
|
const initialHandoffExternalShellVerificationText =
|
|
"blocked-copy-export-verified; copy=match; export=match; summary=match; digest=match";
|
|
assertExternalShellCopyExportVerification(
|
|
doc,
|
|
initialHandoffExternalShellBadgeCopyExportVerification,
|
|
"blocked",
|
|
initialHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellBundle(doc, initialHandoffExternalShellBundle, {
|
|
phase: "blocked",
|
|
action: "save HAL session",
|
|
preflight: "2/4 ready",
|
|
shellBadgeText: initialHandoffExternalShellBadgeText,
|
|
copyText: initialHandoffExternalShellBadgeText,
|
|
exportText: initialHandoffExternalShellBadgeExportText,
|
|
verificationText: initialHandoffExternalShellVerificationText,
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellBundleBadge(
|
|
doc,
|
|
initialHandoffExternalShellBundleBadge,
|
|
"blocked",
|
|
initialHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceipt(doc, initialHandoffExternalShellReceipt, {
|
|
phase: "blocked",
|
|
action: "save HAL session",
|
|
preflight: "2/4 ready",
|
|
bundleBadgeText: `blocked-bundle-verified 1/1 ${initialHandoffReviewPacket.digest}`,
|
|
bundleState: "blocked-external-shell-bundle-verified",
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerification(doc, initialHandoffExternalShellReceiptVerification, {
|
|
phase: "blocked",
|
|
receiptText: initialHandoffExternalShellReceipt.receiptText,
|
|
receiptState: "blocked-external-shell-receipt-verified",
|
|
bundleBadgeText: `blocked-bundle-verified 1/1 ${initialHandoffReviewPacket.digest}`,
|
|
bundleBadgeState: "blocked-bundle-verified",
|
|
bundleState: "blocked-external-shell-bundle-verified",
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerificationBadge(
|
|
doc,
|
|
initialHandoffExternalShellReceiptVerificationBadge,
|
|
"blocked",
|
|
initialHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditSnapshot(doc, initialHandoffExternalShellReceiptAuditSnapshot, {
|
|
phase: "blocked",
|
|
receiptText: initialHandoffExternalShellReceipt.receiptText,
|
|
verificationText: initialHandoffExternalShellReceiptVerification.verificationText,
|
|
badgeText: initialHandoffExternalShellReceiptVerificationBadge.badgeText,
|
|
badgeState: "blocked-external-shell-receipt-verification-verified",
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExport(doc, initialHandoffExternalShellReceiptAuditCopyExport, {
|
|
phase: "blocked",
|
|
auditState: "blocked-external-shell-receipt-audit-verified",
|
|
auditText: initialHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExportVerification(
|
|
doc,
|
|
initialHandoffExternalShellReceiptAuditCopyExportVerification,
|
|
"blocked",
|
|
initialHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditCopyExportVerificationBadge(
|
|
doc,
|
|
initialHandoffExternalShellReceiptAuditCopyExportVerificationBadge,
|
|
"blocked",
|
|
initialHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditBundle(doc, initialHandoffExternalShellReceiptAuditBundle, {
|
|
phase: "blocked",
|
|
auditText: initialHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
copyText: initialHandoffExternalShellReceiptAuditCopyExport.copyText,
|
|
exportText: initialHandoffExternalShellReceiptAuditCopyExport.exportText,
|
|
verificationText: initialHandoffExternalShellReceiptAuditCopyExportVerification.verificationText,
|
|
badgeText: initialHandoffExternalShellReceiptAuditCopyExportVerificationBadge.badgeText,
|
|
badgeState: "blocked-receipt-audit-copy-export-verification-verified",
|
|
digest: initialHandoffReviewPacket.digest,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDom(doc, {
|
|
phase: "blocked",
|
|
action: "save HAL session",
|
|
preflight: "2/4 ready",
|
|
reviewNoteText: initialHandoffReviewNote.noteText,
|
|
reviewPacket: initialHandoffReviewPacket,
|
|
reviewPacketCopyExport: initialHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: initialHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: initialHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: initialHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: initialHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: initialHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: initialHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: initialHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: initialHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: initialHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: initialHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDataset(doc, {
|
|
phase: "blocked",
|
|
action: "save HAL session",
|
|
preflight: "2/4 ready",
|
|
reviewNoteText: initialHandoffReviewNote.noteText,
|
|
reviewPacket: initialHandoffReviewPacket,
|
|
reviewPacketCopyExport: initialHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: initialHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: initialHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: initialHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: initialHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: initialHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: initialHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: initialHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: initialHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: initialHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: initialHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffSummaryCompatibility(doc, initialEvidenceSessionHandoff, api.getRunSummary?.(), {
|
|
phase: "blocked",
|
|
ready: false,
|
|
detail: "not-saved/not-release-ready; diagnostics blocked; baseline 82/82/77/0",
|
|
evidence: "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked",
|
|
halSession: "not-saved/not-release-ready",
|
|
savedDiagnostics: "blocked",
|
|
machineSession: "unchecked/not-loaded",
|
|
baseline: HANDOFF_BASELINE,
|
|
});
|
|
assertLegacyHandoffOperatorSnapshotCompatibility(doc, initialHandoffSnapshot, {
|
|
phase: "blocked",
|
|
ready: false,
|
|
detail: "not-saved/not-release-ready; diagnostics blocked; baseline 82/82/77/0",
|
|
baseline: HANDOFF_BASELINE,
|
|
});
|
|
assertLegacyHandoffActionPlanCompatibility(doc, initialHandoffActionPlan, initialHandoffCompactStatus, {
|
|
phase: "blocked",
|
|
ready: false,
|
|
action: "save HAL session",
|
|
baseline: HANDOFF_BASELINE,
|
|
detail: "not-saved/not-release-ready; diagnostics blocked; baseline 82/82/77/0",
|
|
historyLatest: "-",
|
|
});
|
|
assertLegacyHandoffPreflightAndHistoryCompatibility(
|
|
doc,
|
|
initialHandoffPreflight,
|
|
initialHandoffReviewNote,
|
|
api.getStatusHistory(),
|
|
{
|
|
phase: "blocked",
|
|
ready: false,
|
|
readyCount: 2,
|
|
totalCount: 4,
|
|
action: "save HAL session",
|
|
detail: "not-saved/not-release-ready; diagnostics blocked; baseline 82/82/77/0",
|
|
halSessionValue: "not-saved/not-release-ready",
|
|
diagnosticsValue: "blocked",
|
|
baseline: HANDOFF_BASELINE,
|
|
historyLatest: "-",
|
|
},
|
|
);
|
|
if (
|
|
diagnosticsArtifact.apiName !== "real-browser-simulation-diagnostics-artifact" ||
|
|
diagnosticsArtifact.statusHistory.length === 0 ||
|
|
diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" ||
|
|
diagnosticsArtifact.virtualHalSimulationReplacement?.ready !== true ||
|
|
diagnosticsArtifact.virtualHalSimulationReplacement?.sourceCompliance?.webSimulationSatisfied !== true ||
|
|
diagnosticsArtifact.virtualHalSourceCompliance?.complete !== true ||
|
|
diagnosticsArtifact.virtualHalSourceCompliance?.webSimulationSatisfied !== true ||
|
|
!diagnosticsArtifact.virtualHalSourceCompliance?.sourceFiles?.includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128") ||
|
|
!diagnosticsArtifact.virtualHalSourceCompliance?.sourceFiles?.includes("linuxcnc/src/hal/utils/halcmd_commands.cc") ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessBoundary?.complete !== true ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessBoundary?.boundaryClass !== "L4-USER-M-PROCESS" ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessState?.activeUserMCode !== "M129" ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessState?.activeMode !== "turn" ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessState?.executionEnabled !== false ||
|
|
diagnosticsArtifact.virtualHalMillturnUserMProcessState?.promotionAllowed !== false ||
|
|
diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.complete !== true ||
|
|
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") ||
|
|
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini") ||
|
|
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_z_compensation/qtdragon_hd_z_compensation.ini") ||
|
|
diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.complete !== true ||
|
|
diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.complete !== true ||
|
|
diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.candidateCount !== 11 ||
|
|
diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.readyCandidateCount !== 11 ||
|
|
diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.promotionAllowed !== false ||
|
|
!diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.rows?.some((row) =>
|
|
row.id === "trt-boat-xyzac" &&
|
|
row.currentNodeInventoryStatus === "PASS" &&
|
|
row.currentMatrixBrowserStatus === "REP" &&
|
|
row.targetBrowserEvidence === "browser-diagnostics-expansion" &&
|
|
row.promotionAllowed === false &&
|
|
row.sourceFiles?.includes("linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzac.ngc")
|
|
) ||
|
|
!diagnosticsArtifact.virtualHalSimConfigEvidenceExpansion?.rows?.some((row) =>
|
|
row.id === "trt-xyzac-switchkins" &&
|
|
row.currentNodeInventoryStatus === "PASS" &&
|
|
row.currentMatrixBrowserStatus === "REP" &&
|
|
row.targetBrowserEvidence === "browser-diagnostics-expansion" &&
|
|
row.promotionAllowed === false &&
|
|
row.sourceFiles?.includes("linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc")
|
|
) ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.ready !== true ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.preferredCandidateId !== "qtdragon-multi-joint-on-abort" ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 ||
|
|
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=82 passed=82 skipped=77 unexpected_fail=0" ||
|
|
promotionCandidateSummary.ready !== true ||
|
|
promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" ||
|
|
doc.body.dataset.promotionCandidateReady !== "true" ||
|
|
doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" ||
|
|
doc.body.dataset.evidenceExpansionReady !== "true" ||
|
|
doc.body.dataset.evidenceExpansionCount !== "11" ||
|
|
doc.body.dataset.evidenceExpansionReadyCount !== "11" ||
|
|
api.getVirtualHalEvidenceExpansionSummary?.().ready !== true ||
|
|
api.getVirtualHalEvidenceExpansionSummary?.().candidateIds?.join(", ") !== "woodpecker-on-abort, puma-cube, melfa-example, trt-boat-xyzac, trt-xyzac-switchkins, trt-boat-xyzbc, trt-impeller-7bl-xyzac, trt-xyzac-switchkins-test-1, trt-xyzac-switchkins-test-2, trt-xyzac-switchkins-test-3, trt-xyzbc-switchkins" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="candidate-count"]')?.textContent !== "11/11 ready" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="candidate-list"]')?.textContent !== "woodpecker-on-abort, puma-cube, melfa-example, trt-boat-xyzac, trt-xyzac-switchkins, trt-boat-xyzbc, trt-impeller-7bl-xyzac, trt-xyzac-switchkins-test-1, trt-xyzac-switchkins-test-2, trt-xyzac-switchkins-test-3, trt-xyzbc-switchkins" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="family-count"]')?.textContent !== "3" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="family-summary"]')?.textContent !== "woodpecker: 1/1 ready; vismach-remap-sims: 2/2 ready; five-axis-trt: 8/8 ready" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="family-source-count-list"]')?.textContent !== "woodpecker:3, vismach-remap-sims:6, five-axis-trt:12" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" ||
|
|
doc.querySelector('[data-evidence-expansion-summary-value="inventory-baseline"]')?.textContent !== "unchanged" ||
|
|
doc.body.dataset.evidenceExpansionFamilyDrilldownReady !== "true" ||
|
|
doc.body.dataset.evidenceExpansionFamilyDrilldownCount !== "3" ||
|
|
api.getVirtualHalEvidenceExpansionFamilyDrilldown?.().length !== 3 ||
|
|
doc.querySelector('[data-evidence-expansion-family-row="five-axis-trt"]')?.dataset.evidenceExpansionFamilyReady !== "true" ||
|
|
doc.querySelector('[data-evidence-expansion-family-row="five-axis-trt"]')?.dataset.evidenceExpansionFamilySourceKindSummary !== "ini=1 gcode=8 remap-subroutine=3" ||
|
|
doc.querySelector('[data-evidence-expansion-family-value="five-axis-trt"]')?.textContent !== "8/8 ready; ini=1 gcode=8 remap-subroutine=3" ||
|
|
doc.body.dataset.evidenceStatusStripReady !== "true" ||
|
|
doc.body.dataset.evidenceStatusStripCandidates !== "11/11" ||
|
|
doc.body.dataset.evidenceStatusStripFamilies !== "3/3" ||
|
|
doc.body.dataset.evidenceStatusStripBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceStatusStripPromotion !== "locked" ||
|
|
api.getEvidenceStatusStripSummary?.().ready !== true ||
|
|
doc.querySelector('[data-evidence-status-strip-value="candidates"]')?.textContent !== "11/11" ||
|
|
doc.querySelector('[data-evidence-status-strip-value="families"]')?.textContent !== "3/3" ||
|
|
doc.querySelector('[data-evidence-status-strip-value="baseline"]')?.textContent !== "82/82/77/0" ||
|
|
doc.querySelector('[data-evidence-status-strip-value="promotion"]')?.textContent !== "locked" ||
|
|
doc.body.dataset.evidenceStatusCopyExportReady !== "true" ||
|
|
doc.body.dataset.evidenceStatusCopyExportText !== "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked" ||
|
|
api.getEvidenceStatusCopyExportViewModel?.().copyText !== "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked" ||
|
|
api.getEvidenceStatusCopyExportViewModel?.().fields?.baseline !== "82/82/77/0" ||
|
|
doc.querySelector('[data-evidence-status-copy-export-value="copy-text"]')?.textContent !== "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked" ||
|
|
initialHandoffReviewNote.apiName !== "real-browser-simulation-evidence-session-handoff-review-note" ||
|
|
initialHandoffReviewNote.phase !== "blocked" ||
|
|
initialHandoffReviewNote.action !== "save HAL session" ||
|
|
initialHandoffReviewNote.preflight !== "2/4 ready" ||
|
|
initialHandoffReviewNote.baseline !== "82/82/77/0" ||
|
|
initialHandoffReviewNote.latestHistory !== "-" ||
|
|
initialHandoffReviewNote.noteText !== "phase=blocked; action=save HAL session; preflight=2/4 ready; baseline=82/82/77/0; latest=-" ||
|
|
initialHandoffReviewPacket.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet" ||
|
|
initialHandoffReviewPacket.phase !== "blocked" ||
|
|
initialHandoffReviewPacket.action !== "save HAL session" ||
|
|
initialHandoffReviewPacket.preflight !== "2/4 ready" ||
|
|
initialHandoffReviewPacket.baseline !== "82/82/77/0" ||
|
|
initialHandoffReviewPacket.latestHistory !== "-" ||
|
|
initialHandoffReviewPacket.packet?.reviewNote !== initialHandoffReviewNote.noteText ||
|
|
initialHandoffReviewPacket.packet?.preflight?.rows?.length !== 4 ||
|
|
initialHandoffReviewPacket.packet?.compactStatus?.historyLatest !== "-" ||
|
|
JSON.parse(initialHandoffReviewPacket.packetJson).preflight.summary !== "2/4 ready" ||
|
|
!/^h[0-9a-f]{8}$/.test(initialHandoffReviewPacket.digest) ||
|
|
initialHandoffReviewPacketCopyExport.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-copy-export" ||
|
|
initialHandoffReviewPacketCopyExport.phase !== "blocked" ||
|
|
initialHandoffReviewPacketCopyExport.ready !== false ||
|
|
initialHandoffReviewPacketCopyExport.action !== "save HAL session" ||
|
|
initialHandoffReviewPacketCopyExport.preflight !== "2/4 ready" ||
|
|
initialHandoffReviewPacketCopyExport.baseline !== "82/82/77/0" ||
|
|
initialHandoffReviewPacketCopyExport.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffReviewPacketCopyExport.packetJson !== initialHandoffReviewPacket.packetJson ||
|
|
initialHandoffReviewPacketCopyExport.reviewNote !== initialHandoffReviewNote.noteText ||
|
|
initialHandoffReviewPacketCopyExport.copyText !== initialHandoffReviewPacketCopyText ||
|
|
initialHandoffReviewPacketCopyExport.exportText !== initialHandoffReviewPacket.packetJson ||
|
|
initialHandoffReviewPacketCopyExport.summaryText !== `blocked | ${initialHandoffReviewPacket.digest} | 2/4 ready` ||
|
|
initialHandoffReviewPacketVerification.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-verification" ||
|
|
initialHandoffReviewPacketVerification.phase !== "blocked" ||
|
|
initialHandoffReviewPacketVerification.ready !== true ||
|
|
initialHandoffReviewPacketVerification.statusText !== "verified" ||
|
|
initialHandoffReviewPacketVerification.digestMatches !== true ||
|
|
initialHandoffReviewPacketVerification.jsonParseReady !== true ||
|
|
initialHandoffReviewPacketVerification.reviewNoteMatches !== true ||
|
|
initialHandoffReviewPacketVerification.preflightMatches !== true ||
|
|
initialHandoffReviewPacketVerification.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffReviewPacketVerification.preflight !== "2/4 ready" ||
|
|
initialHandoffReviewPacketVerification.reviewNote !== initialHandoffReviewNote.noteText ||
|
|
initialHandoffReviewPacketVerification.packetJson !== initialHandoffReviewPacket.packetJson ||
|
|
initialHandoffReviewPacketVerification.rows?.length !== 4 ||
|
|
!initialHandoffReviewPacketVerification.rows?.every((row) => row.ready === true) ||
|
|
initialHandoffReviewPacketVerification.summaryText !== `blocked | verified | digest=match json=ready note=match preflight=match` ||
|
|
initialHandoffReviewPacketVerificationBadge.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-verification-badge" ||
|
|
initialHandoffReviewPacketVerificationBadge.phase !== "blocked" ||
|
|
initialHandoffReviewPacketVerificationBadge.statusText !== "verified" ||
|
|
initialHandoffReviewPacketVerificationBadge.ready !== true ||
|
|
initialHandoffReviewPacketVerificationBadge.readyCount !== 4 ||
|
|
initialHandoffReviewPacketVerificationBadge.totalCount !== 4 ||
|
|
initialHandoffReviewPacketVerificationBadge.badgeState !== "blocked-verified" ||
|
|
initialHandoffReviewPacketVerificationBadge.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffReviewPacketVerificationBadge.badgeText !== `blocked-verified 4/4 ${initialHandoffReviewPacket.digest}` ||
|
|
initialHandoffReviewPacketVerificationBadge.statusbarBadgeText !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
initialHandoffReviewPacketVerificationBadge.statusbarBadgeState !== "blocked-verified" ||
|
|
initialHandoffStatusbarSnapshot.apiName !== "real-browser-simulation-evidence-session-handoff-statusbar-snapshot" ||
|
|
initialHandoffStatusbarSnapshot.phase !== "blocked" ||
|
|
initialHandoffStatusbarSnapshot.action !== "save HAL session" ||
|
|
initialHandoffStatusbarSnapshot.preflight !== "2/4 ready" ||
|
|
initialHandoffStatusbarSnapshot.packetBadgeText !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
initialHandoffStatusbarSnapshot.packetBadgeState !== "blocked-verified" ||
|
|
initialHandoffStatusbarSnapshot.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffStatusbarSnapshot.baseline !== "82/82/77/0" ||
|
|
initialHandoffStatusbarSnapshot.ready !== false ||
|
|
initialHandoffStatusbarSnapshot.summaryText !== initialHandoffStatusbarSnapshotSummary ||
|
|
initialHandoffStatusbarReceipt.apiName !== "real-browser-simulation-evidence-session-handoff-statusbar-receipt" ||
|
|
initialHandoffStatusbarReceipt.phase !== "blocked" ||
|
|
initialHandoffStatusbarReceipt.action !== "save HAL session" ||
|
|
initialHandoffStatusbarReceipt.preflight !== "2/4 ready" ||
|
|
initialHandoffStatusbarReceipt.badge !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
initialHandoffStatusbarReceipt.packetBadgeState !== "blocked-verified" ||
|
|
initialHandoffStatusbarReceipt.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffStatusbarReceipt.baseline !== "82/82/77/0" ||
|
|
initialHandoffStatusbarReceipt.ready !== false ||
|
|
initialHandoffStatusbarReceipt.receiptText !== initialHandoffStatusbarReceiptText ||
|
|
initialHandoffStatusbarReceipt.copyText !== initialHandoffStatusbarReceiptText ||
|
|
initialHandoffStatusbarReceipt.summaryText !== initialHandoffStatusbarReceiptText ||
|
|
initialHandoffStatusbarReceiptVerification.apiName !== "real-browser-simulation-evidence-session-handoff-statusbar-receipt-verification" ||
|
|
initialHandoffStatusbarReceiptVerification.phase !== "blocked" ||
|
|
initialHandoffStatusbarReceiptVerification.ready !== true ||
|
|
initialHandoffStatusbarReceiptVerification.statusText !== "verified" ||
|
|
initialHandoffStatusbarReceiptVerification.receiptMatches !== true ||
|
|
initialHandoffStatusbarReceiptVerification.digestMatches !== true ||
|
|
initialHandoffStatusbarReceiptVerification.badgeMatches !== true ||
|
|
initialHandoffStatusbarReceiptVerification.summaryText !== initialHandoffStatusbarReceiptVerificationSummary ||
|
|
initialHandoffStatusbarReceiptVerification.receiptText !== initialHandoffStatusbarReceiptText ||
|
|
initialHandoffStatusbarReceiptVerification.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffStatusbarReceiptVerification.badge !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
initialHandoffStatusbarReceiptVerification.baseline !== "82/82/77/0" ||
|
|
initialHandoffStatusbarReceiptBadge.apiName !== "real-browser-simulation-evidence-session-handoff-statusbar-receipt-badge" ||
|
|
initialHandoffStatusbarReceiptBadge.phase !== "blocked" ||
|
|
initialHandoffStatusbarReceiptBadge.statusText !== "verified" ||
|
|
initialHandoffStatusbarReceiptBadge.ready !== true ||
|
|
initialHandoffStatusbarReceiptBadge.readyCount !== 3 ||
|
|
initialHandoffStatusbarReceiptBadge.totalCount !== 3 ||
|
|
initialHandoffStatusbarReceiptBadge.receiptBadgeState !== "blocked-receipt-verified" ||
|
|
initialHandoffStatusbarReceiptBadge.receiptBadgeText !== initialHandoffStatusbarReceiptBadgeText ||
|
|
initialHandoffStatusbarReceiptBadge.statusbarReceiptBadgeText !== initialHandoffStatusbarReceiptBadgeText ||
|
|
initialHandoffStatusbarReceiptBadge.statusbarReceiptBadgeState !== "blocked-receipt-verified" ||
|
|
initialHandoffStatusbarReceiptBadge.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffExternalShellBadgeSnapshot.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-badge-snapshot" ||
|
|
initialHandoffExternalShellBadgeSnapshot.phase !== "blocked" ||
|
|
initialHandoffExternalShellBadgeSnapshot.action !== "save HAL session" ||
|
|
initialHandoffExternalShellBadgeSnapshot.preflight !== "2/4 ready" ||
|
|
initialHandoffExternalShellBadgeSnapshot.packetBadgeText !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
initialHandoffExternalShellBadgeSnapshot.receiptBadgeText !== initialHandoffStatusbarReceiptBadgeText ||
|
|
initialHandoffExternalShellBadgeSnapshot.baseline !== "82/82/77/0" ||
|
|
initialHandoffExternalShellBadgeSnapshot.digest !== initialHandoffReviewPacket.digest ||
|
|
initialHandoffExternalShellBadgeSnapshot.ready !== true ||
|
|
initialHandoffExternalShellBadgeSnapshot.shellBadgeState !== "blocked-shell-verified" ||
|
|
initialHandoffExternalShellBadgeSnapshot.shellBadgeText !== initialHandoffExternalShellBadgeText ||
|
|
initialHandoffExternalShellBadgeSnapshot.statusbarShellBadgeText !== initialHandoffExternalShellBadgeText ||
|
|
initialHandoffExternalShellBadgeCopyExport.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-badge-copy-export" ||
|
|
initialHandoffExternalShellBadgeCopyExport.phase !== "blocked" ||
|
|
initialHandoffExternalShellBadgeCopyExport.action !== "save HAL session" ||
|
|
initialHandoffExternalShellBadgeCopyExport.preflight !== "2/4 ready" ||
|
|
initialHandoffExternalShellBadgeCopyExport.ready !== true ||
|
|
initialHandoffExternalShellBadgeCopyExport.shellBadgeState !== "blocked-shell-verified" ||
|
|
initialHandoffExternalShellBadgeCopyExport.shellBadgeText !== initialHandoffExternalShellBadgeText ||
|
|
initialHandoffExternalShellBadgeCopyExport.copyText !== initialHandoffExternalShellBadgeText ||
|
|
initialHandoffExternalShellBadgeCopyExport.exportText !== initialHandoffExternalShellBadgeExportText ||
|
|
initialHandoffExternalShellBadgeCopyExport.summaryText !== initialHandoffExternalShellBadgeSummary ||
|
|
initialHandoffExternalShellBadgeCopyExport.digest !== initialHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketPhase !== "blocked" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketAction !== "save HAL session" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketPreflight !== "2/4 ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketLatest !== "-" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPhase !== "blocked" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptAction !== "save HAL session" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPreflight !== "2/4 ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadge !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPacketBadgeState !== "blocked-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptDigest !== initialHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptReady !== "false" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptText !== initialHandoffStatusbarReceiptText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationPhase !== "blocked" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReceiptMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigestMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadgeMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigest !== initialHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadge !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationSummary !== initialHandoffStatusbarReceiptVerificationSummary ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgePhase !== "blocked" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeState !== "blocked-receipt-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeText !== initialHandoffStatusbarReceiptBadgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReadyCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeTotalCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeDigest !== initialHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPhase !== "blocked" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotAction !== "save HAL session" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPreflight !== "2/4 ready" ||
|
|
doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" ||
|
|
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=2" ||
|
|
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "82/82 pass; 77 skip" ||
|
|
doc.querySelector('[data-promotion-candidate-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" ||
|
|
doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" ||
|
|
!diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.familyRows?.some((row) =>
|
|
row.id === "qtdragon-on-abort" &&
|
|
row.candidateCount === 6 &&
|
|
row.completeCount === 6 &&
|
|
row.explicitBrowserDiagnosticsCount === 6 &&
|
|
row.explicitBrowserDiagnosticsReady === true &&
|
|
row.complete === true
|
|
) ||
|
|
!expectedPromotionCandidates.every(([id, ...sourceFiles]) =>
|
|
diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.rows?.some((row) =>
|
|
row.id === id &&
|
|
row.complete === true &&
|
|
row.currentNodeInventoryStatus === "PASS" &&
|
|
row.currentMatrixBrowserStatus === "explicit-browser-diagnostics" &&
|
|
row.targetBrowserEvidence === "explicit-browser-diagnostics" &&
|
|
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
|
|
)
|
|
) ||
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.complete !== true ||
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.blockedFixturePromotionViolations?.length !== 0 ||
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.blockedBoundaryEvidenceViolations?.length !== 0 ||
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.auditPromotionViolations?.length !== 0 ||
|
|
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, declarationSourceFile, declarationValue, declarationLine, ...sourceFiles]) =>
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.rows?.some((row) =>
|
|
row.id === id &&
|
|
row.complete === true &&
|
|
row.nonMainFixture === true &&
|
|
row.targetBrowserEvidence === "non-main-fixture-diagnostics" &&
|
|
row.fixturePath === fixturePath &&
|
|
row.declaredOnly === declaredOnly &&
|
|
row.requiresDeclarationEvidence === true &&
|
|
row.declarationEvidenceReady === true &&
|
|
row.declarationEvidence?.sourceFile === declarationSourceFile &&
|
|
row.declarationEvidence?.key === "NGCGUI_SUBFILE" &&
|
|
row.declarationEvidence?.value === declarationValue &&
|
|
row.declarationEvidence?.line === declarationLine &&
|
|
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
|
|
)
|
|
) ||
|
|
!expectedBlockedMacroLoadFixtures.every(([id, fixturePath, blockedKind]) =>
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.blockedRows?.some((row) =>
|
|
row.id === id &&
|
|
row.fixturePath === fixturePath &&
|
|
row.blockedKind === blockedKind &&
|
|
row.excludedFromPositiveFixtures === true &&
|
|
row.boundaryEvidenceReady === true &&
|
|
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
|
|
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
|
|
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
|
|
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
|
|
row.complete === true
|
|
)
|
|
) ||
|
|
!expectedMacroLoadAuditCandidates.every(([id, fixturePath, auditStatus]) =>
|
|
diagnosticsArtifact.virtualHalSimConfigMacroLoadFixtures?.auditRows?.some((row) =>
|
|
row.id === id &&
|
|
row.fixturePath === fixturePath &&
|
|
row.auditStatus === auditStatus &&
|
|
row.promotionAllowed === false &&
|
|
row.excludedFromPositiveFixtures === true &&
|
|
row.boundaryEvidenceReady === true &&
|
|
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
|
|
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
|
|
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
|
|
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
|
|
row.complete === true
|
|
)
|
|
) ||
|
|
diagnosticsArtifact.virtualHalCommandScriptFixtures?.complete !== true ||
|
|
!diagnosticsArtifact.virtualHalCommandScriptFixtures?.coveredActions?.includes("loadusr") ||
|
|
diagnosticsArtifact.virtualHalMotionControllerMatrix?.complete !== true ||
|
|
!diagnosticsArtifact.virtualHalMotionControllerMatrix?.sourceFiles?.includes("linuxcnc/src/emc/motion/motion.c") ||
|
|
diagnosticsArtifact.limitsHome.source !== "browser-virtual-hal" ||
|
|
diagnosticsArtifact.preview.renderer !== "threejs" ||
|
|
diagnosticsArtifact.toolTable.state !== "not-loaded"
|
|
) {
|
|
throw new Error(`simulation diagnostics artifact initial state drift: ${JSON.stringify(diagnosticsArtifact)}`);
|
|
}
|
|
const droState = api.getDroState();
|
|
if (droState?.source !== "linuxcnc-canonical-motion") {
|
|
throw new Error(`simulation DRO source drift: ${JSON.stringify(droState)}`);
|
|
}
|
|
for (const field of ["distanceToGo", "workOffsetG54", "g92Offset", "toolLengthOffset"]) {
|
|
if (droState[field].x !== "0.000") {
|
|
throw new Error(`simulation DRO ${field} should use zeroed AXIS default when unavailable`);
|
|
}
|
|
}
|
|
if (droState.velocity !== "0.000" || doc.querySelector("[data-dro-velocity]")?.textContent !== "0.000") {
|
|
throw new Error("simulation DRO velocity should use zeroed AXIS default when unavailable");
|
|
}
|
|
const previewExtents = doc.querySelector("[data-preview-extents]");
|
|
const previewOrigin = doc.querySelector("[data-preview-origin]");
|
|
const extentsLabel = doc.querySelector("[data-preview-extents-label]")?.textContent ?? "";
|
|
if (
|
|
!previewExtents ||
|
|
Number(previewExtents.getAttribute("width")) <= 0 ||
|
|
Number(previewExtents.getAttribute("height")) <= 0 ||
|
|
previewOrigin?.getAttribute("cx") !== "0" ||
|
|
previewOrigin?.getAttribute("cy") !== "0" ||
|
|
!extentsLabel.includes("X ") ||
|
|
!extentsLabel.includes("Y ")
|
|
) {
|
|
throw new Error("simulation preview missing AXIS extents/origin overlay");
|
|
}
|
|
const fitViewBox = doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox");
|
|
api.zoomPreview(1.5);
|
|
const zoomedViewBox = doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox");
|
|
if (!fitViewBox || zoomedViewBox === fitViewBox || !doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points")) {
|
|
throw new Error("simulation preview zoom did not change a populated toolpath viewBox");
|
|
}
|
|
api.resetPreview();
|
|
const resetViewBox = doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox");
|
|
if (resetViewBox !== fitViewBox || doc.querySelector("[data-preview-zoom]")?.textContent !== "100%") {
|
|
throw new Error(`simulation preview reset did not restore fit viewBox: ${resetViewBox} !== ${fitViewBox}`);
|
|
}
|
|
api.panPreview(0.2, 0);
|
|
const pannedViewBox = doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox");
|
|
if (pannedViewBox === fitViewBox || !doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points")) {
|
|
throw new Error("simulation preview pan did not move a populated toolpath viewBox");
|
|
}
|
|
const pannedPreview = api.getPreviewState();
|
|
if (
|
|
pannedPreview.renderer !== "threejs" ||
|
|
pannedPreview.previewVersion !== 3 ||
|
|
pannedPreview.three.revision !== "183" ||
|
|
pannedPreview.three.axisLines !== 3 ||
|
|
pannedPreview.three.workPlanes !== 1 ||
|
|
pannedPreview.three.envelopeEdges !== 12 ||
|
|
pannedPreview.three.toolMarkerObjects < 3 ||
|
|
pannedPreview.three.scaleBarObjects !== 4 ||
|
|
pannedPreview.layers.tool !== true ||
|
|
pannedPreview.layers.envelope !== true ||
|
|
pannedPreview.layers.scale !== true ||
|
|
pannedPreview.three.visibleLayers?.tool !== true ||
|
|
pannedPreview.three.pathPoints !== state.motion.length ||
|
|
!pannedPreview.three.machineEnvelope ||
|
|
!pannedPreview.three.toolhead ||
|
|
!pannedPreview.three.toolGeometry ||
|
|
!pannedPreview.three.scaleBar ||
|
|
!pannedPreview.hud?.tool?.includes("X") ||
|
|
!pannedPreview.hud?.envelope?.includes("Z") ||
|
|
!pannedPreview.hud?.renderer?.includes("Three.js r183") ||
|
|
pannedPreview.pan.x <= 0 ||
|
|
!doc.querySelector("[data-preview-pan]")?.textContent.includes("x")
|
|
) {
|
|
throw new Error(`simulation preview pan state did not update API/DOM: ${JSON.stringify(pannedPreview)}`);
|
|
}
|
|
const hudText = doc.querySelector('[data-axis-shell="preview-hud"]')?.textContent ?? "";
|
|
if (!hudText.includes("Envelope") || !hudText.includes("Renderer") || !hudText.includes("Three.js r183")) {
|
|
throw new Error(`simulation preview HUD missing AXIS renderer/envelope state: ${hudText}`);
|
|
}
|
|
const initialLayers = api.getPreviewLayerState();
|
|
if (!initialLayers.traverse || !initialLayers.feed || !initialLayers.arc || !initialLayers.tool || !initialLayers.envelope || !initialLayers.scale) {
|
|
throw new Error(`simulation preview layers should default to visible: ${JSON.stringify(initialLayers)}`);
|
|
}
|
|
const hiddenToolPreview = api.setPreviewLayer("tool", false);
|
|
if (
|
|
hiddenToolPreview.layers.tool !== false ||
|
|
hiddenToolPreview.three.visibleLayers.tool !== false ||
|
|
hiddenToolPreview.three.toolMarkerObjects !== 0 ||
|
|
hiddenToolPreview.three.toolGeometry?.objectCount !== 0 ||
|
|
doc.querySelector('[data-preview-layer="tool"]')?.checked !== false
|
|
) {
|
|
throw new Error(`simulation preview tool layer did not hide tool geometry: ${JSON.stringify(hiddenToolPreview.three)}`);
|
|
}
|
|
const hiddenEnvelopePreview = api.setPreviewLayer("envelope", false);
|
|
if (
|
|
hiddenEnvelopePreview.layers.envelope !== false ||
|
|
hiddenEnvelopePreview.three.visibleLayers.envelope !== false ||
|
|
hiddenEnvelopePreview.three.envelopeEdges !== 0 ||
|
|
doc.querySelector('[data-preview-layer="envelope"]')?.checked !== false
|
|
) {
|
|
throw new Error(`simulation preview envelope layer did not hide envelope: ${JSON.stringify(hiddenEnvelopePreview.three)}`);
|
|
}
|
|
const hiddenScalePreview = api.setPreviewLayer("scale", false);
|
|
if (
|
|
hiddenScalePreview.layers.scale !== false ||
|
|
hiddenScalePreview.three.visibleLayers.scale !== false ||
|
|
hiddenScalePreview.three.scaleBarObjects !== 0 ||
|
|
hiddenScalePreview.three.scaleBar?.length !== 0 ||
|
|
doc.querySelector('[data-preview-layer="scale"]')?.checked !== false
|
|
) {
|
|
throw new Error(`simulation preview scale layer did not hide scale bar: ${JSON.stringify(hiddenScalePreview.three)}`);
|
|
}
|
|
api.setPreviewLayer("tool", true);
|
|
api.setPreviewLayer("envelope", true);
|
|
const restoredLayerPreview = api.setPreviewLayer("scale", true);
|
|
if (
|
|
restoredLayerPreview.layers.tool !== true ||
|
|
restoredLayerPreview.layers.envelope !== true ||
|
|
restoredLayerPreview.layers.scale !== true ||
|
|
restoredLayerPreview.three.toolMarkerObjects < 3 ||
|
|
restoredLayerPreview.three.envelopeEdges !== 12 ||
|
|
restoredLayerPreview.three.scaleBarObjects !== 4
|
|
) {
|
|
throw new Error(`simulation preview layers did not restore AXIS default view: ${JSON.stringify(restoredLayerPreview.three)}`);
|
|
}
|
|
const statusbarState = api.getAxisStatusbarState();
|
|
if (
|
|
statusbarState.apiName !== "real-browser-simulation-axis-statusbar-state" ||
|
|
!statusbarState.preview.includes("Top") ||
|
|
statusbarState.handoff !== "blocked" ||
|
|
statusbarState.handoffAction !== "save HAL session" ||
|
|
statusbarState.handoffActionDetail !== "2/4 ready" ||
|
|
statusbarState.handoffPacketBadgeText !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
statusbarState.handoffPacketBadgeState !== "blocked-verified" ||
|
|
statusbarState.handoffPacketBadgeDigest !== initialHandoffReviewPacket.digest ||
|
|
statusbarState.handoffDetail !== "not-saved/not-release-ready; diagnostics blocked; baseline 82/82/77/0" ||
|
|
doc.body.dataset.axisStatusbarHandoff !== "blocked" ||
|
|
doc.body.dataset.axisStatusbarHandoffAction !== "save HAL session" ||
|
|
doc.body.dataset.axisStatusbarHandoffActionDetail !== "2/4 ready" ||
|
|
doc.body.dataset.axisStatusbarHandoffPacketBadgeText !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.axisStatusbarHandoffPacketBadgeState !== "blocked-verified" ||
|
|
doc.body.dataset.axisStatusbarHandoffPacketBadgeDigest !== initialHandoffReviewPacket.digest ||
|
|
doc.querySelector('[data-statusbar="preview"]')?.textContent !== statusbarState.preview ||
|
|
doc.querySelector('[data-statusbar="handoff-packet-badge"]')?.textContent !== initialHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.axisStatusbarPreview !== statusbarState.preview
|
|
) {
|
|
throw new Error(`simulation AXIS statusbar state drift: ${JSON.stringify(statusbarState)}`);
|
|
}
|
|
const reloadedState = await api.reloadCurrentProgram();
|
|
if (
|
|
reloadedState.apiName !== "real-browser-simulation-state" ||
|
|
reloadedState.summary.ready !== true ||
|
|
!doc.querySelector("[data-canonical-output]")?.textContent.includes("canon_event=")
|
|
) {
|
|
throw new Error(`simulation reload current program did not execute through LinuxCNC WASM: ${JSON.stringify(reloadedState.summary)}`);
|
|
}
|
|
const isoPreview = api.setPreviewViewMode("iso");
|
|
if (
|
|
isoPreview.viewMode !== "iso" ||
|
|
doc.querySelector("[data-toolpath-three]")?.dataset.threeViewMode !== "iso" ||
|
|
doc.querySelector("[data-preview-view-label]")?.textContent !== "Iso"
|
|
) {
|
|
throw new Error(`simulation Three.js preview view mode did not switch to Iso: ${JSON.stringify(isoPreview)}`);
|
|
}
|
|
const sidePreview = api.setPreviewViewMode("side");
|
|
if (sidePreview.viewMode !== "side" || doc.querySelector("[data-preview-view-mode=\"side\"]")?.dataset.active !== "true") {
|
|
throw new Error(`simulation Three.js preview view mode did not switch to Side: ${JSON.stringify(sidePreview)}`);
|
|
}
|
|
api.setPreviewViewMode("top");
|
|
api.fitPreview();
|
|
const fitPreviewState = api.getPreviewState();
|
|
if (doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox") !== fitViewBox || fitPreviewState.pan.x !== 0 || fitPreviewState.pan.y !== 0) {
|
|
throw new Error("simulation preview fit did not clear pan offset");
|
|
}
|
|
const threeCanvas = doc.querySelector("[data-toolpath-three]");
|
|
const beforeWheelZoom = api.getPreviewState().zoom;
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.WheelEvent("wheel", { deltaY: -100, bubbles: true, cancelable: true }));
|
|
if (api.getPreviewState().zoom <= beforeWheelZoom) {
|
|
throw new Error("simulation Three.js wheel zoom did not update preview state");
|
|
}
|
|
const dragStart = api.getPreviewState();
|
|
const rect = threeCanvas.getBoundingClientRect();
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.PointerEvent("pointerdown", {
|
|
pointerId: 7,
|
|
clientX: rect.left + rect.width / 2,
|
|
clientY: rect.top + rect.height / 2,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}));
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.PointerEvent("pointermove", {
|
|
pointerId: 7,
|
|
clientX: rect.left + rect.width / 2 + 40,
|
|
clientY: rect.top + rect.height / 2 + 20,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}));
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.PointerEvent("pointerup", {
|
|
pointerId: 7,
|
|
clientX: rect.left + rect.width / 2 + 40,
|
|
clientY: rect.top + rect.height / 2 + 20,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}));
|
|
const dragEnd = api.getPreviewState();
|
|
if (dragEnd.pan.x === dragStart.pan.x && dragEnd.pan.y === dragStart.pan.y) {
|
|
throw new Error("simulation Three.js drag pan did not update preview state");
|
|
}
|
|
const cursorState = api.getPreviewCursorState();
|
|
if (
|
|
cursorState.apiName !== "real-browser-simulation-preview-cursor-state" ||
|
|
cursorState.active !== true ||
|
|
!Number.isFinite(cursorState.x) ||
|
|
!Number.isFinite(cursorState.y) ||
|
|
!cursorState.label.includes("X") ||
|
|
doc.querySelector("[data-preview-crosshair]")?.dataset.active !== "true" ||
|
|
!doc.querySelector("[data-preview-crosshair-label]")?.textContent.includes("X") ||
|
|
!doc.querySelector("[data-preview-hud=\"cursor\"]")?.textContent.includes("X")
|
|
) {
|
|
throw new Error(`simulation Three.js cursor/crosshair state did not update: ${JSON.stringify(cursorState)}`);
|
|
}
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.PointerEvent("pointerleave", {
|
|
pointerId: 7,
|
|
clientX: rect.left + rect.width / 2 + 40,
|
|
clientY: rect.top + rect.height / 2 + 20,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}));
|
|
if (api.getPreviewCursorState().active !== false || doc.querySelector("[data-preview-crosshair]")?.dataset.active !== "false") {
|
|
throw new Error("simulation Three.js cursor/crosshair state did not clear on pointer leave");
|
|
}
|
|
threeCanvas.dispatchEvent(new frame.contentWindow.MouseEvent("dblclick", { bubbles: true, cancelable: true }));
|
|
const doubleClickFit = api.getPreviewState();
|
|
if (doubleClickFit.pan.x !== 0 || doubleClickFit.pan.y !== 0 || doubleClickFit.zoom !== 1) {
|
|
throw new Error("simulation Three.js double-click fit did not reset preview state");
|
|
}
|
|
const initialEditor = api.getProgramText();
|
|
if (!initialEditor.text.includes("G1 X1 Y0 Z0 F100")) {
|
|
throw new Error("simulation editor did not receive initial built-in program text");
|
|
}
|
|
|
|
doc.querySelector('[data-axis-tab="mdi"]')?.click();
|
|
if (!doc.querySelector('[data-axis-panel="manual"]')?.hidden || doc.querySelector('[data-axis-panel="mdi"]')?.hidden) {
|
|
throw new Error("AXIS-style Manual/MDI tab switch failed");
|
|
}
|
|
doc.querySelector('[data-axis-tab="manual"]')?.click();
|
|
if (doc.querySelector('[data-axis-panel="manual"]')?.hidden || !doc.querySelector('[data-axis-panel="mdi"]')?.hidden) {
|
|
throw new Error("AXIS-style Manual tab restore failed");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F5", bubbles: true, cancelable: true }));
|
|
if (doc.querySelector('[data-axis-panel="mdi"]')?.hidden || doc.body.dataset.lastShortcut !== "F5 MDI") {
|
|
throw new Error("AXIS-style F5 shortcut did not switch to MDI");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F3", bubbles: true, cancelable: true }));
|
|
if (doc.querySelector('[data-axis-panel="manual"]')?.hidden || doc.body.dataset.lastShortcut !== "F3 Manual") {
|
|
throw new Error("AXIS-style F3 shortcut did not switch to Manual");
|
|
}
|
|
|
|
doc.querySelector('[data-axis-tab="dro"]')?.click();
|
|
if (!doc.querySelector('[data-axis-panel="preview"]')?.hidden || doc.querySelector('[data-axis-panel="dro"]')?.hidden) {
|
|
throw new Error("AXIS-style Preview/DRO tab switch failed");
|
|
}
|
|
if (doc.querySelector('[data-axis-shell="dro-readout"]')?.textContent.includes("undefined")) {
|
|
throw new Error("AXIS-style DRO should not render undefined fields");
|
|
}
|
|
doc.querySelector('[data-axis-tab="preview"]')?.click();
|
|
if (doc.querySelector('[data-axis-panel="preview"]')?.hidden || !doc.querySelector('[data-axis-panel="dro"]')?.hidden) {
|
|
throw new Error("AXIS-style Preview tab restore failed");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F7", bubbles: true, cancelable: true }));
|
|
if (doc.querySelector('[data-axis-panel="dro"]')?.hidden || doc.body.dataset.lastShortcut !== "F7 DRO") {
|
|
throw new Error("AXIS-style F7 shortcut did not switch to DRO");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F6", bubbles: true, cancelable: true }));
|
|
if (doc.querySelector('[data-axis-panel="preview"]')?.hidden || doc.body.dataset.lastShortcut !== "F6 Preview") {
|
|
throw new Error("AXIS-style F6 shortcut did not switch to Preview");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F1", bubbles: true, cancelable: true }));
|
|
if (api.getVirtualHalState().task.state !== "ESTOP_RESET" || doc.body.dataset.virtualHalTaskState !== "ESTOP_RESET" || doc.body.dataset.lastShortcut !== "F1 ESTOP") {
|
|
throw new Error(`AXIS-style F1 did not reset browser virtual HAL ESTOP: ${JSON.stringify(api.getVirtualHalState())}`);
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F2", bubbles: true, cancelable: true }));
|
|
if (api.getVirtualHalState().task.state !== "ON" || doc.body.dataset.virtualHalTaskState !== "ON" || doc.body.dataset.lastShortcut !== "F2 Power") {
|
|
throw new Error(`AXIS-style F2 did not power browser virtual HAL machine: ${JSON.stringify(api.getVirtualHalState())}`);
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "Home", ctrlKey: true, bubbles: true, cancelable: true }));
|
|
if (api.getVirtualHalLimitsHomeState().axes.x.homed !== "yes" || doc.body.dataset.lastShortcut !== "Ctrl-Home Home All") {
|
|
throw new Error(`AXIS-style Ctrl-Home did not home browser virtual HAL axes: ${JSON.stringify(api.getVirtualHalLimitsHomeState())}`);
|
|
}
|
|
doc.querySelector('[data-jog-increment]').value = "0.01 in";
|
|
doc.querySelector('[data-axis-jog="+"]')?.click();
|
|
if (api.getVirtualHalState().pins["jog.x"] !== true || api.getVirtualHalDroState().actual.x !== "0.010" || doc.querySelector('[data-dro-actual="x"]')?.textContent !== "0.010") {
|
|
throw new Error(`AXIS-style jog did not update browser virtual HAL DRO/pins: ${JSON.stringify(api.getVirtualHalState())}`);
|
|
}
|
|
doc.querySelector('[data-axis-spindle="cw"]')?.click();
|
|
doc.querySelector('[data-axis-coolant="flood"]').checked = true;
|
|
doc.querySelector('[data-axis-coolant="flood"]').dispatchEvent(new frame.contentWindow.Event("change", { bubbles: true }));
|
|
const virtualMachineStatus = api.getVirtualHalMachineStatusState();
|
|
if (
|
|
virtualMachineStatus.spindle.state !== "on" ||
|
|
virtualMachineStatus.spindle.direction !== "cw" ||
|
|
virtualMachineStatus.coolant.flood !== "on" ||
|
|
doc.querySelector('[data-axis-spindle-status]')?.dataset.state !== "ready" ||
|
|
doc.querySelector('[data-axis-coolant-status]')?.dataset.state !== "ready"
|
|
) {
|
|
throw new Error(`AXIS-style spindle/coolant did not update browser virtual HAL machine status: ${JSON.stringify(virtualMachineStatus)}`);
|
|
}
|
|
api.writeVirtualHalPin({ name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1500 });
|
|
api.applyVirtualHalPinUpdates([
|
|
{ name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 1.5 },
|
|
{ name: "halui.tool.number", type: "HAL_U32", value: 8 },
|
|
]);
|
|
if (
|
|
api.readVirtualHalPin("spindle.0.speed-out")?.value !== 1500 ||
|
|
api.readVirtualHalPin("motion.tooloffset.z")?.value !== 1.5 ||
|
|
api.readVirtualHalPin("halui.tool.number")?.value !== 8 ||
|
|
api.getVirtualHalIntegrityReport().complete !== true
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL pin service did not update/read pins: ${JSON.stringify(api.getVirtualHalPinRegistry())}`);
|
|
}
|
|
const virtualHalcmdResult = api.executeVirtualHalcmd("loadrt trivkins\naddf motion-controller servo-thread\nsetp axis.x.pos-cmd 1.75\nshow pin axis.x.*");
|
|
if (
|
|
virtualHalcmdResult.apiName !== "linuxcnc-wasm-virtual-halcmd-result" ||
|
|
virtualHalcmdResult.ok !== true ||
|
|
!virtualHalcmdResult.output.includes("axis.x.pos-cmd") ||
|
|
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75
|
|
) {
|
|
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`);
|
|
}
|
|
const savedVirtualHalSession = await api.saveVirtualHalSessionSnapshot({
|
|
sessionId: "axis-browser-virtual-hal-session",
|
|
machineId: "axis-browser-virtual-hal",
|
|
filename: "virtual-hal-session.json",
|
|
gcodeFilename: "axis-opfs-roundtrip.ngc",
|
|
});
|
|
if (
|
|
savedVirtualHalSession.phase !== "saved" ||
|
|
savedVirtualHalSession.revision < virtualHalcmdResult.state.revision ||
|
|
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.sourceCompliance?.complete ||
|
|
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.motionControllerMatrix?.complete ||
|
|
savedVirtualHalSession.releaseDiagnosticsReady !== true ||
|
|
savedVirtualHalSession.sessionDiagnostics?.ready !== true ||
|
|
savedVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
|
|
savedVirtualHalSession.sessionDiagnostics?.validation?.motionControllerMatrixReady !== true ||
|
|
savedVirtualHalSession.snapshot?.payload?.virtualHal?.millturnUserMProcessBoundary?.complete !== true ||
|
|
savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMillturnUserMProcessBoundary?.complete !== true ||
|
|
savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMotionControllerMatrix?.complete !== true ||
|
|
doc.body.dataset.virtualHalSessionPhase !== "saved" ||
|
|
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Saved HAL session")
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL session save failed: ${JSON.stringify(savedVirtualHalSession)}`);
|
|
}
|
|
const savedEvidenceSessionHandoff = api.getEvidenceSessionHandoffSummary();
|
|
const savedHandoffSnapshot = api.getEvidenceSessionHandoffOperatorSnapshot();
|
|
const savedHandoffActionPlan = api.getEvidenceSessionHandoffOperatorActionPlan();
|
|
const savedHandoffCompactStatus = api.getEvidenceSessionHandoffOperatorCompactStatus();
|
|
const savedHandoffPreflight = api.getEvidenceSessionHandoffPreflightChecklist();
|
|
const savedHandoffReviewNote = api.getEvidenceSessionHandoffReviewNote();
|
|
const savedHandoffReviewPacket = api.getEvidenceSessionHandoffReviewPacket();
|
|
const savedHandoffReviewPacketCopyExport = api.getEvidenceSessionHandoffReviewPacketCopyExport();
|
|
const savedHandoffReviewPacketVerification = api.getEvidenceSessionHandoffReviewPacketVerification();
|
|
const savedHandoffReviewPacketVerificationBadge = api.getEvidenceSessionHandoffReviewPacketVerificationBadge();
|
|
const savedHandoffStatusbarSnapshot = api.getEvidenceSessionHandoffStatusbarSnapshot();
|
|
const savedHandoffStatusbarReceipt = api.getEvidenceSessionHandoffStatusbarReceipt();
|
|
const savedHandoffStatusbarReceiptVerification = api.getEvidenceSessionHandoffStatusbarReceiptVerification();
|
|
const savedHandoffStatusbarReceiptBadge = api.getEvidenceSessionHandoffStatusbarReceiptBadge();
|
|
const savedHandoffExternalShellBadgeSnapshot = api.getEvidenceSessionHandoffExternalShellBadgeSnapshot();
|
|
const savedHandoffExternalShellBadgeCopyExport = api.getEvidenceSessionHandoffExternalShellBadgeCopyExport();
|
|
const savedHandoffExternalShellBadgeCopyExportVerification = api.getEvidenceSessionHandoffExternalShellBadgeCopyExportVerification();
|
|
const savedHandoffExternalShellBundle = api.getEvidenceSessionHandoffExternalShellBundle();
|
|
const savedHandoffExternalShellBundleBadge = api.getEvidenceSessionHandoffExternalShellBundleBadge();
|
|
const savedHandoffExternalShellReceipt = api.getEvidenceSessionHandoffExternalShellReceipt();
|
|
const savedHandoffExternalShellReceiptVerification = api.getEvidenceSessionHandoffExternalShellReceiptVerification();
|
|
const savedHandoffExternalShellReceiptVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptVerificationBadge();
|
|
const savedHandoffExternalShellReceiptAuditSnapshot = api.getEvidenceSessionHandoffExternalShellReceiptAuditSnapshot();
|
|
const savedHandoffExternalShellReceiptAuditCopyExport = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExport();
|
|
const savedHandoffExternalShellReceiptAuditCopyExportVerification = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerification();
|
|
const savedHandoffExternalShellReceiptAuditCopyExportVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadge();
|
|
const savedHandoffExternalShellReceiptAuditBundle = api.getEvidenceSessionHandoffExternalShellReceiptAuditBundle();
|
|
const savedHandoffReviewPacketCopyText = `phase=ready; action=ready for handoff review; preflight=4/4 ready; digest=${savedHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const savedHandoffStatusbarSnapshotSummary = `ready | ready for handoff review | 4/4 ready | ${savedHandoffReviewPacketVerificationBadge.badgeText} | 82/82/77/0`;
|
|
const savedHandoffStatusbarReceiptText = `phase=ready; action=ready for handoff review; preflight=4/4 ready; badge=${savedHandoffReviewPacketVerificationBadge.badgeText}; digest=${savedHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const savedHandoffStatusbarReceiptVerificationSummary = "ready | verified | receipt=match digest=match badge=match";
|
|
const savedHandoffStatusbarReceiptBadgeText = `ready-receipt-verified 3/3 ${savedHandoffReviewPacket.digest}`;
|
|
const savedHandoffExternalShellBadgeText = `ready-shell-verified; action=ready for handoff review; preflight=4/4 ready; packet=${savedHandoffReviewPacketVerificationBadge.badgeText}; receipt=${savedHandoffStatusbarReceiptBadgeText}; baseline=82/82/77/0; digest=${savedHandoffReviewPacket.digest}`;
|
|
const savedHandoffExternalShellBadgeExportText = JSON.stringify({
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
shellBadgeState: "ready-shell-verified",
|
|
shellBadgeText: savedHandoffExternalShellBadgeText,
|
|
packetBadgeText: savedHandoffReviewPacketVerificationBadge.badgeText,
|
|
receiptBadgeText: savedHandoffStatusbarReceiptBadgeText,
|
|
baseline: "82/82/77/0",
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
const savedHandoffExternalShellBadgeSummary = `ready | ${savedHandoffReviewPacket.digest} | ready-shell-verified`;
|
|
const savedHandoffExternalShellVerificationText =
|
|
"ready-copy-export-verified; copy=match; export=match; summary=match; digest=match";
|
|
assertExternalShellCopyExportVerification(
|
|
doc,
|
|
savedHandoffExternalShellBadgeCopyExportVerification,
|
|
"ready",
|
|
savedHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellBundle(doc, savedHandoffExternalShellBundle, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
shellBadgeText: savedHandoffExternalShellBadgeText,
|
|
copyText: savedHandoffExternalShellBadgeText,
|
|
exportText: savedHandoffExternalShellBadgeExportText,
|
|
verificationText: savedHandoffExternalShellVerificationText,
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellBundleBadge(
|
|
doc,
|
|
savedHandoffExternalShellBundleBadge,
|
|
"ready",
|
|
savedHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceipt(doc, savedHandoffExternalShellReceipt, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
bundleBadgeText: `ready-bundle-verified 1/1 ${savedHandoffReviewPacket.digest}`,
|
|
bundleState: "ready-external-shell-bundle-verified",
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerification(doc, savedHandoffExternalShellReceiptVerification, {
|
|
phase: "ready",
|
|
receiptText: savedHandoffExternalShellReceipt.receiptText,
|
|
receiptState: "ready-external-shell-receipt-verified",
|
|
bundleBadgeText: `ready-bundle-verified 1/1 ${savedHandoffReviewPacket.digest}`,
|
|
bundleBadgeState: "ready-bundle-verified",
|
|
bundleState: "ready-external-shell-bundle-verified",
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerificationBadge(
|
|
doc,
|
|
savedHandoffExternalShellReceiptVerificationBadge,
|
|
"ready",
|
|
savedHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditSnapshot(doc, savedHandoffExternalShellReceiptAuditSnapshot, {
|
|
phase: "ready",
|
|
receiptText: savedHandoffExternalShellReceipt.receiptText,
|
|
verificationText: savedHandoffExternalShellReceiptVerification.verificationText,
|
|
badgeText: savedHandoffExternalShellReceiptVerificationBadge.badgeText,
|
|
badgeState: "ready-external-shell-receipt-verification-verified",
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExport(doc, savedHandoffExternalShellReceiptAuditCopyExport, {
|
|
phase: "ready",
|
|
auditState: "ready-external-shell-receipt-audit-verified",
|
|
auditText: savedHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExportVerification(
|
|
doc,
|
|
savedHandoffExternalShellReceiptAuditCopyExportVerification,
|
|
"ready",
|
|
savedHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditCopyExportVerificationBadge(
|
|
doc,
|
|
savedHandoffExternalShellReceiptAuditCopyExportVerificationBadge,
|
|
"ready",
|
|
savedHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditBundle(doc, savedHandoffExternalShellReceiptAuditBundle, {
|
|
phase: "ready",
|
|
auditText: savedHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
copyText: savedHandoffExternalShellReceiptAuditCopyExport.copyText,
|
|
exportText: savedHandoffExternalShellReceiptAuditCopyExport.exportText,
|
|
verificationText: savedHandoffExternalShellReceiptAuditCopyExportVerification.verificationText,
|
|
badgeText: savedHandoffExternalShellReceiptAuditCopyExportVerificationBadge.badgeText,
|
|
badgeState: "ready-receipt-audit-copy-export-verification-verified",
|
|
digest: savedHandoffReviewPacket.digest,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDom(doc, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
reviewNoteText: savedHandoffReviewNote.noteText,
|
|
reviewPacket: savedHandoffReviewPacket,
|
|
reviewPacketCopyExport: savedHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: savedHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: savedHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: savedHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: savedHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: savedHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: savedHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: savedHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: savedHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: savedHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: savedHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDataset(doc, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
reviewNoteText: savedHandoffReviewNote.noteText,
|
|
reviewPacket: savedHandoffReviewPacket,
|
|
reviewPacketCopyExport: savedHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: savedHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: savedHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: savedHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: savedHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: savedHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: savedHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: savedHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: savedHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: savedHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: savedHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffSummaryCompatibility(doc, savedEvidenceSessionHandoff, api.getRunSummary?.(), {
|
|
phase: "ready",
|
|
ready: true,
|
|
detail: "saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
evidence: "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked",
|
|
halSession: "saved/release-ready",
|
|
savedDiagnostics: "ready",
|
|
machineSession: "unchecked/not-loaded",
|
|
baseline: HANDOFF_BASELINE,
|
|
});
|
|
assertLegacyHandoffOperatorSnapshotCompatibility(doc, savedHandoffSnapshot, {
|
|
phase: "ready",
|
|
ready: true,
|
|
detail: "saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
baseline: HANDOFF_BASELINE,
|
|
historyLatestKind: "handoff",
|
|
historyLatestMessage: "ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
});
|
|
assertLegacyHandoffActionPlanCompatibility(doc, savedHandoffActionPlan, savedHandoffCompactStatus, {
|
|
phase: "ready",
|
|
ready: true,
|
|
action: "ready for handoff review",
|
|
baseline: HANDOFF_BASELINE,
|
|
detail: "saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
historyLatest: "handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
});
|
|
assertLegacyHandoffPreflightAndHistoryCompatibility(
|
|
doc,
|
|
savedHandoffPreflight,
|
|
savedHandoffReviewNote,
|
|
api.getStatusHistory(),
|
|
{
|
|
phase: "ready",
|
|
ready: true,
|
|
readyCount: 4,
|
|
totalCount: 4,
|
|
action: "ready for handoff review",
|
|
detail: "saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
halSessionValue: "saved/release-ready",
|
|
diagnosticsValue: "ready",
|
|
baseline: HANDOFF_BASELINE,
|
|
historyLatest: "handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
},
|
|
);
|
|
if (
|
|
savedEvidenceSessionHandoff.ready !== true ||
|
|
savedEvidenceSessionHandoff.phase !== "ready" ||
|
|
savedEvidenceSessionHandoff.fields.evidence !== "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked" ||
|
|
savedEvidenceSessionHandoff.fields.virtualHalSession !== "saved/release-ready" ||
|
|
savedEvidenceSessionHandoff.fields.savedDiagnostics !== "ready" ||
|
|
savedEvidenceSessionHandoff.fields.machineSession !== "unchecked/not-loaded" ||
|
|
savedEvidenceSessionHandoff.fields.baseline !== "82/82/77/0" ||
|
|
savedHandoffReviewNote.phase !== "ready" ||
|
|
savedHandoffReviewNote.action !== "ready for handoff review" ||
|
|
savedHandoffReviewNote.preflight !== "4/4 ready" ||
|
|
savedHandoffReviewNote.baseline !== "82/82/77/0" ||
|
|
savedHandoffReviewNote.latestHistory !== "handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
savedHandoffReviewNote.noteText !== "phase=ready; action=ready for handoff review; preflight=4/4 ready; baseline=82/82/77/0; latest=handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
savedHandoffReviewPacket.phase !== "ready" ||
|
|
savedHandoffReviewPacket.action !== "ready for handoff review" ||
|
|
savedHandoffReviewPacket.preflight !== "4/4 ready" ||
|
|
savedHandoffReviewPacket.latestHistory !== "handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
savedHandoffReviewPacket.packet?.reviewNote !== savedHandoffReviewNote.noteText ||
|
|
savedHandoffReviewPacket.packet?.preflight?.ready !== true ||
|
|
savedHandoffReviewPacket.packet?.preflight?.rows?.length !== 4 ||
|
|
JSON.parse(savedHandoffReviewPacket.packetJson).compactStatus.phase !== "ready" ||
|
|
!/^h[0-9a-f]{8}$/.test(savedHandoffReviewPacket.digest) ||
|
|
savedHandoffReviewPacketCopyExport.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-copy-export" ||
|
|
savedHandoffReviewPacketCopyExport.phase !== "ready" ||
|
|
savedHandoffReviewPacketCopyExport.ready !== true ||
|
|
savedHandoffReviewPacketCopyExport.action !== "ready for handoff review" ||
|
|
savedHandoffReviewPacketCopyExport.preflight !== "4/4 ready" ||
|
|
savedHandoffReviewPacketCopyExport.baseline !== "82/82/77/0" ||
|
|
savedHandoffReviewPacketCopyExport.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffReviewPacketCopyExport.packetJson !== savedHandoffReviewPacket.packetJson ||
|
|
savedHandoffReviewPacketCopyExport.reviewNote !== savedHandoffReviewNote.noteText ||
|
|
savedHandoffReviewPacketCopyExport.copyText !== savedHandoffReviewPacketCopyText ||
|
|
savedHandoffReviewPacketCopyExport.exportText !== savedHandoffReviewPacket.packetJson ||
|
|
savedHandoffReviewPacketCopyExport.summaryText !== `ready | ${savedHandoffReviewPacket.digest} | 4/4 ready` ||
|
|
savedHandoffReviewPacketVerification.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-verification" ||
|
|
savedHandoffReviewPacketVerification.phase !== "ready" ||
|
|
savedHandoffReviewPacketVerification.ready !== true ||
|
|
savedHandoffReviewPacketVerification.statusText !== "verified" ||
|
|
savedHandoffReviewPacketVerification.digestMatches !== true ||
|
|
savedHandoffReviewPacketVerification.jsonParseReady !== true ||
|
|
savedHandoffReviewPacketVerification.reviewNoteMatches !== true ||
|
|
savedHandoffReviewPacketVerification.preflightMatches !== true ||
|
|
savedHandoffReviewPacketVerification.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffReviewPacketVerification.preflight !== "4/4 ready" ||
|
|
savedHandoffReviewPacketVerification.reviewNote !== savedHandoffReviewNote.noteText ||
|
|
savedHandoffReviewPacketVerification.packetJson !== savedHandoffReviewPacket.packetJson ||
|
|
savedHandoffReviewPacketVerification.rows?.length !== 4 ||
|
|
!savedHandoffReviewPacketVerification.rows?.every((row) => row.ready === true) ||
|
|
savedHandoffReviewPacketVerification.summaryText !== `ready | verified | digest=match json=ready note=match preflight=match` ||
|
|
savedHandoffReviewPacketVerificationBadge.apiName !== "real-browser-simulation-evidence-session-handoff-review-packet-verification-badge" ||
|
|
savedHandoffReviewPacketVerificationBadge.phase !== "ready" ||
|
|
savedHandoffReviewPacketVerificationBadge.statusText !== "verified" ||
|
|
savedHandoffReviewPacketVerificationBadge.ready !== true ||
|
|
savedHandoffReviewPacketVerificationBadge.readyCount !== 4 ||
|
|
savedHandoffReviewPacketVerificationBadge.totalCount !== 4 ||
|
|
savedHandoffReviewPacketVerificationBadge.badgeState !== "ready-verified" ||
|
|
savedHandoffReviewPacketVerificationBadge.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffReviewPacketVerificationBadge.badgeText !== `ready-verified 4/4 ${savedHandoffReviewPacket.digest}` ||
|
|
savedHandoffReviewPacketVerificationBadge.statusbarBadgeText !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
savedHandoffReviewPacketVerificationBadge.statusbarBadgeState !== "ready-verified" ||
|
|
savedHandoffStatusbarSnapshot.phase !== "ready" ||
|
|
savedHandoffStatusbarSnapshot.action !== "ready for handoff review" ||
|
|
savedHandoffStatusbarSnapshot.preflight !== "4/4 ready" ||
|
|
savedHandoffStatusbarSnapshot.packetBadgeText !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
savedHandoffStatusbarSnapshot.packetBadgeState !== "ready-verified" ||
|
|
savedHandoffStatusbarSnapshot.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffStatusbarSnapshot.baseline !== "82/82/77/0" ||
|
|
savedHandoffStatusbarSnapshot.ready !== true ||
|
|
savedHandoffStatusbarSnapshot.summaryText !== savedHandoffStatusbarSnapshotSummary ||
|
|
savedHandoffStatusbarReceipt.phase !== "ready" ||
|
|
savedHandoffStatusbarReceipt.action !== "ready for handoff review" ||
|
|
savedHandoffStatusbarReceipt.preflight !== "4/4 ready" ||
|
|
savedHandoffStatusbarReceipt.badge !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
savedHandoffStatusbarReceipt.packetBadgeState !== "ready-verified" ||
|
|
savedHandoffStatusbarReceipt.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffStatusbarReceipt.baseline !== "82/82/77/0" ||
|
|
savedHandoffStatusbarReceipt.ready !== true ||
|
|
savedHandoffStatusbarReceipt.receiptText !== savedHandoffStatusbarReceiptText ||
|
|
savedHandoffStatusbarReceipt.copyText !== savedHandoffStatusbarReceiptText ||
|
|
savedHandoffStatusbarReceipt.summaryText !== savedHandoffStatusbarReceiptText ||
|
|
savedHandoffStatusbarReceiptVerification.phase !== "ready" ||
|
|
savedHandoffStatusbarReceiptVerification.ready !== true ||
|
|
savedHandoffStatusbarReceiptVerification.statusText !== "verified" ||
|
|
savedHandoffStatusbarReceiptVerification.receiptMatches !== true ||
|
|
savedHandoffStatusbarReceiptVerification.digestMatches !== true ||
|
|
savedHandoffStatusbarReceiptVerification.badgeMatches !== true ||
|
|
savedHandoffStatusbarReceiptVerification.summaryText !== savedHandoffStatusbarReceiptVerificationSummary ||
|
|
savedHandoffStatusbarReceiptVerification.receiptText !== savedHandoffStatusbarReceiptText ||
|
|
savedHandoffStatusbarReceiptVerification.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffStatusbarReceiptVerification.badge !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
savedHandoffStatusbarReceiptVerification.baseline !== "82/82/77/0" ||
|
|
savedHandoffStatusbarReceiptBadge.phase !== "ready" ||
|
|
savedHandoffStatusbarReceiptBadge.statusText !== "verified" ||
|
|
savedHandoffStatusbarReceiptBadge.ready !== true ||
|
|
savedHandoffStatusbarReceiptBadge.readyCount !== 3 ||
|
|
savedHandoffStatusbarReceiptBadge.totalCount !== 3 ||
|
|
savedHandoffStatusbarReceiptBadge.receiptBadgeState !== "ready-receipt-verified" ||
|
|
savedHandoffStatusbarReceiptBadge.receiptBadgeText !== savedHandoffStatusbarReceiptBadgeText ||
|
|
savedHandoffStatusbarReceiptBadge.statusbarReceiptBadgeText !== savedHandoffStatusbarReceiptBadgeText ||
|
|
savedHandoffStatusbarReceiptBadge.statusbarReceiptBadgeState !== "ready-receipt-verified" ||
|
|
savedHandoffStatusbarReceiptBadge.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffExternalShellBadgeSnapshot.phase !== "ready" ||
|
|
savedHandoffExternalShellBadgeSnapshot.action !== "ready for handoff review" ||
|
|
savedHandoffExternalShellBadgeSnapshot.preflight !== "4/4 ready" ||
|
|
savedHandoffExternalShellBadgeSnapshot.packetBadgeText !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
savedHandoffExternalShellBadgeSnapshot.receiptBadgeText !== savedHandoffStatusbarReceiptBadgeText ||
|
|
savedHandoffExternalShellBadgeSnapshot.baseline !== "82/82/77/0" ||
|
|
savedHandoffExternalShellBadgeSnapshot.digest !== savedHandoffReviewPacket.digest ||
|
|
savedHandoffExternalShellBadgeSnapshot.ready !== true ||
|
|
savedHandoffExternalShellBadgeSnapshot.shellBadgeState !== "ready-shell-verified" ||
|
|
savedHandoffExternalShellBadgeSnapshot.shellBadgeText !== savedHandoffExternalShellBadgeText ||
|
|
savedHandoffExternalShellBadgeSnapshot.statusbarShellBadgeText !== savedHandoffExternalShellBadgeText ||
|
|
savedHandoffExternalShellBadgeCopyExport.apiName !== "real-browser-simulation-evidence-session-handoff-external-shell-badge-copy-export" ||
|
|
savedHandoffExternalShellBadgeCopyExport.phase !== "ready" ||
|
|
savedHandoffExternalShellBadgeCopyExport.action !== "ready for handoff review" ||
|
|
savedHandoffExternalShellBadgeCopyExport.preflight !== "4/4 ready" ||
|
|
savedHandoffExternalShellBadgeCopyExport.ready !== true ||
|
|
savedHandoffExternalShellBadgeCopyExport.shellBadgeState !== "ready-shell-verified" ||
|
|
savedHandoffExternalShellBadgeCopyExport.shellBadgeText !== savedHandoffExternalShellBadgeText ||
|
|
savedHandoffExternalShellBadgeCopyExport.copyText !== savedHandoffExternalShellBadgeText ||
|
|
savedHandoffExternalShellBadgeCopyExport.exportText !== savedHandoffExternalShellBadgeExportText ||
|
|
savedHandoffExternalShellBadgeCopyExport.summaryText !== savedHandoffExternalShellBadgeSummary ||
|
|
savedHandoffExternalShellBadgeCopyExport.digest !== savedHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketAction !== "ready for handoff review" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketPreflight !== "4/4 ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketLatest !== "handoff: ready saved saved/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptAction !== "ready for handoff review" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPreflight !== "4/4 ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadge !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPacketBadgeState !== "ready-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptDigest !== savedHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptText !== savedHandoffStatusbarReceiptText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReceiptMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigestMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadgeMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigest !== savedHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadge !== savedHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationSummary !== savedHandoffStatusbarReceiptVerificationSummary ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgePhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeState !== "ready-receipt-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeText !== savedHandoffStatusbarReceiptBadgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReadyCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeTotalCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeDigest !== savedHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotAction !== "ready for handoff review" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPreflight !== "4/4 ready"
|
|
) {
|
|
throw new Error(`AXIS-style evidence/session handoff did not become ready after save: ${JSON.stringify(savedEvidenceSessionHandoff)}`);
|
|
}
|
|
const savedSessionDiagnosticsArtifact = api.exportVirtualHalSessionDiagnosticsArtifact();
|
|
const diagnosticsWithSavedSession = api.exportDiagnosticsArtifact();
|
|
if (
|
|
savedSessionDiagnosticsArtifact.ready !== true ||
|
|
savedSessionDiagnosticsArtifact.phase !== "release-ready" ||
|
|
savedSessionDiagnosticsArtifact.sessionId !== "axis-browser-virtual-hal-session" ||
|
|
savedSessionDiagnosticsArtifact.validation?.ready !== true ||
|
|
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.ready !== true ||
|
|
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.validation?.ready !== true ||
|
|
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalSourceCompliance?.complete !== true ||
|
|
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalMillturnUserMProcessBoundary?.complete !== true
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL session diagnostics did not become release-ready: ${JSON.stringify(diagnosticsWithSavedSession.virtualHalSessionDiagnostics)}`);
|
|
}
|
|
api.executeVirtualHalcmd("setp axis.x.pos-cmd 9.5\nloadusr post-save-component");
|
|
if (api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 9.5) {
|
|
throw new Error("AXIS-style virtual HAL pre-restore mutation did not apply");
|
|
}
|
|
const restoredVirtualHalSession = await api.restoreVirtualHalSessionSnapshot({
|
|
sessionId: "axis-browser-virtual-hal-session",
|
|
filename: "virtual-hal-session.json",
|
|
});
|
|
if (
|
|
restoredVirtualHalSession.phase !== "restored" ||
|
|
restoredVirtualHalSession.restored !== true ||
|
|
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75 ||
|
|
api.getVirtualHalState().hal.loadedComponents.some(({ component }) => component === "post-save-component") ||
|
|
!api.getVirtualHalState().hal.functions.some(({ name }) => name === "motion-controller") ||
|
|
restoredVirtualHalSession.releaseDiagnosticsReady !== true ||
|
|
restoredVirtualHalSession.sessionDiagnostics?.ready !== true ||
|
|
restoredVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
|
|
doc.body.dataset.virtualHalSessionPhase !== "restored" ||
|
|
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Restored HAL session")
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL session restore failed: ${JSON.stringify(restoredVirtualHalSession)}`);
|
|
}
|
|
const restoredEvidenceSessionHandoff = api.getEvidenceSessionHandoffSummary();
|
|
const restoredHandoffSnapshot = api.getEvidenceSessionHandoffOperatorSnapshot();
|
|
const restoredHandoffActionPlan = api.getEvidenceSessionHandoffOperatorActionPlan();
|
|
const restoredHandoffCompactStatus = api.getEvidenceSessionHandoffOperatorCompactStatus();
|
|
const restoredHandoffPreflight = api.getEvidenceSessionHandoffPreflightChecklist();
|
|
const restoredHandoffReviewNote = api.getEvidenceSessionHandoffReviewNote();
|
|
const restoredHandoffReviewPacket = api.getEvidenceSessionHandoffReviewPacket();
|
|
const restoredHandoffReviewPacketCopyExport = api.getEvidenceSessionHandoffReviewPacketCopyExport();
|
|
const restoredHandoffReviewPacketVerification = api.getEvidenceSessionHandoffReviewPacketVerification();
|
|
const restoredHandoffReviewPacketVerificationBadge = api.getEvidenceSessionHandoffReviewPacketVerificationBadge();
|
|
const restoredHandoffStatusbarSnapshot = api.getEvidenceSessionHandoffStatusbarSnapshot();
|
|
const restoredHandoffStatusbarReceipt = api.getEvidenceSessionHandoffStatusbarReceipt();
|
|
const restoredHandoffStatusbarReceiptVerification = api.getEvidenceSessionHandoffStatusbarReceiptVerification();
|
|
const restoredHandoffStatusbarReceiptBadge = api.getEvidenceSessionHandoffStatusbarReceiptBadge();
|
|
const restoredHandoffExternalShellBadgeSnapshot = api.getEvidenceSessionHandoffExternalShellBadgeSnapshot();
|
|
const restoredHandoffExternalShellBadgeCopyExport = api.getEvidenceSessionHandoffExternalShellBadgeCopyExport();
|
|
const restoredHandoffExternalShellBadgeCopyExportVerification = api.getEvidenceSessionHandoffExternalShellBadgeCopyExportVerification();
|
|
const restoredHandoffExternalShellBundle = api.getEvidenceSessionHandoffExternalShellBundle();
|
|
const restoredHandoffExternalShellBundleBadge = api.getEvidenceSessionHandoffExternalShellBundleBadge();
|
|
const restoredHandoffExternalShellReceipt = api.getEvidenceSessionHandoffExternalShellReceipt();
|
|
const restoredHandoffExternalShellReceiptVerification = api.getEvidenceSessionHandoffExternalShellReceiptVerification();
|
|
const restoredHandoffExternalShellReceiptVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptVerificationBadge();
|
|
const restoredHandoffExternalShellReceiptAuditSnapshot = api.getEvidenceSessionHandoffExternalShellReceiptAuditSnapshot();
|
|
const restoredHandoffExternalShellReceiptAuditCopyExport = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExport();
|
|
const restoredHandoffExternalShellReceiptAuditCopyExportVerification = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerification();
|
|
const restoredHandoffExternalShellReceiptAuditCopyExportVerificationBadge = api.getEvidenceSessionHandoffExternalShellReceiptAuditCopyExportVerificationBadge();
|
|
const restoredHandoffExternalShellReceiptAuditBundle = api.getEvidenceSessionHandoffExternalShellReceiptAuditBundle();
|
|
const restoredHandoffReviewPacketCopyText = `phase=ready; action=ready for handoff review; preflight=4/4 ready; digest=${restoredHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const restoredHandoffStatusbarSnapshotSummary = `ready | ready for handoff review | 4/4 ready | ${restoredHandoffReviewPacketVerificationBadge.badgeText} | 82/82/77/0`;
|
|
const restoredHandoffStatusbarReceiptText = `phase=ready; action=ready for handoff review; preflight=4/4 ready; badge=${restoredHandoffReviewPacketVerificationBadge.badgeText}; digest=${restoredHandoffReviewPacket.digest}; baseline=82/82/77/0`;
|
|
const restoredHandoffStatusbarReceiptVerificationSummary = "ready | verified | receipt=match digest=match badge=match";
|
|
const restoredHandoffStatusbarReceiptBadgeText = `ready-receipt-verified 3/3 ${restoredHandoffReviewPacket.digest}`;
|
|
const restoredHandoffExternalShellBadgeText = `ready-shell-verified; action=ready for handoff review; preflight=4/4 ready; packet=${restoredHandoffReviewPacketVerificationBadge.badgeText}; receipt=${restoredHandoffStatusbarReceiptBadgeText}; baseline=82/82/77/0; digest=${restoredHandoffReviewPacket.digest}`;
|
|
const restoredHandoffExternalShellBadgeExportText = JSON.stringify({
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
shellBadgeState: "ready-shell-verified",
|
|
shellBadgeText: restoredHandoffExternalShellBadgeText,
|
|
packetBadgeText: restoredHandoffReviewPacketVerificationBadge.badgeText,
|
|
receiptBadgeText: restoredHandoffStatusbarReceiptBadgeText,
|
|
baseline: "82/82/77/0",
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
const restoredHandoffExternalShellBadgeSummary = `ready | ${restoredHandoffReviewPacket.digest} | ready-shell-verified`;
|
|
const restoredHandoffExternalShellVerificationText =
|
|
"ready-copy-export-verified; copy=match; export=match; summary=match; digest=match";
|
|
assertExternalShellCopyExportVerification(
|
|
doc,
|
|
restoredHandoffExternalShellBadgeCopyExportVerification,
|
|
"ready",
|
|
restoredHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellBundle(doc, restoredHandoffExternalShellBundle, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
shellBadgeText: restoredHandoffExternalShellBadgeText,
|
|
copyText: restoredHandoffExternalShellBadgeText,
|
|
exportText: restoredHandoffExternalShellBadgeExportText,
|
|
verificationText: restoredHandoffExternalShellVerificationText,
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellBundleBadge(
|
|
doc,
|
|
restoredHandoffExternalShellBundleBadge,
|
|
"ready",
|
|
restoredHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceipt(doc, restoredHandoffExternalShellReceipt, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
bundleBadgeText: `ready-bundle-verified 1/1 ${restoredHandoffReviewPacket.digest}`,
|
|
bundleState: "ready-external-shell-bundle-verified",
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerification(doc, restoredHandoffExternalShellReceiptVerification, {
|
|
phase: "ready",
|
|
receiptText: restoredHandoffExternalShellReceipt.receiptText,
|
|
receiptState: "ready-external-shell-receipt-verified",
|
|
bundleBadgeText: `ready-bundle-verified 1/1 ${restoredHandoffReviewPacket.digest}`,
|
|
bundleBadgeState: "ready-bundle-verified",
|
|
bundleState: "ready-external-shell-bundle-verified",
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptVerificationBadge(
|
|
doc,
|
|
restoredHandoffExternalShellReceiptVerificationBadge,
|
|
"ready",
|
|
restoredHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditSnapshot(doc, restoredHandoffExternalShellReceiptAuditSnapshot, {
|
|
phase: "ready",
|
|
receiptText: restoredHandoffExternalShellReceipt.receiptText,
|
|
verificationText: restoredHandoffExternalShellReceiptVerification.verificationText,
|
|
badgeText: restoredHandoffExternalShellReceiptVerificationBadge.badgeText,
|
|
badgeState: "ready-external-shell-receipt-verification-verified",
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExport(doc, restoredHandoffExternalShellReceiptAuditCopyExport, {
|
|
phase: "ready",
|
|
auditState: "ready-external-shell-receipt-audit-verified",
|
|
auditText: restoredHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertExternalShellReceiptAuditCopyExportVerification(
|
|
doc,
|
|
restoredHandoffExternalShellReceiptAuditCopyExportVerification,
|
|
"ready",
|
|
restoredHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditCopyExportVerificationBadge(
|
|
doc,
|
|
restoredHandoffExternalShellReceiptAuditCopyExportVerificationBadge,
|
|
"ready",
|
|
restoredHandoffReviewPacket.digest,
|
|
);
|
|
assertExternalShellReceiptAuditBundle(doc, restoredHandoffExternalShellReceiptAuditBundle, {
|
|
phase: "ready",
|
|
auditText: restoredHandoffExternalShellReceiptAuditSnapshot.auditText,
|
|
copyText: restoredHandoffExternalShellReceiptAuditCopyExport.copyText,
|
|
exportText: restoredHandoffExternalShellReceiptAuditCopyExport.exportText,
|
|
verificationText: restoredHandoffExternalShellReceiptAuditCopyExportVerification.verificationText,
|
|
badgeText: restoredHandoffExternalShellReceiptAuditCopyExportVerificationBadge.badgeText,
|
|
badgeState: "ready-receipt-audit-copy-export-verification-verified",
|
|
digest: restoredHandoffReviewPacket.digest,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDom(doc, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
reviewNoteText: restoredHandoffReviewNote.noteText,
|
|
reviewPacket: restoredHandoffReviewPacket,
|
|
reviewPacketCopyExport: restoredHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: restoredHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: restoredHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: restoredHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: restoredHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: restoredHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: restoredHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: restoredHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: restoredHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: restoredHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: restoredHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffReviewStatusbarDataset(doc, {
|
|
phase: "ready",
|
|
action: "ready for handoff review",
|
|
preflight: "4/4 ready",
|
|
reviewNoteText: restoredHandoffReviewNote.noteText,
|
|
reviewPacket: restoredHandoffReviewPacket,
|
|
reviewPacketCopyExport: restoredHandoffReviewPacketCopyExport,
|
|
reviewPacketCopyText: restoredHandoffReviewPacketCopyText,
|
|
reviewPacketVerification: restoredHandoffReviewPacketVerification,
|
|
reviewPacketVerificationBadge: restoredHandoffReviewPacketVerificationBadge,
|
|
statusbarSnapshotSummary: restoredHandoffStatusbarSnapshotSummary,
|
|
statusbarReceiptText: restoredHandoffStatusbarReceiptText,
|
|
statusbarReceiptVerificationSummary: restoredHandoffStatusbarReceiptVerificationSummary,
|
|
statusbarReceiptBadgeText: restoredHandoffStatusbarReceiptBadgeText,
|
|
externalShellBadgeText: restoredHandoffExternalShellBadgeText,
|
|
externalShellBadgeExportText: restoredHandoffExternalShellBadgeExportText,
|
|
externalShellBadgeSummary: restoredHandoffExternalShellBadgeSummary,
|
|
});
|
|
assertLegacyHandoffSummaryCompatibility(doc, restoredEvidenceSessionHandoff, api.getRunSummary?.(), {
|
|
phase: "ready",
|
|
ready: true,
|
|
detail: "restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
evidence: "candidates=11/11; families=3/3; baseline=82/82/77/0; promotion=locked",
|
|
halSession: "restored/release-ready",
|
|
savedDiagnostics: "ready",
|
|
machineSession: "unchecked/not-loaded",
|
|
baseline: HANDOFF_BASELINE,
|
|
});
|
|
assertLegacyHandoffOperatorSnapshotCompatibility(doc, restoredHandoffSnapshot, {
|
|
phase: "ready",
|
|
ready: true,
|
|
detail: "restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
baseline: HANDOFF_BASELINE,
|
|
historyLatestKind: "handoff",
|
|
historyLatestMessage: "ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
});
|
|
assertLegacyHandoffActionPlanCompatibility(doc, restoredHandoffActionPlan, restoredHandoffCompactStatus, {
|
|
phase: "ready",
|
|
ready: true,
|
|
action: "ready for handoff review",
|
|
baseline: HANDOFF_BASELINE,
|
|
detail: "restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
historyLatest: "handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
});
|
|
assertLegacyHandoffPreflightAndHistoryCompatibility(
|
|
doc,
|
|
restoredHandoffPreflight,
|
|
restoredHandoffReviewNote,
|
|
api.getStatusHistory(),
|
|
{
|
|
phase: "ready",
|
|
ready: true,
|
|
readyCount: 4,
|
|
totalCount: 4,
|
|
action: "ready for handoff review",
|
|
detail: "restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
halSessionValue: "restored/release-ready",
|
|
diagnosticsValue: "ready",
|
|
baseline: HANDOFF_BASELINE,
|
|
historyLatest: "handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0",
|
|
},
|
|
);
|
|
if (
|
|
restoredEvidenceSessionHandoff.ready !== true ||
|
|
restoredEvidenceSessionHandoff.fields.virtualHalSession !== "restored/release-ready" ||
|
|
restoredEvidenceSessionHandoff.fields.savedDiagnostics !== "ready" ||
|
|
restoredHandoffReviewNote.phase !== "ready" ||
|
|
restoredHandoffReviewNote.action !== "ready for handoff review" ||
|
|
restoredHandoffReviewNote.preflight !== "4/4 ready" ||
|
|
restoredHandoffReviewNote.latestHistory !== "handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
restoredHandoffReviewNote.noteText !== "phase=ready; action=ready for handoff review; preflight=4/4 ready; baseline=82/82/77/0; latest=handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
restoredHandoffReviewPacket.phase !== "ready" ||
|
|
restoredHandoffReviewPacket.preflight !== "4/4 ready" ||
|
|
restoredHandoffReviewPacket.latestHistory !== "handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
restoredHandoffReviewPacket.packet?.reviewNote !== restoredHandoffReviewNote.noteText ||
|
|
JSON.parse(restoredHandoffReviewPacket.packetJson).latestHistory !== "handoff: ready restored restored/release-ready; diagnostics ready; baseline 82/82/77/0" ||
|
|
restoredHandoffReviewPacketCopyExport.phase !== "ready" ||
|
|
restoredHandoffReviewPacketCopyExport.ready !== true ||
|
|
restoredHandoffReviewPacketCopyExport.action !== "ready for handoff review" ||
|
|
restoredHandoffReviewPacketCopyExport.preflight !== "4/4 ready" ||
|
|
restoredHandoffReviewPacketCopyExport.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffReviewPacketCopyExport.packetJson !== restoredHandoffReviewPacket.packetJson ||
|
|
restoredHandoffReviewPacketCopyExport.reviewNote !== restoredHandoffReviewNote.noteText ||
|
|
restoredHandoffReviewPacketCopyExport.copyText !== restoredHandoffReviewPacketCopyText ||
|
|
restoredHandoffReviewPacketCopyExport.exportText !== restoredHandoffReviewPacket.packetJson ||
|
|
restoredHandoffReviewPacketCopyExport.summaryText !== `ready | ${restoredHandoffReviewPacket.digest} | 4/4 ready` ||
|
|
restoredHandoffReviewPacketVerification.phase !== "ready" ||
|
|
restoredHandoffReviewPacketVerification.ready !== true ||
|
|
restoredHandoffReviewPacketVerification.statusText !== "verified" ||
|
|
restoredHandoffReviewPacketVerification.digestMatches !== true ||
|
|
restoredHandoffReviewPacketVerification.jsonParseReady !== true ||
|
|
restoredHandoffReviewPacketVerification.reviewNoteMatches !== true ||
|
|
restoredHandoffReviewPacketVerification.preflightMatches !== true ||
|
|
restoredHandoffReviewPacketVerification.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffReviewPacketVerification.preflight !== "4/4 ready" ||
|
|
restoredHandoffReviewPacketVerification.reviewNote !== restoredHandoffReviewNote.noteText ||
|
|
restoredHandoffReviewPacketVerification.packetJson !== restoredHandoffReviewPacket.packetJson ||
|
|
restoredHandoffReviewPacketVerification.rows?.length !== 4 ||
|
|
!restoredHandoffReviewPacketVerification.rows?.every((row) => row.ready === true) ||
|
|
restoredHandoffReviewPacketVerification.summaryText !== `ready | verified | digest=match json=ready note=match preflight=match` ||
|
|
restoredHandoffReviewPacketVerificationBadge.phase !== "ready" ||
|
|
restoredHandoffReviewPacketVerificationBadge.statusText !== "verified" ||
|
|
restoredHandoffReviewPacketVerificationBadge.ready !== true ||
|
|
restoredHandoffReviewPacketVerificationBadge.readyCount !== 4 ||
|
|
restoredHandoffReviewPacketVerificationBadge.totalCount !== 4 ||
|
|
restoredHandoffReviewPacketVerificationBadge.badgeState !== "ready-verified" ||
|
|
restoredHandoffReviewPacketVerificationBadge.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffReviewPacketVerificationBadge.badgeText !== `ready-verified 4/4 ${restoredHandoffReviewPacket.digest}` ||
|
|
restoredHandoffReviewPacketVerificationBadge.statusbarBadgeText !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
restoredHandoffReviewPacketVerificationBadge.statusbarBadgeState !== "ready-verified" ||
|
|
restoredHandoffStatusbarSnapshot.phase !== "ready" ||
|
|
restoredHandoffStatusbarSnapshot.action !== "ready for handoff review" ||
|
|
restoredHandoffStatusbarSnapshot.preflight !== "4/4 ready" ||
|
|
restoredHandoffStatusbarSnapshot.packetBadgeText !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
restoredHandoffStatusbarSnapshot.packetBadgeState !== "ready-verified" ||
|
|
restoredHandoffStatusbarSnapshot.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffStatusbarSnapshot.baseline !== "82/82/77/0" ||
|
|
restoredHandoffStatusbarSnapshot.ready !== true ||
|
|
restoredHandoffStatusbarSnapshot.summaryText !== restoredHandoffStatusbarSnapshotSummary ||
|
|
restoredHandoffStatusbarReceipt.phase !== "ready" ||
|
|
restoredHandoffStatusbarReceipt.action !== "ready for handoff review" ||
|
|
restoredHandoffStatusbarReceipt.preflight !== "4/4 ready" ||
|
|
restoredHandoffStatusbarReceipt.badge !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
restoredHandoffStatusbarReceipt.packetBadgeState !== "ready-verified" ||
|
|
restoredHandoffStatusbarReceipt.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffStatusbarReceipt.baseline !== "82/82/77/0" ||
|
|
restoredHandoffStatusbarReceipt.ready !== true ||
|
|
restoredHandoffStatusbarReceipt.receiptText !== restoredHandoffStatusbarReceiptText ||
|
|
restoredHandoffStatusbarReceipt.copyText !== restoredHandoffStatusbarReceiptText ||
|
|
restoredHandoffStatusbarReceipt.summaryText !== restoredHandoffStatusbarReceiptText ||
|
|
restoredHandoffStatusbarReceiptVerification.phase !== "ready" ||
|
|
restoredHandoffStatusbarReceiptVerification.ready !== true ||
|
|
restoredHandoffStatusbarReceiptVerification.statusText !== "verified" ||
|
|
restoredHandoffStatusbarReceiptVerification.receiptMatches !== true ||
|
|
restoredHandoffStatusbarReceiptVerification.digestMatches !== true ||
|
|
restoredHandoffStatusbarReceiptVerification.badgeMatches !== true ||
|
|
restoredHandoffStatusbarReceiptVerification.summaryText !== restoredHandoffStatusbarReceiptVerificationSummary ||
|
|
restoredHandoffStatusbarReceiptVerification.receiptText !== restoredHandoffStatusbarReceiptText ||
|
|
restoredHandoffStatusbarReceiptVerification.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffStatusbarReceiptVerification.badge !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
restoredHandoffStatusbarReceiptVerification.baseline !== "82/82/77/0" ||
|
|
restoredHandoffStatusbarReceiptBadge.phase !== "ready" ||
|
|
restoredHandoffStatusbarReceiptBadge.statusText !== "verified" ||
|
|
restoredHandoffStatusbarReceiptBadge.ready !== true ||
|
|
restoredHandoffStatusbarReceiptBadge.readyCount !== 3 ||
|
|
restoredHandoffStatusbarReceiptBadge.totalCount !== 3 ||
|
|
restoredHandoffStatusbarReceiptBadge.receiptBadgeState !== "ready-receipt-verified" ||
|
|
restoredHandoffStatusbarReceiptBadge.receiptBadgeText !== restoredHandoffStatusbarReceiptBadgeText ||
|
|
restoredHandoffStatusbarReceiptBadge.statusbarReceiptBadgeText !== restoredHandoffStatusbarReceiptBadgeText ||
|
|
restoredHandoffStatusbarReceiptBadge.statusbarReceiptBadgeState !== "ready-receipt-verified" ||
|
|
restoredHandoffStatusbarReceiptBadge.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffExternalShellBadgeSnapshot.phase !== "ready" ||
|
|
restoredHandoffExternalShellBadgeSnapshot.action !== "ready for handoff review" ||
|
|
restoredHandoffExternalShellBadgeSnapshot.preflight !== "4/4 ready" ||
|
|
restoredHandoffExternalShellBadgeSnapshot.packetBadgeText !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
restoredHandoffExternalShellBadgeSnapshot.receiptBadgeText !== restoredHandoffStatusbarReceiptBadgeText ||
|
|
restoredHandoffExternalShellBadgeSnapshot.baseline !== "82/82/77/0" ||
|
|
restoredHandoffExternalShellBadgeSnapshot.digest !== restoredHandoffReviewPacket.digest ||
|
|
restoredHandoffExternalShellBadgeSnapshot.ready !== true ||
|
|
restoredHandoffExternalShellBadgeSnapshot.shellBadgeState !== "ready-shell-verified" ||
|
|
restoredHandoffExternalShellBadgeSnapshot.shellBadgeText !== restoredHandoffExternalShellBadgeText ||
|
|
restoredHandoffExternalShellBadgeSnapshot.statusbarShellBadgeText !== restoredHandoffExternalShellBadgeText ||
|
|
restoredHandoffExternalShellBadgeCopyExport.phase !== "ready" ||
|
|
restoredHandoffExternalShellBadgeCopyExport.action !== "ready for handoff review" ||
|
|
restoredHandoffExternalShellBadgeCopyExport.preflight !== "4/4 ready" ||
|
|
restoredHandoffExternalShellBadgeCopyExport.ready !== true ||
|
|
restoredHandoffExternalShellBadgeCopyExport.shellBadgeState !== "ready-shell-verified" ||
|
|
restoredHandoffExternalShellBadgeCopyExport.shellBadgeText !== restoredHandoffExternalShellBadgeText ||
|
|
restoredHandoffExternalShellBadgeCopyExport.copyText !== restoredHandoffExternalShellBadgeText ||
|
|
restoredHandoffExternalShellBadgeCopyExport.exportText !== restoredHandoffExternalShellBadgeExportText ||
|
|
restoredHandoffExternalShellBadgeCopyExport.summaryText !== restoredHandoffExternalShellBadgeSummary ||
|
|
restoredHandoffExternalShellBadgeCopyExport.digest !== restoredHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketDigest !== restoredHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketJson !== restoredHandoffReviewPacket.packetJson ||
|
|
doc.body.dataset.evidenceSessionHandoffReviewPacketSummary !== restoredHandoffReviewPacket.summaryText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptAction !== "ready for handoff review" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPreflight !== "4/4 ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadge !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptPacketBadgeState !== "ready-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptDigest !== restoredHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptText !== restoredHandoffStatusbarReceiptText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationReceiptMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigestMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadgeMatches !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationDigest !== restoredHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBadge !== restoredHandoffReviewPacketVerificationBadge.badgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationBaseline !== "82/82/77/0" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptVerificationSummary !== restoredHandoffStatusbarReceiptVerificationSummary ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgePhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReady !== "true" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeStatus !== "verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeState !== "ready-receipt-verified" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeText !== restoredHandoffStatusbarReceiptBadgeText ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeReadyCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeTotalCount !== "3" ||
|
|
doc.body.dataset.evidenceSessionHandoffStatusbarReceiptBadgeDigest !== restoredHandoffReviewPacket.digest ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPhase !== "ready" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotAction !== "ready for handoff review" ||
|
|
doc.body.dataset.evidenceSessionHandoffExternalShellBadgeSnapshotPreflight !== "4/4 ready"
|
|
) {
|
|
throw new Error(`AXIS-style evidence/session handoff did not stay ready after restore: ${JSON.stringify(restoredEvidenceSessionHandoff)}`);
|
|
}
|
|
const virtualHalCommandScriptFixtures = api.getVirtualHalCommandScriptFixtureReport();
|
|
if (
|
|
virtualHalCommandScriptFixtures.apiName !== "linuxcnc-wasm-virtual-hal-command-script-fixture-report" ||
|
|
virtualHalCommandScriptFixtures.complete !== true ||
|
|
!virtualHalCommandScriptFixtures.coveredActions.includes("setp") ||
|
|
!virtualHalCommandScriptFixtures.coveredActions.includes("stop") ||
|
|
!virtualHalCommandScriptFixtures.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc")
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL command fixtures did not pass: ${JSON.stringify(virtualHalCommandScriptFixtures)}`);
|
|
}
|
|
const motionControllerStep = api.stepVirtualHalMotionController({
|
|
target: { x: 2.25 },
|
|
maxVelocity: 10,
|
|
dt: 0.1,
|
|
cycleCount: 2,
|
|
});
|
|
if (
|
|
motionControllerStep.apiName !== "linuxcnc-wasm-virtual-motion-controller-step" ||
|
|
motionControllerStep.ok !== true ||
|
|
motionControllerStep.state.position.x <= 1.75
|
|
) {
|
|
throw new Error(`AXIS-style virtual motion controller did not step: ${JSON.stringify(motionControllerStep)}`);
|
|
}
|
|
const motionControllerMatrix = api.getVirtualHalMotionControllerMatrixReport();
|
|
if (
|
|
motionControllerMatrix.apiName !== "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report" ||
|
|
motionControllerMatrix.complete !== true ||
|
|
motionControllerMatrix.webSimulationSatisfied !== true ||
|
|
motionControllerMatrix.manifestChecked !== true ||
|
|
motionControllerMatrix.missingManifestFiles.length !== 0 ||
|
|
!motionControllerMatrix.requiredPins.includes("motion.distance-to-go") ||
|
|
!motionControllerMatrix.requiredPins.includes("motion.in-position") ||
|
|
!motionControllerMatrix.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c") ||
|
|
!motionControllerMatrix.simConfigTargets.includes("external-offsets") ||
|
|
!motionControllerMatrix.simConfigTargets.includes("qtdragon-on-abort") ||
|
|
!motionControllerMatrix.simConfigTargets.includes("vismach-remap-sims") ||
|
|
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini") ||
|
|
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini") ||
|
|
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/vismach/puma/puma.ini") ||
|
|
!motionControllerMatrix.rows.every(({ manifestChecked, missingManifestFiles }) => manifestChecked === true && missingManifestFiles.length === 0) ||
|
|
!motionControllerMatrix.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true)
|
|
) {
|
|
throw new Error(`AXIS-style virtual motion controller matrix did not pass: ${JSON.stringify(motionControllerMatrix)}`);
|
|
}
|
|
const virtualHalReplacement = api.getVirtualHalSimulationReplacementReport();
|
|
const promotionCandidateReport = api.getVirtualHalSimConfigPromotionCandidateReport();
|
|
const promotionFamilyRows = api.getVirtualHalPromotionFamilyRows();
|
|
const macroLoadFixtureReport = api.getVirtualHalSimConfigMacroLoadFixtureReport();
|
|
if (
|
|
virtualHalReplacement.apiName !== "linuxcnc-wasm-virtual-hal-simulation-replacement-report" ||
|
|
virtualHalReplacement.ready !== true ||
|
|
virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true ||
|
|
virtualHalReplacement.replacements.halcmd.ready !== true ||
|
|
virtualHalReplacement.replacements["motion-controller"].ready !== true ||
|
|
virtualHalReplacement.sourceCompliance?.complete !== true ||
|
|
virtualHalReplacement.sourceCompliance?.webSimulationSatisfied !== true ||
|
|
virtualHalReplacement.simConfigSourceCoverage?.complete !== true ||
|
|
virtualHalReplacement.commandScriptFixtures?.complete !== true ||
|
|
virtualHalReplacement.motionControllerMatrix?.complete !== true ||
|
|
api.getVirtualHalSourceComplianceReport().complete !== true ||
|
|
api.getVirtualHalSourceComplianceReport().commandScriptFixtures?.complete !== true ||
|
|
api.getVirtualHalSourceComplianceReport().motionControllerMatrix?.complete !== true ||
|
|
!api.getVirtualHalSourceComplianceReport().sourceFiles.includes("linuxcnc/src/emc/motion/motion.c") ||
|
|
api.getVirtualHalSimConfigSourceCoverageReport().complete !== true ||
|
|
!api.getVirtualHalSimConfigSourceCoverageReport().sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc") ||
|
|
promotionCandidateReport.complete !== true ||
|
|
promotionCandidateReport.inventoryBaselineUnchanged !== true ||
|
|
doc.body.dataset.promotionFamilyReady !== "true" ||
|
|
doc.body.dataset.promotionFamilyCount !== "3" ||
|
|
promotionFamilyRows.length !== 3 ||
|
|
![
|
|
["qtdragon-on-abort", "6/6 ready; sources=12"],
|
|
["vismach-remap-sims", "1/1 ready; sources=3"],
|
|
["rose-engine-rcone-demo", "1/1 ready; sources=2"],
|
|
].every(([id, value]) =>
|
|
doc.querySelector(`[data-promotion-family-row="${id}"]`)?.dataset.promotionFamilyReady === "true" &&
|
|
doc.querySelector(`[data-promotion-family-value="${id}"]`)?.textContent === value
|
|
) ||
|
|
!expectedPromotionCandidates.every(([id, ...sourceFiles]) =>
|
|
promotionCandidateReport.rows.some((row) =>
|
|
row.id === id &&
|
|
row.complete === true &&
|
|
row.targetBrowserEvidence === "explicit-browser-diagnostics" &&
|
|
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
|
|
)
|
|
) ||
|
|
macroLoadFixtureReport.complete !== true ||
|
|
macroLoadFixtureReport.inventoryBaselineUnchanged !== true ||
|
|
macroLoadFixtureReport.standaloneMainViolations.length !== 0 ||
|
|
macroLoadFixtureReport.blockedFixturePromotionViolations.length !== 0 ||
|
|
macroLoadFixtureReport.blockedBoundaryEvidenceViolations.length !== 0 ||
|
|
macroLoadFixtureReport.auditPromotionViolations.length !== 0 ||
|
|
!expectedMacroLoadFixtures.every(([id, fixturePath, declaredOnly, declarationSourceFile, declarationValue, declarationLine, ...sourceFiles]) =>
|
|
macroLoadFixtureReport.rows.some((row) =>
|
|
row.id === id &&
|
|
row.complete === true &&
|
|
row.nonMainFixture === true &&
|
|
row.fixturePath === fixturePath &&
|
|
row.declaredOnly === declaredOnly &&
|
|
row.requiresDeclarationEvidence === true &&
|
|
row.declarationEvidenceReady === true &&
|
|
row.declarationEvidence?.sourceFile === declarationSourceFile &&
|
|
row.declarationEvidence?.key === "NGCGUI_SUBFILE" &&
|
|
row.declarationEvidence?.value === declarationValue &&
|
|
row.declarationEvidence?.line === declarationLine &&
|
|
sourceFiles.every((sourceFile) => row.sourceFiles.includes(sourceFile))
|
|
)
|
|
) ||
|
|
!expectedBlockedMacroLoadFixtures.every(([id, fixturePath, blockedKind]) =>
|
|
macroLoadFixtureReport.blockedRows.some((row) =>
|
|
row.id === id &&
|
|
row.fixturePath === fixturePath &&
|
|
row.blockedKind === blockedKind &&
|
|
row.excludedFromPositiveFixtures === true &&
|
|
row.boundaryEvidenceReady === true &&
|
|
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
|
|
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
|
|
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
|
|
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
|
|
row.complete === true
|
|
)
|
|
) ||
|
|
!expectedMacroLoadAuditCandidates.every(([id, fixturePath, auditStatus]) =>
|
|
macroLoadFixtureReport.auditRows.some((row) =>
|
|
row.id === id &&
|
|
row.fixturePath === fixturePath &&
|
|
row.auditStatus === auditStatus &&
|
|
row.promotionAllowed === false &&
|
|
row.excludedFromPositiveFixtures === true &&
|
|
row.boundaryEvidenceReady === true &&
|
|
row.boundaryEvidence?.baselineSummary?.executed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.passed === 28 &&
|
|
row.boundaryEvidence?.baselineSummary?.skipped === 131 &&
|
|
row.boundaryEvidence?.baselineSummary?.unexpectedFail === 0 &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv"] === "de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9" &&
|
|
row.boundaryEvidence?.sourceArtifactHashes?.["wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"] === "b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae" &&
|
|
row.boundaryEvidence?.boundarySummary?.recommendedBlocked === "UNAVAILABLE" &&
|
|
row.boundaryEvidence?.boundarySummary?.dependencies?.includes("missing_vendored_ini:") &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.vendored === 0 &&
|
|
row.boundaryEvidence?.iniBoundarySummary?.reportAvailable === 0 &&
|
|
row.complete === true
|
|
)
|
|
) ||
|
|
api.getVirtualRealtimeHalRuntimeReport().ready !== true ||
|
|
api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true
|
|
) {
|
|
throw new Error(`AXIS-style virtual HAL simulation replacement report drift: ${JSON.stringify(virtualHalReplacement)}`);
|
|
}
|
|
const urlView = api.applyAxisViewFromUrl("?axis_left=mdi&axis_right=dro");
|
|
if (
|
|
urlView.left !== "mdi" ||
|
|
urlView.right !== "dro" ||
|
|
doc.querySelector('[data-axis-panel="mdi"]')?.hidden ||
|
|
doc.querySelector('[data-axis-panel="dro"]')?.hidden ||
|
|
doc.body.dataset.axisLeftView !== "mdi" ||
|
|
doc.body.dataset.axisRightView !== "dro"
|
|
) {
|
|
throw new Error(`AXIS-style URL view state did not switch panels: ${JSON.stringify(urlView)}`);
|
|
}
|
|
api.applyAxisViewFromUrl("?axis_left=manual&axis_right=preview");
|
|
|
|
api.resetPlayback();
|
|
if (doc.body.dataset.playbackIndex !== "0") {
|
|
throw new Error("simulation playback did not reset to first frame");
|
|
}
|
|
const firstHead = doc.querySelector("[data-toolpath-head]");
|
|
const firstCx = firstHead?.getAttribute("cx");
|
|
const firstCy = firstHead?.getAttribute("cy");
|
|
const firstExecutedPoints = doc.querySelector("[data-toolpath-executed-polyline]")?.getAttribute("points") ?? "";
|
|
const firstThreeExecutedPoints = doc.querySelector("[data-toolpath-three]")?.dataset.threeExecutedPoints ?? "";
|
|
if (!doc.querySelector('[data-program-line="1"]') || doc.querySelector('[data-program-line="2"]')?.dataset.active !== "false") {
|
|
throw new Error("simulation reset playback did not render first active program line");
|
|
}
|
|
|
|
const secondFrame = api.stepPlayback(1);
|
|
if (secondFrame.index !== 1 || secondFrame.activeLine !== 2) {
|
|
throw new Error(`simulation step playback drift: ${JSON.stringify(secondFrame)}`);
|
|
}
|
|
const secondExecutedPoints = doc.querySelector("[data-toolpath-executed-polyline]")?.getAttribute("points") ?? "";
|
|
if (secondExecutedPoints === firstExecutedPoints || !secondExecutedPoints.includes("1,0")) {
|
|
throw new Error("simulation executed toolpath did not advance on step");
|
|
}
|
|
if (doc.querySelector("[data-toolpath-three]")?.dataset.threeExecutedPoints === firstThreeExecutedPoints) {
|
|
throw new Error("simulation Three.js executed toolpath did not advance on step");
|
|
}
|
|
if (firstHead?.getAttribute("cx") === firstCx && firstHead?.getAttribute("cy") === firstCy) {
|
|
throw new Error("simulation toolhead did not move on playback step");
|
|
}
|
|
if (doc.querySelector('[data-program-line="2"]')?.dataset.active !== "true") {
|
|
throw new Error("simulation step playback did not highlight active G-code line");
|
|
}
|
|
if (doc.querySelector('[data-motion-row="1"]')?.dataset.active !== "true") {
|
|
throw new Error("simulation step playback did not highlight active motion row");
|
|
}
|
|
|
|
doc.querySelector("[data-playback-next]")?.click();
|
|
if (doc.body.dataset.playbackIndex !== "2") {
|
|
throw new Error("AXIS-style toolbar step button did not advance playback");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true, cancelable: true }));
|
|
if (doc.body.dataset.playbackIndex !== "3" || doc.body.dataset.lastShortcut !== "ArrowRight Step") {
|
|
throw new Error("AXIS-style ArrowRight shortcut did not advance playback");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "ArrowLeft", bubbles: true, cancelable: true }));
|
|
if (doc.body.dataset.playbackIndex !== "2" || doc.body.dataset.lastShortcut !== "ArrowLeft Step") {
|
|
throw new Error("AXIS-style ArrowLeft shortcut did not reverse playback");
|
|
}
|
|
api.zoomPreview(1.5);
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "f", bubbles: true, cancelable: true }));
|
|
if (api.getPreviewState().zoom !== 1 || doc.body.dataset.lastShortcut !== "F Fit") {
|
|
throw new Error("AXIS-style fit shortcut did not reset preview zoom");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: " ", bubbles: true, cancelable: true }));
|
|
if (!api.isPlaybackRunning() || doc.body.dataset.lastShortcut !== "Space Play") {
|
|
throw new Error("AXIS-style Space shortcut did not start playback");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: " ", bubbles: true, cancelable: true }));
|
|
if (api.isPlaybackRunning() || doc.body.dataset.lastShortcut !== "Space Pause") {
|
|
throw new Error("AXIS-style Space shortcut did not pause playback");
|
|
}
|
|
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "r", bubbles: true, cancelable: true }));
|
|
for (let i = 0; i < 120 && api.getStatusHistory()[0]?.kind !== "run"; i += 1) {
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
}
|
|
if (
|
|
doc.body.dataset.lastShortcut !== "R Run" ||
|
|
api.getStatusHistory()[0]?.kind !== "run" ||
|
|
!api.getStatusHistory().some(({ kind, message }) => kind === "shortcut" && message === "R Run")
|
|
) {
|
|
throw new Error(`AXIS-style R shortcut did not record run action: ${JSON.stringify(api.getStatusHistory())}`);
|
|
}
|
|
|
|
const finishedFrame = api.finishPlayback();
|
|
if (finishedFrame.index !== state.motion.length - 1 || doc.body.dataset.playbackComplete !== "true") {
|
|
throw new Error("simulation finish playback did not reach final frame");
|
|
}
|
|
|
|
for (const program of programs) {
|
|
const nextState = await api.runProgramById(program.id);
|
|
assertRenderedState(doc, nextState);
|
|
const resetFrame = api.resetPlayback();
|
|
if (resetFrame.index !== 0 || doc.body.dataset.playbackIndex !== "0") {
|
|
throw new Error(`simulation playback reset failed for ${program.id}`);
|
|
}
|
|
if (nextState.program.id !== program.id) {
|
|
throw new Error(`simulation API returned wrong program id for ${program.id}`);
|
|
}
|
|
for (const motionType of program.expectedMotionTypes) {
|
|
if (!nextState.summary.motionTypes.includes(motionType)) {
|
|
throw new Error(`simulation program ${program.id} missing motion type ${motionType}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const arcState = await api.runProgramById("arc-g2-g3");
|
|
const arcPoints = doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points") ?? "";
|
|
if (!arcState.summary.motionTypes.includes("ARC_FEED")) {
|
|
throw new Error("arc simulation did not produce ARC_FEED");
|
|
}
|
|
if (!arcPoints.includes("1,-1") || !arcPoints.includes("0,0")) {
|
|
throw new Error(`arc simulation toolpath points did not use canonical arc endpoints: ${arcPoints}`);
|
|
}
|
|
const arcPreview = api.getPreviewState();
|
|
if (arcPreview.three.arcSamplePoints <= arcState.motion.filter(({ type }) => type === "ARC_FEED").length * 2) {
|
|
throw new Error(`arc simulation Three.js preview did not sample canonical arcs: ${JSON.stringify(arcPreview.three)}`);
|
|
}
|
|
|
|
const drillState = await api.runProgramById("drill-g81");
|
|
if (drillState.motion.filter(({ type }) => type === "STRAIGHT_FEED").length < 3) {
|
|
throw new Error("drill simulation did not produce expected feed plunges");
|
|
}
|
|
|
|
const customProgramText = [
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X2.5 Y0.5 Z0 F75",
|
|
"G1 X2.5 Y1.5 Z0",
|
|
"M2",
|
|
"",
|
|
].join("\n");
|
|
const customState = await api.runProgramText(customProgramText, {
|
|
label: "operator-real-part.ngc",
|
|
source: "text",
|
|
sourceLabel: "Operator pasted G-code",
|
|
});
|
|
assertRenderedState(doc, customState);
|
|
if (customState.program?.id !== "custom" || customState.program?.source !== "text") {
|
|
throw new Error("custom text program state metadata drift");
|
|
}
|
|
if (!customState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=2.5 y=0.5 z=0")) {
|
|
throw new Error("custom text program did not execute through LinuxCNC WASM");
|
|
}
|
|
if (doc.body.dataset.simulationProgramId !== "custom" || doc.body.dataset.simulationProgramSource !== "text") {
|
|
throw new Error("custom text program body dataset drift");
|
|
}
|
|
if (!doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Operator pasted G-code")) {
|
|
throw new Error("custom text program source did not render in statusbar");
|
|
}
|
|
if (!api.getProgramText().text.includes("G1 X2.5 Y0.5 Z0 F75")) {
|
|
throw new Error("custom text program did not sync into editable G-code pane");
|
|
}
|
|
const customRecentPrograms = api.getRecentPrograms();
|
|
if (
|
|
customRecentPrograms.length !== 1 ||
|
|
customRecentPrograms[0].source !== "text" ||
|
|
customRecentPrograms[0].label !== "operator-real-part.ngc" ||
|
|
!doc.querySelector("[data-recent-program-list]")?.textContent.includes("operator-real-part.ngc")
|
|
) {
|
|
throw new Error(`custom text program did not populate AXIS recent programs: ${JSON.stringify(customRecentPrograms)}`);
|
|
}
|
|
api.setProgramText("G90 G17 G0 X0 Y0 Z0\nM2\n", {
|
|
label: "temporary-editor.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
});
|
|
doc.querySelector("[data-recent-program-item]")?.click();
|
|
if (!api.getProgramText().text.includes("X2.5 Y0.5") || api.getProgramText().metadata.source !== "recent") {
|
|
throw new Error(`recent program item did not restore text into editor: ${JSON.stringify(api.getProgramText())}`);
|
|
}
|
|
|
|
const machineStatusProgram = [
|
|
"S1200 M3",
|
|
"M5",
|
|
"M7",
|
|
"M8",
|
|
"M9",
|
|
"M48",
|
|
"M50 P0",
|
|
"M50 P1",
|
|
"T2",
|
|
"M6",
|
|
"G43 H2",
|
|
"G49",
|
|
"M61 Q2",
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X1 Y1 Z0 F50",
|
|
"M2",
|
|
"",
|
|
].join("\n");
|
|
const machineStatusRun = await api.runProgramText(machineStatusProgram, {
|
|
label: "axis-machine-status.ngc",
|
|
source: "text",
|
|
sourceLabel: "AXIS machine status probe",
|
|
});
|
|
assertRenderedState(doc, machineStatusRun);
|
|
const machineStatus = api.getMachineStatusState();
|
|
if (
|
|
machineStatus.spindle.state !== "off" ||
|
|
machineStatus.spindle.direction !== "cw" ||
|
|
machineStatus.spindle.speed !== "1200" ||
|
|
machineStatus.coolant.mist !== "off" ||
|
|
machineStatus.coolant.flood !== "off" ||
|
|
machineStatus.tool.selected !== "2" ||
|
|
machineStatus.tool.current !== "2" ||
|
|
machineStatus.tool.lengthOffset !== "z=0.000" ||
|
|
machineStatus.overrides.feed !== "enabled"
|
|
) {
|
|
throw new Error(`simulation machine status did not derive from LinuxCNC canonical output: ${JSON.stringify(machineStatus)}`);
|
|
}
|
|
if (
|
|
doc.querySelector('[data-machine-status="spindle-speed"]')?.textContent !== "1200" ||
|
|
doc.querySelector('[data-machine-status="coolant-flood"]')?.textContent !== "off" ||
|
|
doc.querySelector('[data-machine-status="tool-current"]')?.textContent !== "2"
|
|
) {
|
|
throw new Error("simulation machine status did not render into AXIS panels");
|
|
}
|
|
|
|
const fileProgram = new frame.contentWindow.File(
|
|
[customProgramText.replace("X2.5", "X3.25")],
|
|
"real-loaded-file.ngc",
|
|
{ type: "text/plain" },
|
|
);
|
|
const fileState = await api.loadProgramFile(fileProgram);
|
|
assertRenderedState(doc, fileState);
|
|
if (fileState.program?.source !== "file" || fileState.program?.filename !== "real-loaded-file.ngc") {
|
|
throw new Error("loaded file program metadata drift");
|
|
}
|
|
if (!fileState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=3.25 y=0.5 z=0")) {
|
|
throw new Error("loaded file program did not execute changed G-code through LinuxCNC WASM");
|
|
}
|
|
if (!api.getProgramText().metadata.filename || !api.getProgramText().text.includes("G1 X3.25 Y0.5 Z0 F75")) {
|
|
throw new Error("loaded file program did not sync into editor state");
|
|
}
|
|
|
|
const loadedText = api.loadProgramText(
|
|
"G90 G17 G0 X0 Y0 Z0\nG1 X3.5 Y1.75 Z0 F82\nM2\n",
|
|
{
|
|
label: "loaded-through-api.ngc",
|
|
source: "loaded-text",
|
|
sourceLabel: "Loaded through API",
|
|
},
|
|
);
|
|
if (!loadedText.text.includes("X3.5 Y1.75") || api.getProgramText().metadata.source !== "loaded-text") {
|
|
throw new Error(`loadProgramText did not stage editor text: ${JSON.stringify(loadedText)}`);
|
|
}
|
|
if (doc.body.dataset.simulationProgramSource !== "loaded-text" || !doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Loaded through API")) {
|
|
throw new Error("loadProgramText did not render source metadata");
|
|
}
|
|
|
|
doc.querySelector("[data-mdi-program-text]").value = [
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X4.5 Y1.125 Z0 F85",
|
|
"M2",
|
|
"",
|
|
].join("\n");
|
|
doc.querySelector("[data-load-mdi-program]")?.click();
|
|
if (!api.getProgramText().text.includes("X4.5 Y1.125") || !doc.querySelector("[data-mdi-status]")?.textContent.includes("Loaded 3 MDI lines")) {
|
|
throw new Error("MDI load control did not stage text into editor");
|
|
}
|
|
const mdiState = await api.runMdiProgramText();
|
|
assertRenderedState(doc, mdiState);
|
|
if (mdiState.program?.source !== "mdi" || !mdiState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=4.5 y=1.125 z=0")) {
|
|
throw new Error("MDI text did not execute through LinuxCNC WASM");
|
|
}
|
|
const mdiHistory = api.getMdiHistory();
|
|
if (
|
|
mdiHistory.length !== 1 ||
|
|
!mdiHistory[0].summary.includes("X4.5 Y1.125") ||
|
|
doc.body.dataset.mdiHistoryCount !== "1" ||
|
|
!doc.querySelector("[data-mdi-history-list]")?.textContent.includes("X4.5 Y1.125")
|
|
) {
|
|
throw new Error(`MDI history did not record successful LinuxCNC-backed run: ${JSON.stringify(mdiHistory)}`);
|
|
}
|
|
doc.querySelector("[data-mdi-program-text]").value = "G90 G17 G0 X0 Y0 Z0\nM2\n";
|
|
doc.querySelector("[data-mdi-history-item]")?.click();
|
|
if (!doc.querySelector("[data-mdi-program-text]")?.value.includes("X4.5 Y1.125")) {
|
|
throw new Error("MDI history item did not restore text into the MDI pane");
|
|
}
|
|
api.clearMdiHistory();
|
|
if (api.getMdiHistory().length !== 0 || doc.body.dataset.mdiHistoryCount !== "0" || !doc.querySelector("[data-mdi-history-list]")?.textContent.includes("No MDI history")) {
|
|
throw new Error(`MDI history clear did not reset API/DOM state: ${JSON.stringify(api.getMdiHistory())}`);
|
|
}
|
|
|
|
api.setProgramText(
|
|
[
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X4.75 Y2.25 Z0 F95",
|
|
"M2",
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
label: "edited-in-axis-pane.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
},
|
|
);
|
|
const editorState = api.getProgramText();
|
|
if (!editorState.text.includes("X4.75") || editorState.metadata.label !== "edited-in-axis-pane.ngc") {
|
|
throw new Error("simulation editable G-code state did not accept setProgramText");
|
|
}
|
|
const editorRunState = await api.runEditorProgramText();
|
|
assertRenderedState(doc, editorRunState);
|
|
if (!editorRunState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=4.75 y=2.25 z=0")) {
|
|
throw new Error("editor G-code did not execute changed text through LinuxCNC WASM");
|
|
}
|
|
if (!doc.querySelector("[data-program-editor]")?.value.includes("X4.75")) {
|
|
throw new Error("editor DOM did not retain edited G-code text");
|
|
}
|
|
|
|
const opfsProgramText = [
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X6.5 Y1.25 Z0 F105",
|
|
"M2",
|
|
"",
|
|
].join("\n");
|
|
api.setProgramText(opfsProgramText, {
|
|
label: "axis-opfs-roundtrip.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
});
|
|
doc.querySelector("[data-opfs-program-filename]").value = "axis-opfs-roundtrip.ngc";
|
|
const savedOpfs = await api.saveProgramToOpfs();
|
|
if (savedOpfs.opfsPath !== "linuxcnc/gcode/axis-opfs-roundtrip.ngc" || !savedOpfs.ready) {
|
|
throw new Error(`simulation OPFS save metadata drift: ${JSON.stringify(savedOpfs)}`);
|
|
}
|
|
api.setProgramText("G90 G17 G0 X0 Y0 Z0\nG1 X0.125 Y0.125 Z0 F10\nM2\n", {
|
|
label: "unsaved-editor-change.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
});
|
|
const loadedOpfsState = await api.loadProgramFromOpfs("axis-opfs-roundtrip.ngc");
|
|
assertRenderedState(doc, loadedOpfsState);
|
|
if (loadedOpfsState.program?.source !== "opfs" || loadedOpfsState.program?.opfsPath !== "linuxcnc/gcode/axis-opfs-roundtrip.ngc") {
|
|
throw new Error(`simulation OPFS load metadata drift: ${JSON.stringify(loadedOpfsState.program)}`);
|
|
}
|
|
if (!loadedOpfsState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=6.5 y=1.25 z=0")) {
|
|
throw new Error("OPFS loaded program did not execute saved G-code through LinuxCNC WASM");
|
|
}
|
|
if (api.getProgramText().metadata.source !== "opfs" || !api.getProgramText().text.includes("X6.5 Y1.25")) {
|
|
throw new Error("OPFS loaded program did not sync into editable G-code pane");
|
|
}
|
|
if (doc.body.dataset.simulationProgramSource !== "opfs" || !doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Loaded OPFS: linuxcnc/gcode/axis-opfs-roundtrip.ngc")) {
|
|
throw new Error("OPFS loaded program source did not render in statusbar");
|
|
}
|
|
const opfsState = api.getOpfsProgramState();
|
|
if (opfsState.status !== "Loaded OPFS: linuxcnc/gcode/axis-opfs-roundtrip.ngc") {
|
|
throw new Error(`simulation OPFS state status drift: ${JSON.stringify(opfsState)}`);
|
|
}
|
|
|
|
const machineId = "axis-browser-readiness";
|
|
const sessionId = "axis-browser-readiness-session";
|
|
const snapshotFilename = "axis-browser-readiness.json";
|
|
const gcodeFilename = "axis-browser-readiness.ngc";
|
|
const paths = machineFilePaths(machineId);
|
|
const initialReadiness = await api.checkMachineSessionReadiness({
|
|
machineId,
|
|
sessionId,
|
|
snapshotFilename,
|
|
gcodeFilename,
|
|
});
|
|
if (initialReadiness.ready || !initialReadiness.missing.includes("ini") || !initialReadiness.missing.includes("gcode")) {
|
|
throw new Error(`simulation initial machine readiness should be blocked: ${JSON.stringify(initialReadiness)}`);
|
|
}
|
|
if (doc.body.dataset.machineSessionReady !== "false" || doc.querySelector("[data-machine-readiness-phase]")?.textContent !== "blocked") {
|
|
throw new Error("simulation blocked machine readiness did not render");
|
|
}
|
|
|
|
await saveMachineTextFiles(machineId, {
|
|
ini: `[EMC]
|
|
MACHINE = axis-browser-readiness
|
|
|
|
[RS274NGC]
|
|
PARAMETER_FILE = linuxcnc.var
|
|
|
|
[EMCIO]
|
|
TOOL_TABLE = tool.tbl
|
|
`,
|
|
parameters: "5161 0.0\n5162 0.0\n5220 1\n5221 0.0\n5399 0.0\n",
|
|
toolTable: "T1 P1 Z0.0 D0.125 ;axis readiness\n",
|
|
});
|
|
await saveGcodeProgram(gcodeFilename, "G0 X0 Y0\nG1 X1 Y1 F20\nM2\n");
|
|
await saveMachineSessionSnapshot(sessionId, machineId, {
|
|
filename: snapshotFilename,
|
|
gcodeFilename,
|
|
createdAt: "2026-06-16T00:00:00.000Z",
|
|
metadata: {
|
|
workflow: "axis-real-simulation-readiness-smoke",
|
|
},
|
|
});
|
|
const readyReadiness = await api.checkMachineSessionReadiness({
|
|
machineId,
|
|
sessionId,
|
|
snapshotFilename,
|
|
gcodeFilename,
|
|
});
|
|
if (!readyReadiness.ready || readyReadiness.phase !== "ready" || readyReadiness.missing.length !== 0) {
|
|
throw new Error(`simulation machine readiness should be ready: ${JSON.stringify(readyReadiness)}`);
|
|
}
|
|
if (readyReadiness.paths.ini !== paths.ini || readyReadiness.paths.gcode !== gcodeProgramPath(gcodeFilename)) {
|
|
throw new Error(`simulation machine readiness paths drift: ${JSON.stringify(readyReadiness.paths)}`);
|
|
}
|
|
if (api.getMachineReadiness().machineId !== machineId || doc.body.dataset.machineSessionReady !== "true") {
|
|
throw new Error("simulation ready machine readiness state did not sync to API/body");
|
|
}
|
|
if (
|
|
doc.querySelector("[data-machine-readiness-phase]")?.textContent !== "ready" ||
|
|
doc.querySelector("[data-machine-readiness-missing]")?.textContent !== "none" ||
|
|
doc.querySelector('[data-machine-readiness-path="ini"]')?.textContent !== paths.ini ||
|
|
doc.querySelector('[data-machine-readiness-path="gcode"]')?.textContent !== gcodeProgramPath(gcodeFilename)
|
|
) {
|
|
throw new Error("simulation ready machine readiness did not render expected paths");
|
|
}
|
|
const loadedSession = await api.loadReadyMachineSession({
|
|
machineId,
|
|
sessionId,
|
|
snapshotFilename,
|
|
gcodeFilename,
|
|
iniWasmPath: "/work/axis-smoke/machine.ini",
|
|
parameterWasmPath: "/work/axis-smoke/linuxcnc.var",
|
|
toolTableWasmPath: "/work/axis-smoke/tool.tbl",
|
|
});
|
|
if (!loadedSession.loaded || loadedSession.phase !== "loaded") {
|
|
throw new Error(`simulation machine session should load into WASM: ${JSON.stringify(loadedSession)}`);
|
|
}
|
|
if (
|
|
loadedSession.ini.wasmPath !== "/work/axis-smoke/machine.ini" ||
|
|
loadedSession.parameters.wasmPath !== "/work/axis-smoke/linuxcnc.var" ||
|
|
loadedSession.toolTable.wasmPath !== "/work/axis-smoke/tool.tbl"
|
|
) {
|
|
throw new Error(`simulation machine session WASM path drift: ${JSON.stringify(loadedSession)}`);
|
|
}
|
|
if (
|
|
api.getMachineSessionLoadState().machineId !== machineId ||
|
|
doc.body.dataset.machineSessionLoaded !== "true" ||
|
|
doc.querySelector('[data-machine-session-loaded-path="ini"]')?.textContent !== "/work/axis-smoke/machine.ini" ||
|
|
doc.querySelector('[data-machine-session-loaded-path="parameters"]')?.textContent !== "/work/axis-smoke/linuxcnc.var" ||
|
|
doc.querySelector('[data-machine-session-loaded-path="toolTable"]')?.textContent !== "/work/axis-smoke/tool.tbl"
|
|
) {
|
|
throw new Error("simulation machine session load state did not sync to API/body/DOM");
|
|
}
|
|
const toolTableSummary = api.getToolTableSummary();
|
|
if (
|
|
toolTableSummary.state !== "loaded" ||
|
|
toolTableSummary.wasmPath !== "/work/axis-smoke/tool.tbl" ||
|
|
toolTableSummary.toolCount !== 1 ||
|
|
!toolTableSummary.firstTool.includes("T1 P1") ||
|
|
!doc.querySelector('[data-tool-table-summary="paths"]')?.textContent.includes("/work/axis-smoke/tool.tbl") ||
|
|
!doc.querySelector('[data-tool-table-summary="tool"]')?.textContent.includes("axis readiness") ||
|
|
doc.querySelector('[data-tool-table-row="1"]')?.children.length !== 5 ||
|
|
!doc.querySelector('[data-tool-table-row="1"]')?.textContent.includes("axis readiness")
|
|
) {
|
|
throw new Error(`simulation tool table summary did not render loaded session tool table: ${JSON.stringify(toolTableSummary)}`);
|
|
}
|
|
if (!api.getStatusHistory().some(({ kind, message }) => kind === "session" && message.includes("/work/axis-smoke/tool.tbl"))) {
|
|
throw new Error(`simulation status history should record loaded tool table session: ${JSON.stringify(api.getStatusHistory())}`);
|
|
}
|
|
const loadedDiagnosticsArtifact = api.exportDiagnosticsArtifact();
|
|
if (
|
|
loadedDiagnosticsArtifact.toolTable.state !== "loaded" ||
|
|
loadedDiagnosticsArtifact.toolTable.rows.length !== 1 ||
|
|
loadedDiagnosticsArtifact.limitsHome.axes.x.fault !== "none" ||
|
|
!loadedDiagnosticsArtifact.statusHistory.some(({ kind }) => kind === "session")
|
|
) {
|
|
throw new Error(`simulation diagnostics artifact did not include session/tool/limits state: ${JSON.stringify(loadedDiagnosticsArtifact)}`);
|
|
}
|
|
if (api.getRunSummary().sessionLoadPhase !== "loaded" || doc.querySelector('[data-run-summary="session-state"]')?.textContent !== "ready / loaded") {
|
|
throw new Error(`simulation run summary did not reflect ready loaded session: ${JSON.stringify(api.getRunSummary())}`);
|
|
}
|
|
const loadedRunControl = api.getRunControlState();
|
|
if (loadedRunControl.phase !== "loaded" || loadedRunControl.loadedIniPath !== "/work/axis-smoke/machine.ini") {
|
|
throw new Error(`simulation run control should report loaded session availability before session-backed run: ${JSON.stringify(loadedRunControl)}`);
|
|
}
|
|
api.setProgramText(
|
|
[
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X7.25 Y3.5 Z0 F115",
|
|
"M2",
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
label: "axis-session-editor.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
},
|
|
);
|
|
const sessionEditorRun = await api.runEditorProgramText();
|
|
assertRenderedState(doc, sessionEditorRun);
|
|
if (sessionEditorRun.execution?.mode !== "linuxcnc-wasm-with-ini" || sessionEditorRun.execution?.iniPath !== "/work/axis-smoke/machine.ini") {
|
|
throw new Error(`simulation editor run did not use loaded session INI: ${JSON.stringify(sessionEditorRun.execution)}`);
|
|
}
|
|
if (!sessionEditorRun.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=7.25 y=3.5 z=0")) {
|
|
throw new Error("simulation editor run with loaded session did not execute expected G-code");
|
|
}
|
|
if (!sessionEditorRun.program?.sourceLabel.includes("Session INI: /work/axis-smoke/machine.ini")) {
|
|
throw new Error(`simulation editor run source label missing loaded session INI: ${sessionEditorRun.program?.sourceLabel}`);
|
|
}
|
|
if (
|
|
api.getRunMode().mode !== "session-backed" ||
|
|
api.getRunMode().iniPath !== "/work/axis-smoke/machine.ini" ||
|
|
doc.body.dataset.runMode !== "session-backed" ||
|
|
!doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Session-backed: /work/axis-smoke/machine.ini")
|
|
) {
|
|
throw new Error("simulation run mode did not switch to session-backed after loaded-session execution");
|
|
}
|
|
if (
|
|
api.getRunSummary().executionMode !== "linuxcnc-wasm-with-ini" ||
|
|
api.getRunSummary().sessionIniPath !== "/work/axis-smoke/machine.ini" ||
|
|
doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "/work/axis-smoke/machine.ini" ||
|
|
api.getRunControlState().phase !== "ready"
|
|
) {
|
|
throw new Error(`simulation run summary missing session-backed execution details: ${JSON.stringify(api.getRunSummary())}`);
|
|
}
|
|
const standaloneMode = api.setUseLoadedSession(false);
|
|
if (standaloneMode.mode !== "standalone" || standaloneMode.useLoadedSession !== false || doc.querySelector("[data-use-loaded-session]")?.checked !== false) {
|
|
throw new Error(`simulation run-mode toggle did not disable loaded session: ${JSON.stringify(standaloneMode)}`);
|
|
}
|
|
api.setProgramText(
|
|
[
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X7.75 Y3.75 Z0 F116",
|
|
"M2",
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
label: "axis-standalone-after-session.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
},
|
|
);
|
|
const standaloneEditorRun = await api.runEditorProgramText();
|
|
assertRenderedState(doc, standaloneEditorRun);
|
|
if (standaloneEditorRun.execution?.mode !== "linuxcnc-wasm" || standaloneEditorRun.execution?.iniPath !== null) {
|
|
throw new Error(`simulation standalone toggle run should not use loaded session INI: ${JSON.stringify(standaloneEditorRun.execution)}`);
|
|
}
|
|
if (api.getRunMode().mode !== "standalone" || doc.body.dataset.runMode !== "standalone") {
|
|
throw new Error("simulation run mode should stay standalone after disabled-session run");
|
|
}
|
|
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "-") {
|
|
throw new Error(`simulation run summary should return to standalone details: ${JSON.stringify(api.getRunSummary())}`);
|
|
}
|
|
if (api.getRunControlState().reason !== "Loaded machine session bypassed by Use Session toggle") {
|
|
throw new Error(`simulation run control should explain loaded-session bypass: ${JSON.stringify(api.getRunControlState())}`);
|
|
}
|
|
const restoredSessionMode = api.setUseLoadedSession(true);
|
|
if (restoredSessionMode.useLoadedSession !== true || doc.querySelector("[data-use-loaded-session]")?.checked !== true) {
|
|
throw new Error(`simulation run-mode toggle did not re-enable loaded session: ${JSON.stringify(restoredSessionMode)}`);
|
|
}
|
|
|
|
api.setProgramText(
|
|
[
|
|
"G90 G17 G0 X0 Y0 Z0",
|
|
"G1 X8.5 Y4.25 Z0 F125",
|
|
"M2",
|
|
"",
|
|
].join("\n"),
|
|
{
|
|
label: "axis-session-opfs.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
},
|
|
);
|
|
doc.querySelector("[data-opfs-program-filename]").value = "axis-session-opfs.ngc";
|
|
await api.saveProgramToOpfs();
|
|
api.setProgramText("G90 G17 G0 X0 Y0 Z0\nG1 X0.5 Y0.5 Z0 F10\nM2\n", {
|
|
label: "unsaved-session-editor-change.ngc",
|
|
source: "editor",
|
|
sourceLabel: "Editor text",
|
|
});
|
|
const sessionOpfsRun = await api.loadProgramFromOpfs("axis-session-opfs.ngc");
|
|
assertRenderedState(doc, sessionOpfsRun);
|
|
if (sessionOpfsRun.execution?.mode !== "linuxcnc-wasm-with-ini" || sessionOpfsRun.execution?.iniPath !== "/work/axis-smoke/machine.ini") {
|
|
throw new Error(`simulation OPFS run did not use loaded session INI: ${JSON.stringify(sessionOpfsRun.execution)}`);
|
|
}
|
|
if (!sessionOpfsRun.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=8.5 y=4.25 z=0")) {
|
|
throw new Error("simulation OPFS run with loaded session did not execute saved G-code");
|
|
}
|
|
if (!sessionOpfsRun.program?.sourceLabel.includes("Loaded OPFS: linuxcnc/gcode/axis-session-opfs.ngc") || !sessionOpfsRun.program?.sourceLabel.includes("Session INI: /work/axis-smoke/machine.ini")) {
|
|
throw new Error(`simulation OPFS run source label missing OPFS/session details: ${sessionOpfsRun.program?.sourceLabel}`);
|
|
}
|
|
if (
|
|
api.getRunSummary().opfsProgramPath !== "linuxcnc/gcode/axis-session-opfs.ngc" ||
|
|
doc.querySelector('[data-run-summary="opfs-program"]')?.textContent !== "linuxcnc/gcode/axis-session-opfs.ngc"
|
|
) {
|
|
throw new Error(`simulation run summary missing OPFS program path: ${JSON.stringify(api.getRunSummary())}`);
|
|
}
|
|
|
|
document.getElementById("axis-diagnostics-artifact").textContent = JSON.stringify(api.exportDiagnosticsArtifact());
|
|
status.textContent = "browser_real_simulation_page_smoke=ok";
|
|
} catch (error) {
|
|
status.textContent = `browser_real_simulation_page_smoke=fail ${error.stack || error.message}`;
|
|
throw error;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|