fix: stabilise multi-turn question progression

This commit is contained in:
2026-08-03 13:55:39 +01:00
parent 34c25fcb43
commit fe6a9925cb
4 changed files with 452 additions and 16 deletions
+153
View File
@@ -1443,6 +1443,159 @@ describe("applyValidatedProposal", () => {
expect(secondResult.compatibilityFailures).toEqual([]);
});
it("reselects a non-repeated comparison sibling instead of asking the same evidence question again", () => {
const { graph, proposal } = makeComparabilityUpdateFixture();
const firstResult = applyValidatedProposal({
situationGraph: graph,
proposal,
previousQuestion:
"Were these figures measured on the same basis and at the same scale?",
answer:
"Yes. Both figures cover the same accounting period and are taken from the same management accounts.",
});
expect(firstResult.success).toBe(true);
expect(firstResult.selectedQuestion?.question).toBe(
"What evidence would clarify how the two observations were measured?",
);
const secondResult = applyValidatedProposal({
situationGraph: firstResult.updatedSituationGraph,
proposal: {
addedNodes: [],
updatedNodes: [
{
nodeId: firstResult.selectedQuestion.nodeId,
previousStatus: "unknown",
newStatus: "resolved",
previousValue: null,
newValue:
"Both measures come from the same monthly reporting pack and use the same source system.",
reason: "The answer resolves the measurement clarification child.",
},
],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: [firstResult.selectedQuestion.nodeId],
affectedNodeIds: [],
selectedQuestion: null,
},
previousQuestion: firstResult.selectedQuestion.question,
answer:
"Both measures come from the same monthly reporting pack and use the same source system.",
});
expect(secondResult.success).toBe(true);
expect(secondResult.selectedQuestion?.nodeId).not.toBe(
firstResult.selectedQuestion.nodeId,
);
expect(secondResult.selectedQuestion?.question).not.toBe(
firstResult.selectedQuestion.question,
);
expect(secondResult.finalQuestion).not.toBe(
firstResult.selectedQuestion.question,
);
expect(secondResult.noQuestionReason).toBeNull();
expect(secondResult.selectedQuestion?.nodeId).toBe(
secondResult.newActiveUnknownNodeId,
);
});
it("rejects a compound selected question before returning it", () => {
const graph = makeCommercialUpdateFixture();
const result = applyValidatedProposal({
situationGraph: graph,
proposal: {
...makeMeaningfulNoOpProposal(),
selectedQuestion: {
nodeId: "n-commercial-parent",
question:
"What changed during the period that could explain why work is taking longer, and how were the two observations measured?",
reason: "Invalid compound follow-up.",
},
},
});
expect(result.success).toBe(false);
expect(result.stage).toBe("proposal_compatibility");
expect(result.errors).toContain(
"selectedQuestion must be a single non-compound question",
);
});
it("allows a legitimate single-concept or-question", () => {
const graph = makeCommercialUpdateFixture();
const result = applyValidatedProposal({
situationGraph: graph,
proposal: {
...makeMeaningfulNoOpProposal(),
selectedQuestion: {
nodeId: "n-commercial-parent",
question: "Is the problem caused by timing or measurement basis?",
reason: "Single concept contrast.",
},
},
});
expect(result.success).toBe(true);
});
it("returns no question when the graph is truly complete after resolution", () => {
const graph = makeGraph({
centralStatement: "A single missing fact needs confirmation.",
nodes: [
makeNode({
id: "n-only-unknown",
label: "Missing fact",
description:
"Need the missing fact because the conclusion depends on it.",
kind: "unknown",
status: "unknown",
confidence: "high",
}),
],
edges: [],
activeUnknownNodeId: "n-only-unknown",
resolvedNodeIds: [],
currentSummary: "Single unknown fixture",
});
const result = applyValidatedProposal({
situationGraph: graph,
proposal: {
addedNodes: [],
updatedNodes: [
{
nodeId: "n-only-unknown",
previousStatus: "unknown",
newStatus: "resolved",
previousValue: null,
newValue: "Confirmed.",
reason: "The answer resolves the only unknown.",
},
],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: ["n-only-unknown"],
affectedNodeIds: [],
selectedQuestion: null,
},
previousQuestion: "What is the missing fact?",
answer: "Confirmed.",
});
expect(result.success).toBe(true);
expect(result.selectedQuestion).toBeNull();
expect(result.finalQuestion).toBeNull();
expect(result.newActiveUnknownNodeId).toBeNull();
expect(result.noQuestionReason).toBe(
"No unresolved unknown candidates remain after this update.",
);
});
it("does not allow a decision-mode active unknown to remain a comparison child", () => {
const graph = makeCommercialUpdateFixture();
graph.nodes.push(