fix: remove v0.2 caserun runtime fallback
This commit is contained in:
@@ -9,9 +9,6 @@
|
||||
"commitId": "df7a4e6e551fa90d64bde5537cc000f89d63dd20",
|
||||
"subdir": "projects/01_baseline"
|
||||
},
|
||||
"runtime": {
|
||||
"apiUrl": "http://74.48.78.17:19667"
|
||||
},
|
||||
"expected": {
|
||||
"compile": {
|
||||
"success": true,
|
||||
|
||||
@@ -21,7 +21,12 @@ test("cloud-api exposes web CaseRun cases, run status, events and aggregate", as
|
||||
}), "utf8");
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), sampleSpecYaml(), "utf8");
|
||||
const seen: any[] = [];
|
||||
const accessController = {
|
||||
async ensureBootstrap() {},
|
||||
async requireNavAccess() { return true; }
|
||||
};
|
||||
const server = createCloudApiServer({
|
||||
accessController,
|
||||
env: { PATH: process.env.PATH, HWLAB_CASERUN_CASE_REPO: root, HWLAB_CASERUN_STATE_DIR: stateRoot },
|
||||
caseRunExecutor: async (context: any, emit: any) => {
|
||||
seen.push(context.parsed);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { prepareCaseRun } from "./src/hwlab-caserun-lib.ts";
|
||||
|
||||
const NOW = "2026-06-06T00:00:00.000Z";
|
||||
const SUBJECT_COMMIT = "df7a4e6e551fa90d64bde5537cc000f89d63dd20";
|
||||
const TEST_RUNTIME_API_URL = "http://api.test";
|
||||
|
||||
test("CaseRun records source root baseline so pre-existing dirty files are not attributed to the run", async () => {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-source-root-"));
|
||||
@@ -26,7 +27,6 @@ test("CaseRun records source root baseline so pre-existing dirty files are not a
|
||||
commitId: SUBJECT_COMMIT,
|
||||
subdir: "projects/01_baseline"
|
||||
},
|
||||
runtime: { apiUrl: "http://hwlab.test" },
|
||||
agentTask: { workspace: "subjectWorktree", prompt: "record source root baseline", constraints: [] }
|
||||
}, null, 2), "utf8");
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), [
|
||||
@@ -64,7 +64,7 @@ test("CaseRun records source root baseline so pre-existing dirty files are not a
|
||||
|
||||
const result = await prepareCaseRun({
|
||||
parsed: { _: [], caseRepo, runId: "source-root-baseline" },
|
||||
env: {},
|
||||
env: { HWLAB_RUNTIME_API_URL: TEST_RUNTIME_API_URL },
|
||||
fetchImpl: fetchImpl as typeof fetch,
|
||||
cwd: root,
|
||||
now: () => NOW,
|
||||
|
||||
@@ -9,6 +9,9 @@ import { artifactsFromKeilStatusForTest, extractKeilJobId, jobStatusCommandForTe
|
||||
|
||||
const SUBJECT_COMMIT_ID = "df7a4e6e551fa90d64bde5537cc000f89d63dd20";
|
||||
const SUBJECT_REPO_LOCAL_PATH = "F:\\Work\\HWLAB-CASE-F103";
|
||||
const TEST_RUNTIME_API_URL = "http://api.test";
|
||||
const TEST_RUNTIME_WEB_URL = "http://web.test";
|
||||
const TEST_RUNTIME_ENV = { HWLAB_RUNTIME_API_URL: TEST_RUNTIME_API_URL, HWLAB_RUNTIME_WEB_URL: TEST_RUNTIME_WEB_URL };
|
||||
|
||||
test("hwlab-cli case prepare copies a case hwpod-spec into isolated run state", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
@@ -24,7 +27,7 @@ test("hwlab-cli case prepare copies a case hwpod-spec into isolated run state",
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), hwpodSpecText(), "utf8");
|
||||
|
||||
const requests: any[] = [];
|
||||
const result = await runHwlabCli(["case", "prepare", "d601-f103-v2-compile", "--case-repo", caseRepo, "--run-id", "run-test"], { cwd, now: () => "2026-06-05T00:00:00.000Z", fetchImpl: subjectWorktreeFetch(requests) });
|
||||
const result = await runHwlabCli(["case", "prepare", "d601-f103-v2-compile", "--case-repo", caseRepo, "--run-id", "run-test"], { cwd, now: () => "2026-06-05T00:00:00.000Z", env: TEST_RUNTIME_ENV, fetchImpl: subjectWorktreeFetch(requests) });
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.payload.action, "case.prepare.prepare");
|
||||
@@ -57,6 +60,40 @@ test("hwlab-cli case prepare copies a case hwpod-spec into isolated run state",
|
||||
}
|
||||
});
|
||||
|
||||
test("case prepare requires an explicit v0.3 runtime API URL", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const caseRepo = path.join(root, "hwlab-case-registry");
|
||||
const cwd = path.join(root, "hwlab");
|
||||
try {
|
||||
await createCaseRepo(caseRepo, "d601-f103-v2-compile");
|
||||
await mkdir(cwd, { recursive: true });
|
||||
|
||||
const result = await runHwlabCli(["case", "prepare", "d601-f103-v2-compile", "--case-repo", caseRepo], { cwd, now: () => "2026-06-05T00:00:00.000Z" });
|
||||
|
||||
assert.equal(result.exitCode, 1);
|
||||
assert.equal(result.payload.error.code, "caserun_runtime_api_url_required");
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("case prepare rejects the removed v0.2 runtime API URL", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const caseRepo = path.join(root, "hwlab-case-registry");
|
||||
const cwd = path.join(root, "hwlab");
|
||||
try {
|
||||
await createCaseRepo(caseRepo, "d601-f103-v2-compile");
|
||||
await mkdir(cwd, { recursive: true });
|
||||
|
||||
const result = await runHwlabCli(["case", "prepare", "d601-f103-v2-compile", "--case-repo", caseRepo], { cwd, now: () => "2026-06-05T00:00:00.000Z", env: { HWLAB_RUNTIME_API_URL: "http://74.48.78.17:19667" } });
|
||||
|
||||
assert.equal(result.exitCode, 1);
|
||||
assert.equal(result.payload.error.code, "caserun_removed_v02_runtime_url");
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("case prepare rejects legacy subject repo/ref without a local repo path and commit id", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const caseRepo = path.join(root, "hwlab-case-registry");
|
||||
@@ -70,7 +107,7 @@ test("case prepare rejects legacy subject repo/ref without a local repo path and
|
||||
await writeFile(path.join(caseDir, "case.json"), JSON.stringify({ contractVersion: "hwpod-case-v1", caseId: "legacy-case", hwpodSpec: "hwpod-spec.yaml", subject: { repo: "git@github.com:pikasTech/D601-HWLAB.git", ref: "main" } }, null, 2), "utf8");
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), hwpodSpecText(), "utf8");
|
||||
|
||||
const result = await runHwlabCli(["case", "prepare", "legacy-case", "--case-repo", caseRepo], { cwd, now: () => "2026-06-05T00:00:00.000Z", fetchImpl: subjectWorktreeFetch([]) });
|
||||
const result = await runHwlabCli(["case", "prepare", "legacy-case", "--case-repo", caseRepo], { cwd, now: () => "2026-06-05T00:00:00.000Z", env: TEST_RUNTIME_ENV, fetchImpl: subjectWorktreeFetch([]) });
|
||||
|
||||
assert.equal(result.exitCode, 1);
|
||||
assert.equal(result.payload.error.code, "subject_repo_local_path_required");
|
||||
@@ -90,7 +127,7 @@ test("case prepare uses the new registry by default and refuses the removed hwpo
|
||||
await createCaseRepo(removedRepo, caseId);
|
||||
await mkdir(cwd, { recursive: true });
|
||||
|
||||
const defaultResult = await runHwlabCli(["case", "prepare", caseId, "--run-id", "run-default"], { cwd, now: () => "2026-06-05T00:00:00.000Z", fetchImpl: subjectWorktreeFetch([]) });
|
||||
const defaultResult = await runHwlabCli(["case", "prepare", caseId, "--run-id", "run-default"], { cwd, now: () => "2026-06-05T00:00:00.000Z", env: TEST_RUNTIME_ENV, fetchImpl: subjectWorktreeFetch([]) });
|
||||
assert.equal(defaultResult.exitCode, 0);
|
||||
assert.equal(defaultResult.payload.caseRepo, newRepo);
|
||||
|
||||
@@ -123,7 +160,7 @@ test("case prompt previews the real assembled prompt in Chinese without starting
|
||||
}, null, 2), "utf8");
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), hwpodSpecText(), "utf8");
|
||||
|
||||
const result = await runHwlabCli(["case", "prompt", "d601-f103-v2-prompt-preview", "--case-repo", caseRepo, "--run-id", "run-prompt-preview"], { cwd, now: () => "2026-06-08T00:00:00.000Z" });
|
||||
const result = await runHwlabCli(["case", "prompt", "d601-f103-v2-prompt-preview", "--case-repo", caseRepo, "--run-id", "run-prompt-preview"], { cwd, now: () => "2026-06-08T00:00:00.000Z", env: TEST_RUNTIME_ENV });
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.payload.action, "case.prompt");
|
||||
@@ -190,6 +227,7 @@ test("case run start returns immediately and status/result/logs are short pollin
|
||||
], {
|
||||
cwd,
|
||||
now: () => "2026-06-05T00:00:00.000Z",
|
||||
env: TEST_RUNTIME_ENV,
|
||||
startProcess: async (input) => {
|
||||
workerArgs = input.args;
|
||||
await writeFile(input.stdoutPath, "worker accepted\n", "utf8");
|
||||
@@ -328,7 +366,6 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
hwpodSpec: "hwpod-spec.yaml",
|
||||
subject: { repoLocalPath: SUBJECT_REPO_LOCAL_PATH, commitId: SUBJECT_COMMIT_ID, subdir: "projects/01_baseline" },
|
||||
agentTask: { workspace: "subjectWorktree", prompt: "请通过 HWPOD 做一个很小的源码修改。", timeoutMs: 234000, pollIntervalMs: 10 },
|
||||
runtime: { apiUrl: "http://api.test", webUrl: "http://web.test" }
|
||||
}, null, 2), "utf8");
|
||||
await writeFile(path.join(caseDir, "hwpod-spec.yaml"), hwpodSpecText(), "utf8");
|
||||
|
||||
@@ -367,7 +404,7 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
await writeFile(path.join(runDir, "worker.stderr.log"), "", "utf8");
|
||||
sawAgentRunningState = true;
|
||||
},
|
||||
env: { HWLAB_API_KEY: "hwl_live_test_case_agent_key" },
|
||||
env: { ...TEST_RUNTIME_ENV, HWLAB_API_KEY: "hwl_live_test_case_agent_key" },
|
||||
runProcess: async (command) => {
|
||||
runProcessCalls.push(command);
|
||||
const gitArgs = command.slice(1);
|
||||
@@ -594,6 +631,11 @@ test("case run archives rendered trace transcript when finalResponse is null", a
|
||||
{ traceId: "trc_final_null", seq: 2, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "/bin/sh -lc 'hwpod build --spec .hwlab/hwpod-spec.yaml'", stdoutSummary: "build completed", exitCode: 0, createdAt: "2026-06-06T00:00:02.000Z" },
|
||||
{ traceId: "trc_final_null", seq: 3, label: "agentrun:terminal:failed", type: "result", terminal: true, status: "failed", message: "Error running remote compact task: unexpected status 404 Not Found", createdAt: "2026-06-06T00:00:03.000Z" }
|
||||
],
|
||||
rows: [
|
||||
{ rowId: "trace-assistant:1", seq: 1, tone: "source", header: "00:00:01 助手消息", body: "我先检查当前 HWPOD。" },
|
||||
{ rowId: "tool:build", seq: 2, tone: "ok", header: "00:00:02 ok commandExecution", bodyFormat: "text", body: "hwpod build --spec .hwlab/hwpod-spec.yaml\nstdout:\nbuild completed\nexitCode=0" },
|
||||
{ rowId: "trace-terminal:3", seq: 3, tone: "blocked", header: "00:00:03 终止失败", terminal: true, body: "Error running remote compact task: unexpected status 404 Not Found" }
|
||||
],
|
||||
error: { message: "Error running remote compact task: unexpected status 404 Not Found" }
|
||||
};
|
||||
const evidence = {
|
||||
@@ -604,7 +646,7 @@ test("case run archives rendered trace transcript when finalResponse is null", a
|
||||
autoEvaluation: false,
|
||||
status: "recorded",
|
||||
subject: run.subject,
|
||||
agent: { traceId: "trc_final_null", sessionId: "ses_final_null", conversationId: "cnv_final_null", threadId: "thread-final-null", providerProfile: "deepseek", projectId: "prj_hwpod_workbench", promptSha256: "prompt-sha" },
|
||||
agent: { traceId: "trc_final_null", baseUrl: TEST_RUNTIME_WEB_URL, sessionId: "ses_final_null", conversationId: "cnv_final_null", threadId: "thread-final-null", providerProfile: "deepseek", projectId: "prj_hwpod_workbench", promptSha256: "prompt-sha" },
|
||||
agentTrace: run.agentTrace,
|
||||
agentDiff: run.agentDiff,
|
||||
hwpod: { source: "case-run-runner-post-agent-compile-check", exitCode: 0 },
|
||||
@@ -705,7 +747,7 @@ test("case aggregate writes one registry markdown entry without grading or broad
|
||||
runId,
|
||||
runDir: "/root/hwlab-v02/.state/hwlab-cli/caserun/case04-completed-run",
|
||||
caseRepo,
|
||||
apiUrl: "http://74.48.78.17:19667",
|
||||
apiUrl: TEST_RUNTIME_API_URL,
|
||||
compileOnly: true,
|
||||
createdAt: "2026-06-08T00:00:00.000Z",
|
||||
completedAt: "2026-06-08T00:10:00.000Z",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPEC: PJ2026-01010305 71FREQ preinstall draft-2026-06-26-71freq-v03-hwpod-preinstall.
|
||||
// Responsibility: v0.3 CaseRun runtime-origin resolution and compile-only HWPOD smoke orchestration.
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { closeSync, openSync } from "node:fs";
|
||||
@@ -7,8 +10,8 @@ import path from "node:path";
|
||||
import { renderTraceRowsMarkdown, traceDisplayRows, traceNoiseEventCount, type TraceEventRow } from "./hwlab-cli/trace-renderer.ts";
|
||||
import { readHwpodSpec } from "./hwpod-harness-lib.ts";
|
||||
|
||||
const DEFAULT_API_URL = "http://74.48.78.17:19667";
|
||||
const DEFAULT_WEB_URL = "http://74.48.78.17:19666";
|
||||
const REMOVED_V02_API_URL = "http://74.48.78.17:19667";
|
||||
const REMOVED_V02_WEB_URL = "http://74.48.78.17:19666";
|
||||
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
||||
const DEFAULT_JOB_TIMEOUT_MS = 50000;
|
||||
const MAX_JOB_TIMEOUT_MS = 300000;
|
||||
@@ -378,7 +381,7 @@ export async function startCaseRun(context: CaseContext) {
|
||||
caseFile: "",
|
||||
sourceSpecPath: "",
|
||||
specPath: path.join(runDir, ".hwlab", "hwpod-spec.yaml"),
|
||||
apiUrl: text(context.parsed.apiUrl ?? context.parsed.apiBaseUrl ?? context.parsed.runtimeApiUrl ?? context.env.HWLAB_RUNTIME_API_URL) || DEFAULT_API_URL,
|
||||
apiUrl: apiUrlFrom(context, {}),
|
||||
compileOnly: true,
|
||||
createdAt: context.now(),
|
||||
updatedAt: context.now(),
|
||||
@@ -1367,12 +1370,19 @@ async function caseFetchJson(context: CaseContext, url: string, init: RequestIni
|
||||
|
||||
function caseAgentHeaders(context: CaseContext) {
|
||||
const apiKey = text(context.env.HWLAB_API_KEY);
|
||||
if (!apiKey.startsWith(HWLAB_API_KEY_PREFIX)) throw cliError("api_key_required", "case run Code Agent stage requires HWLAB_API_KEY for HWLAB v0.2 Cloud Web", { allowed: "HWLAB_API_KEY" });
|
||||
if (!apiKey.startsWith(HWLAB_API_KEY_PREFIX)) throw cliError("api_key_required", "case run Code Agent stage requires HWLAB_API_KEY for HWLAB Cloud Web", { allowed: "HWLAB_API_KEY" });
|
||||
return { "content-type": "application/json", authorization: `Bearer ${apiKey}` };
|
||||
}
|
||||
|
||||
function webUrlFrom(context: CaseContext, definition: any) {
|
||||
return text(context.parsed.baseUrl ?? context.parsed.webUrl ?? definition?.runtime?.webUrl ?? context.env.HWLAB_RUNTIME_WEB_URL ?? context.env.HWLAB_CLOUD_WEB_URL) || DEFAULT_WEB_URL;
|
||||
const value = text(context.parsed.baseUrl ?? context.parsed.webUrl ?? context.env.HWLAB_RUNTIME_WEB_URL ?? context.env.HWLAB_CLOUD_WEB_URL);
|
||||
if (!value) {
|
||||
throw cliError("caserun_runtime_web_url_required", "CaseRun v0.3 requires an explicit runtime Web URL from --base-url, --web-url, HWLAB_RUNTIME_WEB_URL, or HWLAB_CLOUD_WEB_URL; case runtime.webUrl fallback is removed.", {
|
||||
allowed: ["--base-url", "--web-url", "HWLAB_RUNTIME_WEB_URL", "HWLAB_CLOUD_WEB_URL"],
|
||||
removedFallbacks: ["case.runtime.webUrl", REMOVED_V02_WEB_URL]
|
||||
});
|
||||
}
|
||||
return rejectRemovedV02RuntimeUrl(value, "web");
|
||||
}
|
||||
|
||||
function firstHwpodOutput(body: any) {
|
||||
@@ -1755,7 +1765,23 @@ function keilJobStatus(body: any) {
|
||||
}
|
||||
|
||||
function apiUrlFrom(context: CaseContext, definition: any) {
|
||||
return text(context.parsed.apiUrl ?? context.parsed.apiBaseUrl ?? context.parsed.runtimeApiUrl ?? definition?.runtime?.apiUrl ?? context.env.HWLAB_RUNTIME_API_URL) || DEFAULT_API_URL;
|
||||
const value = text(context.parsed.apiUrl ?? context.parsed.apiBaseUrl ?? context.parsed.runtimeApiUrl ?? context.env.HWLAB_RUNTIME_API_URL ?? context.env.HWLAB_CASERUN_RUNTIME_API_URL);
|
||||
if (!value) {
|
||||
throw cliError("caserun_runtime_api_url_required", "CaseRun v0.3 requires an explicit runtime API URL from --api-url, --api-base-url, --runtime-api-url, HWLAB_RUNTIME_API_URL, or HWLAB_CASERUN_RUNTIME_API_URL; case runtime.apiUrl fallback is removed.", {
|
||||
allowed: ["--api-url", "--api-base-url", "--runtime-api-url", "HWLAB_RUNTIME_API_URL", "HWLAB_CASERUN_RUNTIME_API_URL"],
|
||||
removedFallbacks: ["case.runtime.apiUrl", REMOVED_V02_API_URL]
|
||||
});
|
||||
}
|
||||
return rejectRemovedV02RuntimeUrl(value, "api");
|
||||
}
|
||||
|
||||
function rejectRemovedV02RuntimeUrl(value: string, kind: "api" | "web") {
|
||||
const normalized = value.replace(/\/+$/u, "");
|
||||
const removed = kind === "web" ? REMOVED_V02_WEB_URL : REMOVED_V02_API_URL;
|
||||
if (normalized === removed) {
|
||||
throw cliError("caserun_removed_v02_runtime_url", `CaseRun v0.3 does not support the removed v0.2 ${kind} runtime URL`, { kind, url: normalized });
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function stateRoot(context: CaseContext) {
|
||||
@@ -1780,16 +1806,11 @@ function agentTraceLookupForRun(run: PreparedCaseRun, evidence?: any) {
|
||||
const agent = run.agent ?? evidence?.agent;
|
||||
const traceId = text(agent?.traceId ?? evidence?.agentTrace?.traceId ?? run.agentTrace?.traceId);
|
||||
if (!traceId) return null;
|
||||
const baseUrl = text(agent?.baseUrl ?? run.agent?.baseUrl ?? evidence?.agent?.baseUrl) || runtimeWebUrl(run.definition) || DEFAULT_WEB_URL;
|
||||
const baseUrl = text(agent?.baseUrl ?? run.agent?.baseUrl ?? evidence?.agent?.baseUrl);
|
||||
if (!baseUrl) return null;
|
||||
return agentTraceLookup({ baseUrl, traceId, sessionId: text(agent?.sessionId), conversationId: text(agent?.conversationId), threadId: text(agent?.threadId) });
|
||||
}
|
||||
|
||||
function runtimeWebUrl(definition: Record<string, unknown>) {
|
||||
const runtime = definition.runtime;
|
||||
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return "";
|
||||
return text((runtime as Record<string, unknown>).webUrl);
|
||||
}
|
||||
|
||||
function renderEvidenceSummary(evidence: any) {
|
||||
const agentStage = agentStageSummary(evidence);
|
||||
const traceLookup = evidence.traceLookup ?? evidence.agent?.traceLookup;
|
||||
@@ -2644,7 +2665,7 @@ async function loadControlRunSkeleton(context: CaseContext, caseId: string, runI
|
||||
caseFile: "",
|
||||
sourceSpecPath: "",
|
||||
specPath: path.join(runDir, ".hwlab", "hwpod-spec.yaml"),
|
||||
apiUrl: text(context.parsed.apiUrl ?? context.parsed.apiBaseUrl ?? context.parsed.runtimeApiUrl ?? context.env.HWLAB_RUNTIME_API_URL) || DEFAULT_API_URL,
|
||||
apiUrl: apiUrlFrom(context, {}),
|
||||
compileOnly: true,
|
||||
createdAt: context.now(),
|
||||
updatedAt: context.now(),
|
||||
|
||||
Reference in New Issue
Block a user