test(uok): fail-closed contract for triage-apply gate emission

Adds the missing test case that confirms the fail-closed semantics
the parallel worker shipped in slice 3a: when the trace writer
cannot persist a UOK gate record (e.g. .sf/traces is unwritable),
runTriageApply MUST abort before any subagent runs and surface the
emission failure as the run error.

This pins down the contract codex Q5 noted as soft: enrichment
failures are debug-only, but PRIMARY gate emission for the apply
flow is hard-required. Without observable gates, an apply that
mutates the ledger has no audit trail — refusing is the right call.

Test asserts: trace-dir write failure → ok=false, error contains
"UOK gate emission failed for trusted-agent-source-gate", and the
mocked agentRunner was never invoked.

Suite: 1682/1682.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-14 18:08:29 +02:00
parent 454e051aed
commit 61d3031007

View file

@ -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);
});
});