feat: 实现 CaseRun 运行时观测合同

This commit is contained in:
root
2026-07-16 18:49:12 +02:00
parent f63ec3beb1
commit c279b570ef
7 changed files with 517 additions and 13 deletions
@@ -0,0 +1,98 @@
import assert from "node:assert/strict";
import { test } from "bun:test";
import { compileHwpodNodeOpsPlan } from "../src/hwpod-harness-lib.ts";
import { caseRunArtifactEntries, caseRunArtifactManifest, renderValidationObservationSummary } from "../src/hwlab-caserun-closeout.ts";
import { caseValidationPlanFromDefinition, normalizeValidationStepResult, validationReplayRelationship } from "../src/hwlab-caserun-validation.ts";
const DOCUMENT = {
metadata: { name: "constart-71freq-c" },
spec: {
nodeBinding: { nodeId: "node-nc01-71freq" },
workspace: { path: "C:\\work\\caserun" },
targetDevice: { board: "71-FREQ" },
debugProbe: { type: "daplink" },
boardComm: { jsonrpcTcp: { host: "192.168.0.154", port: 8000, command: "py -3 board-comm-cli.py" } },
ioProbe: {
probes: [{ id: "main41.ai0.current", quantity: "current", unit: "mA", endpointRef: "main41", read: { method: "get", path: "ai/current", valuePath: "data.value" }, sample: { count: 3 } }],
endpoints: { main41: { kind: "boardCommJsonRpcTcp", host: "192.168.0.154", port: 8000, command: "py -3 board-comm-cli.py" } }
}
}
};
test("CaseRun derives compile-only and runtime validation plans from case definition", () => {
const compile = caseValidationPlanFromDefinition({});
assert.equal(compile.mode, "compile-only");
assert.equal(compile.compileOnly, true);
assert.deepEqual(compile.steps.map((step) => step.kind), ["build"]);
const ioProbe = caseValidationPlanFromDefinition({ validation: { mode: "io-probe", ioProbe: { probeId: "main41.ai0.current" } } });
assert.equal(ioProbe.compileOnly, false);
assert.deepEqual(ioProbe.steps.map((step) => step.kind), ["build", "download", "ioProbe"]);
const boardComm = caseValidationPlanFromDefinition({ validation: { mode: "board-comm", boardComm: { method: "get", path: "system/status" } } });
assert.deepEqual(boardComm.steps.map((step) => step.kind), ["build", "download", "boardComm"]);
});
test("HWPOD compiles ioProbe and board-comm validation into service cmd.run operations", () => {
const ioProbe = compileHwpodNodeOpsPlan({ document: DOCUMENT, intent: "io.probe.read", args: { probeId: "main41.ai0.current", count: 3 } });
assert.equal(ioProbe.ops[0].op, "cmd.run");
assert.equal(ioProbe.ops[0].args.commandBinding.kind, "io-probe");
assert.equal(ioProbe.ops[0].args.commandBinding.probeId, "main41.ai0.current");
const boardComm = compileHwpodNodeOpsPlan({ document: DOCUMENT, intent: "io.board-comm.jrpctcp", args: { method: "get", path: "system/status" } });
assert.equal(boardComm.ops[0].op, "cmd.run");
assert.equal(boardComm.ops[0].args.commandBinding.kind, "board-comm");
assert.equal(boardComm.ops[0].args.commandBinding.path, "system/status");
});
test("CaseRun normalizes authoritative ioProbe statistics and replay relationships", () => {
const plan = caseValidationPlanFromDefinition({ validation: { mode: "io-probe", ioProbe: { probeId: "main41.ai0.current", quantity: "current", unit: "mA" } } });
const step = plan.steps[2];
const normalized = normalizeValidationStepResult({
step,
document: DOCUMENT,
exitCode: 0,
payload: { body: { hwpodId: "constart-71freq-c", nodeId: "node-nc01-71freq", results: [{ opId: "op_probe", op: "cmd.run", ok: true, status: "completed", workspacePath: "C:\\work\\caserun", output: { stdout: JSON.stringify({ observationId: "obs-1", probeId: "main41.ai0.current", quantity: "current", unit: "mA", samples: [11, 12, 13], stats: { count: 3, mean: 12, min: 11, max: 13 }, rawArtifactRef: "artifacts/io/main41-ai0-current.json" }) } }] } }
});
assert.equal(normalized.blocker, undefined);
assert.equal(normalized.observation.sampleCount, 3);
assert.equal(normalized.observation.mean, 12);
assert.equal(normalized.observation.min, 11);
assert.equal(normalized.observation.max, 13);
assert.equal(normalized.observation.evidenceLevel, "external-physical-reading");
const replay = validationReplayRelationship(plan, [normalized]);
assert.deepEqual(replay.observationIds, ["obs-1"]);
assert.equal(replay.relationships[0].actionId, step.actionId);
assert.equal(replay.relationships[0].operationId, "op_probe");
});
test("CaseRun records board response and returns typed blockers for missing observations", () => {
const plan = caseValidationPlanFromDefinition({ validation: { mode: "board-comm", boardComm: { method: "get", path: "system/status" } } });
const step = plan.steps[2];
const observed = normalizeValidationStepResult({
step,
document: DOCUMENT,
exitCode: 0,
payload: { body: { hwpodId: "constart-71freq-c", nodeId: "node-nc01-71freq", results: [{ opId: "op_board", op: "cmd.run", ok: true, status: "completed", workspacePath: "C:\\work\\caserun", output: { stdout: JSON.stringify({ observationId: "obs-board", response: { ok: true, firmware: "1.2.3" }, rawArtifactRef: "artifacts/board/system-status.json" }) } }] } }
});
assert.equal(observed.blocker, undefined);
assert.equal(observed.observation.response.firmware, "1.2.3");
assert.equal(observed.observation.evidenceLevel, "board-internal-response");
const missing = normalizeValidationStepResult({ step, document: DOCUMENT, exitCode: 0, payload: { body: { hwpodId: "constart-71freq-c", nodeId: "node-nc01-71freq", results: [{ opId: "op_board", op: "cmd.run", ok: true, status: "completed", workspacePath: "C:\\work\\caserun", output: {} }] } } });
assert.equal(missing.blocker.code, "hwpod_validation_observation_inconsistent");
});
test("manifest and aggregate project archived observation facts without grading Agent final", () => {
const run: any = { caseId: "case-1", runId: "run-1", runDir: "/tmp/run-1", caseRepo: "/tmp/registry", specPath: "/tmp/run-1/.hwlab/hwpod-spec.yaml", compileOnly: false };
assert.equal(caseRunArtifactEntries(run).some((entry) => entry.rel === "validation-observations.json"), true);
const evidence = { validation: { plan: { mode: "io-probe", compileOnly: false }, results: [{ observation: { actionId: "validation_03_ioProbe", operationId: "op_probe", kind: "ioProbe", probeId: "main41.ai0.current", quantity: "current", unit: "mA", sampleCount: 3, mean: 12, min: 11, max: 13, evidenceLevel: "external-physical-reading", rawArtifactRef: "artifacts/io.json" } }] }, replay: { observationIds: ["obs-1"] }, agentFinal: { present: true, text: "agent answer" } };
const manifest: any = caseRunArtifactManifest({ now: () => "2026-07-16T00:00:00.000Z" } as any, run, evidence, [], [], {} as any);
assert.equal(manifest.validation.plan.mode, "io-probe");
assert.deepEqual(manifest.replay.observationIds, ["obs-1"]);
assert.equal(manifest.agentFinal.text, "agent answer");
const markdown = renderValidationObservationSummary(evidence.validation);
assert.match(markdown, /external-physical-reading/u);
assert.match(markdown, /main41\.ai0\.current/u);
});
+1 -1
View File
@@ -451,7 +451,7 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
if (command[0] === "git" && gitArgs[0] === "diff") return { command, exitCode: 1, stdout: "", stderr: "" };
if (command[0] === "git" && gitArgs[0] === "commit") return { command, exitCode: 0, stdout: "[main abc1234] data: archive caserun run-agent-flow\n", stderr: "" };
if (command[0] === "git" && gitArgs[0] === "push") return { command, exitCode: 0, stdout: "", stderr: "To github.com:pikasTech/hwlab-case-registry.git\n" };
return { command, exitCode: 0, stdout: JSON.stringify({ body: { results: [{ output: { stdout: JSON.stringify({ job_id: "job-compile-1" }) } }] } }), stderr: "" };
return { command, exitCode: 0, stdout: JSON.stringify({ body: { hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", results: [{ opId: "op_build", op: "debug.build", ok: true, status: "completed", workspacePath: `${SUBJECT_REPO_LOCAL_PATH}\\.worktree\\caserun-run-agent-flow`, output: { stdout: JSON.stringify({ job_id: "job-compile-1" }) } }] } }), stderr: "" };
},
fetchImpl: caseRunFlowFetch(requests)
});