fix: authenticate CaseRun HWPOD prepare

This commit is contained in:
root
2026-07-16 21:19:40 +02:00
parent 66afd74c06
commit 5b60491e59
4 changed files with 113 additions and 3 deletions
+34
View File
@@ -5,11 +5,45 @@ import path from "node:path";
import { test } from "bun:test";
import { prepareCaseRun } from "./src/hwlab-caserun-lib.ts";
import { subjectWorktreeFailureDetails } from "./src/hwlab-caserun-subject.ts";
const NOW = "2026-06-06T00:00:00.000Z";
const SUBJECT_COMMIT = "df7a4e6e551fa90d64bde5537cc000f89d63dd20";
const TEST_RUNTIME_API_URL = "http://api.test";
test("subject worktree failures preserve common Windows operation identity without run-specific constants", () => {
for (const repoLocalPath of ["F:\\Work\\HWLAB-CASE-F103", "F:\\Work\\ConStart"]) {
const plan = {
contractVersion: "hwpod-node-ops-v1",
planId: "case_subject_test",
hwpodId: "d601-f103-v2",
nodeId: "node-d601-f103-v2",
intent: "cmd.run",
ops: [
{ opId: "op_01_subject_commit", op: "cmd.run", args: { workspacePath: repoLocalPath } },
{ opId: "op_02_subject_worktree_add", op: "cmd.run", args: { workspacePath: repoLocalPath } },
{ opId: "op_03_subject_worktree_head", op: "cmd.run", args: { workspacePath: repoLocalPath } }
]
};
const body = {
contractVersion: "hwpod-node-ops-v1",
planId: plan.planId,
hwpodId: plan.hwpodId,
nodeId: plan.nodeId,
status: "blocked",
results: plan.ops.map((op) => ({ opId: op.opId, op: op.op, ok: false, status: "blocked", blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "node unavailable token=hwl_live_should_not_leak" } })),
blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "node unavailable token=hwl_live_should_not_leak" }
};
const failure = subjectWorktreeFailureDetails(body, plan, { stderr: "" });
assert.equal(failure.partialWrite, false);
assert.equal(failure.blocker.details.dispatched, false);
assert.equal(failure.operation.nodeId, "node-d601-f103-v2");
assert.deepEqual(failure.operation.ops.map((op: any) => op.opId), plan.ops.map((op) => op.opId));
assert.doesNotMatch(failure.errorSummary, /hwl_live_should_not_leak/u);
}
});
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-"));
try {
+45 -2
View File
@@ -119,9 +119,10 @@ export async function requestSubjectWorktree(context: CaseContext, input: { case
const body = parseJsonMaybe(textBody) ?? { raw: textBody };
const status = subjectWorktreeStatus(response.status, body, { commitId: subject.commitId, worktreePath });
if (!status.ok) {
throw cliError("subject_worktree_prepare_failed", "failed to prepare isolated subject worktree on the HWPOD node", clean({ httpStatus: response.status, nodeId: document.spec.nodeBinding.nodeId, repoLocalPath: subject.repoLocalPath, commitId: subject.commitId, worktreePath, exitCode: status.exitCode, stdout: clipText(status.stdout), stderr: clipText(status.stderr), body: compactObject(body) }));
const failure = subjectWorktreeFailureDetails(body, plan, status);
throw cliError("subject_worktree_prepare_failed", "failed to prepare isolated subject worktree on the HWPOD node", clean({ httpStatus: response.status, nodeId: document.spec.nodeBinding.nodeId, repoLocalPath: subject.repoLocalPath, commitId: subject.commitId, worktreePath, exitCode: status.exitCode, partialWrite: failure.partialWrite, dispatched: false, operation: failure.operation, errorSummary: failure.errorSummary, blocker: failure.blocker }));
}
return clean({ httpStatus: response.status, planId: plan.planId, nodeId: document.spec.nodeBinding.nodeId, exitCode: status.exitCode, stdout: clipText(status.stdout), stderr: clipText(status.stderr), keilSidecars: keilSidecarSummary(status.stdout) });
return clean({ httpStatus: response.status, planId: plan.planId, nodeId: document.spec.nodeBinding.nodeId, exitCode: status.exitCode, operation: subjectWorktreeOperationIdentity(body, plan), stdout: clipText(status.stdout), stderr: clipText(status.stderr), keilSidecars: keilSidecarSummary(status.stdout) });
}
export function subjectWorktreeStatus(httpStatus: number, body: any, expected: { commitId: string; worktreePath: string }) {
@@ -136,6 +137,48 @@ export function subjectWorktreeStatus(httpStatus: number, body: any, expected: {
return { ok, exitCode: exitCodes.find((exitCode) => exitCode !== 0) ?? exitCodes[exitCodes.length - 1], stdout, stderr, actualHead, actualWorktreePath: expected.worktreePath };
}
export function subjectWorktreeFailureDetails(body: any, plan: any, status: { stderr?: string }) {
const results = subjectWorktreeResults(body);
const mutatingOpIds = new Set(["op_02_subject_worktree_add", "op_04_keil_sidecars"]);
const partialWrite = results.length > 0 ? results.some((result: any) => mutatingOpIds.has(text(result?.opId)) && result?.ok === true) : null;
const sourceBlocker = body?.blocker ?? results.find((result: any) => result?.blocker)?.blocker ?? body?.error ?? null;
const errorSummary = redactSubjectWorktreeError(text(sourceBlocker?.summary ?? sourceBlocker?.message ?? status.stderr) || "HWPOD subject worktree operation failed without an error summary");
const blocker = clean({
code: text(sourceBlocker?.code) || "subject_worktree_prepare_failed",
layer: text(sourceBlocker?.layer) || "hwpod-node",
retryable: typeof sourceBlocker?.retryable === "boolean" ? sourceBlocker.retryable : true,
summary: errorSummary,
details: clean({ nodeId: plan.nodeId, planId: plan.planId, hwpodId: plan.hwpodId, partialWrite, dispatched: false })
});
return { partialWrite, errorSummary, blocker, operation: subjectWorktreeOperationIdentity(body, plan) };
}
function subjectWorktreeOperationIdentity(body: any, plan: any) {
const results = subjectWorktreeResults(body);
const resultById = new Map(results.map((result: any) => [text(result?.opId), result]));
return clean({
contractVersion: text(body?.contractVersion) || plan.contractVersion,
planId: text(body?.planId) || plan.planId,
hwpodId: text(body?.hwpodId) || plan.hwpodId,
nodeId: text(body?.nodeId) || plan.nodeId,
intent: plan.intent,
ops: plan.ops.map((op: any) => {
const result: any = resultById.get(text(op.opId));
return clean({ opId: op.opId, op: op.op, status: text(result?.status) || null, ok: typeof result?.ok === "boolean" ? result.ok : null, blockerCode: text(result?.blocker?.code) || null });
})
});
}
function subjectWorktreeResults(body: any) {
return Array.isArray(body?.body?.results) ? body.body.results : Array.isArray(body?.results) ? body.results : [];
}
function redactSubjectWorktreeError(value: string) {
return clipText(value)
.replace(/\b(?:hwl_live|sk|ghp)_[A-Za-z0-9_-]+\b/gu, "[REDACTED]")
.replace(/\b(?:authorization|api[_-]?key|token|secret|password)\s*[:=]\s*\S+/giu, "$1=[REDACTED]");
}
export function keilSidecarCopyOp(document: any, subject: { repoLocalPath: string }, hwpodId: string, relativeWorktreePath: string) {
const project = subjectRelativeKeilProject(document, subject.repoLocalPath);
if (!project) return null;