diff --git a/src/tests/headless-triage-uok-gates.test.ts b/src/tests/headless-triage-uok-gates.test.ts index 2c352c49f..de4687ae2 100644 --- a/src/tests/headless-triage-uok-gates.test.ts +++ b/src/tests/headless-triage-uok-gates.test.ts @@ -8,7 +8,14 @@ * surface=headless, runControl=supervised, permissionProfile=high, traceId= * the flowId. The outcome reflects the decision (pass/fail/manual-attention). */ -import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; @@ -179,4 +186,25 @@ describe("runTriageApply emits gate_run trace events with schema-v2 metadata", ( // Review gate must NOT fire because plan validation blocked the flow. expect(reviewEvents).toHaveLength(0); }); + + test("trace_write_failure_fails_closed_before_agents_run", async () => { + const project = makeProject(); + writeFileSync(join(project, ".sf", "traces"), "not a directory"); + let calls = 0; + + const result = await runTriageApply(project, "triage prompt", { + candidateCount: 1, + allowUntrustedRunner: true, + agentRunner: async () => { + calls += 1; + return { ok: true, output: deciderPlan, exitCode: 0 }; + }, + }); + + expect(result.ok).toBe(false); + expect(result.error).toContain( + "UOK gate emission failed for trusted-agent-source-gate", + ); + expect(calls).toBe(0); + }); });