import assert from "node:assert/strict"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; import { createFullLinuxCncExecutionBoundary } from "../../app/src/runtime/full-execution-boundary.js"; import { createNativeTaskHalReadinessAudit } from "../../app/src/runtime/native-task-hal-audit.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const projectDir = resolve(__dirname, "../.."); const rootDir = resolve(projectDir, ".."); const phase0Script = resolve(rootDir, "wasm-port/tests/native/verify_task_hal_phase0.sh"); const manifestLog = resolve(rootDir, "wasm-port/build/task-hal/verify_task_hal_source_manifest.stdout.log"); const probeLog = resolve(rootDir, "wasm-port/build/task-hal/probe_trt_task_hal_runtime.stdout.log"); const manifestReport = resolve(rootDir, "wasm-port/build/task-hal/task-hal-source-manifest.tsv"); const artifactDir = resolve(projectDir, "build/readiness"); const artifactPath = resolve(artifactDir, "native-task-hal-readiness.json"); run(phase0Script); const sourceManifest = parseKeyValueFile(manifestLog); const nativeProbe = parseKeyValueFile(probeLog); const fullExecutionBoundary = createFullLinuxCncExecutionBoundary(createPromotedSimulationState()); const audit = createNativeTaskHalReadinessAudit({ sourceManifest, nativeProbe, fullExecutionBoundary, taskHalRuntimeReadiness: { taskRuntimeReady: true, motionRuntimeReady: true, halRuntimeReady: true, halSyncReady: true, }, taskHalStatus: { summary: { taskRuntimeReady: true, motionRuntimeReady: true, halRuntimeReady: true, halSyncReady: true, taskHalComparisonReady: true, }, }, generatedAt: "2026-06-22T00:00:00.000Z", artifactPaths: { readinessJson: relativeFromRoot(artifactPath), sourceManifestLog: relativeFromRoot(manifestLog), sourceManifestReport: relativeFromRoot(manifestReport), nativeProbeLog: relativeFromRoot(probeLog), }, }); assert.equal(audit.status, "ok"); assert.equal(audit.taskHalWebSimulationBoundaryConsistent, true); assert.equal(audit.webSimulation.promoted, true); assert.equal(audit.webSimulation.nativeTaskReady, true); assert.equal(audit.webSimulation.nativeHalSyncReady, true); assert.equal(audit.webSimulation.fullLinuxCncProgramExecutionReady, true); assert.equal(audit.nativeHostAndHardware.nativeProbe, "ok"); assert.match(audit.nativeHostAndHardware.nativeProbeStatus, /^(passed|ready_disabled_by_default|skipped_missing_host_runtime)$/); assert.equal(audit.nativeHostAndHardware.nativePromotionAllowed, false); assert.equal(audit.nativeHostAndHardware.hardwareDrive, false); assert.equal(audit.nativeHostAndHardware.hostRealtimeKernel, false); assert.equal(audit.nativeHostAndHardware.externalUserMProcessReady, false); assert.equal(audit.nativeHostAndHardware.toolDbProcessReady, false); assert.equal(audit.sourceManifest.ready, true); assert.equal(audit.sourceManifest.taskSourceCount > 0, true); assert.equal(audit.sourceManifest.halSourceCount > 0, true); assert.equal(audit.sourceManifest.motionSourceCount > 0, true); assert.equal(audit.gates.task_hal_web_simulation_boundary_consistent, 1); assert.equal(audit.gates.hardware_drive, 0); assert.equal(audit.gates.host_realtime_kernel, 0); assert.equal(audit.gates.external_user_m_process_ready, 0); assert.equal(audit.gates.tool_db_process_ready, 0); assert.equal(audit.gates.promotion_scope, "web_simulation_only"); mkdirSync(artifactDir, { recursive: true }); writeFileSync(artifactPath, `${JSON.stringify(audit, null, 2)}\n`); const saved = JSON.parse(readFileSync(artifactPath, "utf8")); assert.equal(saved.apiName, "web-rtcp-5axis-native-task-hal-readiness-audit"); assert.equal(saved.status, "ok"); console.log("native_task_hal_source_artifact_audit=ok"); console.log(`native_task_hal_readiness_artifact=${relativeFromRoot(artifactPath)}`); console.log("task_hal_web_simulation_boundary_consistent=1"); console.log(`native_task_hal_host_probe_status=${audit.nativeHostAndHardware.nativeProbeStatus}`); console.log("hardware_drive=0"); console.log("host_realtime_kernel=0"); console.log("external_user_m_process_ready=0"); console.log("tool_db_process_ready=0"); console.log("promotion_scope=web_simulation_only"); function createPromotedSimulationState() { const programExecution = { sourceMode: "linuxcnc-interpreter-wasm", plannerTiming: { plannerRuntimeReady: true, semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion", }, summary: { motionEventCount: 3, canonicalEventCount: 8, plannerRuntimeReady: true, }, }; return { machineProfile: "xyzac-trt", linuxCncBoundaryAdapter: { linuxCncKinematicsReady: true, linuxCncInterpreterReady: true, }, rtcpFrame: { semanticBoundary: "linuxcnc_kinematics_wasm_c_abi", readiness: { linuxCncKinematicsReady: true }, }, interpreterRuntimeReadiness: { loaded: true, semanticBoundary: "linuxcnc_interpreter_wasm_canonical_events", }, programExecution, machineFileStaging: { status: "staged", fileCount: 12, }, machineFileExecution: { sourceMode: "linuxcnc-machine-file-remap-wasm", summary: { machineFileExecutionReady: true }, resultText: [ "fiveaxis_ini_open=1", "fiveaxis_remaps_ready=1", "fiveaxis_file_reached_exit=1", "fiveaxis_hal_switchkins: rc=0 found=1 value=1", ].join("\n"), }, taskHalRuntimeReadiness: { taskRuntimeReady: true, motionRuntimeReady: true, halRuntimeReady: true, halSyncReady: true, }, taskHalStatus: { summary: { taskRuntimeReady: true, motionRuntimeReady: true, halRuntimeReady: true, halSyncReady: true, taskHalComparisonReady: true, }, ui: { taskCycle: 2, servoCycle: 20, motionQueueDepth: 0, halChangedPinCount: 4, }, }, }; } function run(scriptPath) { const result = spawnSync("bash", [scriptPath], { cwd: rootDir, encoding: "utf8", }); if (result.status !== 0) { process.stdout.write(result.stdout || ""); process.stderr.write(result.stderr || ""); throw new Error(`${scriptPath} failed with status ${result.status}`); } } function parseKeyValueFile(path) { const fields = {}; for (const line of readFileSync(path, "utf8").split(/\r?\n/)) { if (!line || line.startsWith("#")) continue; const equals = line.indexOf("="); if (equals <= 0) continue; fields[line.slice(0, equals)] = line.slice(equals + 1); } return fields; } function relativeFromRoot(path) { return path.startsWith(`${rootDir}/`) ? path.slice(rootDir.length + 1) : path; }