fix(reasoning): enforce terminal post-mutation eligibility
This commit is contained in:
@@ -234,6 +234,14 @@ Experiment 54N tested whether an interpretation disagreement can be judged for m
|
|||||||
- Focused preservation run passed via `npx vitest run tests/graph/apply-proposal.test.js -t "60B.42|60B.11|replaces downstream pricing"`.
|
- Focused preservation run passed via `npx vitest run tests/graph/apply-proposal.test.js -t "60B.42|60B.11|replaces downstream pricing"`.
|
||||||
- Broader duplicated post-mutation eligibility checks remain unchanged; this fixes the selector contract only, not the full stale-question lifecycle.
|
- Broader duplicated post-mutation eligibility checks remain unchanged; this fixes the selector contract only, not the full stale-question lifecycle.
|
||||||
|
|
||||||
|
### 60B.43 terminal post-mutation eligibility
|
||||||
|
|
||||||
|
- Extended terminal-status exclusion across the remaining post-mutation eligibility checks in `lib/graph/apply-proposal.js` while leaving pre-mutation `validateSelectedQuestion(...)` unchanged.
|
||||||
|
- `isSelectableUnresolvedUnknown(...)`, `remainingUnknownExists`, carried-active reuse, unresolved/eligible candidate listing, and pattern-compatible post-mutation selection now all treat `known|resolved|contradicted` as terminal/non-selectable.
|
||||||
|
- Deterministic 60B.37-shaped closure regression now passes: proposal applies, `n_enterprise_customer_signing` resolves, `n_product_launch_decision` becomes known, `activeUnknownNodeId` becomes null, and final `selectedQuestion` becomes null.
|
||||||
|
- Focused fallback case also passes: when the proposal-selected target becomes known but another real unresolved unknown remains, the terminal target is discarded and the real unresolved node is selected.
|
||||||
|
- Focused preservation run passed via `npx vitest run tests/graph/apply-proposal.test.js -t "60B.43|60B.11|replaces downstream pricing"`; live rerun still required to prove the same outcome end-to-end under live conditions.
|
||||||
|
|
||||||
### When This Knowledge-Management Phase Is Complete
|
### When This Knowledge-Management Phase Is Complete
|
||||||
|
|
||||||
Provisional criteria for review (all confirmed met by Experiment 38 cold-start test):
|
Provisional criteria for review (all confirmed met by Experiment 38 cold-start test):
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Experiment 60B.43 — Terminal post-mutation eligibility
|
||||||
|
|
||||||
|
**Date:** 2026-08-14
|
||||||
|
**Branch:** `feature/post-mutation-terminal-eligibility-v0.38`
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Extend the 60B.42 selector guard to the remaining post-mutation question-target eligibility checks so terminal-status unknown nodes cannot remain active or become the final selected question after mutation.
|
||||||
|
|
||||||
|
## Starting point from 60B.42
|
||||||
|
|
||||||
|
60B.42 fixed `selectActiveUnknownCandidate(...)` so it no longer scores or returns unknown-kind nodes whose status is terminal:
|
||||||
|
|
||||||
|
- `known`
|
||||||
|
- `resolved`
|
||||||
|
- `contradicted`
|
||||||
|
|
||||||
|
That closed one fallback source, but several independent post-mutation checks in `apply-proposal.js` still used weaker eligibility rules and could keep terminal nodes alive through other paths.
|
||||||
|
|
||||||
|
## Remaining post-mutation eligibility changes
|
||||||
|
|
||||||
|
This experiment changed post-mutation eligibility only in `lib/graph/apply-proposal.js`.
|
||||||
|
|
||||||
|
Updated paths:
|
||||||
|
|
||||||
|
- `isSelectableUnresolvedUnknown(...)`
|
||||||
|
- `remainingUnknownExists`
|
||||||
|
- `carriedActiveUnknownStillUnresolved`
|
||||||
|
- `listUnresolvedUnknownCandidates(...)`
|
||||||
|
- `listEligibleUnknownCandidates(...)`
|
||||||
|
- `selectPatternCompatibleUnknownCandidate(...)`
|
||||||
|
|
||||||
|
### Terminal-status rule applied post-mutation
|
||||||
|
|
||||||
|
A selectable unresolved post-mutation target now requires:
|
||||||
|
|
||||||
|
```text
|
||||||
|
kind === unknown
|
||||||
|
status NOT IN [known, resolved, contradicted]
|
||||||
|
not in resolvedNodeIds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important boundary preserved
|
||||||
|
|
||||||
|
`validateSelectedQuestion(...)` was **not** changed.
|
||||||
|
|
||||||
|
The closure proposal remains valid pre-mutation even when:
|
||||||
|
|
||||||
|
- `selectedQuestion.nodeId = n_product_launch_decision`
|
||||||
|
- the same proposal updates `n_product_launch_decision -> known`
|
||||||
|
|
||||||
|
Only after mutation is that now-terminal target discarded.
|
||||||
|
|
||||||
|
## 60B.37 deterministic regression
|
||||||
|
|
||||||
|
Added focused regression:
|
||||||
|
|
||||||
|
- `60B.43 — terminal post-mutation target is cleared after valid decision closure`
|
||||||
|
|
||||||
|
Reproduced the 60B.37-shaped closure:
|
||||||
|
|
||||||
|
- existing active unknown: `n_enterprise_customer_signing`
|
||||||
|
- proposal resolves `n_enterprise_customer_signing`
|
||||||
|
- proposal updates `n_product_launch_decision -> known`
|
||||||
|
- proposal still selects `n_product_launch_decision`
|
||||||
|
- no added nodes or edges
|
||||||
|
|
||||||
|
### Result
|
||||||
|
|
||||||
|
- proposal applied successfully
|
||||||
|
- customer factor resolved in place
|
||||||
|
- decision became known in place
|
||||||
|
- both options preserved unchanged
|
||||||
|
- `activeUnknownNodeId = null`
|
||||||
|
- final `selectedQuestion = null`
|
||||||
|
|
||||||
|
## Fallback-to-real-unknown result
|
||||||
|
|
||||||
|
Added a second focused case where:
|
||||||
|
|
||||||
|
- proposal-selected target becomes `known`
|
||||||
|
- another genuine unresolved unknown remains after mutation
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
- terminal known target discarded
|
||||||
|
- remaining genuine unresolved candidate selected
|
||||||
|
|
||||||
|
## Known-only post-mutation result
|
||||||
|
|
||||||
|
Added a valid mutated case where the final remaining unknown becomes `known` in the same mutation.
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
- `activeUnknownNodeId = null`
|
||||||
|
- `selectedQuestion = null`
|
||||||
|
|
||||||
|
## Preservation checks
|
||||||
|
|
||||||
|
Focused run also preserved:
|
||||||
|
|
||||||
|
- 60B.11 preferred-target behaviour
|
||||||
|
- pricing prerequisite-first behaviour
|
||||||
|
- resolved exclusion
|
||||||
|
- contradicted exclusion
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Command run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitest run tests/graph/apply-proposal.test.js -t "60B.43|60B.11|replaces downstream pricing"
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
- PASS — `14 passed | 76 skipped`
|
||||||
|
|
||||||
|
## What remains unproven until live rerun
|
||||||
|
|
||||||
|
This deterministic bounded fix now clears the exact 60B.37-shaped stale target through the post-mutation production path under test.
|
||||||
|
|
||||||
|
What remains unproven until a live rerun is whether the full runtime/orchestration path with the real customer-signing closure answer produces the same null-question closure end state under live conditions.
|
||||||
@@ -1660,7 +1660,7 @@ function isSelectableUnresolvedUnknown(graph, nodeId) {
|
|||||||
return Boolean(
|
return Boolean(
|
||||||
node &&
|
node &&
|
||||||
node.kind === "unknown" &&
|
node.kind === "unknown" &&
|
||||||
!["resolved", "contradicted"].includes(node.status) &&
|
!["known", "resolved", "contradicted"].includes(node.status) &&
|
||||||
!(graph.resolvedNodeIds || []).includes(node.id),
|
!(graph.resolvedNodeIds || []).includes(node.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1673,9 +1673,7 @@ function listUnresolvedUnknownCandidates(
|
|||||||
|
|
||||||
return (graph.nodes || []).filter(
|
return (graph.nodes || []).filter(
|
||||||
(node) =>
|
(node) =>
|
||||||
node.kind === "unknown" &&
|
isSelectableUnresolvedUnknown(graph, node.id) &&
|
||||||
!["resolved", "contradicted"].includes(node.status) &&
|
|
||||||
!(graph.resolvedNodeIds || []).includes(node.id) &&
|
|
||||||
!resolvedCurrentTurnSet.has(node.id),
|
!resolvedCurrentTurnSet.has(node.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2183,7 +2181,7 @@ function selectPatternCompatibleUnknownCandidate({
|
|||||||
(node) =>
|
(node) =>
|
||||||
node.kind === "unknown" &&
|
node.kind === "unknown" &&
|
||||||
!excluded.has(node.id) &&
|
!excluded.has(node.id) &&
|
||||||
!["resolved", "contradicted"].includes(node.status),
|
isSelectableUnresolvedUnknown(graph, node.id),
|
||||||
)
|
)
|
||||||
.filter(
|
.filter(
|
||||||
(node) =>
|
(node) =>
|
||||||
@@ -3704,11 +3702,9 @@ export function applyValidatedProposal({
|
|||||||
|
|
||||||
const remainingUnknownExists =
|
const remainingUnknownExists =
|
||||||
newActiveUnknownNodeId != null &&
|
newActiveUnknownNodeId != null &&
|
||||||
updatedSituationGraph.nodes.some(
|
isSelectableUnresolvedUnknown(
|
||||||
(node) =>
|
updatedSituationGraph,
|
||||||
node.id === newActiveUnknownNodeId &&
|
newActiveUnknownNodeId,
|
||||||
node.kind === "unknown" &&
|
|
||||||
!updatedSituationGraph.resolvedNodeIds.includes(node.id),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!remainingUnknownExists) {
|
if (!remainingUnknownExists) {
|
||||||
@@ -3846,9 +3842,8 @@ export function applyValidatedProposal({
|
|||||||
: null;
|
: null;
|
||||||
const carriedActiveUnknownStillUnresolved = Boolean(
|
const carriedActiveUnknownStillUnresolved = Boolean(
|
||||||
carriedActiveUnknownNode &&
|
carriedActiveUnknownNode &&
|
||||||
carriedActiveUnknownNode.kind === "unknown" &&
|
isSelectableUnresolvedUnknown(
|
||||||
!["resolved", "contradicted"].includes(carriedActiveUnknownNode.status) &&
|
updatedSituationGraph,
|
||||||
!updatedSituationGraph.resolvedNodeIds.includes(
|
|
||||||
carriedActiveUnknownNode.id,
|
carriedActiveUnknownNode.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4384,3 +4384,278 @@ describe("60B.11 — prerequisite-aware question targeting", () => {
|
|||||||
// The node has no depends_on prerequisites, so it passes the guard
|
// The node has no depends_on prerequisites, so it passes the guard
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("60B.43 — terminal post-mutation target is cleared after valid decision closure", () => {
|
||||||
|
function makeProductLaunchClosureFixture({ 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("accepts the 60B.37-shaped closure proposal, resolves the customer factor, and clears the terminal decision target after mutation", () => {
|
||||||
|
const { graph, ids } = makeProductLaunchClosureFixture();
|
||||||
|
|
||||||
|
const result = applyValidatedProposal({
|
||||||
|
situationGraph: graph,
|
||||||
|
proposal: {
|
||||||
|
updatedNodes: [
|
||||||
|
{
|
||||||
|
nodeId: ids.enterpriseCustomerSigning,
|
||||||
|
previousStatus: "unknown",
|
||||||
|
newStatus: "resolved",
|
||||||
|
previousValue: null,
|
||||||
|
newValue:
|
||||||
|
"Prospective enterprise customer confirmed they will sign if we launch this year.",
|
||||||
|
reason: "The user directly answered the active customer-signing unknown.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeId: ids.productLaunchDecision,
|
||||||
|
previousStatus: "unknown",
|
||||||
|
newStatus: "known",
|
||||||
|
previousValue: null,
|
||||||
|
newValue:
|
||||||
|
"Launching this year is now known to be the better-supported option.",
|
||||||
|
reason:
|
||||||
|
"With customer signing confirmed and no other material uncertainties remaining, the decision is closed.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedNodes: [],
|
||||||
|
addedEdges: [],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||||
|
affectedNodeIds: [ids.productLaunchDecision],
|
||||||
|
selectedQuestion: {
|
||||||
|
nodeId: ids.productLaunchDecision,
|
||||||
|
question: "What outcome would demonstrate enough value to justify launching?",
|
||||||
|
reason:
|
||||||
|
"Regression case: proposal still points at the decision node even though it becomes known in the same turn.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
const resolvedCustomer = result.updatedSituationGraph.nodes.find(
|
||||||
|
(node) => node.id === ids.enterpriseCustomerSigning,
|
||||||
|
);
|
||||||
|
const knownDecision = result.updatedSituationGraph.nodes.find(
|
||||||
|
(node) => node.id === ids.productLaunchDecision,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resolvedCustomer?.status).toBe("resolved");
|
||||||
|
expect(knownDecision?.status).toBe("known");
|
||||||
|
expect(result.updatedSituationGraph.nodes.filter((node) => node.id === ids.productLaunchDecision)).toHaveLength(1);
|
||||||
|
expect(result.updatedSituationGraph.nodes.filter((node) => node.id === ids.launchThisYear)).toHaveLength(1);
|
||||||
|
expect(result.updatedSituationGraph.nodes.filter((node) => node.id === ids.waitTwelveMonths)).toHaveLength(1);
|
||||||
|
expect(result.updatedSituationGraph.activeUnknownNodeId).toBeNull();
|
||||||
|
expect(result.selectedQuestion).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("discards a proposal-selected target that becomes known and falls back to another genuine unresolved candidate", () => {
|
||||||
|
const { graph, ids } = makeProductLaunchClosureFixture({
|
||||||
|
includeFallbackUnknown: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 uncertainty is resolved.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeId: ids.productLaunchDecision,
|
||||||
|
previousStatus: "unknown",
|
||||||
|
newStatus: "known",
|
||||||
|
previousValue: null,
|
||||||
|
newValue: "The decision node itself becomes known.",
|
||||||
|
reason: "The originally selected target closes during mutation.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedNodes: [],
|
||||||
|
addedEdges: [],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [ids.enterpriseCustomerSigning],
|
||||||
|
affectedNodeIds: [ids.productLaunchDecision],
|
||||||
|
selectedQuestion: {
|
||||||
|
nodeId: ids.productLaunchDecision,
|
||||||
|
question: "What outcome would demonstrate enough value to justify launching?",
|
||||||
|
reason: "This target becomes terminal and must be discarded post-mutation.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.updatedSituationGraph.activeUnknownNodeId).toBe(
|
||||||
|
ids.fallbackUnknown,
|
||||||
|
);
|
||||||
|
expect(result.selectedQuestion?.nodeId).toBe(ids.fallbackUnknown);
|
||||||
|
expect(result.selectedQuestion?.nodeId).not.toBe(ids.productLaunchDecision);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns no active target and no final question when only terminal unknowns remain after a valid mutation", () => {
|
||||||
|
const graph = makeGraph({
|
||||||
|
centralStatement: "Known-only terminal graph after mutation",
|
||||||
|
nodes: [
|
||||||
|
makeNode({
|
||||||
|
id: "n_known_terminal",
|
||||||
|
label: "Known terminal unknown",
|
||||||
|
description: "This unknown-kind node starts unresolved but becomes known.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "high",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
activeUnknownNodeId: "n_known_terminal",
|
||||||
|
resolvedNodeIds: [],
|
||||||
|
currentSummary: "A single unknown remains before mutation.",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = applyValidatedProposal({
|
||||||
|
situationGraph: graph,
|
||||||
|
proposal: {
|
||||||
|
updatedNodes: [
|
||||||
|
{
|
||||||
|
nodeId: "n_known_terminal",
|
||||||
|
previousStatus: "unknown",
|
||||||
|
newStatus: "known",
|
||||||
|
previousValue: null,
|
||||||
|
newValue: "The previously active unknown is now known.",
|
||||||
|
reason: "The mutation closes the final unknown.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedNodes: [],
|
||||||
|
addedEdges: [],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [],
|
||||||
|
affectedNodeIds: ["n_known_terminal"],
|
||||||
|
selectedQuestion: {
|
||||||
|
nodeId: "n_known_terminal",
|
||||||
|
question: "What remains to be clarified?",
|
||||||
|
reason: "This target becomes terminal in the same mutation and must be cleared post-mutation.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.updatedSituationGraph.activeUnknownNodeId).toBeNull();
|
||||||
|
expect(result.selectedQuestion).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user