Files
confidence-engine/tests/graph/unknown-answerability.test.js
T

318 lines
11 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
assessUnknownAnswerability,
assessUnknownAtomicity,
} from "@/lib/graph/question-formulator.js";
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.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 makeMeaningfulNoOpProposal() {
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,
};
}
function makeCommercialContainerGraph() {
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 answerability fixture",
});
}
describe("assessUnknownAnswerability", () => {
it("flags commercial-validation container unknowns as non-answerable", () => {
const graph = makeCommercialContainerGraph();
const unknown = graph.nodes[0];
const atomicity = assessUnknownAtomicity({ node: unknown, graph });
const answerability = assessUnknownAnswerability({ node: unknown, graph });
expect(atomicity.atomicity).toBe("composite");
expect(answerability.independentlyAnswerable).toBe(false);
expect(answerability.decompositionRequired).toBe(true);
expect(answerability.prerequisiteConceptCount).toBeGreaterThan(1);
});
it("keeps one-concept denominator unknowns independently answerable", () => {
const unknown = makeNode({
id: "n-denominator",
label: "Complaint rate denominator",
description:
"Need the denominator because it directly determines the complaint rate.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const graph = makeGraph({
centralStatement: "Production increased while complaints increased.",
nodes: [unknown],
edges: [],
activeUnknownNodeId: unknown.id,
resolvedNodeIds: [],
currentSummary: "Denominator answerability fixture",
});
const result = assessUnknownAnswerability({ node: unknown, graph });
expect(result.independentlyAnswerable).toBe(true);
expect(result.decompositionRequired).toBe(false);
expect(result.prerequisiteConceptCount).toBeLessThanOrEqual(1);
});
});
describe("answerability-triggered decomposition", () => {
it("decomposes a non-answerable parent into independently answerable child investigations", () => {
const graph = makeCommercialContainerGraph();
const result = applyValidatedProposal({
situationGraph: graph,
proposal: makeMeaningfulNoOpProposal(),
});
expect(result.success).toBe(true);
expect(result.decompositionPerformed).toBe(true);
expect(result.decompositionTriggeredByAnswerability).toBe(true);
expect(result.selectedContainerUnknown).toBe("n-commercial-parent");
expect(result.selectedChildUnknown).toBe(result.selectedUnknownAfter);
expect(result.independentlyAnswerable).toBe(false);
expect(result.prerequisiteConceptCount).toBeGreaterThan(1);
expect(result.selectedUnknownAfter).not.toBe("n-commercial-parent");
expect(result.selectedQuestion.question).toBe(
"Who experiences this problem?",
);
});
it("keeps the parent unresolved while selecting a child unknown", () => {
const graph = makeCommercialContainerGraph();
const result = applyValidatedProposal({
situationGraph: graph,
proposal: makeMeaningfulNoOpProposal(),
});
const parentNode = result.updatedSituationGraph.nodes.find(
(node) => node.id === "n-commercial-parent",
);
const selectedChild = result.updatedSituationGraph.nodes.find(
(node) => node.id === result.selectedUnknownAfter,
);
expect(parentNode.status).toBe("unknown");
expect(selectedChild.parentId).toBe(parentNode.id);
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);
},
);
});