import { formulateQuestion } from "@/lib/graph/question-formulator.js"; const FOCUSED_ANSWER_SCHEMA_FIELDS = [ "targetNodeId", "observations", "uncertainties", "assumptions", "relationships", "possibleFollowUpQuestions", ]; const FORBIDDEN_GRAPH_MUTATION_FIELDS = [ "addedNodes", "updatedNodes", "removedNodes", "addedEdges", "removedEdges", "resolvedNodeIds", "activeUnknownNodeId", "selectedQuestion", ]; /** * Formulate a question for the explicitly user-selected unresolved node. * * Input: { situationGraph, targetNodeId } * Validates that target exists, is an unknown, and is not resolved. * Calls formulateQuestion with only factual explicit-user-targeting context. * Does NOT invoke selectActiveUnknownCandidate, determineGraphBackedQuestion, * global ranking, or global recommendation. * Does NOT mutate activeUnknownNodeId, selectedQuestion, SituationGraph, or resolvedNodeIds. */ export function formulateQuestionForTarget({ situationGraph, targetNodeId }) { const nodesById = new Map(situationGraph?.nodes?.map((n) => [n.id, n]) || []); const targetNode = nodesById.get(targetNodeId); if (!targetNode) { return { success: false, error: `Target node ${targetNodeId} not found in graph.` }; } if (targetNode.kind !== "unknown") { return { success: false, error: `Target node ${targetNodeId} is not an unknown (kind=${targetNode.kind}).` }; } if (targetNode.status === "resolved") { return { success: false, error: `Target node ${targetNodeId} is already resolved.` }; } // Build explicit user-targeting context — only factual fields from the targeting request. // No activeUnknownNodeId override, no selectedQuestion mutation, no global selection state. const context = { resolvedValues: [], suppressDecisionSufficiencyConfirmation: false, _explicitTargetNodeId: targetNodeId, _explicitTargetLabel: targetNode.label, _explicitTargetDescription: targetNode.description, }; const result = formulateQuestion({ node: targetNode, graph: situationGraph, context }); return { success: true, targetNodeId, question: result.question, strategy: result.strategy ?? null, reasoningPattern: result.reasoningPattern, reasoningPatternReason: result.reasoningPatternReason, reason: result.reason, questionFamily: result.questionFamily, selectedQuestionTemplate: result.selectedQuestionTemplate, allowedQuestionFamilies: result.allowedQuestionFamilies, rejectedQuestionFamilies: result.rejectedQuestionFamilies, }; } /** * Build a focused answer deconstruction prompt for one explicitly user-chosen investigation. */ export function buildFocusedDeconstructPrompt({ targetLabel, targetDescription, centralStatement, question, answer }) { return `You are performing focused answer deconstruction for one explicitly user-chosen investigation. Return exactly one JSON object. Return JSON only. This is NOT a graph update task. Do NOT output graph mutations. Do NOT output selection, ranking, ownership, recommendation, confidence, or next-best-question semantics. Do NOT include any of these fields: addedNodes, updatedNodes, removedNodes, addedEdges, removedEdges, resolvedNodeIds, activeUnknownNodeId, selectedQuestion. Required top-level fields: - targetNodeId - observations - uncertainties - assumptions - relationships - possibleFollowUpQuestions Field rules: - targetNodeId must be included as a string identifying this investigation node - observations: only statements directly supported by the answer - uncertainties: only things the answer explicitly leaves unknown or unclear - assumptions: include only if the answer itself relies on an assumption - relationships: only direct supported relationships among extracted items, each with { from, to, type, rationale } - possibleFollowUpQuestions: unresolved questions genuinely exposed by this answer, unranked Focused case context: - target label: ${targetLabel} - target description: ${targetDescription} - central case statement: ${centralStatement} Question: ${question} Answer: ${answer}`; } /** * Validate that a focused-answer deconstruction result contains only the expected fields. * Returns an array of errors (empty = valid). */ export function validateFocusedDeconstructSchema(result) { const errors = []; for (const field of FOCUSED_ANSWER_SCHEMA_FIELDS) { if (!(field in result)) { errors.push(`Missing required field: ${field}`); } } for (const field of FORBIDDEN_GRAPH_MUTATION_FIELDS) { if (field in result) { errors.push(`Forbidden graph-mutation field present: ${field}`); } } return errors; } export { FOCUSED_ANSWER_SCHEMA_FIELDS, FORBIDDEN_GRAPH_MUTATION_FIELDS };