diff --git a/sdk/typescript/src/worker-progress.ts b/sdk/typescript/src/worker-progress.ts index 83fa1242..e83c240f 100644 --- a/sdk/typescript/src/worker-progress.ts +++ b/sdk/typescript/src/worker-progress.ts @@ -108,6 +108,7 @@ function preflightStatus( item: Readonly>, ): ScanWorkerStatus | null { if ( + item["status"] === "failed" || typeof item["command"] !== "string" || !PREFLIGHT_COMMAND.test(item["command"]) || typeof item["aggregated_output"] !== "string" @@ -128,6 +129,23 @@ function preflightStatus( ) { return null; } + const exitCode = item["exit_code"]; + if (typeof exitCode === "number") { + const expectedExitCode = + payload["status"] === "blocked" + ? 1 + : payload["status"] === "incomplete" + ? 2 + : payload["status"] === "ready" + ? 0 + : null; + if ( + (expectedExitCode !== null && exitCode !== expectedExitCode) || + (expectedExitCode === null && exitCode !== 0) + ) { + return null; + } + } const results = payload["results"]; const delegated = results.filter( (result): result is Record => diff --git a/sdk/typescript/tests-ts/worker-preflight-failure.test.ts b/sdk/typescript/tests-ts/worker-preflight-failure.test.ts new file mode 100644 index 00000000..6155132a --- /dev/null +++ b/sdk/typescript/tests-ts/worker-preflight-failure.test.ts @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test"; +import { workerStatusFromEvent } from "../src/worker-progress.js"; + +function output(status: "ready" | "blocked" | "incomplete" = "ready") { + return JSON.stringify({ + profile: "security_scan", + status, + results: [ + { capability: "delegated_workers", status: "pass", actual: true }, + { capability: "usable_worker_slots_6", status: "pass", actual: 8 }, + ], + }); +} + +function event(overrides: Record = {}) { + return { + type: "item.completed", + item: { + id: "preflight-1", + type: "command_execution", + command: "python3 /plugin/scripts/config_preflight.py --profile security_scan", + aggregated_output: output(), + status: "completed", + exit_code: 0, + ...overrides, + }, + }; +} + +const capability = { + kind: "preflight", + delegation: "available", + configuredSlots: 8, +} as const; + +test("ignores capability output from failed or contradictory preflight commands", () => { + expect(workerStatusFromEvent(event({ status: "failed" }))).toBeNull(); + expect(workerStatusFromEvent(event({ exit_code: 2 }))).toBeNull(); + expect( + workerStatusFromEvent( + event({ aggregated_output: output("blocked"), exit_code: 2 }), + ), + ).toBeNull(); +}); + +test("keeps capability output from valid ready, blocked, and incomplete preflights", () => { + expect(workerStatusFromEvent(event())).toEqual(capability); + expect( + workerStatusFromEvent( + event({ aggregated_output: output("blocked"), exit_code: 1 }), + ), + ).toEqual(capability); + expect( + workerStatusFromEvent( + event({ aggregated_output: output("incomplete"), exit_code: 2 }), + ), + ).toEqual(capability); +});