Files
confidence-engine/tests/graph/live-focused-deconstruct-experiment-helper.test.mjs

1236 lines
41 KiB
JavaScript

/**
* 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);
});
});