Files
confidence-engine/tests/graph/question-formulator.test.js
T

1097 lines
36 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
assessUnknownAtomicity,
formulateQuestion,
formulateTieResolutionQuestion,
selectReasoningPattern,
selectInvestigationStrategy,
} from "@/lib/graph/question-formulator.js";
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
function makeGraphFor(node, extra = {}) {
return makeGraph({
centralStatement: extra.centralStatement || "Decision context",
nodes: [node, ...(extra.nodes || [])],
edges: extra.edges || [],
activeUnknownNodeId: node.id,
resolvedNodeIds: extra.resolvedNodeIds || [],
currentSummary: "Test summary",
});
}
describe("formulateQuestion", () => {
it("atomicity assessment leaves focused unknowns direct and marks broad explanation unknowns composite", () => {
const atomicUnknown = makeNode({
id: "n-atomic",
label: "Complaint rate denominator",
description:
"Need the denominator because it directly determines the complaint rate.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const compositeUnknown = makeNode({
id: "n-composite",
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, because that is needed to investigate their relationship.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const compositeGraph = makeGraphFor(compositeUnknown, {
centralStatement:
"Revenue increased by 18%, but cash in the bank fell over the same period.",
nodes: [
makeNode({
id: "n-revenue-observation",
label: "Revenue increased by 18%.",
description: "Revenue increased by 18%.",
kind: "observation",
status: "supported",
confidence: "high",
}),
makeNode({
id: "n-cash-observation",
label: "Cash in the bank decreased over the same period.",
description: "Cash in the bank decreased over the same period.",
kind: "observation",
status: "supported",
confidence: "high",
}),
],
});
expect(
assessUnknownAtomicity({
node: atomicUnknown,
graph: makeGraphFor(atomicUnknown),
}).atomicity,
).toBe("atomic");
expect(
assessUnknownAtomicity({
node: compositeUnknown,
graph: compositeGraph,
}).atomicity,
).toBe("composite");
});
it("commercial viability plus build decision produces a decision-threshold question", () => {
const unknown = makeNode({
id: "n-commercial",
label: "Uncertainty regarding the commercial value of the product",
description:
"Commercial justification remains unclear because the decision depends on it.",
kind: "unknown",
status: "unknown",
confidence: "high",
parentId: "n-decision",
});
const decision = makeNode({
id: "n-decision",
label: "Build decision",
description: "Decision introduced by the answer.",
kind: "state",
status: "known",
confidence: "medium",
childIds: [unknown.id],
value: "Deciding whether to build the product",
});
const graph = makeGraphFor(unknown, {
nodes: [decision],
resolvedNodeIds: [decision.id],
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.strategy).toBe("decision_threshold");
expect(result.reasoningPattern).toBe("decision");
expect(result.questionFamily).toBe("decision_threshold");
expect(result.question).toContain("What outcome");
expect(result.question.toLowerCase()).toContain("justify");
});
it("commercial viability does not produce a pricing-first question", () => {
const unknown = makeNode({
id: "n-commercial",
label: "Commercial viability",
description:
"Commercial viability remains unresolved because the decision depends on it.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const graph = makeGraphFor(unknown);
const result = formulateQuestion({ node: unknown, graph });
expect(result.question.toLowerCase()).not.toContain("price");
expect(result.question.toLowerCase()).not.toContain("pricing");
});
it("undefined term produces a definition question", () => {
const unknown = makeNode({
id: "n-term",
label: "Success criteria definition",
description:
"Need a definition of the term because the team uses it inconsistently.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBe("definition");
expect(result.reasoningPattern).toBe("definition");
expect(result.questionFamily).toBe("definition");
expect(result.question).toMatch(/^What does /);
});
it("unsupported claim produces an evidence question", () => {
const unknown = makeNode({
id: "n-claim",
label: "Demand claim",
description: "Need evidence because the claim has not been validated.",
kind: "reported_claim",
status: "provisional",
confidence: "low",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBe("evidence_gathering");
expect(result.question).toContain("What evidence");
});
it("missing previous state produces a baseline question", () => {
const unknown = makeNode({
id: "n-baseline",
label: "Baseline conversion rate",
description:
"Need the previous baseline because the change cannot be assessed without it.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBe("baseline_reconstruction");
expect(result.question).toContain("What was the comparable state before");
});
it("conflicting claim produces a contradiction-resolution question", () => {
const unknown = makeNode({
id: "n-conflict",
label: "Conflicting churn claim",
description:
"Need to resolve the inconsistency because the current figures contradict each other.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const contradiction = makeNode({
id: "n-contradiction",
label: "Contradicted report",
description: "Two sources disagree about churn.",
kind: "conclusion",
status: "contradicted",
confidence: "low",
childIds: [unknown.id],
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown, { nodes: [contradiction] }),
});
expect(result.strategy).toBe("contradiction_resolution");
expect(result.reasoningPattern).toBe("contradiction");
expect(result.question).toContain("resolve the contradiction");
});
it("reasoning pattern selection marks commercial validation as decision rather than explanation", () => {
const unknown = makeNode({
id: "n-commercial-pattern",
label:
"Whether the method addresses a genuine, high-priority problem for a specific audience",
description:
"Need to know whether this solves a real problem for a clear audience before continuing development.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"Before investing more, we need to know whether continuing development is commercially justified.",
});
const result = selectReasoningPattern({ node: unknown, graph });
expect(result.pattern).toBe("decision");
});
it("comparison scenario selects the comparison pattern", () => {
const unknown = makeNode({
id: "n-comparison-pattern",
label: "How the two observations were measured",
description:
"Need evidence about the measure used for each observation, because that could help explain the difference.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement: "Traffic increased, but sales stayed flat.",
nodes: [
makeNode({
id: "n-traffic-observation",
label: "Traffic increased.",
description: "Traffic increased.",
kind: "observation",
status: "supported",
confidence: "high",
}),
makeNode({
id: "n-sales-observation",
label: "Sales stayed flat.",
description: "Sales stayed flat.",
kind: "observation",
status: "supported",
confidence: "high",
}),
],
});
const result = selectReasoningPattern({ node: unknown, graph });
expect(result.pattern).toBe("comparison");
});
it("user-owned constraint ambiguity produces a clarification question rather than an evidence request", () => {
const unknown = makeNode({
id: "n-constraint",
label: "Whether avoiding additional risk is a hard constraint",
description:
"Need to know whether avoiding additional risk is a hard constraint or a preference/trade-off.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBeNull();
expect(result.question).toBe(
"Is avoiding additional risk a hard constraint or a preference/trade-off?",
);
});
it("60B.21 signing-status node does not route to decision_audience and preserves the proposition", () => {
const unknown = makeNode({
id: "n-customer-signing-status",
label: "Prospective enterprise customer signing status",
description:
"Uncertainty about whether the prospective enterprise customer will sign if we launch this year, so that its resolution is needed to decide which timing option provides superior net value.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.questionFamily).not.toBe("decision_foundation");
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
expect(result.question.toLowerCase()).toContain(
"whether the prospective enterprise customer will sign if we launch this year",
);
expect(result.question.toLowerCase()).not.toContain(
"relevant customer, user, or value recipient",
);
});
it("legitimate audience identity unknown still routes to decision_audience", () => {
const unknown = makeNode({
id: "n-target-customer",
label: "Who is the target customer?",
description:
"Need to know who the target customer is before continuing development.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"Before investing more, we need to know whether continuing development is commercially justified.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.questionFamily).toBe("decision_foundation");
expect(result.selectedQuestionTemplate).toBe("decision_audience");
expect(result.question).toBe("Who experiences this problem?");
});
it("customer as proposition subject does not imply audience family", () => {
const unknown = makeNode({
id: "n-customer-renewal",
label: "Customer renewal likelihood",
description:
"Uncertainty about whether the customer will renew next year, because that affects the decision.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
expect(result.question.toLowerCase()).toContain(
"whether the customer will renew next year",
);
});
it("user as proposition subject does not imply audience family", () => {
const unknown = makeNode({
id: "n-user-adoption",
label: "User adoption uncertainty",
description:
"Uncertainty about whether users will adopt the change, because that affects the decision.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
expect(result.question.toLowerCase()).toContain(
"whether users will adopt the change",
);
});
it("buyer or stakeholder lexical mentions do not automatically trigger audience discovery", () => {
const unknown = makeNode({
id: "n-stakeholder-approval",
label: "Stakeholder approval uncertainty",
description:
"Uncertainty about whether the stakeholder will approve the plan, because that affects the decision.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
expect(result.question.toLowerCase()).toContain(
"whether the stakeholder will approve the plan",
);
});
it("existing direct interrogative decision behaviour is preserved", () => {
const unknown = makeNode({
id: "n-client-leave",
label: "Will our largest client leave if we relocate?",
description:
"Uncertainty regarding whether our largest client would depart following a relocation to Manchester; matters because their departure would cost approximately £5M per year.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide whether relocating is commercially justified.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question.toLowerCase()).toBe(
"will our largest client leave if we relocate?",
);
});
it("60B.24 regression strips whether-rationale and uses evidence framing without mutating the source node", () => {
const description =
"Whether one prospective enterprise customer will sign if we launch this year; they account for ~£700k of the £1.2M expected annual revenue, so that resolving their intent is needed to assess the financial impact of launching now.";
const unknown = makeNode({
id: "n-60b24-signing-status",
label: "Prospective enterprise customer signing status",
description,
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toContain(
"whether one prospective enterprise customer will sign if we launch this year",
);
expect(result.question).toContain("What evidence would clarify");
expect(result.question).not.toContain("£700k");
expect(result.question).not.toContain("£1.2M");
expect(result.question).not.toContain("resolving their intent");
expect(result.question).not.toContain("financial impact");
expect(unknown.description).toBe(description);
});
it("clean whether proposition uses evidence framing rather than a direct whether-question", () => {
const unknown = makeNode({
id: "n-supplier-renewal",
label: "Supplier renewal likelihood",
description: "Whether the supplier will renew the contract.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew the contract?",
);
expect(result.question).not.toBe(
"Whether the supplier will renew the contract?",
);
});
it("wh-question remains unchanged", () => {
const unknown = makeNode({
id: "n-wh-question",
label: "What would change the preferred option?",
description: "What would change the preferred option?",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe("What would change the preferred option?");
});
it("non-whether semicolon content is not globally truncated", () => {
const unknown = makeNode({
id: "n-semicolon-baseline",
label:
"Unknown baseline measurement basis; current and previous figures were prepared differently.",
description:
"Baseline measurement basis; current and previous figures were prepared differently.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toContain(
"baseline measurement basis; current and previous figures were prepared differently",
);
expect(result.question).not.toContain("What would clarify whether");
});
it("evidence-resolvable competing-cause unknown stays on an evidence route rather than neutral clarification", () => {
const unknown = makeNode({
id: "n-delivery-cause",
label: "Possible causes of the delivery delay",
description:
"Need to determine whether staff capacity or supplier lead times are responsible for the delivery delay.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown, {
centralStatement: "Delivery is delayed and the cause is still unknown.",
}),
});
expect(result.reasoningPattern).toBe("diagnosis");
expect(result.question).toContain("What evidence");
expect(result.question).not.toContain("What would clarify");
});
it("the same unknown can produce different questions when paired with different strategies", () => {
const thresholdUnknown = makeNode({
id: "n-threshold-unknown",
label: "Value threshold",
description: "Need to resolve the value threshold.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const definitionUnknown = makeNode({
id: "n-definition-unknown",
label: "Value term",
description: "Need to resolve what value term refers to in this context.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const decisionGraph = makeGraphFor(thresholdUnknown, {
centralStatement: "We are deciding whether to launch this product.",
nodes: [
makeNode({
id: "n-decision",
label: "Launch decision",
description: "Decision depends on the value threshold.",
kind: "state",
status: "known",
confidence: "medium",
childIds: [thresholdUnknown.id],
value: "Deciding whether to launch the product",
}),
],
});
const definitionGraph = makeGraphFor(definitionUnknown, {
centralStatement:
"The team uses the term value threshold inconsistently.",
nodes: [
makeNode({
id: "n-definition",
label: "Definition disagreement about value threshold",
description:
"Need a definition of value threshold because the term is used inconsistently before comparing options.",
kind: "state",
status: "known",
confidence: "medium",
childIds: [definitionUnknown.id],
}),
],
});
const decisionResult = formulateQuestion({
node: thresholdUnknown,
graph: decisionGraph,
});
const definitionResult = formulateQuestion({
node: definitionUnknown,
graph: definitionGraph,
});
expect(decisionResult.strategy).toBe("decision_threshold");
expect(definitionResult.strategy).toBe("definition");
expect(decisionResult.question).not.toBe(definitionResult.question);
});
it("strategy selection is deterministic and explainable", () => {
const unknown = makeNode({
id: "n-threshold",
label: "Success threshold",
description:
"Need the success threshold because the decision depends on it.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const graph = makeGraphFor(unknown, {
centralStatement: "We need to decide whether to continue investing.",
});
const first = selectInvestigationStrategy({ node: unknown, graph });
const second = selectInvestigationStrategy({ node: unknown, graph });
expect(first).toEqual(second);
expect(first.key).toBe("decision_threshold");
expect(first.reason).toContain("threshold");
});
it("question is singular and answerable", () => {
const unknown = makeNode({
id: "n-evidence",
label: "Evidence of demand",
description:
"Need evidence of demand because the decision depends on it.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question.match(/\?/g) || []).toHaveLength(1);
expect(result.question.toLowerCase()).not.toContain(" and ");
});
it("awkward uncertainty phrasing is rejected via fallback", () => {
const unknown = makeNode({
id: "n-weird",
label: "Uncertainty regarding service reliability",
description: "Unknown service reliability.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).not.toContain("How should uncertainty regarding");
expect(result.question).not.toContain(
"What would resolve uncertainty regarding",
);
});
it("ambiguous contradiction produces a broad distinguishing question without accounting jargon", () => {
const unknown = makeNode({
id: "n-cause-a",
label: "Cash outflow cause",
description: "Unclear explanation for the contradiction.",
kind: "unknown",
status: "unknown",
confidence: "high",
});
const contradiction = makeNode({
id: "n-contradiction",
label: "Divergent movement between revenue and cash",
description: "Two signals moved in opposite directions.",
kind: "relationship",
status: "supported",
confidence: "medium",
});
const revenueObservation = makeNode({
id: "n-revenue-observation",
label: "Revenue increased by 18%.",
description: "Revenue increased by 18%.",
kind: "observation",
status: "supported",
confidence: "high",
});
const cashObservation = makeNode({
id: "n-cash-observation",
label: "Cash in the bank decreased over the same period.",
description: "Cash in the bank decreased over the same period.",
kind: "observation",
status: "supported",
confidence: "high",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"Revenue increased by 18%, but cash in the bank fell over the same period.",
nodes: [contradiction, revenueObservation, cashObservation],
});
const result = formulateTieResolutionQuestion({ graph });
expect(result.question).toBe(
"Were these figures measured on the same basis and at the same scale?",
);
expect(result.comparabilityStatus).toBe("uncertain");
expect(result.question.toLowerCase()).not.toMatch(
/accounts receivable|capex|debt repayments|working capital/,
);
});
it("definition is selected only for genuine definition unknowns", () => {
const unknown = makeNode({
id: "n-definition-only",
label: "Definition of success criteria",
description: "The term is used inconsistently and needs a definition.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBe("definition");
});
it("an unknown about possible causes does not become a definition question", () => {
const unknown = makeNode({
id: "n-causes",
label: "Possible causes of the divergence",
description: "Several causes may explain the divergence.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.reasoningPattern).toBe("diagnosis");
expect(result.strategy).toBeNull();
expect(result.question).toBe(
"What would clarify possible causes of the divergence in this situation?",
);
});
it("malformed punctuation is rejected", () => {
const unknown = makeNode({
id: "n-punct",
label: "Magnitude and nature of cash outflows (operating expenses).",
description:
"Magnitude and nature of cash outflows (operating expenses).",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).not.toContain("). is true?");
expect(result.question).toBe(
"What would clarify magnitude and nature of cash outflows (operating expenses) in this situation?",
);
});
// ── 60B.28 / 60B.30: uncertainty-prefix coverage ───────────────────
it("60B.27 regression: 'Unknown whether...' preserves full proposition", () => {
const unknown = makeNode({
id: "unc-60b27",
label: "Prospective enterprise customer signing likelihood",
description:
"Unknown whether one prospective enterprise customer will sign if we launch this year, so that the remaining £700k of the expected £1.2M annual revenue is realized.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toBe(
"What evidence would clarify whether one prospective enterprise customer will sign if we launch this year?",
);
// Rationale must NOT leak into the final question
expect(result.question).not.toContain("£700k");
expect(result.question).not.toContain("£1.2M");
expect(result.question.toLowerCase()).not.toContain("annual revenue");
});
it("'Uncertain whether...' exposes the full proposition with evidence framing", () => {
const unknown = makeNode({
id: "unc-uncertain",
label: "Supplier renewal uncertainty",
description:
"Uncertain whether the supplier will renew, because the contract is still under review.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew?",
);
});
it("60B.29 regression: 'Uncertainty over whether...' preserves full proposition", () => {
const unknown = makeNode({
id: "unc-60b29",
label: "Prospective enterprise customer signing status",
description:
"Uncertainty over whether the prospective enterprise customer will sign if we launch this year, because their contract accounts for approximately £700,000 of the expected first-year revenue and could materially flip the net-value comparison.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toBe(
"What evidence would clarify whether the prospective enterprise customer will sign if we launch this year?",
);
expect(result.question).not.toContain("£700,000");
expect(result.question).not.toContain("expected first-year revenue");
expect(result.question).not.toContain("materially flip");
expect(result.question).not.toContain("because their contract");
});
it("'Uncertainty over whether...' without rationale uses evidence framing", () => {
const unknown = makeNode({
id: "unc-over",
label: "Supplier renewal status",
description: "Uncertainty over whether the supplier will renew.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew?",
);
});
it("60B.31 regression: bare 'Whether...' description defines proposition even with nominal label", () => {
const unknown = makeNode({
id: "unc-60b31",
label: "Enterprise customer signing decision",
description:
"Whether the prospective enterprise customer will commit this year, because resolving this uncertainty is needed to decide if launching this year provides superior net value over waiting twelve months.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide whether launching this year provides superior net value over waiting twelve months.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toBe(
"What evidence would clarify whether the prospective enterprise customer will commit this year?",
);
expect(result.question).not.toContain("launching");
expect(result.question).not.toContain("superior net value");
expect(result.question).not.toContain("waiting twelve months");
expect(result.question).not.toContain("because");
});
it("bare 'Whether...' with unrelated nominal label remains proposition-specific", () => {
const unknown = makeNode({
id: "unc-bare-nominal",
label: "Supplier contract decision",
description: "Whether the supplier will renew the contract.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew the contract?",
);
});
it("bare 'Whether...' remains unchanged (existing behaviour)", () => {
const unknown = makeNode({
id: "unc-bare-whether",
label: "Supplier renewal likelihood",
description: "Whether the supplier will renew the contract.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew the contract?",
);
});
it("'Uncertainty about whether...' remains unchanged (existing behaviour)", () => {
const unknown = makeNode({
id: "unc-about",
label: "Customer renewal likelihood",
description:
"Uncertainty about whether the customer will renew next year, because that affects the decision.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question.toLowerCase()).toContain(
"whether the customer will renew next year",
);
});
it("'Uncertainty regarding whether...' remains unchanged (existing behaviour)", () => {
const unknown = makeNode({
id: "unc-regarding",
label: "Client retention uncertainty",
description:
"Uncertainty regarding whether our largest client would depart following a relocation to Manchester; matters because their departure would cost approximately £5M per year.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide whether relocating is commercially justified.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question.toLowerCase()).toContain(
"whether our largest client would depart following a relocation to manchester",
);
});
it("direct interrogative label remains direct", () => {
const unknown = makeNode({
id: "unc-direct",
label: "Will our largest client leave if we relocate?",
description:
"Uncertainty regarding whether our largest client would depart following a relocation to Manchester; matters because their departure would cost approximately £5M per year.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide whether relocating is commercially justified.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toBe(
"Will our largest client leave if we relocate?",
);
});
it("generic non-proposition decision unknown remains on its existing non-proposition path", () => {
const unknown = makeNode({
id: "unc-generic-decision",
label: "Launch decision value threshold",
description:
"Need to determine what outcome would be sufficient to justify launching this year.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown, {
centralStatement:
"We need to decide whether launching this year provides superior net value over waiting twelve months.",
}),
});
expect(result.question).not.toContain(
"whether the prospective enterprise customer will commit this year",
);
expect(result.question).not.toContain(
"whether the supplier will renew the contract",
);
});
it("nominal label without explicit 'whether...' falls back to label-based extraction", () => {
const unknown = makeNode({
id: "unc-nominal",
label: "Service reliability uncertainty",
description: "Unknown service reliability.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).not.toContain("How should uncertainty regarding");
});
it("source description is never mutated by question formulation", () => {
const description =
"Unknown whether one prospective enterprise customer will sign if we launch this year, so that the remaining £700k of the expected £1.2M annual revenue is realized.";
const unknown = makeNode({
id: "unc-preserve",
label: "Prospective enterprise customer signing likelihood",
description,
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
formulateQuestion({ node: unknown, graph });
expect(unknown.description).toBe(description);
});
});