import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "bun:test"; import { createHwpodNodeServer, executeHwpodNodeOpsPlan, resolveHwpodNodeCommand } from "./src/hwpod-node-lib.ts"; test("hwpod-node executes minimal workspace node ops", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-")); try { await writeFile(path.join(root, "main.c"), "int main(void) { return 0; }\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_test", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [ { opId: "op_01", op: "node.health", args: { workspacePath: root } }, { opId: "op_02", op: "workspace.ls", args: { workspacePath: root, path: "." } }, { opId: "op_03", op: "workspace.cat", args: { workspacePath: root, path: "main.c" } } ] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.status, "completed"); assert.equal(result.results[1].output.entries[0].name, "main.c"); assert.match(result.results[2].output.content, /int main/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node searches workspace text without requiring host rg", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-rg-")); try { await mkdir(path.join(root, "Middlewares", "Arm-2D"), { recursive: true }); await mkdir(path.join(root, "Objects"), { recursive: true }); await writeFile(path.join(root, "Middlewares", "Arm-2D", "arm_2d.c"), "static int before;\nvoid arm_2d_init(void) {\n}\nstatic int after;\n", "utf8"); await writeFile(path.join(root, "Objects", "ignored.txt"), "arm_2d_init should not be scanned\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_rg", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_rg", op: "workspace.rg", args: { workspacePath: root, pattern: "arm_2d_init", path: "Middlewares", context: 1 } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].ok, true); assert.equal(result.results[0].output.engine, "node-recursive-search"); assert.equal(result.results[0].output.matchCount, 1); assert.ok(result.results[0].output.stdout.includes("Middlewares/Arm-2D/arm_2d.c-1-static int before")); assert.ok(result.results[0].output.stdout.includes("Middlewares/Arm-2D/arm_2d.c:2:void arm_2d_init")); assert.ok(result.results[0].output.stdout.includes("Middlewares/Arm-2D/arm_2d.c-3-}")); assert.equal(result.results[0].output.matches[0].line, 2); assert.equal(result.results[0].output.matches[0].before[0].line, 1); assert.equal(result.results[0].output.matches[0].after[0].line, 3); assert.equal(result.results[0].output.limits.beforeContext, 1); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node HTTP endpoint accepts hwpod-node-ops plans", async () => { const server = createHwpodNodeServer({ now: () => "2026-06-05T00:00:00.000Z", nodeId: "pc-host-1" }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const response = await fetch(`http://127.0.0.1:${server.address().port}/v1/hwpod-node-ops`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_http", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_01", op: "node.version", args: {} }] }) }); const payload = await response.json(); assert.equal(response.status, 200); assert.equal(payload.ok, true); assert.equal(payload.specAuthority, "none"); assert.equal(payload.results[0].op, "node.version"); } finally { await new Promise((resolve, reject) => server.close((error: Error | undefined) => (error ? reject(error) : resolve(undefined)))); } }); test("hwpod-node executes debug ops only through explicit node-side command bindings", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-debug-")); try { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_debug_bound", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_build", op: "debug.build", args: { workspacePath: root, command: "printf build-ok > build.ok" } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].ok, true); assert.equal(result.results[0].output.bindingSource, "hwpod-node-ops.command"); assert.match(await readFile(path.join(root, "build.ok"), "utf8"), /build-ok/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node executes debug commandRuns without shell splitting", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-debug-runs-")); try { const writer = path.join(root, "writer.js"); await writeFile(writer, "const { writeFileSync } = require('node:fs'); writeFileSync(process.argv[2], process.argv.slice(3).join('|'));\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_debug_runs", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_download", op: "debug.download", args: { workspacePath: root, commandRuns: [{ command: process.execPath, argv: [writer, path.join(root, "args.txt"), "CherryUSB MicroLink CMSIS-DAP"] }] } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].ok, true); assert.equal(result.results[0].output.bindingSource, "hwpod-node-ops.commandRuns"); assert.equal(result.results[0].output.commands[0].command.at(-1), "CherryUSB MicroLink CMSIS-DAP"); assert.equal(await readFile(path.join(root, "args.txt"), "utf8"), "CherryUSB MicroLink CMSIS-DAP"); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node reports cmd.run non-zero exits as failed results", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-cmd-fail-")); try { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_cmd_fail", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "sh", argv: ["-c", "echo no >&2; exit 7"] } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, false); assert.equal(result.results[0].ok, false); assert.equal(result.results[0].status, "failed"); assert.equal(result.results[0].output.exitCode, 7); assert.equal(result.results[0].blocker.code, "hwpod_node_command_failed"); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node projects bound command JSON into authoritative observation", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-observation-")); try { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_observation", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_observation", op: "cmd.run", args: { workspacePath: root, command: process.execPath, argv: ["-e", "console.log(JSON.stringify({ observationId: 'obs-node', response: { ok: true } }))"], commandBinding: { kind: "board-comm" } } }] }, { now: () => "2026-07-16T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].output.observation.observationId, "obs-node"); assert.equal(result.results[0].output.observation.response.ok, true); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node refuses plans targeting another node id", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-id-mismatch-")); try { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_node_mismatch", hwpodId: "hwpod-local", nodeId: "node-d601-f103-v2", ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "sh", argv: ["-c", "echo must-not-run"] } }] }, { now: () => "2026-06-05T00:00:00.000Z", nodeId: "g14-host-hwpod-node" }); assert.equal(result.ok, false); assert.equal(result.status, "blocked"); assert.equal(result.localNodeId, "g14-host-hwpod-node"); assert.equal(result.results[0].blocker.code, "hwpod_node_id_mismatch"); assert.equal(result.results[0].blocker.details.requestedNodeId, "node-d601-f103-v2"); assert.equal(result.results[0].blocker.details.localNodeId, "g14-host-hwpod-node"); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node exposes command spawn diagnostics when executable is missing", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-spawn-missing-")); try { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_spawn_missing", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "hwlab-command-that-does-not-exist-1018", argv: ["--version"] } }] }, { now: () => "2026-06-05T00:00:00.000Z", nodeId: "pc-host-1" }); assert.equal(result.ok, false); assert.equal(result.results[0].status, "blocked"); assert.equal(result.results[0].blocker.code, "hwpod_node_command_spawn_failed"); assert.equal(result.results[0].blocker.details.output.commandResolution.requested, "hwlab-command-that-does-not-exist-1018"); assert.equal(result.results[0].blocker.details.output.spawnDiagnostics.platform, process.platform); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node includes structured UART diagnostics when platform blocks serial access", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-uart-")); try { const fixture = path.join(root, "serial-monitor-idle.mjs"); await writeFile(fixture, ` const args = process.argv.slice(2).join(" "); if (args === "monitor status") console.log(JSON.stringify({ action: "monitor_status", success: true, data: { isMonitoring: false, port: null, baudRate: null, lastPort: "COM9", lastBaudRate: 115200 } })); else if (args === "ports") console.log(JSON.stringify({ action: "list_ports", success: true, data: { ports: [{ path: "COM9" }] } })); else { console.error("unsupported " + args); process.exit(2); } `, "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_uart_diag", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_uart", op: "io.uart.read", args: { workspacePath: root, port: "uart1", serialMonitorDir: root, serialMonitorCommand: [process.execPath, fixture], ioProbe: { uart: { id: "uart1", port: "COM9", baudrate: 115200 } } } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); if (process.platform === "win32") return; assert.equal(result.ok, false); assert.equal(result.results[0].status, "blocked"); assert.equal(result.results[0].blocker.code, "hwpod_uart_monitor_start_failed"); assert.equal(result.results[0].blocker.details.requestedPort, "uart1"); assert.equal(result.results[0].blocker.details.resolvedPort, "COM9"); assert.equal(result.results[0].blocker.details.baudRate, 115200); assert.equal(result.results[0].blocker.details.serialMonitor.monitorStatus.data.isMonitoring, false); assert.equal(result.results[0].blocker.details.serialMonitor.start.success, undefined); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node reads UART through serial-monitor binding", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-uart-active-")); try { const fixture = path.join(root, "serial-monitor-fixture.mjs"); await writeFile(fixture, ` import { existsSync, writeFileSync } from "node:fs"; const marker = ${JSON.stringify(path.join(root, "monitor-started"))}; const args = process.argv.slice(2).join(" "); if (args === "monitor status") console.log(JSON.stringify({ action: "monitor_status", success: true, data: existsSync(marker) ? { isMonitoring: true, port: "COM9", baudRate: 115200 } : { isMonitoring: false, port: null, baudRate: null, lastPort: "COM9", lastBaudRate: 115200 } })); else if (args === "monitor start -p COM9 -b 115200") { writeFileSync(marker, "started"); console.log(JSON.stringify({ action: "monitor_start", success: true, data: { port: "COM9", baudRate: 115200 } })); } else if (args === "ports") console.log(JSON.stringify({ action: "list_ports", success: true, data: { ports: [{ path: "COM9" }] } })); else if (args.startsWith("fetch")) console.log(JSON.stringify({ action: "fetch_data", success: true, data: [{ timestamp: "2026-06-06T10:00:01Z", port: "COM9", data: "UART OK" }], count: 1, totalCount: 1, hasMore: false, truncated: false, sourceFile: "fixture.jsonl" })); else { console.error("unsupported " + args); process.exit(2); } `, "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_uart_read", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_uart", op: "io.uart.read", args: { workspacePath: root, port: "uart1", maxBytes: 128, serialMonitorDir: root, serialMonitorCommand: [process.execPath, fixture], ioProbe: { uart: { id: "uart1", port: "COM9", baudrate: 115200 } } } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].status, "completed"); assert.equal(result.results[0].output.bindingSource, "serial-monitor-cli"); assert.equal(result.results[0].output.resolvedPort, "COM9"); assert.equal(result.results[0].output.text, "UART OK"); assert.equal(result.results[0].output.monitorStarted, true); assert.equal(result.results[0].output.monitorStart.success, true); assert.equal(result.results[0].output.monitorStatus.data.isMonitoring, true); assert.equal(result.results[0].output.monitorStatus.data.port, "COM9"); assert.equal(result.results[0].output.monitorStatus.data.baudRate, 115200); assert.deepEqual(result.results[0].output.command.slice(0, 2), [process.execPath, fixture]); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node blocks UART fetch when start does not establish requested binding", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-uart-mismatch-")); try { const fixture = path.join(root, "serial-monitor-mismatch.mjs"); await writeFile(fixture, ` const args = process.argv.slice(2).join(" "); if (args === "monitor status") console.log(JSON.stringify({ success: true, data: { isMonitoring: false, port: null, baudRate: null } })); else if (args === "monitor start -p COM9 -b 115200") console.log(JSON.stringify({ success: true, data: { port: "COM9", baudRate: 115200 } })); else if (args.startsWith("fetch")) { console.error("fetch must not run"); process.exit(3); } else { console.error("unsupported " + args); process.exit(2); } `, "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_uart_mismatch", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_uart", op: "io.uart.read", args: { workspacePath: root, port: "COM9", baudRate: 115200, serialMonitorDir: root, serialMonitorCommand: [process.execPath, fixture] } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, false); assert.equal(result.results[0].blocker.code, "hwpod_uart_monitor_binding_mismatch"); assert.equal(result.results[0].blocker.details.serialMonitor.confirmedStatus.data.isMonitoring, false); } finally { await rm(root, { recursive: true, force: true }); } }); test("python hwpod-node returns typed blocker for non-JSON UART start response", () => { const script = ` import importlib.util, json, logging spec = importlib.util.spec_from_file_location("hwlab_node", "tools/hwlab-node.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) class Executor(module.NodeOpsExecutor): def _spawn_output(self, command, cwd, timeout_ms): action = " ".join(command[-2:]) if command[-2:] == ["monitor", "status"]: return {"ok": True, "stdout": json.dumps({"success": True, "data": {"isMonitoring": False}}), "stderr": "", "exitCode": 0} if "start" in command: return {"ok": True, "stdout": "not-json", "stderr": "", "exitCode": 0} return {"ok": True, "stdout": json.dumps({"success": True, "data": {"ports": []}}), "stderr": "", "exitCode": 0} executor = Executor({"nodeId": "node-test"}, logging.getLogger("test")) print(json.dumps(executor._uart_read({"workspacePath": ".", "port": "COM9", "baudRate": 115200, "serialMonitorCommand": ["serial-monitor"]}))) `; const completed = spawnSync("python3", ["-c", script], { cwd: process.cwd(), encoding: "utf8" }); assert.equal(completed.status, 0, completed.stderr); const result = JSON.parse(completed.stdout); assert.equal(result.ok, false); assert.equal(result.blockerCode, "hwpod_uart_monitor_start_failed"); assert.equal(result.details.serialMonitor.start.stdout, "not-json"); }); test("python hwpod-node maps UART open, write, and close to serial-monitor", () => { const script = ` import importlib.util, json, logging spec = importlib.util.spec_from_file_location("hwlab_node", "tools/hwlab-node.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) class Executor(module.NodeOpsExecutor): def __init__(self): super().__init__({"nodeId": "node-test"}, logging.getLogger("test")) self.commands = [] def _spawn_output(self, command, cwd, timeout_ms): self.commands.append(command) return {"ok": True, "stdout": json.dumps({"success": True}), "stderr": "", "exitCode": 0} executor = Executor() args = {"workspacePath": ".", "port": "COM9", "baudRate": 115200, "data": "status", "serialMonitorCommand": ["serial-monitor"]} results = [executor._execute_op({"opId": "open", "op": "io.uart.open", "args": args}), executor._execute_op({"opId": "write", "op": "io.uart.write", "args": args}), executor._execute_op({"opId": "close", "op": "io.uart.close", "args": args})] print(json.dumps({"results": results, "commands": executor.commands})) `; const completed = spawnSync("python3", ["-c", script], { cwd: process.cwd(), encoding: "utf8" }); assert.equal(completed.status, 0, completed.stderr); const result = JSON.parse(completed.stdout); assert.deepEqual(result.commands, [ ["serial-monitor", "monitor", "start", "-p", "COM9", "-b", "115200"], ["serial-monitor", "send", "status"], ["serial-monitor", "monitor", "stop"], ]); assert.deepEqual(result.results.map((item: any) => item.status), ["completed", "completed", "completed"]); }); test("python hwpod-node starts an unreachable serial-monitor server before opening UART", () => { const script = ` import importlib.util, json, logging spec = importlib.util.spec_from_file_location("hwlab_node", "tools/hwlab-node.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) class Executor(module.NodeOpsExecutor): def __init__(self): super().__init__({"nodeId": "node-test"}, logging.getLogger("test")) self.commands = [] def _spawn_output(self, command, cwd, timeout_ms): self.commands.append(command) if command[-2:] == ["server", "start"]: body = {"success": True} elif len(self.commands) == 1: body = {"success": False, "data": {"request": {"code": "ECONNREFUSED"}, "diagnosis": {"status": "not_running"}}} else: body = {"success": True} return {"ok": True, "stdout": json.dumps(body), "stderr": "", "exitCode": 0} executor = Executor() result = executor._execute_op({"opId": "open", "op": "io.uart.open", "args": {"workspacePath": ".", "port": "COM4", "baudRate": 921600, "serialMonitorCommand": ["serial-monitor"]}}) print(json.dumps({"result": result, "commands": executor.commands})) `; const completed = spawnSync("python3", ["-c", script], { cwd: process.cwd(), encoding: "utf8" }); assert.equal(completed.status, 0, completed.stderr); const result = JSON.parse(completed.stdout); assert.equal(result.result.status, "completed"); assert.deepEqual(result.commands, [ ["serial-monitor", "monitor", "start", "-p", "COM4", "-b", "921600"], ["serial-monitor", "server", "start"], ["serial-monitor", "monitor", "start", "-p", "COM4", "-b", "921600"], ]); }); test("python hwpod-node keeps a pyserial connection across UART operations", () => { const script = ` import importlib.util, json, logging, sys, types spec = importlib.util.spec_from_file_location("hwlab_node", "tools/hwlab-node.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) class Port: def __init__(self, port, baudrate, timeout): self.port = port self.baudrate = baudrate self.is_open = True self.buffer = bytearray(b"ready") self.sent = bytearray() @property def in_waiting(self): return len(self.buffer) def read(self, size): value = bytes(self.buffer[:size]) del self.buffer[:size] return value def write(self, value): self.sent.extend(value) return len(value) def close(self): self.is_open = False sys.modules["serial"] = types.SimpleNamespace(Serial=Port) executor = module.NodeOpsExecutor({"nodeId": "node-test"}, logging.getLogger("test")) common = {"workspacePath": ".", "port": "COM4", "baudRate": 921600, "uartBackend": "pyserial"} results = [ executor._execute_op({"opId": "open", "op": "io.uart.open", "args": common}), executor._execute_op({"opId": "write", "op": "io.uart.write", "args": {**common, "data": "status"}}), executor._execute_op({"opId": "read", "op": "io.uart.read", "args": {**common, "maxBytes": 16}}), executor._execute_op({"opId": "close", "op": "io.uart.close", "args": common}), ] print(json.dumps(results)) `; const completed = spawnSync("python3", ["-c", script], { cwd: process.cwd(), encoding: "utf8" }); assert.equal(completed.status, 0, completed.stderr); const results = JSON.parse(completed.stdout); assert.deepEqual(results.map((item: any) => item.status), ["completed", "completed", "completed", "completed"]); assert.equal(results[1].output.bytes, 6); assert.equal(results[2].output.text, "ready"); assert.equal(results[2].output.bindingSource, "pyserial"); }); test("hwpod-node resolves Git for Windows when service PATH omits git", async () => { const result = await resolveHwpodNodeCommand("git", { platform: "win32", env: {}, fileExists: async (candidate: string) => candidate === "C:\\Program Files\\Git\\cmd\\git.exe" }); assert.equal(result.command, "C:\\Program Files\\Git\\cmd\\git.exe"); assert.equal(result.resolved, true); assert.equal(result.source, "windows-well-known-tool"); }); test("hwpod-node keeps explicit executable paths unchanged", async () => { const result = await resolveHwpodNodeCommand("C:\\Tools\\Git\\cmd\\git.exe", { platform: "win32", env: {}, fileExists: async () => true }); assert.equal(result.command, "C:\\Tools\\Git\\cmd\\git.exe"); assert.equal(result.resolved, false); assert.equal(result.source, "explicit-path"); }); test("hwpod-node applies workspace patches through the stable node op", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-")); try { await writeFile(path.join(root, "main.c"), "int value = 1;\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_patch", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_patch", op: "workspace.apply-patch", args: { workspacePath: root, patch: [ "*** Begin Patch", "*** Update File: main.c", "@@", "-int value = 1;", "+int value = 2;", "*** End Patch", "" ].join("\n") } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, true); assert.equal(result.results[0].ok, true); assert.match(await readFile(path.join(root, "main.c"), "utf8"), /value = 2/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node preserves CRLF when applying workspace patches", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-crlf-")); try { await writeFile(path.join(root, "main.c"), "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_patch_crlf", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_patch", op: "workspace.apply-patch", args: { workspacePath: root, patch: [ "*** Begin Patch", "*** Update File: main.c", "@@", " while (1) {", "+ /* MARKER_OK */", " }", "*** End Patch", "" ].join("\n") } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); const content = await readFile(path.join(root, "main.c"), "utf8"); assert.equal(result.ok, true); assert.match(content, /\r\n while \(1\) \{\r\n \/\* MARKER_OK \*\/\r\n \}\r\n/u); assert.doesNotMatch(content, /[^\r]\n/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node apply-patch accepts UniDesk v2 compatible unified headers and unprefixed context", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-v2-")); try { await writeFile(path.join(root, "main.c"), [ "#include ", "int main(void) {", " printf(\"OLD\\r\\n\");", " return 0;", "}", "" ].join("\n"), "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_patch_v2", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_patch", op: "workspace.apply-patch", args: { workspacePath: root, patch: [ "*** Begin Patch", "*** Update File: main.c", "@@ -1,5 +1,6 @@", "#include ", "int main(void) {", "- printf(\"OLD\\r\\n\");", "+ printf(\"NEW\\r\\n\");", "+ printf(\"TRACE\\r\\n\");", " return 0;", "}", "*** End Patch", "" ].join("\n") } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); const content = await readFile(path.join(root, "main.c"), "utf8"); assert.equal(result.ok, true); assert.equal(result.results[0].output.engine, "codex-apply-patch-v2-compatible"); assert.match(result.results[0].output.hints.join("\n"), /unified-diff hunk header/u); assert.match(result.results[0].output.hints.join("\n"), /unprefixed Update File context line/u); assert.match(content, /printf\("NEW\\r\\n"\);/u); assert.match(content, /printf\("TRACE\\r\\n"\);/u); assert.doesNotMatch(content, /printf\("NEW\r\n"\);/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node apply-patch rejects raw unified diff with a precise format blocker", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-raw-diff-")); try { await writeFile(path.join(root, "main.c"), "int value = 1;\n", "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_patch_raw_diff", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_patch", op: "workspace.apply-patch", args: { workspacePath: root, patch: [ "--- a/main.c", "+++ b/main.c", "@@ -1 +1 @@", "-int value = 1;", "+int value = 2;", "" ].join("\n") } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, false); assert.equal(result.results[0].blocker.code, "unsupported_apply_patch_format"); assert.equal(result.results[0].blocker.details.engine, "codex-apply-patch-v2-compatible"); assert.equal(result.results[0].blocker.details.format, "unified-diff"); assert.match(result.results[0].blocker.summary, /Codex\/UniDesk apply_patch v2 envelope/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node returns apply-patch diagnostics when context is missing", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-diagnostics-")); try { const original = "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n"; await writeFile(path.join(root, "main.c"), original, "utf8"); const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_patch_diag", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_patch", op: "workspace.apply-patch", args: { workspacePath: root, patch: [ "*** Begin Patch", "*** Update File: main.c", "@@", "- while (2) {", "+ while (3) {", "*** End Patch", "" ].join("\n") } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, false); assert.equal(result.results[0].blocker.code, "apply_patch_context_not_found"); assert.equal(result.results[0].blocker.details.fileSha256, sha256(original)); assert.equal(result.results[0].blocker.details.lineEnding, "\r\n"); assert.equal(result.results[0].blocker.details.lineEndingCounts.crlf, 4); assert.equal(result.results[0].blocker.details.normalized, true); assert.match(result.results[0].blocker.details.nodeVersion, /thin-node-ops/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node edits CRLF workspaces with insert-after replace and write ops", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-text-edit-")); try { const filePath = path.join(root, "projects", "01_baseline", "User", "main.c"); await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n", "utf8"); const before = await readFile(filePath, "utf8"); const insert = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_insert", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_insert", op: "workspace.insert-after", args: { workspacePath: root, path: "projects/01_baseline/User/main.c", anchor: " while (1) {", line: " /* MARKER_OK */", expectedSha: sha256(before) } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(insert.ok, true); assert.equal(insert.results[0].output.before.sha256, sha256(before)); assert.equal(insert.results[0].output.after.lineEnding, "\r\n"); assert.equal(insert.results[0].output.anchorMatch, "line-sequence"); assert.deepEqual(insert.results[0].output.diff.preview, ["+ /* MARKER_OK */"]); const inserted = await readFile(filePath, "utf8"); assert.match(inserted, /\r\n \/\* MARKER_OK \*\/\r\n/u); assert.doesNotMatch(inserted, /[^\r]\n/u); const replace = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_replace", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_replace", op: "workspace.replace", args: { workspacePath: root, path: "projects/01_baseline/User/main.c", find: " /* MARKER_OK */", replace: " /* MARKER_REPLACED */", expectedSha: sha256(inserted) } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(replace.ok, true); assert.equal(replace.results[0].output.occurrences, 1); assert.match(await readFile(filePath, "utf8"), /MARKER_REPLACED/u); const dryRun = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_write_dry", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_write", op: "workspace.write", args: { workspacePath: root, path: "new.txt", content: "alpha\nbeta", lineEnding: "crlf", finalNewline: true, dryRun: true } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(dryRun.ok, true); assert.equal(dryRun.results[0].output.dryRun, true); assert.equal(dryRun.results[0].output.after.lineEnding, "\r\n"); await assert.rejects(() => readFile(path.join(root, "new.txt"), "utf8"), /ENOENT/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node insert-after accepts a unique in-line source fragment anchor", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-fragment-anchor-")); try { const filePath = path.join(root, "projects", "01_baseline", "User", "main.c"); await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, "void boot(void) {\r\n printf(\"\\r\\n[D601-F103] device-pod LCD bring-up\\r\\n\");\r\n lcd_init();\r\n}\r\n", "utf8"); const before = await readFile(filePath, "utf8"); const insert = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_insert_fragment", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_insert", op: "workspace.insert-after", args: { workspacePath: root, path: "projects/01_baseline/User/main.c", anchor: "device-pod LCD bring-up", line: " printf(\"[D601-F103] marker\\r\\n\");", expectedSha: sha256(before) } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(insert.ok, true); assert.equal(insert.results[0].output.anchorLine, 2); assert.equal(insert.results[0].output.anchorMatch, "line-fragment"); const inserted = await readFile(filePath, "utf8"); assert.match(inserted, /device-pod LCD bring-up\\r\\n"\);\r\n printf\("\[D601-F103\] marker\\r\\n"\);\r\n lcd_init/u); assert.doesNotMatch(inserted, /[^\r]\n/u); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node insert-after keeps fragment anchors safe when ambiguous", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-fragment-ambiguous-")); try { const filePath = path.join(root, "main.c"); await writeFile(filePath, "printf(\"device-pod LCD bring-up\");\nprintf(\"device-pod LCD bring-up again\");\n", "utf8"); const before = await readFile(filePath, "utf8"); const insert = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_insert_ambiguous_fragment", hwpodId: "hwpod-local", nodeId: "pc-host-1", ops: [{ opId: "op_insert", op: "workspace.insert-after", args: { workspacePath: root, path: "main.c", anchor: "device-pod LCD bring-up", line: "printf(\"marker\");", expectedSha: sha256(before) } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(insert.ok, false); assert.equal(insert.results[0].blocker.code, "workspace_insert_anchor_ambiguous"); assert.equal(insert.results[0].blocker.details.anchorMatch, "line-fragment"); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-node preserves Windows absolute workspace paths for PC-host nodes", async () => { const result = await executeHwpodNodeOpsPlan({ contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_windows_path", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", ops: [{ opId: "op_01", op: "workspace.ls", args: { workspacePath: "F:\\Work\\D601-HWLAB", path: "." } }] }, { now: () => "2026-06-05T00:00:00.000Z" }); assert.equal(result.ok, false); assert.equal(result.results[0].blocker.code, "ENOENT"); assert.match(result.results[0].blocker.summary, /scandir 'F:\\Work\\D601-HWLAB'/u); assert.doesNotMatch(result.results[0].blocker.summary, /\/root\//u); }); function sha256(content: string) { return createHash("sha256").update(content, "utf8").digest("hex"); }