From d6dbc17bdfa1ddb003e4af5c0c18b4f826fa8bfd Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 6 Oct 2026 18:18:10 +0100 Subject: [PATCH] fix(confidence-engine): await focused deconstruction provider --- lib/graph/focused-investigation.js | 4 +- tests/graph/focused-deconstruction.test.mjs | 84 +++++++++---------- tests/graph/live-update-experiment-helper.cjs | 4 +- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/graph/focused-investigation.js b/lib/graph/focused-investigation.js index eee4d96..d1cc7a0 100644 --- a/lib/graph/focused-investigation.js +++ b/lib/graph/focused-investigation.js @@ -198,7 +198,7 @@ export function validateFocusedDeconstructSchema(result) { * Validates result with validateFocusedDeconstructSchema. * Returns validated semantic payload on success, or { validation: false, errors } on schema failure. */ -export function focusedDeconstruct({ +export async function focusedDeconstruct({ provider, modelName, targetNodeId, @@ -216,7 +216,7 @@ export function focusedDeconstruct({ answer, }); - const wrapper = provider.generateReconstruction( + const wrapper = await provider.generateReconstruction( prompt, modelName, focusedDeconstructJsonSchema, diff --git a/tests/graph/focused-deconstruction.test.mjs b/tests/graph/focused-deconstruction.test.mjs index d517622..7622489 100644 --- a/tests/graph/focused-deconstruction.test.mjs +++ b/tests/graph/focused-deconstruction.test.mjs @@ -29,104 +29,104 @@ function makeInvalidResponse() { } describe("focusedDeconstruct — success", () => { - it("calls provider.generateReconstruction exactly once and returns success", () => { + it("calls provider.generateReconstruction exactly once and returns success", async () => { let callCount = 0; - const fake = { generateReconstruction() { callCount++; return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + const fake = { async generateReconstruction() { callCount++; return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(callCount).toBe(1); expect(result.success).toBe(true); }); - it("preserves observations from provider response", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves observations from provider response", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.observations)).toBe(true); expect(result.observations.length).toBeGreaterThan(0); }); - it("preserves uncertainties from provider response", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves uncertainties from provider response", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.uncertainties)).toBe(true); expect(result.uncertainties.length).toBeGreaterThan(0); }); - it("preserves assumptions from provider response", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves assumptions from provider response", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.assumptions)).toBe(true); expect(result.assumptions.length).toBeGreaterThan(0); }); - it("preserves relationships from provider response", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves relationships from provider response", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.relationships)).toBe(true); expect(result.relationships.length).toBeGreaterThan(0); }); - it("preserves possibleFollowUpQuestions from provider response", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves possibleFollowUpQuestions from provider response", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.possibleFollowUpQuestions)).toBe(true); expect(result.possibleFollowUpQuestions.length).toBeGreaterThan(0); }); - it("preserves caller target identity (result.targetNodeId === request targetNodeId)", () => { - const fake = { generateReconstruction() { return { response: makeValidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("preserves caller target identity (result.targetNodeId === request targetNodeId)", async () => { + const fake = { async generateReconstruction() { return { response: makeValidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(result.targetNodeId).toBe(FAKE_TARGET_NODE_ID); }); - it("passes correct prompt to provider", () => { + it("passes correct prompt to provider", async () => { let capturedPrompt = null; - const fake = { generateReconstruction(prompt) { capturedPrompt = prompt; return { response: makeValidResponse() }; } }; - focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + const fake = { async generateReconstruction(prompt) { capturedPrompt = prompt; return { response: makeValidResponse() }; } }; + await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); const expected = buildFocusedDeconstructPrompt({ targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedPrompt).toBe(expected); }); - it("passes modelName unchanged to provider", () => { + it("passes modelName unchanged to provider", async () => { let capturedModel = null; - const fake = { generateReconstruction(prompt, receivedModel) { capturedModel = receivedModel; return { response: makeValidResponse() }; } }; - focusedDeconstruct({ provider: fake, modelName: "my-custom-model-v2", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + const fake = { async generateReconstruction(prompt, receivedModel) { capturedModel = receivedModel; return { response: makeValidResponse() }; } }; + await focusedDeconstruct({ provider: fake, modelName: "my-custom-model-v2", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedModel).toBe("my-custom-model-v2"); }); - it("passes focusedDeconstructJsonSchema as the schema argument", () => { + it("passes focusedDeconstructJsonSchema as the schema argument", async () => { let capturedSchema = null; - const fake = { generateReconstruction(prompt, modelName, schema) { capturedSchema = schema; return { response: makeValidResponse() }; } }; - focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + const fake = { async generateReconstruction(prompt, modelName, schema) { capturedSchema = schema; return { response: makeValidResponse() }; } }; + await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(capturedSchema).toBe(focusedDeconstructJsonSchema); }); }) describe("focusedDeconstruct — validation failure", () => { - it("returns failure result when provider response fails canonical validation", () => { - const fake = { generateReconstruction() { return { response: makeInvalidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("returns failure result when provider response fails canonical validation", async () => { + const fake = { async generateReconstruction() { return { response: makeInvalidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(result.success).toBe(false); }); - it("validation errors remain available on failure result", () => { - const fake = { generateReconstruction() { return { response: makeInvalidResponse() }; } }; - const result = focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + it("validation errors remain available on failure result", async () => { + const fake = { async generateReconstruction() { return { response: makeInvalidResponse() }; } }; + const result = await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(Array.isArray(result.errors)).toBe(true); expect(result.errors.length).toBeGreaterThan(0); }); - it("no second provider call occurs on validation failure", () => { + it("no second provider call occurs on validation failure", async () => { let callCount = 0; - const fake = { generateReconstruction() { callCount++; return { response: makeInvalidResponse() }; } }; - focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); + const fake = { async generateReconstruction() { callCount++; return { response: makeInvalidResponse() }; } }; + await focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER }); expect(callCount).toBe(1); }); }) describe("focusedDeconstruct — provider failure", () => { - it("propagates error with original identity/metadata intact", () => { + it("propagates error with original identity/metadata intact", async () => { const fake = { - generateReconstruction() { + async generateReconstruction() { const err = new Error("Connection refused to ollama endpoint"); err.name = "ProviderError"; err.providerApiPath = "/api/v1/completions"; @@ -134,6 +134,6 @@ describe("focusedDeconstruct — provider failure", () => { throw err; }, }; - expect(() => focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER })).toThrow("Connection refused to ollama endpoint"); + await expect(focusedDeconstruct({ provider: fake, modelName: "test-model", targetNodeId: FAKE_TARGET_NODE_ID, targetLabel: FAKE_LABEL, targetDescription: FAKE_DESCRIPTION, centralStatement: FAKE_CENTRAL, question: FAKE_QUESTION, answer: FAKE_ANSWER })).rejects.toThrow("Connection refused to ollama endpoint"); }); }) diff --git a/tests/graph/live-update-experiment-helper.cjs b/tests/graph/live-update-experiment-helper.cjs index 67968bb..1ecac56 100644 --- a/tests/graph/live-update-experiment-helper.cjs +++ b/tests/graph/live-update-experiment-helper.cjs @@ -331,7 +331,7 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues const centralStatement = situationGraph.centralStatement ?? ""; // Call production-focused deconstruct — exactly one provider generation - const focused = focusedDeconstruct({ + const focused = await focusedDeconstruct({ provider, modelName: model, targetNodeId, @@ -342,6 +342,8 @@ async function composeDeepInvestigationCheckpoint({ situationGraph, previousQues answer, }); + providerGenerateCalls++; // focusedDeconstruct now uses the same provider (1 call) + if (!focused.success) { throw new Error(`focusedDeconstruct failed for episode ${contribSeq}: ${JSON.stringify(focused.errors)}`); }