{"record":{"id":"75b64b4874a459ff","repo":"koala73/worldmonitor","slug":"pagepath-brief-has-unsupported-citation-gap","errorCode":null,"errorMessage":"${pagePath} brief has unsupported citation: ${gap}","messagePattern":"(.+?) brief has unsupported citation: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-crawlable-corpus.mjs","lineNumber":3583,"sourceCode":"// when either artifact reaches <main>, including section titles that are\n// still plain text rather than <h*> tags.\nconst MEANS_FOR_ISO_RE = /^\\s*what this means for [a-z]{2}(?=\\s*(?::|$))/im;\n\nexport function assertCountryBriefPresentation({ pagePath, html, sources }) {\n  const main = corpusMainHtml(html);\n  if (main.includes('**')) {\n    throw new Error(`${pagePath} renders literal markdown emphasis in <main>`);\n  }\n  const brief = intelBriefHtml(html);\n  if (brief && sources !== undefined) {\n    // Check the rendered claim blocks as well as the input. A later formatter\n    // must not add an entity or change a citation after publish-time validation.\n    const claims = [...brief.matchAll(/<(p|li)\\b([^>]*)>([\\s\\S]*?)<\\/\\1>/gi)]\n      .filter((match) => !/\\bclass=\"source\"/.test(match[2]))\n      .map((match) => corpusVisibleText(match[3]).replace(/&(amp|lt|gt|quot|#39);/g,\n        (entity) => ({ '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '\"', '&#39;': \"'\" })[entity]));\n    const gap = briefCitationGroundingGap({ text: claims.join('\\n'), sources });\n    if (gap) throw new Error(`${pagePath} brief has unsupported citation: ${gap}`);\n  }\n  const headingSource = brief ?? main;\n  const headingHits = [...headingSource.matchAll(/<h[1-6]\\b[^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi)];\n  for (const hit of headingHits) {\n    const text = hit[1].replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n    if (MEANS_FOR_ISO_RE.test(text)) {\n      throw new Error(`${pagePath} heading leaks ISO code: ${text}`);\n    }\n  }\n  const briefLines = corpusMainHtml(brief ?? html)\n    .replace(/<br\\b[^>]*>|<\\/(?:p|h[1-6]|li|div)>/gi, '\\n')\n    .replace(/<[^>]+>/g, ' ');\n  if (MEANS_FOR_ISO_RE.test(briefLines)) {\n    throw new Error(`${pagePath} brief heading leaks an ISO-3166 alpha-2 code`);\n  }\n}\n\n// A floor, not completeness. #7615 shipped this as","sourceCodeStart":3565,"sourceCodeEnd":3601,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-crawlable-corpus.mjs#L3565-L3601","documentation":"As part of assertCountryBriefPresentation, the rendered claim blocks (<p>/<li> excluding class=\"source\" blocks, entity-decoded to visible text) are joined and passed to briefCitationGroundingGap along with the brief's sources. If the gap helper finds a citation in the claims not grounded in the provided sources, the build throws naming the gap. This guarantees every published citation in the intel brief traces to a listed source, preventing fabricated or orphaned citations.","triggerScenarios":"Asserting a page where the brief claim text cites a source that does not match any entry in the sources array passed to assertCountryBriefPresentation. Triggered by reordering, renaming, or dropping a source while the brief text still references it, or adding a claim with a new citation without updating sources.","commonSituations":"Brief text edited (by hand or model regeneration) to add a citation but the frozen sources array was not updated; sources list deduplicated or renumbered so citation keys no longer align; a formatter rewrites a citation or entity in the claim block after validation; a mismatched sources array passed to the assertion.","solutions":["Update the brief's sources array so it includes the source referenced by the unsupported citation","Remove or correct the ungrounded citation in the brief claim text","Re-run brief generation with grounding enforcement so claims and sources are produced together","Pass the same sources array used at render time to assertCountryBriefPresentation (mismatched inputs cause false gaps)"],"exampleFix":"// before\nassertCountryBriefPresentation({ pagePath, html, sources: sources.slice(0, 3) });\n// after\nassertCountryBriefPresentation({ pagePath, html, sources: brief.sources });","handlingStrategy":"validation","validationCode":"function ungroundedCitations(claimText, sources) {\n  const available = new Set(sources.map((s) => s.key));\n  return extractCitationKeys(claimText).filter((k) => !available.has(k));\n}","typeGuard":null,"tryCatchPattern":"try {\n  assertCountryBriefPresentation({ pagePath, html, sources: brief.sources });\n} catch (err) {\n  if (err.message.includes('unsupported citation')) {\n    console.error(`Ungrounded citation in ${pagePath}: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Generate brief claims and their sources array together so citations are grounded at authoring time","Never renumber, deduplicate, or slice the sources list independently of the claim text","Pass the exact same sources array to rendering and to assertion helpers","Validate citation grounding immediately after any model regeneration of brief text, before freezing"],"tags":["build","citations","content-integrity"],"backgroundTag":"schema-validation-failed","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}