feat: enforce reasoning pattern consistency
This commit is contained in:
+502
-17
@@ -7,6 +7,7 @@ import {
|
||||
COMPARABILITY_REASONING_NODE_ID,
|
||||
formulateQuestion,
|
||||
formulateTieResolutionQuestion,
|
||||
selectReasoningPattern,
|
||||
} from "./question-formulator.js";
|
||||
import {
|
||||
graphUpdateSchema,
|
||||
@@ -1727,12 +1728,280 @@ function selectedQuestionBelongsToChild(graph, selectedQuestion) {
|
||||
return Boolean(findNodeById(graph, selectedQuestion.nodeId)?.parentId);
|
||||
}
|
||||
|
||||
function selectDecompositionChildCandidate(graph, parentNodeId) {
|
||||
const childCandidates = findDirectChildUnknowns(graph, parentNodeId).filter(
|
||||
(node) =>
|
||||
node.kind === "unknown" &&
|
||||
!["resolved", "contradicted"].includes(node.status),
|
||||
const ALLOWED_NODE_PATTERNS_BY_ACTIVE_PATTERN = {
|
||||
decision: ["decision", "definition"],
|
||||
explanation: ["explanation", "comparison", "definition"],
|
||||
contradiction: ["contradiction", "comparison", "explanation", "definition"],
|
||||
definition: ["definition"],
|
||||
diagnosis: ["diagnosis", "comparison", "definition"],
|
||||
comparison: ["comparison", "definition"],
|
||||
prioritisation: ["prioritisation", "decision", "definition"],
|
||||
};
|
||||
|
||||
function determineActiveReasoningPattern(node, graph) {
|
||||
if (!node || !graph) {
|
||||
return {
|
||||
pattern: null,
|
||||
reason: "No active reasoning pattern could be determined.",
|
||||
};
|
||||
}
|
||||
|
||||
const nodesById = new Map((graph.nodes || []).map((item) => [item.id, item]));
|
||||
let currentParentId = node.parentId;
|
||||
while (currentParentId) {
|
||||
const parentNode = nodesById.get(currentParentId);
|
||||
if (!parentNode) break;
|
||||
const parentSelection = selectReasoningPattern({ node: parentNode, graph });
|
||||
if (parentSelection.pattern && parentSelection.pattern !== "definition") {
|
||||
return {
|
||||
pattern: parentSelection.pattern,
|
||||
reason: `Inherited active reasoning pattern from parent node because ${parentSelection.reason}`,
|
||||
};
|
||||
}
|
||||
currentParentId = parentNode.parentId;
|
||||
}
|
||||
|
||||
const selection = selectReasoningPattern({ node, graph });
|
||||
return {
|
||||
pattern: selection.pattern,
|
||||
reason: selection.reason,
|
||||
};
|
||||
}
|
||||
|
||||
function inferIntrinsicNodePattern(node, graph) {
|
||||
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
|
||||
const observationCount = (graph.nodes || []).filter(
|
||||
(candidate) =>
|
||||
candidate.kind === "observation" && candidate.status === "supported",
|
||||
).length;
|
||||
|
||||
if (
|
||||
/\b(define|definition|meaning|term|terminology|boundaries)\b/.test(text)
|
||||
) {
|
||||
return "definition";
|
||||
}
|
||||
|
||||
if (
|
||||
/\b(contradiction|contradict|conflict|inconsistent|mismatch|opposing)\b/.test(
|
||||
text,
|
||||
)
|
||||
) {
|
||||
return "contradiction";
|
||||
}
|
||||
|
||||
if (
|
||||
/\b(two observations|measured|measurement|basis|scale|same period|different timing|comparable)\b/.test(
|
||||
text,
|
||||
)
|
||||
) {
|
||||
return "comparison";
|
||||
}
|
||||
|
||||
if (
|
||||
observationCount >= 2 &&
|
||||
/\b(explain|explanation|what changed|difference between|divergence|moved differently)\b/.test(
|
||||
text,
|
||||
)
|
||||
) {
|
||||
return "explanation";
|
||||
}
|
||||
|
||||
if (
|
||||
/\b(genuine problem|who experiences|other people experience|how often this problem happens|what happens when this problem is not resolved|how people deal with this problem today|actively look for help|would pay to solve this problem|commercially justified|commercial justification|business case|value|demand|audience|customer|user|alternative|alternatives)\b/.test(
|
||||
text,
|
||||
)
|
||||
) {
|
||||
return "decision";
|
||||
}
|
||||
|
||||
return selectReasoningPattern({ node, graph }).pattern;
|
||||
}
|
||||
|
||||
function assessReasoningPatternCompatibility({ node, graph, activePattern }) {
|
||||
if (!node || !activePattern) {
|
||||
return {
|
||||
compatible: true,
|
||||
activePattern: activePattern ?? null,
|
||||
nodePattern: null,
|
||||
reason: "No active reasoning pattern constraint was applied.",
|
||||
};
|
||||
}
|
||||
|
||||
const nodePattern = inferIntrinsicNodePattern(node, graph);
|
||||
const allowedPatterns = ALLOWED_NODE_PATTERNS_BY_ACTIVE_PATTERN[
|
||||
activePattern
|
||||
] ?? [activePattern];
|
||||
const compatible = allowedPatterns.includes(nodePattern);
|
||||
|
||||
return {
|
||||
compatible,
|
||||
activePattern,
|
||||
nodePattern,
|
||||
allowedPatterns,
|
||||
reason: compatible
|
||||
? `Node remains compatible because ${nodePattern} is allowed during ${activePattern} reasoning.`
|
||||
: `Node is incompatible because ${nodePattern} is not allowed during ${activePattern} reasoning.`,
|
||||
};
|
||||
}
|
||||
|
||||
function isExplicitComparisonFamilyUnknown(node) {
|
||||
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
|
||||
return /\b(two observations|measured|measurement|basis|scale|same period|different timing|comparable)\b/.test(
|
||||
text,
|
||||
);
|
||||
}
|
||||
|
||||
function buildCompatibilityFailure(node, compatibility, reason) {
|
||||
return {
|
||||
nodeId: node?.id ?? null,
|
||||
label: node?.label ?? null,
|
||||
activePattern: compatibility?.activePattern ?? null,
|
||||
nodePattern: compatibility?.nodePattern ?? null,
|
||||
allowedPatterns: compatibility?.allowedPatterns ?? [],
|
||||
rejectionReason: reason || compatibility?.reason || null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRejectedSelectionDiagnostics({
|
||||
node,
|
||||
graph,
|
||||
activePattern,
|
||||
reason,
|
||||
}) {
|
||||
const compatibility = assessReasoningPatternCompatibility({
|
||||
node,
|
||||
graph,
|
||||
activePattern,
|
||||
});
|
||||
|
||||
return {
|
||||
incompatibleNodeIds: node?.id ? [node.id] : [],
|
||||
compatibilityFailures: [
|
||||
buildCompatibilityFailure(node, compatibility, reason),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function selectPatternCompatibleUnknownCandidate({
|
||||
graph,
|
||||
resolvedNodeIds = [],
|
||||
activePattern,
|
||||
excludedNodeIds = [],
|
||||
}) {
|
||||
if (!activePattern) {
|
||||
return selectActiveUnknownCandidate(graph, resolvedNodeIds);
|
||||
}
|
||||
|
||||
const excluded = new Set(excludedNodeIds || []);
|
||||
const incompatibleNodeIds = (graph.nodes || [])
|
||||
.filter(
|
||||
(node) =>
|
||||
node.kind === "unknown" &&
|
||||
!excluded.has(node.id) &&
|
||||
!["resolved", "contradicted"].includes(node.status),
|
||||
)
|
||||
.filter(
|
||||
(node) =>
|
||||
!assessReasoningPatternCompatibility({
|
||||
node,
|
||||
graph,
|
||||
activePattern,
|
||||
}).compatible,
|
||||
)
|
||||
.map((node) => node.id);
|
||||
|
||||
return selectActiveUnknownCandidate(graph, [
|
||||
...new Set([
|
||||
...(resolvedNodeIds || []),
|
||||
...incompatibleNodeIds,
|
||||
...excludedNodeIds,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
function collectPatternCompatibilityDiagnostics({
|
||||
graph,
|
||||
activePattern,
|
||||
candidateNodeIds = [],
|
||||
}) {
|
||||
if (!activePattern) {
|
||||
return {
|
||||
reasoningPatternValidation: {
|
||||
activePattern: null,
|
||||
valid: true,
|
||||
reason: "No active reasoning pattern constraint was applied.",
|
||||
},
|
||||
patternCompatibleNodeCount: 0,
|
||||
incompatibleNodeIds: [],
|
||||
compatibilityFailures: [],
|
||||
graphReasoningIntegrity: "not_applicable",
|
||||
};
|
||||
}
|
||||
|
||||
const candidateSet = new Set(candidateNodeIds || []);
|
||||
const compatibilityFailures = (graph.nodes || [])
|
||||
.filter(
|
||||
(node) =>
|
||||
node.kind === "unknown" &&
|
||||
candidateSet.has(node.id) &&
|
||||
!["resolved", "contradicted"].includes(node.status),
|
||||
)
|
||||
.map((node) => ({
|
||||
node,
|
||||
compatibility: assessReasoningPatternCompatibility({
|
||||
node,
|
||||
graph,
|
||||
activePattern,
|
||||
}),
|
||||
}))
|
||||
.filter(({ compatibility }) => !compatibility.compatible)
|
||||
.map(({ node, compatibility }) =>
|
||||
buildCompatibilityFailure(node, compatibility),
|
||||
);
|
||||
|
||||
return {
|
||||
reasoningPatternValidation: {
|
||||
activePattern,
|
||||
valid: compatibilityFailures.length === 0,
|
||||
reason:
|
||||
compatibilityFailures.length === 0
|
||||
? `All selectable unknowns are compatible with ${activePattern} reasoning.`
|
||||
: `Some selectable unknowns are incompatible with ${activePattern} reasoning.`,
|
||||
},
|
||||
patternCompatibleNodeCount:
|
||||
(candidateNodeIds || []).length - compatibilityFailures.length,
|
||||
incompatibleNodeIds: compatibilityFailures.map((failure) => failure.nodeId),
|
||||
compatibilityFailures,
|
||||
graphReasoningIntegrity:
|
||||
compatibilityFailures.length === 0 ? "valid" : "invalid",
|
||||
};
|
||||
}
|
||||
|
||||
function selectDecompositionChildCandidate(
|
||||
graph,
|
||||
parentNodeId,
|
||||
activePattern = null,
|
||||
) {
|
||||
const childCandidates = findDirectChildUnknowns(graph, parentNodeId)
|
||||
.filter(
|
||||
(node) =>
|
||||
node.kind === "unknown" &&
|
||||
!["resolved", "contradicted"].includes(node.status),
|
||||
)
|
||||
.filter((node) => {
|
||||
if (
|
||||
activePattern === "decision" &&
|
||||
isExplicitComparisonFamilyUnknown(node)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const compatibility = assessReasoningPatternCompatibility({
|
||||
node,
|
||||
graph,
|
||||
activePattern,
|
||||
});
|
||||
return compatibility.compatible;
|
||||
});
|
||||
|
||||
if (childCandidates.length === 0) {
|
||||
return { status: "none", nodeId: null, tiedCandidateIds: [] };
|
||||
@@ -1926,6 +2195,11 @@ function runDeterministicDecomposition({
|
||||
let decompositionTriggeredByQuestionComplexity = false;
|
||||
let decompositionTriggeredByAnswerability = false;
|
||||
let selectedContainerUnknown = null;
|
||||
let activeReasoningPattern = null;
|
||||
let activeReasoningPatternReason = null;
|
||||
let incompatibleNodeIds = [];
|
||||
let compatibilityFailures = [];
|
||||
let replacementActions = [];
|
||||
|
||||
while (workingSelection?.status === "selected" && workingSelection?.nodeId) {
|
||||
const selectedNode = findNodeById(workingGraph, workingSelection.nodeId);
|
||||
@@ -1935,6 +2209,53 @@ function runDeterministicDecomposition({
|
||||
break;
|
||||
}
|
||||
|
||||
if (!activeReasoningPattern) {
|
||||
const activePatternSelection = determineActiveReasoningPattern(
|
||||
selectedNode,
|
||||
workingGraph,
|
||||
);
|
||||
activeReasoningPattern = activePatternSelection.pattern;
|
||||
activeReasoningPatternReason = activePatternSelection.reason;
|
||||
}
|
||||
|
||||
const selectedNodeCompatibility = assessReasoningPatternCompatibility({
|
||||
node: selectedNode,
|
||||
graph: workingGraph,
|
||||
activePattern: activeReasoningPattern,
|
||||
});
|
||||
if (!selectedNodeCompatibility.compatible) {
|
||||
incompatibleNodeIds = appendUniqueValue(
|
||||
incompatibleNodeIds,
|
||||
selectedNode.id,
|
||||
);
|
||||
compatibilityFailures.push(
|
||||
buildCompatibilityFailure(
|
||||
selectedNode,
|
||||
selectedNodeCompatibility,
|
||||
"Selected unknown violated the active reasoning pattern.",
|
||||
),
|
||||
);
|
||||
const replacementSelection = selectPatternCompatibleUnknownCandidate({
|
||||
graph: workingGraph,
|
||||
resolvedNodeIds: workingGraph.resolvedNodeIds,
|
||||
activePattern: activeReasoningPattern,
|
||||
excludedNodeIds: [selectedNode.id],
|
||||
});
|
||||
if (replacementSelection?.status === "selected") {
|
||||
replacementActions.push({
|
||||
rejectedNodeId: selectedNode.id,
|
||||
replacementNodeId: replacementSelection.nodeId,
|
||||
reason:
|
||||
"Replaced an incompatible active unknown with the next pattern-compatible candidate.",
|
||||
});
|
||||
workingSelection = replacementSelection;
|
||||
continue;
|
||||
}
|
||||
decompositionStoppedReason =
|
||||
"No reasoning-pattern-compatible unknown remained available for selection.";
|
||||
break;
|
||||
}
|
||||
|
||||
const atomicityAssessment = assessUnknownAtomicity({
|
||||
node: selectedNode,
|
||||
graph: workingGraph,
|
||||
@@ -1979,6 +2300,7 @@ function runDeterministicDecomposition({
|
||||
const childSelection = selectDecompositionChildCandidate(
|
||||
workingGraph,
|
||||
selectedNode.id,
|
||||
activeReasoningPattern,
|
||||
);
|
||||
if (childSelection.status === "selected") {
|
||||
workingSelection = childSelection;
|
||||
@@ -2013,6 +2335,7 @@ function runDeterministicDecomposition({
|
||||
selectedNode,
|
||||
workingGraph,
|
||||
decompositionDepth,
|
||||
activeReasoningPattern,
|
||||
);
|
||||
|
||||
proposedChildCount = decomposition.proposedChildCount;
|
||||
@@ -2061,6 +2384,7 @@ function runDeterministicDecomposition({
|
||||
workingSelection = selectDecompositionChildCandidate(
|
||||
workingGraph,
|
||||
selectedNode.id,
|
||||
activeReasoningPattern,
|
||||
);
|
||||
|
||||
if (workingSelection?.status !== "selected") {
|
||||
@@ -2112,6 +2436,11 @@ function runDeterministicDecomposition({
|
||||
decompositionTriggeredByQuestionComplexity,
|
||||
decompositionTriggeredByAnswerability,
|
||||
selectedContainerUnknown,
|
||||
activeReasoningPattern,
|
||||
activeReasoningPatternReason,
|
||||
incompatibleNodeIds,
|
||||
compatibilityFailures,
|
||||
replacementActions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2446,20 +2775,75 @@ export function applyValidatedProposal({
|
||||
const resolvedCurrentTurnNodeIds = [
|
||||
...new Set(proposalSnapshot.resolvedUnknownNodeIds || []),
|
||||
];
|
||||
deterministicSelection = isSelectableUnresolvedUnknown(
|
||||
let postPropagationIncompatibleNodeIds = [];
|
||||
let postPropagationCompatibilityFailures = [];
|
||||
let postPropagationReplacementActions = [];
|
||||
let activeUnknownIncompatibleNodeIds = [];
|
||||
let activeUnknownCompatibilityFailures = [];
|
||||
let activeUnknownReplacementActions = [];
|
||||
const preservedSelectedChildNode = isSelectableUnresolvedUnknown(
|
||||
updatedSituationGraph,
|
||||
decompositionResult.selectedChildNodeId,
|
||||
)
|
||||
? {
|
||||
status: "selected",
|
||||
nodeId: decompositionResult.selectedChildNodeId,
|
||||
reason:
|
||||
"Preserved the selected decomposition child because it remains unresolved after propagation.",
|
||||
}
|
||||
: selectActiveUnknownCandidate(
|
||||
? findNodeById(
|
||||
updatedSituationGraph,
|
||||
updatedSituationGraph.resolvedNodeIds,
|
||||
);
|
||||
decompositionResult.selectedChildNodeId,
|
||||
)
|
||||
: null;
|
||||
const preservedSelectedChildCompatibility = preservedSelectedChildNode
|
||||
? assessReasoningPatternCompatibility({
|
||||
node: preservedSelectedChildNode,
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
})
|
||||
: null;
|
||||
|
||||
if (
|
||||
preservedSelectedChildNode &&
|
||||
preservedSelectedChildCompatibility?.compatible
|
||||
) {
|
||||
deterministicSelection = {
|
||||
status: "selected",
|
||||
nodeId: decompositionResult.selectedChildNodeId,
|
||||
reason:
|
||||
"Preserved the selected decomposition child because it remains unresolved and reasoning-pattern-compatible after propagation.",
|
||||
};
|
||||
} else {
|
||||
if (preservedSelectedChildNode) {
|
||||
const rejectionDiagnostics = buildRejectedSelectionDiagnostics({
|
||||
node: preservedSelectedChildNode,
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
reason:
|
||||
"Preserved decomposition child violated the active reasoning pattern after propagation.",
|
||||
});
|
||||
postPropagationIncompatibleNodeIds =
|
||||
rejectionDiagnostics.incompatibleNodeIds;
|
||||
postPropagationCompatibilityFailures =
|
||||
rejectionDiagnostics.compatibilityFailures;
|
||||
}
|
||||
|
||||
deterministicSelection = selectPatternCompatibleUnknownCandidate({
|
||||
graph: updatedSituationGraph,
|
||||
resolvedNodeIds: updatedSituationGraph.resolvedNodeIds,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
excludedNodeIds: preservedSelectedChildNode
|
||||
? [preservedSelectedChildNode.id]
|
||||
: [],
|
||||
});
|
||||
|
||||
if (
|
||||
preservedSelectedChildNode &&
|
||||
deterministicSelection?.status === "selected"
|
||||
) {
|
||||
postPropagationReplacementActions.push({
|
||||
rejectedNodeId: preservedSelectedChildNode.id,
|
||||
replacementNodeId: deterministicSelection.nodeId,
|
||||
reason:
|
||||
"Replaced a preserved decomposition child that violated reasoning-pattern consistency.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (deterministicSelection?.status === "ambiguous") {
|
||||
const orderedSiblingSelection = selectOrderedSiblingCandidate(
|
||||
@@ -2473,14 +2857,76 @@ export function applyValidatedProposal({
|
||||
}
|
||||
}
|
||||
|
||||
const carriedActiveUnknownNode = previousActiveUnknownNodeId
|
||||
? findNodeById(updatedSituationGraph, previousActiveUnknownNodeId)
|
||||
: null;
|
||||
const carriedActiveUnknownStillUnresolved = Boolean(
|
||||
carriedActiveUnknownNode &&
|
||||
carriedActiveUnknownNode.kind === "unknown" &&
|
||||
!["resolved", "contradicted"].includes(carriedActiveUnknownNode.status) &&
|
||||
!updatedSituationGraph.resolvedNodeIds.includes(
|
||||
carriedActiveUnknownNode.id,
|
||||
),
|
||||
);
|
||||
|
||||
if (
|
||||
carriedActiveUnknownStillUnresolved &&
|
||||
decompositionResult.activeReasoningPattern
|
||||
) {
|
||||
const carriedActiveCompatibility = assessReasoningPatternCompatibility({
|
||||
node: carriedActiveUnknownNode,
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
});
|
||||
|
||||
if (!carriedActiveCompatibility.compatible) {
|
||||
activeUnknownIncompatibleNodeIds = [carriedActiveUnknownNode.id];
|
||||
activeUnknownCompatibilityFailures = [
|
||||
buildCompatibilityFailure(
|
||||
carriedActiveUnknownNode,
|
||||
carriedActiveCompatibility,
|
||||
"Carried active unknown violated the active reasoning pattern and was not retained for investigation.",
|
||||
),
|
||||
];
|
||||
|
||||
if (
|
||||
deterministicSelection?.status === "selected" &&
|
||||
deterministicSelection.nodeId !== carriedActiveUnknownNode.id
|
||||
) {
|
||||
activeUnknownReplacementActions = [
|
||||
{
|
||||
rejectedNodeId: carriedActiveUnknownNode.id,
|
||||
replacementNodeId: deterministicSelection.nodeId,
|
||||
reason:
|
||||
"Replaced an incompatible carried active unknown with a pattern-compatible investigation target.",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unresolvedCandidates = listUnresolvedUnknownCandidates(
|
||||
updatedSituationGraph,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
);
|
||||
const eligibleCandidates = listEligibleUnknownCandidates(
|
||||
const eligibleCandidatesBeforeCompatibility = listEligibleUnknownCandidates(
|
||||
updatedSituationGraph,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
);
|
||||
const eligibleCandidates = eligibleCandidatesBeforeCompatibility.filter(
|
||||
(node) =>
|
||||
assessReasoningPatternCompatibility({
|
||||
node,
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
}).compatible,
|
||||
);
|
||||
|
||||
const compatibilityDiagnostics = collectPatternCompatibilityDiagnostics({
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
candidateNodeIds: eligibleCandidates.map((node) => node.id),
|
||||
});
|
||||
|
||||
const atomicityAssessment = decompositionResult.atomicityAssessment;
|
||||
const answerabilityAssessment = decompositionResult.answerabilityAssessment;
|
||||
@@ -2603,6 +3049,14 @@ export function applyValidatedProposal({
|
||||
}
|
||||
: null;
|
||||
|
||||
const finalSelectionCompatibility = finalSelectedQuestion?.nodeId
|
||||
? assessReasoningPatternCompatibility({
|
||||
node: findNodeById(updatedSituationGraph, finalSelectedQuestion.nodeId),
|
||||
graph: updatedSituationGraph,
|
||||
activePattern: decompositionResult.activeReasoningPattern,
|
||||
})
|
||||
: null;
|
||||
|
||||
const finalSelectedChildNodeId =
|
||||
selectedChildNodeId ??
|
||||
(selectedQuestionBelongsToChild(
|
||||
@@ -2638,7 +3092,8 @@ export function applyValidatedProposal({
|
||||
!resultGraphValidation.success ||
|
||||
!resultReferenceValidation?.valid ||
|
||||
resultDuplicateNodeIds.length > 0 ||
|
||||
resultDuplicateEdgeIds.length > 0
|
||||
resultDuplicateEdgeIds.length > 0 ||
|
||||
(finalSelectedQuestion?.nodeId && !finalSelectionCompatibility?.compatible)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -2658,6 +3113,12 @@ export function applyValidatedProposal({
|
||||
({ edgeId, count }) =>
|
||||
`Updated graph contains duplicate edge ID: "${edgeId}" (${count} occurrences)`,
|
||||
),
|
||||
...(!finalSelectionCompatibility?.compatible &&
|
||||
finalSelectedQuestion?.nodeId
|
||||
? [
|
||||
`Active unknown violates reasoning pattern consistency: "${finalSelectedQuestion.nodeId}" is ${finalSelectionCompatibility?.nodePattern} but active pattern is ${finalSelectionCompatibility?.activePattern}`,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -2737,6 +3198,30 @@ export function applyValidatedProposal({
|
||||
childNodeIds: decompositionChildNodeIds,
|
||||
selectedContainerUnknown:
|
||||
decompositionResult.selectedContainerUnknown ?? null,
|
||||
reasoningPatternValidation:
|
||||
compatibilityDiagnostics.reasoningPatternValidation,
|
||||
patternCompatibleNodeCount:
|
||||
compatibilityDiagnostics.patternCompatibleNodeCount,
|
||||
incompatibleNodeIds: [
|
||||
...new Set([
|
||||
...(decompositionResult.incompatibleNodeIds || []),
|
||||
...postPropagationIncompatibleNodeIds,
|
||||
...activeUnknownIncompatibleNodeIds,
|
||||
...compatibilityDiagnostics.incompatibleNodeIds,
|
||||
]),
|
||||
],
|
||||
compatibilityFailures: [
|
||||
...(decompositionResult.compatibilityFailures || []),
|
||||
...postPropagationCompatibilityFailures,
|
||||
...activeUnknownCompatibilityFailures,
|
||||
...compatibilityDiagnostics.compatibilityFailures,
|
||||
],
|
||||
replacementActions: [
|
||||
...(decompositionResult.replacementActions || []),
|
||||
...postPropagationReplacementActions,
|
||||
...activeUnknownReplacementActions,
|
||||
],
|
||||
graphReasoningIntegrity: compatibilityDiagnostics.graphReasoningIntegrity,
|
||||
selectedChildUnknown:
|
||||
finalSelectedChildNodeId ??
|
||||
(deterministicSelection?.status === "selected"
|
||||
|
||||
@@ -65,6 +65,12 @@ function buildDiagnostics({
|
||||
rejectedQuestionFamilies,
|
||||
selectedQuestionTemplate,
|
||||
reasoningPatternReason,
|
||||
reasoningPatternValidation,
|
||||
patternCompatibleNodeCount,
|
||||
incompatibleNodeIds,
|
||||
compatibilityFailures,
|
||||
replacementActions,
|
||||
graphReasoningIntegrity,
|
||||
}) {
|
||||
return {
|
||||
promptVersion: analysis?.promptVersion ?? null,
|
||||
@@ -100,6 +106,12 @@ function buildDiagnostics({
|
||||
rejectedQuestionFamilies: rejectedQuestionFamilies ?? [],
|
||||
selectedQuestionTemplate: selectedQuestionTemplate ?? null,
|
||||
reasoningPatternReason: reasoningPatternReason ?? null,
|
||||
reasoningPatternValidation: reasoningPatternValidation ?? null,
|
||||
patternCompatibleNodeCount: patternCompatibleNodeCount ?? 0,
|
||||
incompatibleNodeIds: incompatibleNodeIds ?? [],
|
||||
compatibilityFailures: compatibilityFailures ?? [],
|
||||
replacementActions: replacementActions ?? [],
|
||||
graphReasoningIntegrity: graphReasoningIntegrity ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,6 +212,12 @@ function buildUpdateDiagnostics({
|
||||
candidateNodeIds,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
noQuestionReason,
|
||||
reasoningPatternValidation,
|
||||
patternCompatibleNodeCount,
|
||||
incompatibleNodeIds,
|
||||
compatibilityFailures,
|
||||
replacementActions,
|
||||
graphReasoningIntegrity,
|
||||
}) {
|
||||
return {
|
||||
promptVersion: promptVersion ?? "v0.4",
|
||||
@@ -291,6 +309,12 @@ function buildUpdateDiagnostics({
|
||||
candidateNodeIds: candidateNodeIds ?? [],
|
||||
resolvedCurrentTurnNodeIds: resolvedCurrentTurnNodeIds ?? [],
|
||||
noQuestionReason: noQuestionReason ?? null,
|
||||
reasoningPatternValidation: reasoningPatternValidation ?? null,
|
||||
patternCompatibleNodeCount: patternCompatibleNodeCount ?? 0,
|
||||
incompatibleNodeIds: incompatibleNodeIds ?? [],
|
||||
compatibilityFailures: compatibilityFailures ?? [],
|
||||
replacementActions: replacementActions ?? [],
|
||||
graphReasoningIntegrity: graphReasoningIntegrity ?? null,
|
||||
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
|
||||
};
|
||||
}
|
||||
@@ -419,6 +443,16 @@ export async function startCase(body) {
|
||||
reasoningPatternReason:
|
||||
initialQuestionResult.selectedQuestion?.reasoningPatternReason ??
|
||||
null,
|
||||
reasoningPatternValidation:
|
||||
initialQuestionResult.reasoningPatternValidation ?? null,
|
||||
patternCompatibleNodeCount:
|
||||
initialQuestionResult.patternCompatibleNodeCount ?? 0,
|
||||
incompatibleNodeIds: initialQuestionResult.incompatibleNodeIds ?? [],
|
||||
compatibilityFailures:
|
||||
initialQuestionResult.compatibilityFailures ?? [],
|
||||
replacementActions: initialQuestionResult.replacementActions ?? [],
|
||||
graphReasoningIntegrity:
|
||||
initialQuestionResult.graphReasoningIntegrity ?? null,
|
||||
}),
|
||||
validationErrors: graphReferenceValidation.errors,
|
||||
statusCode: 500,
|
||||
@@ -475,6 +509,15 @@ export async function startCase(body) {
|
||||
null,
|
||||
reasoningPatternReason:
|
||||
initialQuestionResult.selectedQuestion?.reasoningPatternReason ?? null,
|
||||
reasoningPatternValidation:
|
||||
initialQuestionResult.reasoningPatternValidation ?? null,
|
||||
patternCompatibleNodeCount:
|
||||
initialQuestionResult.patternCompatibleNodeCount ?? 0,
|
||||
incompatibleNodeIds: initialQuestionResult.incompatibleNodeIds ?? [],
|
||||
compatibilityFailures: initialQuestionResult.compatibilityFailures ?? [],
|
||||
replacementActions: initialQuestionResult.replacementActions ?? [],
|
||||
graphReasoningIntegrity:
|
||||
initialQuestionResult.graphReasoningIntegrity ?? null,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -675,6 +718,12 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
||||
candidateNodeIds: [],
|
||||
resolvedCurrentTurnNodeIds: [],
|
||||
noQuestionReason: null,
|
||||
reasoningPatternValidation: null,
|
||||
patternCompatibleNodeCount: 0,
|
||||
incompatibleNodeIds: [],
|
||||
compatibilityFailures: [],
|
||||
replacementActions: [],
|
||||
graphReasoningIntegrity: null,
|
||||
plainLanguageNormalisations: [],
|
||||
unknownSelectionExplanation: explainUnknownSelection(
|
||||
situationGraph,
|
||||
@@ -780,6 +829,14 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
||||
resolvedCurrentTurnNodeIds:
|
||||
applicationResult.resolvedCurrentTurnNodeIds,
|
||||
noQuestionReason: applicationResult.noQuestionReason,
|
||||
reasoningPatternValidation:
|
||||
applicationResult.reasoningPatternValidation,
|
||||
patternCompatibleNodeCount:
|
||||
applicationResult.patternCompatibleNodeCount,
|
||||
incompatibleNodeIds: applicationResult.incompatibleNodeIds,
|
||||
compatibilityFailures: applicationResult.compatibilityFailures,
|
||||
replacementActions: applicationResult.replacementActions,
|
||||
graphReasoningIntegrity: applicationResult.graphReasoningIntegrity,
|
||||
plainLanguageNormalisations:
|
||||
applicationResult.plainLanguageNormalisations,
|
||||
reasoningPattern:
|
||||
@@ -876,6 +933,12 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
||||
candidateNodeIds: [],
|
||||
resolvedCurrentTurnNodeIds: [],
|
||||
noQuestionReason: null,
|
||||
reasoningPatternValidation: null,
|
||||
patternCompatibleNodeCount: 0,
|
||||
incompatibleNodeIds: [],
|
||||
compatibilityFailures: [],
|
||||
replacementActions: [],
|
||||
graphReasoningIntegrity: null,
|
||||
plainLanguageNormalisations: [],
|
||||
reasoningPattern: null,
|
||||
questionFamily: null,
|
||||
|
||||
Reference in New Issue
Block a user