diff --git a/lib/graph/question-formulator.js b/lib/graph/question-formulator.js index 7d337d9..c2f9e85 100644 --- a/lib/graph/question-formulator.js +++ b/lib/graph/question-formulator.js @@ -671,7 +671,9 @@ function countIndependentAnswerDimensions(node, graph) { conjunctionCount, implicitConclusion, multipleEvidenceDimensions: - prerequisiteConceptCount >= 2 || conjunctionCount >= 2, + prerequisiteConceptCount >= 2 || + (conjunctionCount >= 2 && + unresolvedDependencies > 0), }; } diff --git a/tests/graph/unknown-answerability.test.js b/tests/graph/unknown-answerability.test.js index 4bf21bc..1c93fdc 100644 --- a/tests/graph/unknown-answerability.test.js +++ b/tests/graph/unknown-answerability.test.js @@ -136,3 +136,182 @@ describe("answerability-triggered decomposition", () => { expect(selectedChild.status).toBe("unknown"); }); }); + +describe("conjunction corroboration — Option C", () => { + it( + "Case 1: alternative labels for one concept with surface 'or' tokens stays answerable", + () => { + const unknown = makeNode({ + id: "n-case1", + label: + "The actual scenario, problem description, or data set intended for analysis.", + description: + "The actual scenario, problem description, or data set intended for analysis.", + kind: "unknown", + status: "unknown", + confidence: "low", + }); + + const graph = makeGraph({ + centralStatement: "test", + nodes: [unknown], + edges: [], + activeUnknownNodeId: unknown.id, + resolvedNodeIds: [], + currentSummary: "Minimal clarification fixture", + }); + + const answerability = assessUnknownAnswerability({ + node: unknown, + graph, + }); + + expect(answerability.independentlyAnswerable).toBe(true); + expect(answerability.decompositionRequired).toBe(false); + }, + ); + + it( + "Case 2: surface conjunctions alone cannot make composite without corroborating signal — this is the correct trade-off of Option C", + () => { + const unknown = makeNode({ + id: "n-case2", + label: + "What evidence supports the savings estimate and what evidence supports the retention assumption?", + description: + "Determine what data validates both the projected savings and the customer retention figures before making a decision.", + kind: "unknown", + status: "unknown", + confidence: "medium", + }); + + const graph = makeGraph({ + centralStatement: + "Evaluate whether the expansion plan is viable based on financial projections.", + nodes: [unknown], + edges: [], + activeUnknownNodeId: unknown.id, + resolvedNodeIds: [], + currentSummary: "Surface conjunction fixture — no deps", + }); + + const answerability = assessUnknownAnswerability({ + node: unknown, + graph, + }); + + // Option C trade-off: surface 'and/or' without corroborating signal + // (unresolvedDependencies > 0 or prereqConcept >= 2) stays answerable. + // This is the correct behaviour — conjunctionCount alone must not make composite. + expect(answerability.independentlyAnswerable).toBe(true); + expect(answerability.decompositionRequired).toBe(false); + }, + ); + + it( + "Case 2b: same text WITH unresolved dependencies stays composite via corroboration", + () => { + const prereq = makeNode({ + id: "n-case2-dep", + label: "What is the savings estimate?", + description: + "Determine the projected savings figure to use as a reference.", + kind: "unknown", + status: "unknown", + confidence: "medium", + }); + + const unknown = makeNode({ + id: "n-case2-dep-unknown", + label: + "What evidence supports the savings estimate and what evidence supports the retention assumption?", + description: + "Determine what data validates both the projected savings and the customer retention figures before making a decision.", + kind: "unknown", + status: "unknown", + confidence: "medium", + dependsOn: ["n-case2-dep"], + }); + + const graph = makeGraph({ + centralStatement: + "Evaluate whether the expansion plan is viable based on financial projections.", + nodes: [prereq, unknown], + edges: [], + activeUnknownNodeId: unknown.id, + resolvedNodeIds: [], + currentSummary: "Corroborated conjunction fixture", + }); + + const answerability = assessUnknownAnswerability({ + node: unknown, + graph, + }); + + // With unresolvedDependencies > 0 as corroborating signal, + // conjunctionCount >= 2 now correctly flags composite. + expect(answerability.independentlyAnswerable).toBe(false); + expect(answerability.decompositionRequired).toBe(true); + }, + ); + + it( + "Case 3: commercial-validation container unknowns stay composite", + () => { + const graph = makeCommercialContainerGraph(); + const unknown = graph.nodes[0]; + + const answerability = assessUnknownAnswerability({ + node: unknown, + graph, + }); + + expect(answerability.independentlyAnswerable).toBe(false); + expect(answerability.decompositionRequired).toBe(true); + }, + ); + + it( + "Case 4: explicit unresolved dependencies stay composite even without conjunctions", + () => { + const prereq = makeNode({ + id: "n-prereq", + label: "What is the current baseline measurement?", + description: + "Need the baseline to determine the starting point for comparison.", + kind: "unknown", + status: "unknown", + confidence: "medium", + }); + + const unknown = makeNode({ + id: "n-case4", + label: "What is the current state of the primary metric?", + description: + "Determine the current value before comparing against targets.", + kind: "unknown", + status: "unknown", + confidence: "high", + dependsOn: ["n-prereq"], + }); + + const graph = makeGraph({ + centralStatement: + "Evaluate whether the expansion plan is viable based on financial projections.", + nodes: [prereq, unknown], + edges: [], + activeUnknownNodeId: unknown.id, + resolvedNodeIds: [], + currentSummary: "Explicit prerequisite fixture", + }); + + const answerability = assessUnknownAnswerability({ + node: unknown, + graph, + }); + + expect(answerability.independentlyAnswerable).toBe(false); + expect(answerability.decompositionRequired).toBe(true); + }, + ); +});