feat: expose D601 download UART CaseRun
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"contractVersion": "hwpod-case-v1",
|
||||||
|
"caseId": "d601-f103-v2-main-marker-download-uart",
|
||||||
|
"title": "D601-F103-V2 main marker download and UART CaseRun",
|
||||||
|
"mode": "download-uart",
|
||||||
|
"hwpodSpec": "hwpod-spec.yaml",
|
||||||
|
"subject": {
|
||||||
|
"repoLocalPath": "F:\\Work\\HWLAB-CASE-F103",
|
||||||
|
"commitId": "df7a4e6e551fa90d64bde5537cc000f89d63dd20",
|
||||||
|
"subdir": "projects/01_baseline"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"mode": "download-uart",
|
||||||
|
"uart": {
|
||||||
|
"maxBytes": 8192,
|
||||||
|
"timeoutMs": 3000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expected": {
|
||||||
|
"compile": {
|
||||||
|
"success": true,
|
||||||
|
"artifactSuffixes": [
|
||||||
|
"projects/01_baseline/Output/atk_f103.hex",
|
||||||
|
"projects/01_baseline/Output/atk_f103.axf"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"success": true,
|
||||||
|
"summaryMarkers": [
|
||||||
|
"Programming Done",
|
||||||
|
"Verify OK",
|
||||||
|
"Application running"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uart": {
|
||||||
|
"runIdentityRequired": true,
|
||||||
|
"rawArtifactRequired": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"authority": "hwpod-service",
|
||||||
|
"operationSequence": [
|
||||||
|
"debug.build",
|
||||||
|
"debug.download",
|
||||||
|
"io.uart.read"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: hwlab.dev/v0alpha1
|
||||||
|
kind: Hwpod
|
||||||
|
metadata:
|
||||||
|
uid: D601-F103-V2
|
||||||
|
name: d601-f103-v2
|
||||||
|
spec:
|
||||||
|
targetDevice:
|
||||||
|
board: D601-F103-V2
|
||||||
|
mcu: STM32F103
|
||||||
|
workspace:
|
||||||
|
path: "F:\\Work\\HWLAB-CASE-F103"
|
||||||
|
toolchain: keil-mdk
|
||||||
|
keilProject: projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx
|
||||||
|
keilTarget: USART
|
||||||
|
keilCliPath: "C:\\Users\\liang\\.agents\\skills\\keil\\keil-cli.py"
|
||||||
|
debugProbe:
|
||||||
|
type: daplink
|
||||||
|
adapter: keil
|
||||||
|
probeUid: 3FD750C63E342E24
|
||||||
|
probeName: MicroLink CMSIS-DAP
|
||||||
|
programBackend: keil
|
||||||
|
ioProbe:
|
||||||
|
uart:
|
||||||
|
id: uart/1
|
||||||
|
port: COM9
|
||||||
|
baudrate: 115200
|
||||||
|
nodeBinding:
|
||||||
|
nodeId: node-d601-f103-v2
|
||||||
|
nodeType: pc-host
|
||||||
@@ -6,6 +6,7 @@ import { test } from "bun:test";
|
|||||||
|
|
||||||
import { createCloudApiServer } from "./server.ts";
|
import { createCloudApiServer } from "./server.ts";
|
||||||
import { caseRunInternalFetch } from "./server-caserun-http.ts";
|
import { caseRunInternalFetch } from "./server-caserun-http.ts";
|
||||||
|
import { caseValidationPlanFromDefinition, hwpodCommandForValidationStep } from "../../tools/src/hwlab-caserun-validation.ts";
|
||||||
|
|
||||||
test("CaseRun internal requests reuse the existing bootstrap admin API key only for the internal origin", async () => {
|
test("CaseRun internal requests reuse the existing bootstrap admin API key only for the internal origin", async () => {
|
||||||
const calls: Array<{ url: string; authorization: string | null }> = [];
|
const calls: Array<{ url: string; authorization: string | null }> = [];
|
||||||
@@ -114,6 +115,74 @@ test("cloud-api exposes web CaseRun cases, run status, events and aggregate", as
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("builtin CaseRun exposes and preflights the D601 download and UART case", async () => {
|
||||||
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-download-uart-"));
|
||||||
|
const caseId = "d601-f103-v2-main-marker-download-uart";
|
||||||
|
const caseFile = path.join(process.cwd(), "cases", caseId, "case.json");
|
||||||
|
const definition = JSON.parse(await readFile(caseFile, "utf8"));
|
||||||
|
const validationPlan = caseValidationPlanFromDefinition(definition);
|
||||||
|
assert.equal(validationPlan.mode, "download-uart");
|
||||||
|
assert.equal(validationPlan.compileOnly, false);
|
||||||
|
assert.deepEqual(validationPlan.steps.map((step) => step.kind), ["build", "download", "uart"]);
|
||||||
|
assert.deepEqual(validationPlan.steps.map((step) => step.expectedOp), ["debug.build", "debug.download", "io.uart.read"]);
|
||||||
|
assert.deepEqual(validationPlan.steps.map((step) => hwpodCommandForValidationStep(step, {
|
||||||
|
executable: "bun",
|
||||||
|
cliPath: "tools/hwpod-cli.ts",
|
||||||
|
specPath: ".hwlab/hwpod-spec.yaml",
|
||||||
|
reason: "focused preflight"
|
||||||
|
}).slice(2, 4)), [["build", "--spec"], ["download", "--spec"], ["uart", "read"]]);
|
||||||
|
|
||||||
|
const accessController = {
|
||||||
|
async ensureBootstrap() {},
|
||||||
|
async requireNavAccess() { return true; }
|
||||||
|
};
|
||||||
|
const server = createCloudApiServer({
|
||||||
|
accessController,
|
||||||
|
env: { PATH: process.env.PATH, HWLAB_CASERUN_REPO_ROOT: process.cwd(), HWLAB_CASERUN_STATE_DIR: stateRoot, HWLAB_ENVIRONMENT: "v03", HWLAB_CLOUD_API_PORT: "6667" },
|
||||||
|
caseRunExecutor: async (context: any, emit: any) => {
|
||||||
|
await emit("prepared", { runDir: context.parsed.runDir });
|
||||||
|
throw Object.assign(new Error("No HWPOD node is available for focused preflight."), {
|
||||||
|
code: "hwpod_node_unavailable",
|
||||||
|
details: { blocker: { code: "hwpod_node_unavailable", summary: "No HWPOD node is available.", retryable: true } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await listen(server);
|
||||||
|
try {
|
||||||
|
const casesResponse = await fetch(`${serverUrl(server)}/v1/caserun/cases`);
|
||||||
|
const casesPayload = await casesResponse.json();
|
||||||
|
const matches = casesPayload.cases.filter((item: any) => item.caseId === caseId);
|
||||||
|
assert.equal(matches.length, 1);
|
||||||
|
assert.deepEqual(matches[0], {
|
||||||
|
caseId,
|
||||||
|
title: "D601-F103-V2 main marker download and UART CaseRun",
|
||||||
|
mode: "download-uart",
|
||||||
|
hwpodSpec: "hwpod-spec.yaml",
|
||||||
|
hardwareRequired: true,
|
||||||
|
available: true,
|
||||||
|
subject: definition.subject,
|
||||||
|
expected: definition.expected,
|
||||||
|
runtime: definition.runtime
|
||||||
|
});
|
||||||
|
|
||||||
|
const startResponse = await fetch(`${serverUrl(server)}/v1/caserun/runs`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ caseId })
|
||||||
|
});
|
||||||
|
const started = await startResponse.json();
|
||||||
|
assert.equal(startResponse.status, 202);
|
||||||
|
assert.match(started.runId, /^web-d601-f103-v2-main-marker-download-uart-/u);
|
||||||
|
const failed = await waitForRun(server, started.runId, "failed");
|
||||||
|
assert.equal(failed.caseId, caseId);
|
||||||
|
assert.equal(failed.terminal, true);
|
||||||
|
assert.equal(failed.blocker.code, "hwpod_node_unavailable");
|
||||||
|
} finally {
|
||||||
|
await close(server);
|
||||||
|
await rm(stateRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("cloud-api completes a hardware-free software smoke CaseRun", async () => {
|
test("cloud-api completes a hardware-free software smoke CaseRun", async () => {
|
||||||
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-software-smoke-"));
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-software-smoke-"));
|
||||||
const stateRoot = path.join(root, "state");
|
const stateRoot = path.join(root, "state");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function handleCaseRunHttp(request: IncomingMessage, response: Serv
|
|||||||
casesRoute: "/v1/caserun/cases",
|
casesRoute: "/v1/caserun/cases",
|
||||||
runsRoute: "/v1/caserun/runs",
|
runsRoute: "/v1/caserun/runs",
|
||||||
caseAuthority: describeCaseAuthority(caseRepo, cwd),
|
caseAuthority: describeCaseAuthority(caseRepo, cwd),
|
||||||
compileOnly: true
|
compileOnly: false
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ export async function handleCaseRunHttp(request: IncomingMessage, response: Serv
|
|||||||
events: record.events ?? []
|
events: record.events ?? []
|
||||||
});
|
});
|
||||||
if (suffix === "aggregate" && request.method === "GET") return sendJson(response, 200, await aggregateForRecord(record));
|
if (suffix === "aggregate" && request.method === "GET") return sendJson(response, 200, await aggregateForRecord(record));
|
||||||
if (suffix === "cancel" && request.method === "POST") return sendError(response, 409, "caserun_cancel_not_supported", "This compile-only CaseRun worker cannot be cancelled after dispatch.", { runId, status: record.status });
|
if (suffix === "cancel" && request.method === "POST") return sendError(response, 409, "caserun_cancel_not_supported", "This CaseRun worker cannot be cancelled after dispatch.", { runId, status: record.status });
|
||||||
return sendError(response, 405, "method_not_allowed", suffix === "cancel" ? "POST required." : "GET required.");
|
return sendError(response, 405, "method_not_allowed", suffix === "cancel" ? "POST required." : "GET required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function caseHelp() {
|
|||||||
],
|
],
|
||||||
notes: [
|
notes: [
|
||||||
"case prompt 是无服务预览入口,只按真实 CaseRun 组装路径渲染输入 prompt,不准备 worktree、不提交 agent、不访问硬件。",
|
"case prompt 是无服务预览入口,只按真实 CaseRun 组装路径渲染输入 prompt,不准备 worktree、不提交 agent、不访问硬件。",
|
||||||
"case build remains a compile-only HWPOD smoke stage.",
|
"case build executes the validation plan declared by the builtin v0.3 case definition.",
|
||||||
"case run prepares the subject worktree, submits a prompt to HWLAB Code Agent, records agent provenance and workspace diff, then runs compile validation.",
|
"case run prepares the subject worktree, submits a prompt to HWLAB Code Agent, records agent provenance and workspace diff, then runs compile validation.",
|
||||||
"case aggregate is registry-only and offline by default; it reads existing artifacts without querying runtime.",
|
"case aggregate is registry-only and offline by default; it reads existing artifacts without querying runtime.",
|
||||||
"case refresh/backfill defaults to local registry artifacts; only explicit --source runtime may query the existing trace CLI.",
|
"case refresh/backfill defaults to local registry artifacts; only explicit --source runtime may query the existing trace CLI.",
|
||||||
|
|||||||
Reference in New Issue
Block a user