223 lines
8.4 KiB
JavaScript
223 lines
8.4 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
assessUnknownAnswerability,
|
|
assessUnknownAtomicity,
|
|
} from "@/lib/graph/question-formulator.js";
|
|
import {
|
|
applyValidatedProposal,
|
|
determineGraphBackedQuestion,
|
|
} 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", async () => {
|
|
const graph = makeCommercialContainerGraph();
|
|
|
|
const result = await 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", async () => {
|
|
const graph = makeCommercialContainerGraph();
|
|
|
|
const result = await 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");
|
|
});
|
|
|
|
it("invokes bounded semantic fallback when no deterministic family exists and accepts valid children without direct LLM graph mutation", async () => {
|
|
const parent = makeNode({
|
|
id: "n-dependency-parent",
|
|
label: "Which factor is causing the dependency",
|
|
description:
|
|
"Need to know whether team capability, customer or client dependency, responsibility distribution, or insufficient deliberate delegation or stepping away is causing the dependency.",
|
|
kind: "unknown",
|
|
status: "unknown",
|
|
confidence: "medium",
|
|
});
|
|
const graph = makeGraph({
|
|
centralStatement:
|
|
"A dependency persists, but it is unclear whether the cause is team capability, customer dependency, responsibility distribution, or insufficient deliberate delegation.",
|
|
nodes: [parent],
|
|
edges: [],
|
|
activeUnknownNodeId: parent.id,
|
|
resolvedNodeIds: [],
|
|
currentSummary: "Dependency-factor decomposition fixture",
|
|
});
|
|
|
|
const atomicity = assessUnknownAtomicity({ node: parent, graph });
|
|
const answerability = assessUnknownAnswerability({ node: parent, graph });
|
|
const provider = {
|
|
generateReconstruction: async () => ({
|
|
parentNodeId: parent.id,
|
|
proposedChildren: [
|
|
{
|
|
label: "Whether team capability is causing the dependency",
|
|
description:
|
|
"Need to know whether team capability is causing the dependency, because that would narrow the source of the dependency.",
|
|
},
|
|
{
|
|
label: "Whether customer dependency is causing the dependency",
|
|
description:
|
|
"Need to know whether customer dependency is causing the dependency, because that would narrow the source of the dependency.",
|
|
},
|
|
],
|
|
}),
|
|
};
|
|
const result = await determineGraphBackedQuestion({
|
|
situationGraph: graph,
|
|
provider,
|
|
modelName: "mock-ollama",
|
|
});
|
|
|
|
expect(atomicity.atomicity).toBe("atomic");
|
|
expect(answerability.independentlyAnswerable).toBe(false);
|
|
expect(answerability.decompositionRequired).toBe(true);
|
|
expect(result.success).toBe(true);
|
|
expect(result.decompositionAttempted).toBe(true);
|
|
expect(result.decompositionTriggeredByAnswerability).toBe(true);
|
|
expect(result.decompositionAccepted).toBe(true);
|
|
expect(result.proposedChildCount).toBe(2);
|
|
expect(result.acceptedChildCount).toBe(2);
|
|
expect(result.childUnknownCount).toBe(2);
|
|
expect(result.selectedContainerUnknown).toBe(parent.id);
|
|
expect(result.selectedChildUnknown).not.toBe(parent.id);
|
|
expect(result.selectedQuestion).toBeTruthy();
|
|
});
|
|
|
|
it("preserves deterministic family path without invoking semantic fallback", async () => {
|
|
const graph = makeCommercialContainerGraph();
|
|
let providerCalled = false;
|
|
const provider = {
|
|
generateReconstruction: async () => {
|
|
providerCalled = true;
|
|
return { parentNodeId: "n-commercial-parent", proposedChildren: [] };
|
|
},
|
|
};
|
|
|
|
const result = await determineGraphBackedQuestion({
|
|
situationGraph: graph,
|
|
provider,
|
|
modelName: "mock-ollama",
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.decompositionPerformed).toBe(true);
|
|
expect(providerCalled).toBe(false);
|
|
});
|
|
});
|