fix(reasoning): preserve investigation ownership across selection and question rejection

This commit is contained in:
2026-08-17 17:58:45 +01:00
parent d908f3746d
commit 772ae495c6
7 changed files with 850 additions and 4429 deletions
+13 -12
View File
@@ -2871,6 +2871,9 @@ function reseatSelectionAfterQuestionRejection({
: { status: "none", nodeId: null };
}
const QUESTION_FORMULATION_REJECTION_NO_QUESTION_REASON =
"The selected investigation target remains active, but its current graph-backed question formulation was rejected as too complex.";
export function determineGraphBackedQuestion({ situationGraph }) {
const graphSnapshot = cloneJsonSafe(situationGraph);
let updatedSituationGraph = cloneJsonSafe(situationGraph);
@@ -2937,15 +2940,12 @@ export function determineGraphBackedQuestion({ situationGraph }) {
initialQuestionRejected &&
deterministicSelection?.status === "selected"
) {
deterministicSelection = reseatSelectionAfterQuestionRejection({
graph: updatedSituationGraph,
deterministicSelection,
excludedNodeIds: [deterministicSelection.nodeId],
});
questionResult = buildSelectedQuestionResult({
updatedSituationGraph,
deterministicSelection,
});
questionResult = {
...questionResult,
selectedQuestion: null,
questionSuppressedReason:
QUESTION_FORMULATION_REJECTION_NO_QUESTION_REASON,
};
}
if (
@@ -2971,14 +2971,15 @@ export function determineGraphBackedQuestion({ situationGraph }) {
? questionResult.selectedQuestion?.questionSuppressedReason ||
questionResult.selectedQuestion?.reason ||
"Eligible unresolved candidates remain tied after initial graph-backed selection."
: (updatedSituationGraph.nodes || []).some(
: questionResult.questionSuppressedReason ||
((updatedSituationGraph.nodes || []).some(
(node) =>
node.kind === "unknown" &&
!["resolved", "contradicted"].includes(node.status) &&
!(updatedSituationGraph.resolvedNodeIds || []).includes(node.id),
)
? "Compatible unresolved candidates remain, but none produced a valid graph-backed question."
: "No unresolved unknown candidates remain after initial graph construction.";
? "Compatible unresolved candidates remain, but none produced a valid graph-backed question."
: "No unresolved unknown candidates remain after initial graph construction.");
return {
success: true,
+15 -5
View File
@@ -249,7 +249,7 @@ function semanticSignature(candidate) {
);
}
function classifyCandidateOrdering(candidates) {
function classifyCandidateOrdering(candidates, activeNodeId = null) {
const displayOrder = buildCandidateDisplayOrder(candidates);
const best = displayOrder[0] ?? null;
if (!best) {
@@ -319,15 +319,21 @@ function classifyCandidateOrdering(candidates) {
}
if (topStructuralCandidates.length > 0) {
const activeTiedCandidate = activeNodeId
? topStructuralCandidates.find((candidate) => candidate.nodeId === activeNodeId)
: null;
return {
displayOrder,
best,
best: activeTiedCandidate || best,
leadingCandidates: topStructuralCandidates,
status: "selected",
tieType: "complete_unresolved_tie",
usedAlphabeticalOrdering: true,
usedAlphabeticalOrdering: activeTiedCandidate ? false : true,
reason:
"Leading candidates remained tied after score, structural, and semantic checks, so the stable deterministic display order was used as the final fallback.",
activeTiedCandidate
? "Leading candidates remained tied after score, structural, and semantic checks, so existing active investigation ownership was preserved."
: "Leading candidates remained tied after score, structural, and semantic checks, so the stable deterministic display order was used as the final fallback.",
};
}
@@ -624,6 +630,7 @@ export function selectActiveUnknownCandidate(graph, resolvedNodeIds) {
...candidate,
node,
})),
graph?.activeUnknownNodeId ?? null,
);
if (selection.status === "ambiguous") {
@@ -690,7 +697,10 @@ export function explainUnknownSelection(graph, resolvedNodeIds = []) {
...scoreUnknownCandidate(graph, node, resolvedNodeIds),
}));
const selection = classifyCandidateOrdering(candidates);
const selection = classifyCandidateOrdering(
candidates,
graph?.activeUnknownNodeId ?? null,
);
const orderedCandidates = selection.displayOrder;
const selected = selection.best;
const competitors = orderedCandidates