fix(reasoning): scope structural context admission
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
applyValidatedProposal,
|
||||
assessReasoningPatternCompatibility,
|
||||
assessStructuralContextAdmission,
|
||||
} from "@/lib/graph/apply-proposal.js";
|
||||
import { selectReasoningPattern } from "@/lib/graph/question-formulator.js";
|
||||
import { makeGraph, makeNode } from "@/lib/graph/schema.js";
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────
|
||||
|
||||
function buildDecisionContext() {
|
||||
const decision = makeNode({
|
||||
id: "n_relocation_decision",
|
||||
label: "Whether continuing development is commercially justified",
|
||||
description:
|
||||
"Need to know whether continuing development is commercially justified before committing resources.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
return makeGraph({
|
||||
centralStatement:
|
||||
"Relocation decision context for structural embedding tests.",
|
||||
nodes: [decision],
|
||||
edges: [],
|
||||
activeUnknownNodeId: decision.id,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Decision fixture",
|
||||
});
|
||||
}
|
||||
|
||||
function addOption(graph, optionId) {
|
||||
const option = makeNode({
|
||||
id: optionId,
|
||||
label: "Relocate to new office",
|
||||
description: "Move operations to a new location.",
|
||||
kind: "option",
|
||||
status: "provisional",
|
||||
});
|
||||
graph.nodes.push(option);
|
||||
graph.edges.push({
|
||||
id: `${optionId}_contained_in`,
|
||||
fromNodeId: optionId,
|
||||
toNodeId: graph.activeUnknownNodeId,
|
||||
relationship: "contained_in",
|
||||
confidence: "high",
|
||||
description: "Option belongs to this decision.",
|
||||
});
|
||||
}
|
||||
|
||||
function resolveActivePattern(graph) {
|
||||
const activeNode = graph.nodes.find(
|
||||
(n) => n.id === graph.activeUnknownNodeId,
|
||||
);
|
||||
if (!activeNode) return null;
|
||||
return selectReasoningPattern({ node: activeNode, graph }).pattern;
|
||||
}
|
||||
|
||||
function assessBoundedAdmission(graph, node, overrides = {}) {
|
||||
return assessStructuralContextAdmission({
|
||||
node,
|
||||
graph,
|
||||
activePattern: overrides.activePattern ?? resolveActivePattern(graph),
|
||||
activeNodeId: overrides.activeNodeId ?? graph.activeUnknownNodeId,
|
||||
});
|
||||
}
|
||||
|
||||
function buildApplyProposalDecisionFixture() {
|
||||
const decision = makeNode({
|
||||
id: "n_active_decision",
|
||||
label: "Whether relocating is commercially justified",
|
||||
description:
|
||||
"Need to evaluate whether relocating is commercially justified.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
return makeGraph({
|
||||
centralStatement: "Relocation follow-up fixture.",
|
||||
nodes: [decision],
|
||||
edges: [],
|
||||
activeUnknownNodeId: decision.id,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Decision apply-proposal fixture",
|
||||
});
|
||||
}
|
||||
|
||||
// ── Tests ────────────────────────────────────────────────
|
||||
|
||||
describe("reasoning-context compatibility — bounded structural admission (60B.19)", () => {
|
||||
it("Test 1 - Route A: newly-added decision unknown admitted via parent/ancestor chain", () => {
|
||||
const decision = makeNode({
|
||||
id: "n_decision_a",
|
||||
label: "Investment context A",
|
||||
description: "A parent context for the option under review.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const parentOption = makeNode({
|
||||
id: "opt_parent_a",
|
||||
label: "Parent option A",
|
||||
description: "An option for the decision.",
|
||||
kind: "option",
|
||||
status: "provisional",
|
||||
parentId: "n_decision_a",
|
||||
});
|
||||
|
||||
const childUnknown = makeNode({
|
||||
id: "n_child_embedded",
|
||||
label: "What practical issue is blocking progress",
|
||||
description:
|
||||
"Need to identify the specific blocking issue before continuing this line of work.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: "opt_parent_a",
|
||||
});
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement: "Market entry decision.",
|
||||
nodes: [decision, parentOption],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n_decision_a",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Route A fixture",
|
||||
});
|
||||
|
||||
// childUnknown is not yet in graph.nodes; add it so we can build the chain
|
||||
const compat = assessBoundedAdmission(
|
||||
{ ...graph, nodes: [...graph.nodes, childUnknown] },
|
||||
childUnknown,
|
||||
{ activePattern: "decision", activeNodeId: "n_decision_a" },
|
||||
);
|
||||
|
||||
expect(compat.admitted).toBe(true);
|
||||
expect(compat.structuralEmbedding).toBe(true);
|
||||
expect(compat.routeA).toBe(true);
|
||||
});
|
||||
|
||||
it("Test 2 - Route B may_cause admitted", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_client_retention_uncertainty",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_client_retention_uncertainty_may_cause_opt_relocate",
|
||||
fromNodeId: "n_client_retention_uncertainty",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "may_cause",
|
||||
confidence: "medium",
|
||||
description: "May cause option consequence.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(true);
|
||||
expect(compat.structuralEmbedding).toBe(true);
|
||||
expect(compat.routeB).toBe(true);
|
||||
});
|
||||
|
||||
it("Test 3 - Route B causes admitted", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
// Add the node and edge into the graph for traversal.
|
||||
const newUnknown = makeNode({
|
||||
id: "n_client_retention_uncertainty",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_client_retention_uncertainty_causes_opt_relocate",
|
||||
fromNodeId: "n_client_retention_uncertainty",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "causes",
|
||||
confidence: "medium",
|
||||
description: "Causal link.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(true);
|
||||
expect(compat.structuralEmbedding).toBe(true);
|
||||
expect(compat.routeB).toBe(true);
|
||||
});
|
||||
|
||||
it("Test 4 - Route B affects admitted", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_client_retention_uncertainty",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_client_retention_uncertainty_affects_opt_relocate",
|
||||
fromNodeId: "n_client_retention_uncertainty",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "affects",
|
||||
confidence: "medium",
|
||||
description: "Impact link.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(true);
|
||||
expect(compat.structuralEmbedding).toBe(true);
|
||||
expect(compat.routeB).toBe(true);
|
||||
});
|
||||
|
||||
it("Test 5 - supports does NOT qualify as structural embedding", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_supporting_factor",
|
||||
label: "What causes the revenue discrepancy?",
|
||||
description: "Need to understand root cause of divergence.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_supporting_factor_supports_opt_relocate",
|
||||
fromNodeId: "n_supporting_factor",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "supports",
|
||||
confidence: "medium",
|
||||
description: "Support link.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 6 - measures does NOT qualify as structural embedding", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_measuring_node",
|
||||
label: "What causes the revenue discrepancy?",
|
||||
description: "Need to understand root cause of divergence.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_measuring_node_measures_opt_relocate",
|
||||
fromNodeId: "n_measuring_node",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "measures",
|
||||
confidence: "medium",
|
||||
description: "Measurement link.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 7 - depends_on does NOT qualify as structural embedding", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_depends_node",
|
||||
label: "What causes the revenue discrepancy?",
|
||||
description: "Need to understand root cause of divergence.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_depends_node_depends_on_opt_relocate",
|
||||
fromNodeId: "n_depends_node",
|
||||
toNodeId: "opt_relocate",
|
||||
relationship: "depends_on",
|
||||
confidence: "medium",
|
||||
description: "Dependency link.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 8 - arbitrary graph connectivity does NOT produce compatibility", () => {
|
||||
const graph = buildDecisionContext();
|
||||
addOption(graph, "opt_relocate");
|
||||
|
||||
const intermediateNode = makeNode({
|
||||
id: "n_intermediate",
|
||||
label: "Some unrelated factor",
|
||||
description: "Not relevant.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_arbitrary_path",
|
||||
label: "What causes the revenue discrepancy?",
|
||||
description: "Need to understand root cause of divergence.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
graph.nodes.push(intermediateNode, newUnknown);
|
||||
graph.edges.push({
|
||||
id: "n_arbitrary_path_may_cause_intermediate",
|
||||
fromNodeId: "n_arbitrary_path",
|
||||
toNodeId: "n_intermediate",
|
||||
relationship: "may_cause",
|
||||
confidence: "medium",
|
||||
description: "Connects to intermediate, not an option.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 9 - wrong decision context rejects", () => {
|
||||
const otherDecision = makeNode({
|
||||
id: "n_other_decision",
|
||||
label: "Should we launch product B?",
|
||||
description: "Different decision.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const baseGraph = makeGraph({
|
||||
centralStatement: "Testing wrong decision context.",
|
||||
nodes: [buildDecisionContext().nodes[0], otherDecision],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n_relocation_decision",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Wrong context fixture",
|
||||
});
|
||||
|
||||
const wrongOption = makeNode({
|
||||
id: "opt_wrong_context",
|
||||
label: "Launch product B option",
|
||||
description: "Belongs to other decision.",
|
||||
kind: "option",
|
||||
status: "provisional",
|
||||
});
|
||||
|
||||
baseGraph.nodes.push(wrongOption);
|
||||
baseGraph.edges.push({
|
||||
id: "opt_wrong_context_contained_in_other",
|
||||
fromNodeId: "opt_wrong_context",
|
||||
toNodeId: "n_other_decision",
|
||||
relationship: "contained_in",
|
||||
confidence: "high",
|
||||
description: "Wrong decision membership.",
|
||||
});
|
||||
|
||||
const newUnknown = makeNode({
|
||||
id: "n_wrong_context_unknown",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
baseGraph.nodes.push(newUnknown);
|
||||
baseGraph.edges.push({
|
||||
id: "n_wrong_context_unknown_may_cause_opt",
|
||||
fromNodeId: "n_wrong_context_unknown",
|
||||
toNodeId: "opt_wrong_context",
|
||||
relationship: "may_cause",
|
||||
confidence: "medium",
|
||||
description: "May cause wrong option.",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(baseGraph, newUnknown);
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 10 - already-compatible pattern remains unchanged", () => {
|
||||
const decision = makeNode({
|
||||
id: "n_decision_10",
|
||||
label: "Whether proceeding is commercially justified",
|
||||
description:
|
||||
"Need to evaluate whether proceeding is commercially justified.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const definitionNode = makeNode({
|
||||
id: "n_definition_node",
|
||||
label: "Definition of commercially justified",
|
||||
description: "Define what commercially justified means. Define the term.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement: "Decision context.",
|
||||
nodes: [decision, definitionNode],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n_decision_10",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Already compatible fixture",
|
||||
});
|
||||
|
||||
const compat = assessBoundedAdmission(graph, definitionNode);
|
||||
|
||||
expect(compat.intrinsicCompatible).toBe(true);
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 11 - genuine incompatible child under explanation is rejected", () => {
|
||||
const explanationUnknown = makeNode({
|
||||
id: "n_explanation",
|
||||
label: "Explanation for why revenue increased by 18%",
|
||||
description:
|
||||
"Need to understand what change or event could explain these observations.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement:
|
||||
"Revenue increased by 18%, but cash in the bank fell over the same period.",
|
||||
nodes: [
|
||||
explanationUnknown,
|
||||
makeNode({
|
||||
id: "n_revenue_obs",
|
||||
label: "Revenue increased by 18%.",
|
||||
description: "Revenue increased by 18%.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n_cash_obs",
|
||||
label: "Cash in the bank fell over the same period.",
|
||||
description: "Cash in the bank fell.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n_explanation",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Explanation fixture",
|
||||
});
|
||||
|
||||
const diagnosisChild = makeNode({
|
||||
id: "n_diagnosis_child",
|
||||
label: "What practical issue is blocking progress",
|
||||
description:
|
||||
"Need to identify the specific blocking issue before continuing this line of work.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: "n_explanation",
|
||||
});
|
||||
|
||||
graph.nodes.push(diagnosisChild);
|
||||
|
||||
const compat = assessBoundedAdmission(graph, diagnosisChild);
|
||||
const compatibility = assessReasoningPatternCompatibility({
|
||||
node: diagnosisChild,
|
||||
graph,
|
||||
activePattern: resolveActivePattern(graph),
|
||||
activeNodeId: graph.activeUnknownNodeId,
|
||||
});
|
||||
|
||||
expect(compat.admitted).toBe(false);
|
||||
expect(compat.nodePattern).toBe("diagnosis");
|
||||
expect(compat.structuralEmbedding).toBe(false);
|
||||
expect(compatibility.compatible).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 12 - pre-existing unknown does not enter fallback", () => {
|
||||
const graph = buildApplyProposalDecisionFixture();
|
||||
const preExisting = makeNode({
|
||||
id: "n_preexisting_diagnosis",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: graph.activeUnknownNodeId,
|
||||
});
|
||||
graph.nodes.push(preExisting);
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: graph.activeUnknownNodeId,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "known",
|
||||
previousValue: null,
|
||||
newValue: "Decision anchor updated for selection.",
|
||||
reason: "Makes the parent answer-derived for the test.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: {
|
||||
nodeId: "n_preexisting_diagnosis",
|
||||
question: "What practical risk affects this option?",
|
||||
reason: "Pre-existing node should not gain fallback admission.",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("Test 13 - admitted node survives later result validation", () => {
|
||||
const graph = buildApplyProposalDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
answer: "We need to identify the practical risk affecting this option.",
|
||||
previousQuestion: "Should we relocate?",
|
||||
proposal: {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n_added_route_a",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option because it matters to the decision.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: graph.activeUnknownNodeId,
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: graph.activeUnknownNodeId,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "provisional",
|
||||
previousValue: null,
|
||||
newValue: "Decision remains open pending factor clarification.",
|
||||
reason: "The answer introduces a concrete follow-up factor.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: {
|
||||
nodeId: "n_added_route_a",
|
||||
question: "What practical risk affects this option?",
|
||||
reason: "New factor introduced this turn.",
|
||||
},
|
||||
answerMeaning: {
|
||||
userSupportedMeaning:
|
||||
"We need to identify the practical risk affecting this option.",
|
||||
possibleInference: null,
|
||||
supportCategory: null,
|
||||
resolutionGuidance: null,
|
||||
},
|
||||
structuralActionRequired: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.selectedQuestion?.nodeId).toBe("n_added_route_a");
|
||||
});
|
||||
|
||||
it("Test 14 - non-pattern validations still apply", () => {
|
||||
const graph = buildApplyProposalDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
answer: "We need to identify the practical risk affecting this option.",
|
||||
previousQuestion: "Should we relocate?",
|
||||
proposal: {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n_added_invalid",
|
||||
label: "What practical risk affects this option",
|
||||
description:
|
||||
"Need to identify the concrete risk factor affecting this option because it matters to the decision.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: graph.activeUnknownNodeId,
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: graph.activeUnknownNodeId,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "provisional",
|
||||
previousValue: null,
|
||||
newValue: "Decision remains open pending factor clarification.",
|
||||
reason: "The answer introduces a concrete follow-up factor.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: {
|
||||
nodeId: "n_added_invalid",
|
||||
question:
|
||||
"What practical risk affects this option, and how severe is it?",
|
||||
reason: "Compound question should still fail.",
|
||||
},
|
||||
answerMeaning: {
|
||||
userSupportedMeaning:
|
||||
"We need to identify the practical risk affecting this option.",
|
||||
possibleInference: null,
|
||||
supportCategory: null,
|
||||
resolutionGuidance: null,
|
||||
},
|
||||
structuralActionRequired: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors.join(" ")).toContain(
|
||||
"selectedQuestion must be a single non-compound question",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user