fix(confidence-engine): unwrap overview provider response
Production: synthesizeInvestigationOverview() now passes providerResult.response
to validateOverviewResponse() instead of the raw envelope, matching the proven
pattern in current-understanding-synthesis.js.
Regression: fakeProvider returns real-provider envelope shape { response, providerApiPath }
and four new regression tests verify unwrapping succeeds with valid response
and fails correctly with invalid response inside the envelope.
Handoff: documented defect confirmed, fix applied, and owning regression added.
This commit is contained in:
@@ -385,6 +385,14 @@ RAW USER EVIDENCE
|
||||
- Current Understanding and Investigation Report are architecturally distinct artefacts. Report interpretations remain explicit, not evidence.
|
||||
- The `≤5` processing bound was experimental apparatus constraint, NOT product requirement.
|
||||
|
||||
## Investigation overview synthesis — provider envelope defect confirmed
|
||||
|
||||
- `synthesizeInvestigationOverview()` previously passed the full provider envelope (`{ response: { understanding, plausibleInterpretations }, providerApiPath, providerExecution }`) directly into `validateOverviewResponse()`, which expected only `{ understanding, plausibleInterpretations }`. This caused "Missing or invalid required fields" on real providers.
|
||||
- Fix now unwraps `.response` before overview validation, matching the proven pattern in `lib/graph/current-understanding-synthesis.js` (line 296: `validateSynthesisResponse(providerResult.response)`).
|
||||
- Owning regression (`tests/graph/investigation-overview-synthesis.test.js`) now mirrors real provider envelope shape (`{ response: { understanding, plausibleInterpretations }, providerApiPath }`). Regression fails against pre-fix seam and passes after fix.
|
||||
- No live proof yet. Zero live calls made in this handoff.
|
||||
- Next boundary: one bounded live product-launch overview Call A before resuming A/B epistemic input control comparison.
|
||||
|
||||
## HISTORICAL — initial-decomposition v0.61 status
|
||||
|
||||
Initial decomposition experiments (v0.61) ran four successful runs plus one validation failure against the same manufacturing scenario. Key findings:
|
||||
|
||||
@@ -240,9 +240,9 @@ export async function synthesizeInvestigationOverview(params, deps) {
|
||||
// Build overview-specific prompt (keeps evidence/interpretation separate)
|
||||
const prompt = buildOverviewSynthesisPrompt(situationGraph, findings, plausibleInterpretations);
|
||||
|
||||
let rawResponse;
|
||||
let providerResult;
|
||||
try {
|
||||
rawResponse = await provider.generateReconstruction(
|
||||
providerResult = await provider.generateReconstruction(
|
||||
prompt,
|
||||
modelName,
|
||||
);
|
||||
@@ -250,8 +250,8 @@ export async function synthesizeInvestigationOverview(params, deps) {
|
||||
throw new Error(`Overview synthesis provider call failed: ${err.message}`);
|
||||
}
|
||||
|
||||
// Validate against overview-specific contract
|
||||
const validated = validateOverviewResponse(rawResponse);
|
||||
// Validate against overview-specific contract — unwrap provider envelope
|
||||
const validated = validateOverviewResponse(providerResult.response);
|
||||
if (!validated.valid) {
|
||||
throw new Error(`Overview synthesis validation failed: ${validated.reason}`);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,13 @@ const fakeProvider = (defaultResponse) => ({
|
||||
generateReconstruction: vi.fn(() => {
|
||||
return typeof defaultResponse === "function"
|
||||
? defaultResponse()
|
||||
: JSON.stringify({
|
||||
understanding: "Synthesized understanding from evidence.",
|
||||
plausibleInterpretations: "Remaining plausible explanations.",
|
||||
});
|
||||
: {
|
||||
response: {
|
||||
understanding: "Synthesized understanding from evidence.",
|
||||
plausibleInterpretations: "Remaining plausible explanations.",
|
||||
},
|
||||
providerApiPath: "/api/chat",
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -459,4 +462,61 @@ describe("synthesizeInvestigationOverview — full seam", () => {
|
||||
expect(eligible.map((f) => f.id)).toEqual(["f1", "f2"]);
|
||||
expect(eligible).toHaveLength(2);
|
||||
});
|
||||
|
||||
// ── Regression: overview synthesis unwraps provider envelope before validation ───────────────
|
||||
|
||||
describe("overview synthesis — provider envelope unwrapping regression", () => {
|
||||
it("fake provider returns real-provider envelope shape (response + providerApiPath)", async () => {
|
||||
const fake = fakeProvider();
|
||||
await synthesizeInvestigationOverview(
|
||||
{ situationGraph: graph, findings: [], plausibleInterpretations: [] },
|
||||
{ provider: fake }
|
||||
);
|
||||
const callResult = fake.generateReconstruction.mock.calls[0][2];
|
||||
expect(fake.generateReconstruction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("valid envelope response is unwrapped and validated successfully", async () => {
|
||||
const fake = fakeProvider(() => ({
|
||||
response: JSON.stringify({
|
||||
understanding: "Test understanding.",
|
||||
plausibleInterpretations: "Test interpretations.",
|
||||
}),
|
||||
providerApiPath: "/v1/responses",
|
||||
}));
|
||||
const result = await synthesizeInvestigationOverview(
|
||||
{ situationGraph: graph, findings: [], plausibleInterpretations: [] },
|
||||
{ provider: fake }
|
||||
);
|
||||
expect(result.understanding).toBe("Test understanding.");
|
||||
expect(result.plausibleInterpretations).toBe("Test interpretations.");
|
||||
});
|
||||
|
||||
it("invalid response inside envelope fails validation (regression guard)", async () => {
|
||||
const fake = fakeProvider(() => ({
|
||||
response: JSON.stringify({ wrongField: "value" }),
|
||||
providerApiPath: "/v1/responses",
|
||||
}));
|
||||
await expect(
|
||||
synthesizeInvestigationOverview(
|
||||
{ situationGraph: graph, findings: [], plausibleInterpretations: [] },
|
||||
{ provider: fake }
|
||||
)
|
||||
).rejects.toThrow(/validation failed/);
|
||||
});
|
||||
|
||||
it("provider returns .response as raw valid JSON string inside envelope", async () => {
|
||||
const json = JSON.stringify({
|
||||
understanding: "Raw JSON test.",
|
||||
plausibleInterpretations: "String payload.",
|
||||
});
|
||||
const fake = fakeProvider(() => ({ response: json, providerApiPath: "/api/chat" }));
|
||||
const result = await synthesizeInvestigationOverview(
|
||||
{ situationGraph: graph, findings: [], plausibleInterpretations: [] },
|
||||
{ provider: fake }
|
||||
);
|
||||
expect(result.understanding).toBe("Raw JSON test.");
|
||||
expect(fake.generateReconstruction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user