fix(reasoning): recognise decision in sufficiency question

This commit is contained in:
2026-08-14 19:04:17 +01:00
parent 391667777e
commit 912680b967
2 changed files with 215 additions and 8 deletions
-1
View File
@@ -2007,7 +2007,6 @@ export function formulateQuestion({ node, graph, context = {} }) {
// Detected transiently from existing state; no persisted field required. // Detected transiently from existing state; no persisted field required.
if ( if (
reasoningPatternSelection.pattern === "decision" && reasoningPatternSelection.pattern === "decision" &&
node.kind !== "unknown" &&
node.status !== "known" && node.status !== "known" &&
node.status !== "resolved" && node.status !== "resolved" &&
node.status !== "contradicted" && node.status !== "contradicted" &&
+215 -7
View File
@@ -1105,7 +1105,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision for product X.", description: "Launch vs not launch decision for product X.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
childIds: [], childIds: [],
@@ -1137,7 +1137,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision for product X.", description: "Launch vs not launch decision for product X.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
}); });
@@ -1170,7 +1170,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision.", description: "Launch vs not launch decision.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
childIds: [factor.id], childIds: [factor.id],
@@ -1201,7 +1201,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision.", description: "Launch vs not launch decision.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
}); });
@@ -1261,7 +1261,7 @@ describe("formulateQuestion", () => {
id: "n-decision", id: "n-decision",
label: "Build decision", label: "Build decision",
description: "Decision introduced by the answer.", description: "Decision introduced by the answer.",
kind: "state", kind: "unknown",
status: "known", status: "known",
confidence: "medium", confidence: "medium",
childIds: [unknown.id], childIds: [unknown.id],
@@ -1294,7 +1294,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision.", description: "Launch vs not launch decision.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
childIds: [factor.id], childIds: [factor.id],
@@ -1325,7 +1325,7 @@ describe("formulateQuestion", () => {
id: "n_product_launch_decision", id: "n_product_launch_decision",
label: "Whether to launch product X", label: "Whether to launch product X",
description: "Launch vs not launch decision.", description: "Launch vs not launch decision.",
kind: "state", kind: "unknown",
status: "unknown", status: "unknown",
confidence: "medium", confidence: "medium",
}); });
@@ -1342,3 +1342,211 @@ describe("formulateQuestion", () => {
expect(result.reason).toContain("material factors"); expect(result.reason).toContain("material factors");
}); });
}); });
// ── 60B.75 — real decision detection for sufficiency question ────
describe("60B.75 — real decision detection for sufficiency question", () => {
it("Test 1 — actual decision representation reaches State B", () => {
const decision = makeNode({
id: "n_product_launch_decision",
label: "Which option leaves us better off overall?",
description:
"Uncertainty about which product-launch timing option provides superior net value.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(decision, { nodes: [], edges: [] });
expect(hasRemainingMaterialFactors(decision.id, graph)).toBe(false);
const result = formulateQuestion({
node: decision,
graph,
context: { resolvedValues: [] },
});
expect(result.questionFamily).toBe("decision_threshold");
expect(result.selectedQuestionTemplate).toBe(
"decision_threshold_sufficiency_confirmation",
);
expect(result.question).toContain("anything else material");
expect(result.reasoningPattern).toBe("decision");
});
it("Test 2 — ordinary unknown factor does NOT trigger State B", () => {
const unknown = makeNode({
id: "n_evidence_unknown",
label: "Evidence of demand",
description: "Need evidence of demand.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown);
const result = formulateQuestion({
node: unknown,
graph,
});
expect(result.selectedQuestionTemplate).not.toBe(
"decision_threshold_sufficiency_confirmation",
);
});
it("Test 3 — genuine decision with remaining factor does NOT trigger State B", () => {
const factor = makeNode({
id: "n_customer_readiness",
label: "Customer readiness level",
description: "How ready the customer is to adopt.",
kind: "unknown",
status: "unknown",
confidence: "medium",
parentId: "n_product_launch_decision",
});
const decision = makeNode({
id: "n_product_launch_decision",
label: "Which option leaves us better off overall?",
description: "Uncertainty about which product-launch timing option provides superior net value.",
kind: "unknown",
status: "unknown",
confidence: "medium",
childIds: [factor.id],
});
const graph = makeGraphFor(factor, {
nodes: [decision],
edges: [],
});
expect(hasRemainingMaterialFactors(decision.id, graph)).toBe(true);
const result = formulateQuestion({
node: decision,
graph,
context: { resolvedValues: [] },
});
expect(result.selectedQuestionTemplate).not.toBe(
"decision_threshold_sufficiency_confirmation",
);
});
it("Test 4 — explicit confirmation still prevents question path", () => {
const decision = makeNode({
id: "n_product_launch_decision",
label: "Which option leaves us better off overall?",
description:
"Uncertainty about which product-launch timing option provides superior net value.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(decision, { nodes: [], edges: [] });
const result = formulateQuestion({
node: decision,
graph,
context: {
resolvedValues: ["no other material uncertainty remains"],
},
});
expect(result.selectedQuestionTemplate).not.toBe(
"decision_threshold_sufficiency_confirmation",
);
});
it("Test 5 — existing decision_threshold case preserved", () => {
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: "unknown",
status: "known",
confidence: "medium",
childIds: [unknown.id],
});
const graph = makeGraphFor(unknown, {
nodes: [decision],
resolvedNodeIds: [decision.id],
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.questionFamily).toBe("decision_threshold");
});
it("Test 6 — exact 60B.73-style production-shaped fixture", () => {
const optLaunch = makeNode({
id: "opt_launch_this_year",
label: "Launch this year",
description: "New software product launches this year.",
kind: "unknown",
status: "known",
confidence: "high",
parentId: "n_product_launch_decision",
});
const optWait = makeNode({
id: "opt_wait_twelve_months",
label: "Wait twelve months",
description: "Defer product launch by twelve months.",
kind: "unknown",
status: "known",
confidence: "high",
parentId: "n_product_launch_decision",
});
const decision = makeNode({
id: "n_product_launch_decision",
label: "Which option leaves us better off overall?",
description:
"Uncertainty about which of the two product-launch timing options — launch this year or wait twelve months — provides superior net value for the organisation.",
kind: "unknown",
status: "unknown",
confidence: "medium",
childIds: [optLaunch.id, optWait.id],
});
const graph = makeGraphFor(decision, {
nodes: [optLaunch, optWait],
edges: [
{ id: "e-opt-launch-to-dec", fromNodeId: optLaunch.id, toNodeId: decision.id, relationship: "contained_in", confidence: "high", description: "Launch this year option is a candidate for the product launch decision" },
{ id: "e-opt-wait-to-dec", fromNodeId: optWait.id, toNodeId: decision.id, relationship: "contained_in", confidence: "high", description: "Wait twelve months option is a candidate for the product launch decision" },
],
});
expect(hasRemainingMaterialFactors(decision.id, graph)).toBe(false);
const result = formulateQuestion({
node: decision,
graph,
context: { resolvedValues: [] },
});
expect(result.questionFamily).toBe("decision_threshold");
expect(result.selectedQuestionTemplate).toBe(
"decision_threshold_sufficiency_confirmation",
);
expect(result.reasoningPattern).toBe("decision");
expect(result.question).toContain("anything else material");
});
});