import { describe, expect, it } from "vitest"; import { createAnthropicGuard, createOpenAiGuard, type FetchLike } from "./guard-adapters.js"; import { admitGuardAdapter, type GuardRequest, type Verdict } from "./guard.js"; const model = "guard-model-2026-07-12"; const request: GuardRequest = { direction: "outbound", source: "alice#1", destination: "bob#1", text: "meeting at ten", policyVersion: "v1", }; const allow: Verdict = { decision: "allow", category: "coordination", reason: "Routine coordination.", model, policyVersion: "v1", }; const modelAllow = { decision: "allow", category: "coordination", reason: "Routine coordination.", policyVersion: "v1", }; describe("guard admission", () => { it.each([ [ "throws", async () => { throw new Error("network"); }, ], ["malformed", async () => "not an object"], ["extra fields", async () => ({ ...allow, extra: true })], ["wrong model", async () => ({ ...allow, model: "other-model-2026-07-12" })], ])("fails closed when raw adapter %s", async (_name, classifyRaw) => { const guard = admitGuardAdapter({ providerId: "fake", pinnedModel: model, classifyRaw }); await expect(guard.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", model, }); }); it("fails closed on timeout", async () => { const guard = admitGuardAdapter( { providerId: "fake", pinnedModel: model, classifyRaw: () => new Promise(() => {}) }, 5, ); await expect(guard.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); }); it("rejects floating model aliases at construction", () => { expect(() => admitGuardAdapter({ providerId: "fake", pinnedModel: "guard-latest", async classifyRaw() { return allow; }, }), ).toThrow("dated snapshot"); }); it("rejects bare family aliases without a documented immutable id", () => { expect(() => admitGuardAdapter({ providerId: "fake", pinnedModel: "gpt-5.6", async classifyRaw() { return allow; }, }), ).toThrow("dated snapshot"); }); it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])( "accepts documented immutable undated id %s", (pinnedModel) => { expect(() => admitGuardAdapter({ providerId: "fake", pinnedModel, async classifyRaw() { return allow; }, }), ).not.toThrow(); }, ); }); describe("provider adapters", () => { it("uses OpenAI Responses strict JSON schema and accepts a recorded response", async () => { let captured: RequestInit | undefined; const fetch: FetchLike = async (_url, init) => { captured = init; return jsonResponse({ model, status: "completed", output: [ { type: "message", content: [{ type: "output_text", text: JSON.stringify(modelAllow) }] }, ], }); }; const guard = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch }); await expect(guard.classify(request)).resolves.toEqual(allow); const body = JSON.parse(captured!.body as string) as Record; expect(body).toMatchObject({ model, store: false, background: false, tools: [] }); expect(body.text.format).toMatchObject({ type: "json_schema", strict: true }); expect(body.text.format.schema.properties).not.toHaveProperty("model"); expect(body.text.format.schema.required).not.toContain("model"); expect(body.instructions).toContain("outbound DLP"); expect(body.instructions).toContain("Allow ordinary claw-to-claw collaboration"); expect(body.instructions).toContain("Default to allow when no concrete protected value"); expect(body.instructions).toContain('Set policyVersion to exactly "v1".'); }); it("accepts a recorded Anthropic response with distinct inbound instructions", async () => { let captured: RequestInit | undefined; const fetch: FetchLike = async (_url, init) => { captured = init; return jsonResponse({ model, stop_reason: "end_turn", content: [{ type: "text", text: JSON.stringify({ ...modelAllow, category: "safe" }) }], }); }; const guard = createAnthropicGuard({ apiKey: "test", pinnedModel: model, fetch }); const inbound = { ...request, direction: "inbound" as const }; await expect(guard.classify(inbound)).resolves.toMatchObject({ decision: "allow", category: "safe", }); const body = JSON.parse(captured!.body as string) as Record; expect(body.system).toContain("inbound prompt-injection"); expect(body.system).toContain("task requests"); expect(body.system).toContain("a request to collaborate is not steering by itself"); expect(body.system).toContain('Set policyVersion to exactly "v1".'); expect(body.system).not.toContain('"model"'); expect(body.output_config.format.type).toBe("json_schema"); expect(body.output_config.format.schema).toMatchObject({ additionalProperties: false, required: ["decision", "category", "reason", "policyVersion"], }); expect(body.output_config.format.schema.properties).not.toHaveProperty("model"); expect((captured!.headers as Record)["anthropic-version"]).toBe("2023-06-01"); }); it("fails closed on Anthropic prose despite provider schema enforcement", async () => { const guard = createAnthropicGuard({ apiKey: "test", pinnedModel: model, fetch: async () => jsonResponse({ model, stop_reason: "end_turn", content: [{ type: "text", text: `Verdict: ${JSON.stringify(modelAllow)}` }], }), }); await expect(guard.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); }); it.each([ [ "OpenAI", (fetch: FetchLike) => createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch }), ], [ "Anthropic", (fetch: FetchLike) => createAnthropicGuard({ apiKey: "test", pinnedModel: model, fetch }), ], ])( "cancels %s non-200 provider response bodies before failing closed", async (_name, createGuard) => { let cancelled = false; const response = new Response( new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode("partial error body")); }, cancel() { cancelled = true; }, }), { status: 503 }, ); const guard = createGuard(async () => response); await expect(guard.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); expect(cancelled).toBe(true); }, ); it("cancels oversized provider response streams before buffering them fully", async () => { const maxBytes = 256 * 1024; const chunk = new TextEncoder().encode("x".repeat(64 * 1024)); const totalChunks = 64; let emittedChunks = 0; let cancelled = false; const response = new Response( new ReadableStream({ pull(controller) { if (emittedChunks >= totalChunks) { controller.close(); return; } controller.enqueue(chunk); emittedChunks += 1; }, cancel() { cancelled = true; }, }), { headers: { "content-type": "application/json" } }, ); const guard = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: async () => response, }); await expect(guard.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); expect(cancelled).toBe(true); // Allow the overflow chunk plus one chunk queued by the stream implementation. expect(emittedChunks * chunk.byteLength).toBeLessThanOrEqual(maxBytes + chunk.byteLength * 2); }); it("accepts valid provider responses close to the body limit", async () => { const body = JSON.stringify({ model, status: "completed", output: [ { type: "message", content: [{ type: "output_text", text: JSON.stringify(modelAllow) }] }, ], provider_metadata: "x".repeat(240 * 1024), }); const bodyBytes = new TextEncoder().encode(body).byteLength; expect(bodyBytes).toBeGreaterThan(240 * 1024); expect(bodyBytes).toBeLessThan(256 * 1024); const guard = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: async () => new Response(body, { headers: { "content-type": "application/json" } }), }); await expect(guard.classify(request)).resolves.toEqual(allow); }); it("fails closed on malformed JSON and provider model mismatch", async () => { const malformed = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: async () => new Response("not json"), }); await expect(malformed.classify(request)).resolves.toMatchObject({ category: "guard_failure" }); const mismatch = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: async () => jsonResponse({ model: "other-model-2026-07-12", status: "completed", output: [] }), }); await expect(mismatch.classify(request)).resolves.toMatchObject({ category: "guard_failure" }); const duplicate = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: async () => jsonResponse({ model, status: "completed", output: [ { type: "message", content: [ { type: "output_text", text: '{"decision":"allow","decision":"deny","category":"safe","reason":"No.","policyVersion":"v1"}', }, ], }, ], }), }); await expect(duplicate.classify(request)).resolves.toMatchObject({ category: "guard_failure" }); }); it("takes model evidence from the provider and requires the model policy echo", async () => { const responseFor = (modelJson: unknown): FetchLike => async () => jsonResponse({ model, status: "completed", output: [ { type: "message", content: [{ type: "output_text", text: JSON.stringify(modelJson) }], }, ], }); const happy = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: responseFor(modelAllow), }); await expect(happy.classify(request)).resolves.toEqual(allow); const wrongPolicy = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: responseFor({ ...modelAllow, policyVersion: "v2" }), }); await expect(wrongPolicy.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); const { policyVersion: _policyVersion, ...missingPolicyJson } = modelAllow; const missingPolicy = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: responseFor(missingPolicyJson), }); await expect(missingPolicy.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); const unexpectedModel = createOpenAiGuard({ apiKey: "test", pinnedModel: model, fetch: responseFor({ ...modelAllow, model: "invented-2026-01-01" }), }); await expect(unexpectedModel.classify(request)).resolves.toMatchObject({ decision: "deny", category: "guard_failure", }); }); }); function jsonResponse(value: unknown, status = 200): Response { return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" }, }); }