feat(confidence-engine): isolate finding-informed understanding
This commit is contained in:
@@ -222,6 +222,34 @@ function makeProposal(overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// ── v0.49: finding factories for post-authoritative seam test ──
|
||||
|
||||
/** Finding with null disposition → eligible provisional working interpretation */
|
||||
function nullDispositionFinding() {
|
||||
return {
|
||||
id: "finding-a",
|
||||
proposition: "Production volume increased by 12% in Q3.",
|
||||
status: "provisional",
|
||||
userDisposition: null,
|
||||
originatingTargetNodeId: "n-unknown",
|
||||
contributionId: "contrib-test-a",
|
||||
sourceObservation: "user_input",
|
||||
};
|
||||
}
|
||||
|
||||
/** Finding with not_relevant disposition → excluded from understanding */
|
||||
function notRelevantFinding() {
|
||||
return {
|
||||
id: "finding-b",
|
||||
proposition: "Office humidity levels fluctuate seasonally.",
|
||||
status: "provisional",
|
||||
userDisposition: "not_relevant",
|
||||
originatingTargetNodeId: "n-unknown",
|
||||
contributionId: "contrib-test-b",
|
||||
sourceObservation: "user_input",
|
||||
};
|
||||
}
|
||||
|
||||
function makeComparabilityScenarioGraph() {
|
||||
return makeGraph({
|
||||
centralStatement:
|
||||
@@ -1474,6 +1502,116 @@ describe("lib/graph/orchestrator startCase", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// ── v0.49: post-authoritative finding-informed understanding seam test ──
|
||||
|
||||
it("post-authoritative findings modify summary but preserve authoritative graph result invariant", async () => {
|
||||
const { updateCase } = await import("@/lib/graph/orchestrator.js");
|
||||
|
||||
const mockGraph = makeGraph({
|
||||
centralStatement: "Complaint counts increased while production also increased.",
|
||||
nodes: [
|
||||
makeNode({
|
||||
id: "n-unknown",
|
||||
label: "Complaint rate denominator",
|
||||
description: "Need the denominator for the complaint rate",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n-unknown",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Nodes: 1 unknown | Edges: 0 total",
|
||||
});
|
||||
|
||||
const mockApplicationResult = {
|
||||
success: true,
|
||||
updatedSituationGraph: mockGraph,
|
||||
graphUpdate: { addedNodes: [], updatedNodes: [] },
|
||||
selectedQuestion: null,
|
||||
affectedNodeIds: [],
|
||||
resolvedUnknownNodeIds: ["n-unknown"],
|
||||
previousActiveUnknownNodeId: "n-unknown",
|
||||
newActiveUnknownNodeId: null,
|
||||
changesApplied: 1,
|
||||
reasoningState: {
|
||||
comparabilityStatus: "uncertain",
|
||||
comparabilityReason: "deferred",
|
||||
comparabilityEvidence: [],
|
||||
relationshipStatus: "insufficient_information",
|
||||
relationshipReason: "deferred",
|
||||
relationshipAssessed: false,
|
||||
contradictionReasoningAllowed: false,
|
||||
reasoningStages: [],
|
||||
},
|
||||
previousReasoningState: null,
|
||||
graphReferenceValidation: { valid: true, errors: [] },
|
||||
diagnosticEvidence: {},
|
||||
};
|
||||
|
||||
const applyValidatedProposal = vi.fn().mockReturnValue(mockApplicationResult);
|
||||
const provider = {
|
||||
generateReconstruction: vi.fn().mockResolvedValue(makeProposal()),
|
||||
};
|
||||
|
||||
// Control: no findings → summary should remain as authoritative gives
|
||||
const controlRequest = makeUpdateRequest({ situationGraph: mockGraph });
|
||||
const controlResult = await updateCase(controlRequest, {
|
||||
provider,
|
||||
config: MOCK_CONFIG,
|
||||
applyValidatedProposal,
|
||||
applyProposal: true,
|
||||
});
|
||||
|
||||
// Treatment A: eligible null disposition Finding (schema expects "findings" key)
|
||||
const treatmentAResult = await updateCase(
|
||||
{ ...controlRequest, findings: [nullDispositionFinding()] },
|
||||
{ provider, config: MOCK_CONFIG, applyValidatedProposal, applyProposal: true },
|
||||
);
|
||||
|
||||
// Treatment B: not_relevant Finding (should NOT affect summary)
|
||||
const treatmentBResult = await updateCase(
|
||||
{ ...controlRequest, findings: [notRelevantFinding()] },
|
||||
{ provider, config: MOCK_CONFIG, applyValidatedProposal, applyProposal: true },
|
||||
);
|
||||
|
||||
// 1. Authoritative graph result is structurally identical (control vs treatment)
|
||||
expect(controlResult.success).toBe(true);
|
||||
expect(treatmentAResult.success).toBe(true);
|
||||
expect(treatmentBResult.success).toBe(true);
|
||||
|
||||
expect(controlResult.updatedSituationGraph).toEqual(mockApplicationResult.updatedSituationGraph);
|
||||
expect(treatmentAResult.updatedSituationGraph).toEqual(mockApplicationResult.updatedSituationGraph);
|
||||
expect(treatmentBResult.updatedSituationGraph).toEqual(mockApplicationResult.updatedSituationGraph);
|
||||
|
||||
// 2. summary differs: control == authoritative, treatmentA has evidence appended
|
||||
expect(controlResult.summary).toBe(mockGraph.currentSummary);
|
||||
expect(treatmentAResult.summary).not.toBe(mockGraph.currentSummary);
|
||||
expect(treatmentAResult.summary).toContain("Evidence");
|
||||
expect(treatmentAResult.summary).toContain(
|
||||
"Production volume increased by 12% in Q3.",
|
||||
);
|
||||
|
||||
// 3. not_relevant Finding does NOT affect summary
|
||||
expect(treatmentBResult.summary).toBe(mockGraph.currentSummary);
|
||||
|
||||
// 4. selectedQuestion / frontier fields are identical across all cases
|
||||
expect(controlResult.selectedQuestion).toEqual(treatmentAResult.selectedQuestion);
|
||||
expect(controlResult.selectedQuestion).toEqual(treatmentBResult.selectedQuestion);
|
||||
expect(controlResult.resolvedUnknownNodeIds).toEqual(treatmentAResult.resolvedUnknownNodeIds);
|
||||
expect(controlResult.resolvedUnknownNodeIds).toEqual(treatmentBResult.resolvedUnknownNodeIds);
|
||||
|
||||
// 5. authoritative stages and proposal are identical
|
||||
expect(controlResult.stage).toBe(treatmentAResult.stage);
|
||||
expect(controlResult.stage).toBe(treatmentBResult.stage);
|
||||
expect(controlResult.proposal).toEqual(treatmentAResult.proposal);
|
||||
expect(controlResult.proposal).toEqual(treatmentBResult.proposal);
|
||||
|
||||
// 6. Findings still never enter buildGraphUpdatePrompt (verify provider was called)
|
||||
expect(provider.generateReconstruction).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("startCase no longer copies analysis nextQuestion directly when a graph-backed question exists", async () => {
|
||||
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
|
||||
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
||||
|
||||
Reference in New Issue
Block a user