/** * Deterministic verification of the focused-deconstruct experiment helper. * * Zero live calls. Uses injected fake provider for complete isolation. */ import { describe, it, expect } from "vitest"; import { buildFocusedDeconstructPrompt, validateFocusedDeconstructSchema } from "@/lib/graph/focused-investigation.js"; import { runLiveFocusedDeconstructExperiment, runLiveFocusedPromptExperiment, runLiveFocusedFrontierPromptExperiment, runLiveFocusedObservationFrontierPromptExperiment, runLiveFocusedRelationshipFrontierPromptExperiment, runLiveFocusedAssumptionFrontierPromptExperiment, runLiveFocusedObservationAssumptionFrontierPromptExperiment } from "./live-focused-deconstruct-experiment-helper.mjs"; // Minimum env for the helper's OLLAMA_MODEL guard (never contacts a real model) process.env.OLLAMA_MODEL = "test-model"; // ── env-loading verification ─────────────────────────────────────────── describe("environment loading", () => { it("helper loads .env.local before any function executes", async () => { // The helper already ran dotenv.config({ path: ".env.local" }) at import time. // If OLLAMA_MODEL was configured in .env.local, the module-level guard would see it. // If not, the test below explicitly sets it — so this test validates the chain works. // We verify by confirming no unhandled "not set in environment" error propagates // when we invoke through the helper with a safe injected provider. const fakeResult = makeValidResult({ targetNodeId: "env-verify-node" }); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedDeconstructExperiment({ ...params, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.targetNodeId).toBe(params.targetNodeId); }); }); // ── fixture ──────────────────────────────────────────────────────────── const params = { targetNodeId: "test-node-01", targetLabel: "Team capability constraints on task handoff boundaries", targetDescription: "The extent to which current skill levels and bandwidth dictate the limit of delegable tasks.", centralStatement: "A business owner seeks to delegate routine operational tasks but lacks clarity on scope.", question: "What was the comparable state before capability constraints?", answer: "She handles weekly supplier payments herself without checking for six months.", }; const suppliedPrompt = "EXACT_PROMPT_CONTENT_BYTE_IDENTICAL"; // ── fake provider builder ────────────────────────────────────────────── function makeFakeProvider(result) { const calls = []; return { generateReconstruction: async (prompt, model) => { calls.push({ prompt, model }); if (result instanceof Error) throw result; return result; }, getCalls: () => calls, }; } // ── valid deconstruct result fixture ─────────────────────────────────── function makeValidResult(extraFields = {}) { return { targetNodeId: "injected-node", observations: ["obs-1"], uncertainties: ["unc-1"], assumptions: [], relationships: [], possibleFollowUpQuestions: ["q-1"], ...extraFields, }; } // ── existing API ──────────────────────────────────────────────────────── describe("runLiveFocusedDeconstructExperiment", () => { it("produces a validated structured result through an injected fake provider", async () => { const fakeResult = makeValidResult({ targetNodeId: "injected-node" }); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedDeconstructExperiment({ ...params, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.targetNodeId).toBe(params.targetNodeId); expect(result.observations).toEqual(fakeResult.observations); expect(result.uncertainties).toEqual(fakeResult.uncertainties); expect(result.assumptions).toEqual(fakeResult.assumptions); expect(result.relationships).toEqual(fakeResult.relationships); expect(result.possibleFollowUpQuestions).toEqual(fakeResult.possibleFollowUpQuestions); expect(result.elapsedMs).toBeGreaterThanOrEqual(0); }); it("passes invalid result to schema validation and fails", async () => { const fakeProvider = makeFakeProvider({ targetNodeId: "injected-node", // missing required fields — will fail validation }); await expect( runLiveFocusedDeconstructExperiment({ ...params, provider: fakeProvider, }) ).rejects.toThrow("Focused deconstruction result did not match expected schema"); }); it("propagates provider exception without retry", async () => { const err = new Error("network failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedDeconstructExperiment({ ...params, provider: fakeProvider, }) ).rejects.toBe(err); }); }); // ── new supplied-prompt API ──────────────────────────────────────────── describe("runLiveFocusedPromptExperiment", () => { it("passes an arbitrary supplied prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeValidResult({ targetNodeId: "supplied-node" }); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedPromptExperiment({ prompt: suppliedPrompt, targetNodeId: "supplied-node", provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(suppliedPrompt); }); it("returns the same structured result shape as existing API", async () => { const fakeResult = makeValidResult({ targetNodeId: "supplied-node" }); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedPromptExperiment({ prompt: suppliedPrompt, targetNodeId: "supplied-node", provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.targetNodeId).toBe("supplied-node"); expect(result.observations).toEqual(fakeResult.observations); expect(result.uncertainties).toEqual(fakeResult.uncertainties); expect(result.assumptions).toEqual(fakeResult.assumptions); expect(result.relationships).toEqual(fakeResult.relationships); expect(result.possibleFollowUpQuestions).toEqual(fakeResult.possibleFollowUpQuestions); expect(result.elapsedMs).toBeGreaterThanOrEqual(0); }); it("passes invalid result to schema validation and fails", async () => { const fakeProvider = makeFakeProvider({ targetNodeId: "supplied-node", // missing required fields — will fail validation }); await expect( runLiveFocusedPromptExperiment({ prompt: suppliedPrompt, targetNodeId: "supplied-node", provider: fakeProvider, }) ).rejects.toThrow("Focused deconstruction result did not match expected schema"); }); it("propagates provider exception without retry", async () => { const err = new Error("network failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedPromptExperiment({ prompt: suppliedPrompt, targetNodeId: "supplied-node", provider: fakeProvider, }) ).rejects.toBe(err); }); }); // ── frontier runner (EXP37 minimal two-field contract) ───────────────── const FRONTIER_PROMPT = "EXACT_FRONTIER_TEST_PROMPT_BYTE_IDENTICAL"; function makeMinimalFrontierResult() { return { uncertainties: ["What is still unknown?"], possibleFollowUpQuestions: ["What happened in the failed cases?"], }; } describe("runLiveFocusedFrontierPromptExperiment", () => { it("passes prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeMinimalFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(FRONTIER_PROMPT); }); it("accepts valid minimal result", async () => { const fakeResult = makeMinimalFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.uncertainties).toEqual(["What is still unknown?"]); expect(result.possibleFollowUpQuestions).toEqual(["What happened in the failed cases?"]); expect(result.elapsedMs).toBeGreaterThanOrEqual(0); }); it("rejects empty uncertainties array", async () => { const fakeProvider = makeFakeProvider({ uncertainties: [], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects uncertainties array with two elements", async () => { const fakeProvider = makeFakeProvider({ uncertainties: ["one", "two"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects missing uncertainties", async () => { const fakeProvider = makeFakeProvider({ possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must be an array"); }); it("rejects empty possibleFollowUpQuestions array", async () => { const fakeProvider = makeFakeProvider({ uncertainties: ["one uncertainty"], possibleFollowUpQuestions: [], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects possibleFollowUpQuestions array with two elements", async () => { const fakeProvider = makeFakeProvider({ uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["q1", "q2"], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects missing possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ uncertainties: ["one uncertainty"], }); await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must be an array"); }); it("propagates provider exception without retry", async () => { const err = new Error("provider failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }) ).rejects.toBe(err); }); it("does not require full-deconstruction fields", async () => { const fakeResult = makeMinimalFrontierResult(); // No targetNodeId, observations, assumptions, or relationships present const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect("targetNodeId" in result).toBe(false); expect("observations" in result).toBe(false); expect("assumptions" in result).toBe(false); expect("relationships" in result).toBe(false); }); }); // ── observation-frontier runner (EXP38 three-field contract) ───────────── const OBSERVATION_FRONTIER_TEST_PROMPT = "OBSERVATION FRONTIER TEST PROMPT"; function makeObservationFrontierResult() { return { observations: [ "She handles weekly supplier payments herself.", "Failed or unusual payments come back to the owner.", ], uncertainties: ["What is different about the failed or unusual payments?"], possibleFollowUpQuestions: [ "What is different about the failed or unusual payments that means they come back to you?", ], }; } describe("runLiveFocusedObservationFrontierPromptExperiment", () => { it("passes prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeObservationFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(OBSERVATION_FRONTIER_TEST_PROMPT); }); it("accepts valid three-field result", async () => { const fakeResult = makeObservationFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.observations).toEqual(fakeResult.observations); expect(result.uncertainties).toEqual(fakeResult.uncertainties); expect(result.possibleFollowUpQuestions).toEqual(fakeResult.possibleFollowUpQuestions); expect(result.elapsedMs).toBeGreaterThanOrEqual(0); }); it("rejects empty observations array", async () => { const fakeProvider = makeFakeProvider({ observations: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("observations must contain at least one element"); }); it("rejects non-string observation element", async () => { const fakeProvider = makeFakeProvider({ observations: ["valid", "also valid", 123], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("observations[2] must be a non-empty string"); }); it("rejects zero uncertainties", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], uncertainties: [], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects two uncertainties", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], uncertainties: ["one", "two"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects zero possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: [], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects two possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["q1", "q2"], }); await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("does not require assumptions, relationships, or targetNodeId", async () => { const result = makeObservationFrontierResult(); // Ensure no extra fields delete result.assumptions; delete result.relationships; delete result.targetNodeId; const fakeProvider = makeFakeProvider(result); const run = await runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(run.success).toBe(true); }); it("propagates provider exception without retry", async () => { const err = new Error("provider failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toBe(err); }); }); // ── relationship-frontier runner (EXP44 three-field contract with relationships) ───────────── const RELATIONSHIP_FRONTIER_TEST_PROMPT = "RELATIONSHIP FRONTIER TEST PROMPT"; function makeRelationshipFrontierResult() { return { relationships: [ "She handles weekly supplier payments herself.", "Failed or unusual payments come back to the owner.", ], uncertainties: ["What is different about the failed or unusual payments?"], possibleFollowUpQuestions: [ "What is different about the failed or unusual payments that means they come back to you?", ], }; } describe("runLiveFocusedRelationshipFrontierPromptExperiment", () => { it("passes prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeRelationshipFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(RELATIONSHIP_FRONTIER_TEST_PROMPT); }); it("accepts valid relationship-frontier result", async () => { const fakeResult = makeRelationshipFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.relationships).toEqual(fakeResult.relationships); expect(result.uncertainties).toEqual(fakeResult.uncertainties); expect(result.possibleFollowUpQuestions).toEqual(fakeResult.possibleFollowUpQuestions); expect(result.elapsedMs).toBeGreaterThanOrEqual(0); }); it("rejects empty relationships array", async () => { const fakeProvider = makeFakeProvider({ relationships: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("relationships must contain at least one element"); }); it("rejects non-string relationship element", async () => { const fakeProvider = makeFakeProvider({ relationships: ["valid", 123], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("relationships[1] must be a non-empty string"); }); it("rejects zero uncertainties", async () => { const fakeProvider = makeFakeProvider({ relationships: ["rel-1"], uncertainties: [], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects two uncertainties", async () => { const fakeProvider = makeFakeProvider({ relationships: ["rel-1"], uncertainties: ["one", "two"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects zero possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ relationships: ["rel-1"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: [], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects two possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ relationships: ["rel-1"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["q1", "q2"], }); await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("does not require observations, assumptions, or targetNodeId", async () => { const result = makeRelationshipFrontierResult(); // Ensure no extra fields delete result.observations; delete result.assumptions; delete result.targetNodeId; const fakeProvider = makeFakeProvider(result); const run = await runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(run.success).toBe(true); }); it("propagates provider exception without retry", async () => { const err = new Error("provider failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toBe(err); }); }); // ── assumption-frontier runner (EXP45) ───────────────────────────────── const ASSUMPTION_FRONTIER_TEST_PROMPT = "ASSUMPTION FRONTIER TEST PROMPT"; function makeAssumptionFrontierResultWithNoAssumptions() { return { assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }; } function makeAssumptionFrontierResultWithOneAssumption() { return { assumptions: ["one genuinely attributable assumption"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }; } describe("runLiveFocusedAssumptionFrontierPromptExperiment", () => { it("passes prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeAssumptionFrontierResultWithOneAssumption(); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(ASSUMPTION_FRONTIER_TEST_PROMPT); }); it("accepts valid result with no assumptions", async () => { const fakeResult = makeAssumptionFrontierResultWithNoAssumptions(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.assumptions).toEqual([]); expect(result.uncertainties).toEqual(["one uncertainty"]); expect(result.possibleFollowUpQuestions).toEqual(["one question"]); }); it("accepts valid result with one assumption", async () => { const fakeResult = makeAssumptionFrontierResultWithOneAssumption(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.assumptions).toEqual(["one genuinely attributable assumption"]); expect(result.uncertainties).toEqual(["one uncertainty"]); expect(result.possibleFollowUpQuestions).toEqual(["one question"]); }); it("rejects empty string assumption element", async () => { const fakeProvider = makeFakeProvider({ assumptions: ["", "valid"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("assumptions[0] must be a non-empty string"); }); it("rejects non-string assumption element", async () => { const fakeProvider = makeFakeProvider({ assumptions: [123], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("assumptions[0] must be a non-empty string"); }); it("rejects zero uncertainties", async () => { const fakeProvider = makeFakeProvider({ assumptions: [], uncertainties: [], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects multiple uncertainties", async () => { const fakeProvider = makeFakeProvider({ assumptions: [], uncertainties: ["one", "two"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects zero possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: [], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects multiple possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["q1", "q2"], }); await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("does not require observations, relationships, or targetNodeId", async () => { const result = makeAssumptionFrontierResultWithNoAssumptions(); delete result.observations; delete result.relationships; delete result.targetNodeId; const fakeProvider = makeFakeProvider(result); const run = await runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(run.success).toBe(true); }); it("propagates provider exception without retry", async () => { const err = new Error("provider failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toBe(err); }); }); // ── observation+assumption-frontier runner (EXP46) ───────────────────── const OBS_ASSUMP_FRONTIER_TEST_PROMPT = "OBSERVATION ASSUMPTION FRONTIER TEST PROMPT"; function makeObservationAssumptionFrontierResultWithEmptyAssumptions() { return { observations: ["one directly supported observation"], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }; } function makeObservationAssumptionFrontierResultWithOneAssumption() { return { observations: ["one directly supported observation"], assumptions: ["one assumption"], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }; } describe("runLiveFocusedObservationAssumptionFrontierPromptExperiment", () => { it("passes prompt byte-for-byte to the injected provider", async () => { const fakeResult = makeObservationAssumptionFrontierResultWithEmptyAssumptions(); const fakeProvider = makeFakeProvider(fakeResult); await runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); const calls = fakeProvider.getCalls(); expect(calls.length).toBe(1); expect(calls[0].prompt).toBe(OBS_ASSUMP_FRONTIER_TEST_PROMPT); }); // ── Valid with empty assumptions ── it("accepts valid result with empty assumptions", async () => { const fakeResult = makeObservationAssumptionFrontierResultWithEmptyAssumptions(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.observations).toEqual(["one directly supported observation"]); expect(result.assumptions).toEqual([]); expect(result.uncertainties).toEqual(["one uncertainty"]); expect(result.possibleFollowUpQuestions).toEqual(["one question"]); }); // ── Valid with one assumption ── it("accepts valid result with one assumption", async () => { const fakeResult = makeObservationAssumptionFrontierResultWithOneAssumption(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); expect(result.observations).toEqual(["one directly supported observation"]); expect(result.assumptions).toEqual(["one assumption"]); expect(result.uncertainties).toEqual(["one uncertainty"]); expect(result.possibleFollowUpQuestions).toEqual(["one question"]); }); // ── Reject invalid observations ── it("rejects empty observations array", async () => { const fakeProvider = makeFakeProvider({ observations: [], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("observations must contain at least one element"); }); it("rejects empty-string observation item", async () => { const fakeProvider = makeFakeProvider({ observations: [""], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("observations[0] must be a non-empty string"); }); it("rejects non-string observation item", async () => { const fakeProvider = makeFakeProvider({ observations: [123], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("observations[0] must be a non-empty string"); }); // ── Reject invalid assumptions ── it("rejects empty-string assumption item", async () => { const fakeProvider = makeFakeProvider({ observations: ["one observation"], assumptions: [""], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("assumptions[0] must be a non-empty string"); }); it("rejects non-string assumption item", async () => { const fakeProvider = makeFakeProvider({ observations: ["one observation"], assumptions: [null], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("assumptions[0] must be a non-empty string"); }); it("accepts empty assumptions array as valid", async () => { const fakeResult = makeObservationAssumptionFrontierResultWithEmptyAssumptions(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); }); // ── Reject invalid frontier cardinality ── it("rejects zero uncertainties", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], assumptions: [], uncertainties: [], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects multiple uncertainties", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], assumptions: [], uncertainties: ["one", "two"], possibleFollowUpQuestions: ["one question"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("uncertainties must contain exactly one element"); }); it("rejects zero possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: [], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); it("rejects multiple possibleFollowUpQuestions", async () => { const fakeProvider = makeFakeProvider({ observations: ["obs-1"], assumptions: [], uncertainties: ["one uncertainty"], possibleFollowUpQuestions: ["q1", "q2"], }); await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toThrow("possibleFollowUpQuestions must contain exactly one element"); }); // ── Other fields not required ── it("does not require relationships or targetNodeId", async () => { const result = makeObservationAssumptionFrontierResultWithEmptyAssumptions(); delete result.targetNodeId; const fakeProvider = makeFakeProvider(result); const run = await runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(run.success).toBe(true); }); // ── Existing APIs remain green ── it("existing API runLiveFocusedDeconstructExperiment remains callable", async () => { const fakeResult = makeValidResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedDeconstructExperiment({ ...params, provider: fakeProvider, }); expect(result.success).toBe(true); }); it("existing API runLiveFocusedFrontierPromptExperiment remains callable", async () => { const fakeResult = makeMinimalFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedFrontierPromptExperiment({ prompt: FRONTIER_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); }); it("existing API runLiveFocusedObservationFrontierPromptExperiment remains callable", async () => { const fakeResult = makeObservationFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedObservationFrontierPromptExperiment({ prompt: OBSERVATION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); }); it("existing API runLiveFocusedRelationshipFrontierPromptExperiment remains callable", async () => { const fakeResult = makeRelationshipFrontierResult(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedRelationshipFrontierPromptExperiment({ prompt: RELATIONSHIP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); }); it("existing API runLiveFocusedAssumptionFrontierPromptExperiment remains callable", async () => { const fakeResult = makeAssumptionFrontierResultWithOneAssumption(); const fakeProvider = makeFakeProvider(fakeResult); const result = await runLiveFocusedAssumptionFrontierPromptExperiment({ prompt: ASSUMPTION_FRONTIER_TEST_PROMPT, provider: fakeProvider, }); expect(result.success).toBe(true); }); // ── Provider error propagates directly ── it("propagates provider exception without retry", async () => { const err = new Error("provider failure"); const calls = []; const fakeProvider = { generateReconstruction: async (prompt, model) => { calls.push(1); throw err; }, }; await expect( runLiveFocusedObservationAssumptionFrontierPromptExperiment({ prompt: OBS_ASSUMP_FRONTIER_TEST_PROMPT, provider: fakeProvider, }) ).rejects.toBe(err); }); });