const ISSUE_MESSAGES = { question_missing: "No user-facing Open Question was generated.", question_empty: "The generated Open Question was empty.", question_not_interrogative: "The generated Open Question was not a direct interrogative.", question_indirect_proposition: "The generated Open Question used an indirect uncertainty proposition.", }; /** * Narrow presentation validation only. This does not attempt to prove semantic * equivalence with the canonical uncertainty and never rewrites either value. */ export function validateOpenQuestionCandidate(candidate) { if (candidate === undefined || candidate === null) { return { valid: false, category: "question_missing" }; } if (typeof candidate !== "string" || candidate.trim().length === 0) { return { valid: false, category: "question_empty" }; } const question = candidate.trim(); if (/^whether\b/i.test(question)) { return { valid: false, category: "question_indirect_proposition" }; } if (!question.endsWith("?")) { return { valid: false, category: "question_not_interrogative" }; } return { valid: true, question }; } /** * Remove invalid presentation candidates without changing canonical descriptions, * IDs, ordering, relationships, or any other reconstruction content. */ export function validateReconstructionOpenQuestions(reconstruction) { const issues = []; const importantUnknowns = (reconstruction?.importantUnknowns ?? []).map( (unknown, index) => { const validation = validateOpenQuestionCandidate(unknown.question); if (validation.valid) { return { ...unknown, question: validation.question }; } const { question: _invalidQuestion, ...canonicalUnknown } = unknown; issues.push({ category: validation.category, path: ["reconstruction", "importantUnknowns", index, "question"], unknownId: unknown.id, message: ISSUE_MESSAGES[validation.category], severity: "nonfatal", }); return canonicalUnknown; }, ); return { reconstruction: { ...reconstruction, importantUnknowns }, issues, }; }