fix(tools): v0.2 device-pod-cli maps build/download evidence to first-class workspace.evidence/debug.evidence (HWLAB #801)
The CLI was emitting workspace.build/debug.download with action=evidence for "<pod>:workspace build evidence <jobId>" and "<pod>:debug-probe download evidence <jobId>". Cloud-api accepted the sub-action form, but the device-pod executor mapped it to device-host-cli argv as "workspace build evidence" / "debug-probe download evidence" -- neither of which exists in the host CLI dispatcher; the host returned "unsupported command: --profile-json-b64 ..." and the evidence job was marked failed with blocker.code gateway_dispatch_failed. Per docs/reference/g14.md (v0.2 device-pod cloud-api architecture), "The evidence sub-action on workspace.evidence / debug.evidence is a first-class intent, not a workspace.build sub-action." The executor's deviceHostArgs already produces the correct argv for the first-class intent, so the fix lives in the CLI selector parser: when operation === "build" and rest[1] === "evidence", emit intent=workspace.evidence with kind=build and the explicit jobId; the same shape for operation === "download" -> debug.evidence with kind=download. Non-evidence sub-actions (start, status, wait, output, cancel) are unchanged. Live verification on G14 against http://74.48.78.17:19667 (D601-F103-V2, profileHash sha256:0352cd14...): - before: device-host-cli argv = "workspace build evidence" -> gateway_dispatch_failed - after: device-host-cli argv = "workspace evidence build <jobId> ..." accepted by cloud-api with status=running, blocker=null - the executor argv proves the fix; the D601 Windows host F:\Work\ConStart\tools\device-host-cli.mjs is still a stale D601 ops-side copy and is tracked separately in #779 (out of scope for #801). Adds: - device-pod-cli.test.ts: evidence sub-action -> first-class intent - device-pod-cli.test.ts: build start / download start regression (non-evidence sub-actions keep workspace.build / debug.download) Out of scope: COM9 -> COM3 UART port drift on the Windows device pod host (separate issue from #801; no UART/profile port code touched).
This commit is contained in:
@@ -462,6 +462,87 @@ test("device-pod-cli forwards host build and download job ids as named device-po
|
||||
});
|
||||
});
|
||||
|
||||
test("device-pod-cli maps build evidence / download evidence to first-class workspace.evidence / debug.evidence intents (HWLAB #801)", async () => {
|
||||
const seen: any[] = [];
|
||||
const fetchImpl = async (url, init) => {
|
||||
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
||||
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
||||
};
|
||||
const build = await runAssembledDevicePodCli([
|
||||
"D601-F103-V2:workspace:/projects/01_baseline",
|
||||
"build",
|
||||
"evidence",
|
||||
"job_devicepod_7ce895d1-bb3e-4daa-b4a1-6d7d05522923",
|
||||
"--tail",
|
||||
"60"
|
||||
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
||||
assert.equal(build.exitCode, 0);
|
||||
assert.deepEqual(seen[0].body, {
|
||||
intent: "workspace.evidence",
|
||||
args: {
|
||||
kind: "build",
|
||||
jobId: "job_devicepod_7ce895d1-bb3e-4daa-b4a1-6d7d05522923",
|
||||
tail: 60
|
||||
}
|
||||
});
|
||||
|
||||
const download = await runAssembledDevicePodCli([
|
||||
"D601-F103-V2:debug-probe",
|
||||
"download",
|
||||
"evidence",
|
||||
"job_devicepod_fb23ec52-339c-45cb-9b82-1af1fb7b6ee0",
|
||||
"--full"
|
||||
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
||||
assert.equal(download.exitCode, 0);
|
||||
assert.deepEqual(seen[1].body, {
|
||||
intent: "debug.evidence",
|
||||
args: {
|
||||
kind: "download",
|
||||
jobId: "job_devicepod_fb23ec52-339c-45cb-9b82-1af1fb7b6ee0",
|
||||
full: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("device-pod-cli keeps build start / download start on workspace.build / debug.download intents (HWLAB #801 regression)", async () => {
|
||||
const seen: any[] = [];
|
||||
const fetchImpl = async (url, init) => {
|
||||
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
||||
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
||||
};
|
||||
const build = await runAssembledDevicePodCli([
|
||||
"D601-F103-V2:workspace:/projects/01_baseline",
|
||||
"build",
|
||||
"start",
|
||||
"--reason",
|
||||
"smoke test"
|
||||
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
||||
assert.equal(build.exitCode, 0);
|
||||
assert.deepEqual(seen[0].body, {
|
||||
intent: "workspace.build",
|
||||
args: { action: "start" },
|
||||
reason: "smoke test"
|
||||
});
|
||||
|
||||
const download = await runAssembledDevicePodCli([
|
||||
"D601-F103-V2:debug-probe",
|
||||
"download",
|
||||
"start",
|
||||
"--capture-uart",
|
||||
"uart/1",
|
||||
"--capture-duration-ms",
|
||||
"8000",
|
||||
"--reason",
|
||||
"smoke test"
|
||||
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
||||
assert.equal(download.exitCode, 0);
|
||||
assert.deepEqual(seen[1].body, {
|
||||
intent: "debug.download",
|
||||
args: { action: "start", captureUart: "uart/1", captureDurationMs: 8000 },
|
||||
reason: "smoke test"
|
||||
});
|
||||
});
|
||||
|
||||
test("device-pod-cli maps selector build/download cloud job aliases to job routes", async () => {
|
||||
const seen: any[] = [];
|
||||
const fetchImpl = async (url, init) => {
|
||||
|
||||
@@ -296,10 +296,17 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
Object.assign(args, passthroughOptions(parsed, ["encoding", "charset", "createDirs"]));
|
||||
}
|
||||
else if (operation === "build") {
|
||||
intent = "workspace.build";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, ["target", "timeoutMs", "clean", "dryRun"]));
|
||||
if (text(rest[1]) === "evidence") {
|
||||
intent = "workspace.evidence";
|
||||
args.kind = "build";
|
||||
args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, ["tail", "full", "target"]));
|
||||
} else {
|
||||
intent = "workspace.build";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, ["target", "timeoutMs", "clean", "dryRun"]));
|
||||
}
|
||||
}
|
||||
else throw cliError("unsupported_workspace_operation", `unsupported workspace operation: ${operation}`);
|
||||
} else if (selector.surface === "debug-probe") {
|
||||
@@ -312,18 +319,25 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
);
|
||||
}
|
||||
else if (operation === "download") {
|
||||
intent = "debug.download";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, [
|
||||
"target",
|
||||
"timeoutMs",
|
||||
"captureUart",
|
||||
"captureDurationMs",
|
||||
"durationMs",
|
||||
"port",
|
||||
"baudRate"
|
||||
]));
|
||||
if (text(rest[1]) === "evidence") {
|
||||
intent = "debug.evidence";
|
||||
args.kind = "download";
|
||||
args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, ["tail", "full", "target"]));
|
||||
} else {
|
||||
intent = "debug.download";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, [
|
||||
"target",
|
||||
"timeoutMs",
|
||||
"captureUart",
|
||||
"captureDurationMs",
|
||||
"durationMs",
|
||||
"port",
|
||||
"baudRate"
|
||||
]));
|
||||
}
|
||||
}
|
||||
else if (operation === "reset") intent = "debug.reset";
|
||||
else throw cliError(
|
||||
|
||||
Reference in New Issue
Block a user