Feature/product platform foundation v0.62 #1
@@ -58,6 +58,64 @@ function normaliseText(value) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
// ── 60B.64 — raw-answer explicit sufficiency confirmation ──────────
|
||||
|
||||
const CONTRADICTION_PHRASES = [
|
||||
/\bam not\b/i,
|
||||
/\bnot (?:saying|claiming|asserting)\b/i,
|
||||
/still \w+ material/i,
|
||||
];
|
||||
|
||||
const CONFIRMATION_PHRASES = [
|
||||
"no other material uncertainty remains",
|
||||
"no other material uncertainties remain",
|
||||
"no further material uncertainty remains",
|
||||
"no further material uncertainties remain",
|
||||
"no remaining material uncertainty",
|
||||
"no remaining material uncertainties",
|
||||
"no remaining material difference",
|
||||
"no remaining material differences",
|
||||
"nothing else material is uncertain",
|
||||
"nothing else material remains uncertain",
|
||||
];
|
||||
|
||||
const CONFIRMATION_PATTERNS = [
|
||||
/\bno (?:other|further) material \w+?(?:\s+between\b)/i,
|
||||
/\bthe\s+\w+\s+is\s+(?:complete|resolved|closed|settled)\s*(?:now|already)?/i,
|
||||
];
|
||||
|
||||
/**
|
||||
* Deterministic raw-answer confirmation that no other material
|
||||
* uncertainty remains after a decision factor has been resolved.
|
||||
*
|
||||
* Returns true only when the raw user answer directly states
|
||||
* sufficiency using a bounded explicit phrase family.
|
||||
*
|
||||
* Does NOT use: model-generated meaning, node reason text, or NLP.
|
||||
*/
|
||||
export function isUserConfirmationOfNoRemainingUncertainty(answer) {
|
||||
if (!answer || typeof answer !== "string") return false;
|
||||
|
||||
const lower = answer.toLowerCase();
|
||||
|
||||
// Reject contradictory wording first
|
||||
for (const phrase of CONTRADICTION_PHRASES) {
|
||||
if (phrase.test(lower)) return false;
|
||||
}
|
||||
|
||||
// Check explicit confirmation phrases
|
||||
for (const phrase of CONFIRMATION_PHRASES) {
|
||||
if (lower.includes(phrase)) return true;
|
||||
}
|
||||
|
||||
// Check bounded regex patterns
|
||||
for (const pattern of CONFIRMATION_PATTERNS) {
|
||||
if (pattern.test(lower)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildNodeById(graph, addedNodes = []) {
|
||||
return new Map(
|
||||
[...graph.nodes, ...addedNodes].map((node) => [node.id, node]),
|
||||
@@ -3773,6 +3831,158 @@ export function applyValidatedProposal({
|
||||
reasoningResolution.reasoningStateOverride,
|
||||
);
|
||||
updatedSituationGraph.reasoningState = nextReasoningState;
|
||||
|
||||
// ── 60B.64 — explicit decision-sufficiency closure ───────────────
|
||||
// Integrate after post-mutation / post-propagation and before
|
||||
// final active-target / selectedQuestion selection.
|
||||
//
|
||||
// When all represented material factors are resolved AND the raw
|
||||
// user answer explicitly confirms no further material uncertainty,
|
||||
// resolve the existing parent decision in place.
|
||||
|
||||
let closureApplied = false;
|
||||
|
||||
// Build a virtual "resolved this turn" set — at this point node statuses
|
||||
// in updatedSituationGraph have NOT been reconciled yet, so we must
|
||||
// derive what is resolved from proposalSnapshot instead of reading graph.
|
||||
const pendingResolvedIds = new Set([
|
||||
...proposalSnapshot.resolvedUnknownNodeIds,
|
||||
...proposalSnapshot.updatedNodes
|
||||
.filter((u) => u.newStatus === "resolved")
|
||||
.map((u) => u.nodeId),
|
||||
]);
|
||||
|
||||
function checkRemainingFactorsVirtual(decisionNodeId) {
|
||||
const nodes = updatedSituationGraph.nodes || [];
|
||||
const edges = updatedSituationGraph.edges || [];
|
||||
const nodesById = new Map(nodes.map((n) => [n.id, n]));
|
||||
|
||||
const decisionNode = nodesById.get(decisionNodeId);
|
||||
if (!decisionNode) return 0;
|
||||
|
||||
// Collect all option IDs that belong to this decision via contained_in
|
||||
const decisionOptionIds = new Set();
|
||||
for (const edge of edges) {
|
||||
if (
|
||||
edge.relationship === "contained_in" &&
|
||||
edge.toNodeId === decisionNodeId
|
||||
) {
|
||||
decisionOptionIds.add(edge.fromNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
function getAncestorNode(nodeId, depth = 0) {
|
||||
if (depth > 50) return null;
|
||||
const n = nodesById.get(nodeId);
|
||||
if (!n?.parentId) return null;
|
||||
return nodesById.get(n.parentId) ?? null;
|
||||
}
|
||||
|
||||
function isVirtualUnresolvedUnknown(candidateNode) {
|
||||
// A node is "virtually unresolved" only if it hasn't been resolved this turn
|
||||
// and its current graph status isn't terminal.
|
||||
if (pendingResolvedIds.has(candidateNode.id)) return false;
|
||||
if (!TERMINAL_STATUSES.includes(candidateNode.status)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const materialFactorIds = new Set();
|
||||
|
||||
for (const node of nodes) {
|
||||
if (node.id === decisionNodeId || !isVirtualUnresolvedUnknown(node)) continue;
|
||||
let currentParent = getAncestorNode(node.id);
|
||||
while (currentParent) {
|
||||
if (currentParent.id === decisionNodeId) {
|
||||
materialFactorIds.add(node.id);
|
||||
break;
|
||||
}
|
||||
currentParent = getAncestorNode(currentParent.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const childId of decisionNode.childIds || []) {
|
||||
const childNode = nodesById.get(childId);
|
||||
if (childNode && isVirtualUnresolvedUnknown(childNode)) {
|
||||
materialFactorIds.add(childId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const edge of edges) {
|
||||
if (edge.toNodeId !== decisionNodeId || edge.relationship !== "depends_on") continue;
|
||||
const source = nodesById.get(edge.fromNodeId);
|
||||
if (source && isVirtualUnresolvedUnknown(source)) {
|
||||
materialFactorIds.add(edge.fromNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const edge of edges) {
|
||||
if (edge.relationship !== "affects" && edge.relationship !== "may_cause" && edge.relationship !== "causes") continue;
|
||||
const source = nodesById.get(edge.fromNodeId);
|
||||
const targetOptionId = edge.toNodeId;
|
||||
if (!source || !isVirtualUnresolvedUnknown(source) || !decisionOptionIds.has(targetOptionId)) continue;
|
||||
materialFactorIds.add(edge.fromNodeId);
|
||||
}
|
||||
|
||||
for (const edge of edges) {
|
||||
if (edge.relationship !== "contained_in") continue;
|
||||
const fromNode = nodesById.get(edge.fromNodeId);
|
||||
if (!fromNode || !isVirtualUnresolvedUnknown(fromNode)) continue;
|
||||
for (const innerEdge of edges) {
|
||||
if (
|
||||
innerEdge.relationship === "contained_in" &&
|
||||
innerEdge.fromNodeId === edge.toNodeId &&
|
||||
innerEdge.toNodeId === decisionNodeId
|
||||
) {
|
||||
materialFactorIds.add(edge.fromNodeId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return materialFactorIds.size;
|
||||
}
|
||||
|
||||
for (const parentNode of updatedSituationGraph.nodes || []) {
|
||||
if (parentNode.kind !== "unknown") continue;
|
||||
if (TERMINAL_STATUSES.includes(parentNode.status)) continue;
|
||||
if (!(updatedSituationGraph.edges || []).some(
|
||||
(e) => e.relationship === "contained_in" && e.toNodeId === parentNode.id,
|
||||
)) continue;
|
||||
|
||||
const remaining = checkRemainingFactorsVirtual(parentNode.id);
|
||||
const explicitConfirmation = isUserConfirmationOfNoRemainingUncertainty(answer);
|
||||
|
||||
if (remaining === 0 && explicitConfirmation) {
|
||||
parentNode.status = "resolved";
|
||||
ensureResolvedUnknownId(proposalSnapshot, parentNode.id);
|
||||
|
||||
const existingUpdate = proposalSnapshot.updatedNodes.find(
|
||||
(u) => u.nodeId === parentNode.id,
|
||||
);
|
||||
if (!existingUpdate) {
|
||||
proposalSnapshot.updatedNodes.push({
|
||||
nodeId: parentNode.id,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: parentNode.value ?? null,
|
||||
newValue: parentNode.value ?? null,
|
||||
reason:
|
||||
"All represented material factors resolved and raw user answer explicitly confirmed no further material uncertainty remains.",
|
||||
});
|
||||
} else {
|
||||
existingUpdate.newStatus = "resolved";
|
||||
if (existingUpdate.previousStatus == null) {
|
||||
existingUpdate.previousStatus = "unknown";
|
||||
}
|
||||
if (existingUpdate.previousValue === undefined) {
|
||||
existingUpdate.previousValue = parentNode.value ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
closureApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedCurrentTurnNodeIds = [
|
||||
...new Set(proposalSnapshot.resolvedUnknownNodeIds || []),
|
||||
];
|
||||
|
||||
@@ -5362,3 +5362,423 @@ describe("60B.61 — decision remaining-material-factor detection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── 60B.64 — explicit decision sufficiency closure ────────────────
|
||||
|
||||
describe("60B.64 — explicit decision sufficiency closure", () => {
|
||||
function makeClosureDecisionFixture({ includeFallbackUnknown = false } = {}) {
|
||||
const productLaunchDecision = 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 provides superior net value for the organisation.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
const launchThisYear = makeNode({
|
||||
id: "opt_launch_this_year",
|
||||
label: "Launch this year",
|
||||
description: "Launch the new software product this year.",
|
||||
kind: "option",
|
||||
status: "known",
|
||||
confidence: "high",
|
||||
});
|
||||
const waitTwelveMonths = makeNode({
|
||||
id: "opt_wait_twelve_months",
|
||||
label: "Wait twelve months",
|
||||
description: "Wait twelve months before launching the product.",
|
||||
kind: "option",
|
||||
status: "known",
|
||||
confidence: "high",
|
||||
});
|
||||
const enterpriseCustomerSigning = makeNode({
|
||||
id: "n_enterprise_customer_signing",
|
||||
label: "Prospective enterprise customer signing status",
|
||||
description:
|
||||
"Unknown whether one prospective enterprise customer will sign if we launch this year, because they account for approximately £700,000 of the £1.2 million expected annual revenue.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const nodes = [
|
||||
productLaunchDecision,
|
||||
launchThisYear,
|
||||
waitTwelveMonths,
|
||||
enterpriseCustomerSigning,
|
||||
];
|
||||
const edges = [
|
||||
makeEdge({
|
||||
id: "e-opt-launch-to-dec",
|
||||
fromNodeId: launchThisYear.id,
|
||||
toNodeId: productLaunchDecision.id,
|
||||
relationship: "contained_in",
|
||||
confidence: "high",
|
||||
description: "Launch this year option is a candidate for the product launch decision.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-opt-wait-to-dec",
|
||||
fromNodeId: waitTwelveMonths.id,
|
||||
toNodeId: productLaunchDecision.id,
|
||||
relationship: "contained_in",
|
||||
confidence: "high",
|
||||
description: "Wait twelve months option is a candidate for the product launch decision.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-customer-signing-to-launch-option",
|
||||
fromNodeId: enterpriseCustomerSigning.id,
|
||||
toNodeId: launchThisYear.id,
|
||||
relationship: "contained_in",
|
||||
confidence: "high",
|
||||
description: "Customer signing status is material to launching this year.",
|
||||
}),
|
||||
];
|
||||
|
||||
if (includeFallbackUnknown) {
|
||||
const fallbackUnknown = makeNode({
|
||||
id: "n_other_market_evidence",
|
||||
label: "Other market evidence gap",
|
||||
description:
|
||||
"Need other market evidence because the remaining launch case still depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
});
|
||||
nodes.push(fallbackUnknown);
|
||||
edges.push(
|
||||
makeEdge({
|
||||
id: "e-fallback-to-launch-option",
|
||||
fromNodeId: fallbackUnknown.id,
|
||||
toNodeId: launchThisYear.id,
|
||||
relationship: "may_cause",
|
||||
confidence: "medium",
|
||||
description: "Fallback unresolved evidence remains material to launch timing.",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement:
|
||||
"We are evaluating two product-launch timing options: launching the new software product this year or waiting twelve months.",
|
||||
nodes,
|
||||
edges,
|
||||
activeUnknownNodeId: enterpriseCustomerSigning.id,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary:
|
||||
"Customer signing is the active material uncertainty in the product-launch decision.",
|
||||
});
|
||||
|
||||
return {
|
||||
graph,
|
||||
ids: {
|
||||
productLaunchDecision: productLaunchDecision.id,
|
||||
launchThisYear: launchThisYear.id,
|
||||
waitTwelveMonths: waitTwelveMonths.id,
|
||||
enterpriseCustomerSigning: enterpriseCustomerSigning.id,
|
||||
fallbackUnknown: includeFallbackUnknown
|
||||
? "n_other_market_evidence"
|
||||
: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ── Test 1 — exact 60B.56 negative closure (raw confirmation present) ──
|
||||
it("Test 1 — exact 60B.56 wording closes: customer factor resolved, decision = resolved", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue:
|
||||
"No. The enterprise customer has now confirmed in writing that they will not sign if we launch this year, so the £700,000 of expected annual revenue from them will not be received.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer:
|
||||
"There are no other material uncertainties between launching this year and waiting twelve months.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const customerFactor = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.enterpriseCustomerSigning,
|
||||
);
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(customerFactor?.status).toBe("resolved");
|
||||
expect(decision?.status).toBe("resolved");
|
||||
expect(result.updatedSituationGraph.activeUnknownNodeId).toBeNull();
|
||||
expect(result.selectedQuestion).toBeNull();
|
||||
|
||||
// Decision identity preserved — no new nodes/edges added
|
||||
expect(result.updatedSituationGraph.nodes).toHaveLength(4);
|
||||
expect(result.updatedSituationGraph.edges).toHaveLength(3);
|
||||
});
|
||||
|
||||
// ── Test 2 — positive-equivalent closure preserved ──
|
||||
it("Test 2 — bounded paraphrase confirms and closes the decision", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed for launch this year.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer:
|
||||
"no remaining material uncertainty exists between the options.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).toBe("resolved");
|
||||
expect(result.updatedSituationGraph.activeUnknownNodeId).toBeNull();
|
||||
expect(result.selectedQuestion).toBeNull();
|
||||
});
|
||||
|
||||
// ── Test 3 — last factor resolves but no confirmation ──
|
||||
it("Test 3 — without explicit raw confirmation decision remains open", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed for launch this year.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer: "The customer has confirmed they will sign.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).not.toBe("resolved");
|
||||
});
|
||||
|
||||
// ── Test 4 — another genuine factor remains ──
|
||||
it("Test 4 — explicit confirmation but remaining factor blocks closure", () => {
|
||||
const { graph: baseGraph, ids } = makeClosureDecisionFixture({
|
||||
includeFallbackUnknown: true,
|
||||
});
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: baseGraph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer:
|
||||
"There are no other material uncertainties between launching this year and waiting twelve months.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
// Should NOT close because fallback unknown remains unresolved
|
||||
expect(decision?.status).not.toBe("resolved");
|
||||
});
|
||||
|
||||
// ── Test 5 — contradictory confirmation wording rejected ──
|
||||
it("Test 5 — contradictory wording rejects closure", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer: "There is still another material uncertainty.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).not.toBe("resolved");
|
||||
});
|
||||
|
||||
// ── Test 6 — negated phrase rejected ──
|
||||
it("Test 6 — negated phrase rejects confirmation", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer: "I am not saying there are no other material uncertainties.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).not.toBe("resolved");
|
||||
});
|
||||
|
||||
// ── Test 7 — bounded supported paraphrase accepted ──
|
||||
it("Test 7 — narrow equivalent phrase confirms sufficiency", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer: "No remaining material uncertainty exists between the options.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).toBe("resolved");
|
||||
});
|
||||
|
||||
// ── Test 8 — vague completion language excluded ──
|
||||
it("Test 8 — vague phrases like 'That's it' do not confirm", () => {
|
||||
const { graph, ids } = makeClosureDecisionFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.enterpriseCustomerSigning,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Customer signing confirmed.",
|
||||
reason: "The active customer-signing unknown is resolved.",
|
||||
},
|
||||
],
|
||||
addedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||
affectedNodeIds: [ids.productLaunchDecision],
|
||||
},
|
||||
answer: "That's it.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const decision = result.updatedSituationGraph.nodes.find(
|
||||
(n) => n.id === ids.productLaunchDecision,
|
||||
);
|
||||
|
||||
expect(decision?.status).not.toBe("resolved");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user