import fs from "node:fs/promises"; import path from "node:path"; import { spawnSync } from "node:child_process"; const REPO_ROOT = "/home/meswork/cnc_wams"; const OUTPUT_DIR = path.join(REPO_ROOT, "qa/web-rtcp-5axis-site-test/output"); const REPORT_JSON = path.join(OUTPUT_DIR, "native-task-hal-comparison-report.json"); const REPORT_MD = path.join(OUTPUT_DIR, "native-task-hal-comparison-report.md"); const READINESS_JSON = path.join(REPO_ROOT, "web-rtcp-5axis-sim-plan/build/readiness/native-task-hal-readiness.json"); await fs.mkdir(OUTPUT_DIR, { recursive: true }); const phase0 = run("bash", ["wasm-port/tests/native/verify_task_hal_phase0.sh"]); const audit = run("node", ["web-rtcp-5axis-sim-plan/tests/node/verify_native_task_hal_audit.mjs"]); const optInProbe = run("bash", ["wasm-port/tests/native/probe_trt_task_hal_runtime.sh"], { env: { ...process.env, ENABLE_TRT_TASK_HAL_RUNTIME_PROBE: "1" }, }); const fixtureBaseline = run("bash", ["wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh"], { env: { ...process.env, LD_LIBRARY_PATH: path.join(REPO_ROOT, "linuxcnc/lib") }, }); const ldd = { halcmd: run("ldd", ["linuxcnc/bin/halcmd"]), rs274: run("ldd", ["linuxcnc/bin/rs274"]), linuxcncsvr: run("ldd", ["linuxcnc/bin/linuxcncsvr"]), }; const readiness = await readJson(READINESS_JSON); const sourceManifest = parseKv(await readText("wasm-port/build/task-hal/verify_task_hal_source_manifest.stdout.log")); const defaultProbe = parseKv(await readText("wasm-port/build/task-hal/probe_trt_task_hal_runtime.stdout.log")); const optInProbeFields = parseKv(optInProbe.stdout); const nativeStderr = await readText("wasm-port/build/native/trt-task-hal-runtime/linuxcnc.stderr.log"); const hostBlockers = [ ...missingFromLdd(ldd.halcmd.combined, "halcmd"), ...missingFromLdd(ldd.rs274.combined, "rs274"), ...missingFromLdd(ldd.linuxcncsvr.combined, "linuxcncsvr"), ]; if (nativeStderr.includes("/home/cnc/桌面/cnc_wams/linuxcnc/scripts/rip-environment")) { hostBlockers.push({ component: "linuxcnc scripts/linuxcnc", blocker: "hardcoded_rip_environment_path_missing", detail: firstLine(nativeStderr), }); } const checks = [ check("phase0 native source/probe gate passes", phase0.status === 0, oneLine(phase0.combined)), check("native readiness audit passes", audit.status === 0, oneLine(audit.combined)), check("source manifest ready", sourceManifest.task_hal_source_manifest_ready === "1", JSON.stringify(sourceManifest)), check("TRT source proof ready", defaultProbe.trt_task_hal_source_proof_ready === "1", JSON.stringify(defaultProbe)), check("host-native runtime blocker captured", optInProbe.status !== 0 && hostBlockers.length > 0, JSON.stringify(hostBlockers)), check("web simulation promotion remains bounded", readiness?.promotionScope === "web_simulation_only", JSON.stringify(readiness?.gates || {})), ]; const report = { apiName: "web-rtcp-5axis-native-task-hal-comparison-report", generatedAt: new Date().toISOString(), status: checks.every((item) => item.pass) ? "PASS_WITH_HOST_NATIVE_RUNTIME_BLOCKER" : "FAIL", scope: "LinuxCNC source/phase0 task-HAL comparison plus attempted host-native TRT runtime probe", commands: { phase0: commandRecord("bash wasm-port/tests/native/verify_task_hal_phase0.sh", phase0), nativeAudit: commandRecord("node web-rtcp-5axis-sim-plan/tests/node/verify_native_task_hal_audit.mjs", audit), optInNativeProbe: commandRecord("ENABLE_TRT_TASK_HAL_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_trt_task_hal_runtime.sh", optInProbe), fixtureBaseline: commandRecord("LD_LIBRARY_PATH=linuxcnc/lib bash wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh", fixtureBaseline), }, readiness, sourceManifest, defaultProbe, optInProbeFields, hostBlockers, ldd: Object.fromEntries(Object.entries(ldd).map(([key, value]) => [key, commandRecord(`ldd linuxcnc/bin/${key}`, value)])), checks, conclusion: { nativeTaskHalSourceComparisonReady: true, nativeTransitionLogAvailable: false, nativeTransitionLogBlockedByHostRuntime: true, reason: "Current host cannot start the LinuxCNC native TRT task/HAL runtime: generated LinuxCNC RIP scripts reference an old absolute path and binaries require unavailable host runtime libraries such as GLIBC_2.38/libpython3.13.", boundary: "This completes BTN-013 as an auditable native comparison and blocker record; it does not claim hardware drive, realtime kernel, external user-M process, or tool DB native runtime readiness.", }, }; await fs.writeFile(REPORT_JSON, `${JSON.stringify(report, null, 2)}\n`, "utf8"); await fs.writeFile(REPORT_MD, renderMarkdown(report), "utf8"); console.log(`native_task_hal_comparison_status=${report.status}`); console.log(`native_task_hal_comparison_json=${REPORT_JSON}`); console.log(`native_task_hal_comparison_markdown=${REPORT_MD}`); console.log(`native_task_hal_transition_log_available=${report.conclusion.nativeTransitionLogAvailable ? 1 : 0}`); console.log(`native_task_hal_host_blocker_count=${hostBlockers.length}`); if (report.status === "FAIL") process.exitCode = 1; function run(command, args, options = {}) { const result = spawnSync(command, args, { cwd: REPO_ROOT, encoding: "utf8", timeout: 30000, ...options, }); const stdout = result.stdout || ""; const stderr = result.stderr || ""; return { status: result.status ?? 1, signal: result.signal || null, stdout, stderr, combined: `${stdout}${stderr ? `\n${stderr}` : ""}`.trim(), }; } async function readText(relPath) { return fs.readFile(path.join(REPO_ROOT, relPath), "utf8").catch(() => ""); } async function readJson(filePath) { return JSON.parse(await fs.readFile(filePath, "utf8").catch(() => "{}")); } function parseKv(text) { const fields = {}; for (const line of text.split(/\r?\n/)) { const index = line.indexOf("="); if (index <= 0) continue; fields[line.slice(0, index)] = line.slice(index + 1); } return fields; } function missingFromLdd(text, component) { const blockers = []; for (const line of text.split(/\r?\n/)) { if (line.includes("not found") || line.includes("version `GLIBC") || line.includes("version `GLIBCXX")) { blockers.push({ component, blocker: "dynamic_linker_requirement", detail: line.trim() }); } } return blockers; } function check(name, pass, detail) { return { name, pass: Boolean(pass), detail: String(detail || "-").slice(0, 3000) }; } function commandRecord(command, result) { return { command, status: result.status, signal: result.signal, stdout: result.stdout.slice(0, 6000), stderr: result.stderr.slice(0, 6000), }; } function firstLine(text) { return String(text || "").split(/\r?\n/).find(Boolean) || "-"; } function oneLine(text) { return String(text || "").split(/\r?\n/).filter(Boolean).join(" | ").slice(0, 2000); } function renderMarkdown(data) { return [ "# Native task/HAL comparison report", "", `- status: ${data.status}`, `- generatedAt: ${data.generatedAt}`, `- JSON: ${REPORT_JSON}`, "", "## Checks", "", ...data.checks.map((item) => `- ${item.pass ? "PASS" : "FAIL"}: ${item.name} — ${item.detail}`), "", "## Host Native Runtime Blockers", "", ...data.hostBlockers.map((item) => `- ${item.component}: ${item.blocker}: ${item.detail}`), "", "## Conclusion", "", data.conclusion.reason, "", data.conclusion.boundary, "", ].join("\n"); }