import { describe, expect, it } from "vitest"; import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js"; import { classifyObservationRelationship, formulateQuestion, selectReasoningPattern, } from "@/lib/graph/question-formulator.js"; import { makeGraph, makeNode } from "@/lib/graph/schema.js"; const COMMERCIAL_SCENARIO = "I have developed a new reasoning method that aims to help people determine whether they have enough justified confidence to make a decision. I believe it could become a commercial product, but I do not yet know whether it solves a genuine problem, whether people would value it enough to pay for it, or whether it is fundamentally different from existing AI tools. Before investing significant time and money into building it further, I want to determine whether continuing development is commercially justified."; function makeCommercialGraph() { const parent = makeNode({ id: "n-commercial-parent", label: "Commercial justification for whether continuing development is commercially justified", description: "Need to know whether this solves a genuine problem, whether people would value it enough to pay for it, and whether it is commercially justified before continuing development.", kind: "unknown", status: "unknown", confidence: "medium", }); return makeGraph({ centralStatement: COMMERCIAL_SCENARIO, nodes: [parent], edges: [], activeUnknownNodeId: parent.id, resolvedNodeIds: [], currentSummary: "Commercial pattern fixture", }); } function makeSeedProposal() { return { addedNodes: [ makeNode({ id: "n-anchor", label: "Update anchor", description: "Anchor state introduced by the answer because the update must contain a meaningful change.", kind: "state", status: "known", confidence: "low", }), ], updatedNodes: [], addedEdges: [], removedEdgeIds: [], resolvedUnknownNodeIds: [], affectedNodeIds: [], selectedQuestion: null, }; } describe("reasoning-pattern validation", () => { it("keeps the commercial-method scenario in decision mode without comparability-style unknowns", () => { const seeded = applyValidatedProposal({ situationGraph: makeCommercialGraph(), proposal: makeSeedProposal(), }); expect(seeded.success).toBe(true); expect(seeded.selectedQuestion?.reasoningPattern).toBe("decision"); expect( seeded.updatedSituationGraph.nodes.some((node) => /two observations|measured|different timing/i.test(node.label), ), ).toBe(false); const followUp = applyValidatedProposal({ situationGraph: seeded.updatedSituationGraph, proposal: { addedNodes: [], updatedNodes: [ { nodeId: seeded.selectedQuestion.nodeId, previousStatus: "unknown", newStatus: "resolved", previousValue: null, newValue: "I experience it myself when I am trying to decide whether a project, idea or investment is justified, but I do not yet know how common that problem is for other people.", reason: "Answered by the user.", }, ], addedEdges: [], removedEdgeIds: [], resolvedUnknownNodeIds: [seeded.selectedQuestion.nodeId], affectedNodeIds: [], selectedQuestion: null, }, previousQuestion: seeded.selectedQuestion.question, answer: "I experience it myself when I am trying to decide whether a project, idea or investment is justified, but I do not yet know how common that problem is for other people.", }); expect(followUp.success).toBe(true); expect(followUp.selectedQuestion?.reasoningPattern).toBe("decision"); expect(followUp.reasoningPatternValidation).toMatchObject({ activePattern: "decision", valid: true, }); expect(followUp.graphReasoningIntegrity).toBe("valid"); expect(followUp.incompatibleNodeIds).toEqual([]); expect(followUp.selectedQuestion?.question.toLowerCase()).not.toMatch( /two observations|measured|same basis|same scale|different timing/, ); }); it("allows comparability-style reasoning in an explanation scenario", () => { const unknown = makeNode({ id: "n-explanation", label: "Explanation for why revenue increased by 18%, but cash in the bank fell over the same period", description: "Need to understand what change or event could explain why these observations differ.", kind: "unknown", status: "unknown", confidence: "medium", }); const graph = makeGraph({ centralStatement: "Revenue increased by 18%, but cash in the bank fell over the same period.", nodes: [ unknown, makeNode({ id: "n-revenue", label: "Revenue increased by 18%.", description: "Revenue increased by 18%.", kind: "observation", status: "supported", confidence: "high", }), makeNode({ id: "n-cash", label: "Cash in the bank fell over the same period.", description: "Cash in the bank fell over the same period.", kind: "observation", status: "supported", confidence: "high", }), ], edges: [], activeUnknownNodeId: unknown.id, resolvedNodeIds: [], currentSummary: "Explanation fixture", }); const pattern = selectReasoningPattern({ node: unknown, graph }); const question = formulateQuestion({ node: unknown, graph }); const relationship = classifyObservationRelationship(graph); expect(pattern.pattern).toBe("explanation"); expect(question.reasoningPattern).toBe("explanation"); expect(relationship.questionRequired).toBe(true); }); it("rejects explanation-family tie resolution for duplicate observations", () => { const graph = makeGraph({ centralStatement: "Sales doubled. Sales doubled.", nodes: [ makeNode({ id: "n-sales-1", label: "Sales doubled.", description: "Sales doubled.", kind: "observation", status: "supported", confidence: "high", }), makeNode({ id: "n-sales-2", label: "Sales doubled.", description: "Sales doubled.", kind: "observation", status: "supported", confidence: "high", }), ], edges: [], activeUnknownNodeId: null, resolvedNodeIds: [], currentSummary: "Duplicate observation fixture", }); const relationship = classifyObservationRelationship(graph); expect(relationship.relationshipStatus).toBe("duplicate"); expect(relationship.questionRequired).toBe(false); }); it("keeps definition scenarios inside compatible node families", () => { const unknown = makeNode({ id: "n-definition", label: "Definition of justified confidence", description: "The term needs clearer boundaries.", kind: "unknown", status: "unknown", confidence: "medium", }); const graph = makeGraph({ centralStatement: "The team uses justified confidence inconsistently.", nodes: [unknown], edges: [], activeUnknownNodeId: unknown.id, resolvedNodeIds: [], currentSummary: "Definition fixture", }); const pattern = selectReasoningPattern({ node: unknown, graph }); const result = formulateQuestion({ node: unknown, graph }); expect(pattern.pattern).toBe("definition"); expect(result.reasoningPattern).toBe("definition"); expect(result.allowedQuestionFamilies).toEqual(["definition"]); }); it("replaces an incompatible decision-mode active unknown with a pattern-compatible candidate", () => { const graph = makeGraph({ centralStatement: "Before investing more, we need to know whether continuing development is commercially justified.", nodes: [ makeNode({ id: "n-commercial-parent", label: "Commercial justification for whether continuing development is commercially justified", description: "Need to know whether this solves a genuine problem before continuing development.", kind: "unknown", status: "unknown", confidence: "medium", }), makeNode({ id: "n-incompatible-child", label: "How the two observations were measured", description: "Need evidence about the measure used for each observation before comparing them.", kind: "unknown", status: "unknown", confidence: "medium", parentId: "n-commercial-parent", }), ], edges: [], activeUnknownNodeId: "n-incompatible-child", resolvedNodeIds: [], currentSummary: "Incompatible active unknown fixture", }); const result = applyValidatedProposal({ situationGraph: graph, proposal: makeSeedProposal(), }); expect(result.success).toBe(true); expect(result.reasoningPatternValidation).toMatchObject({ activePattern: "decision", valid: true, }); expect(result.graphReasoningIntegrity).toBe("valid"); expect(result.incompatibleNodeIds).toContain("n-incompatible-child"); expect(result.compatibilityFailures).toEqual( expect.arrayContaining([ expect.objectContaining({ nodeId: "n-incompatible-child", activePattern: "decision", nodePattern: "comparison", }), ]), ); expect(result.replacementActions).toEqual( expect.arrayContaining([ expect.objectContaining({ rejectedNodeId: "n-incompatible-child", replacementNodeId: result.selectedQuestion?.nodeId, }), ]), ); expect(result.selectedQuestion?.nodeId).not.toBe("n-incompatible-child"); expect(result.selectedQuestion?.reasoningPattern).toBe("decision"); }); });