{"record":{"id":"66d09218d7a2a736","repo":"koala73/worldmonitor","slug":"pagepath-claims-claim-against-a-math-round-agedays-day-old","errorCode":null,"errorMessage":"${pagePath} claims \"${claim}\" against a ${Math.round(ageDays)}-day-old live-pulse snapshot; derive the movement phrase from snapshot age or freeze a current pulse","messagePattern":"(.+?) claims \"(.+?)\" against a (.+?)-day-old live-pulse snapshot; derive the movement phrase from snapshot age or freeze a current pulse","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-crawlable-corpus.mjs","lineNumber":450,"sourceCode":"  };\n}\n\nexport function livePulseMovementClaimLastmod(capturedAt, now = Date.now()) {\n  const ageDays = livePulseSnapshotAgeDays(capturedAt, now);\n  if (!(ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS)) return null;\n  const capturedAtMs = Date.parse(`${String(capturedAt || '').slice(0, 10)}T00:00:00Z`);\n  if (!Number.isFinite(capturedAtMs)) return null;\n  return new Date(\n    capturedAtMs + MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS * 86_400_000,\n  ).toISOString().slice(0, 10);\n}\n\nexport function assertLivePulseMovementClaim(html, { pagePath, ageDays }) {\n  if (!(ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS)) return;\n  const haystack = String(html || '');\n  for (const claim of CII_MOVEMENT_RECENCY_CLAIMS) {\n    if (haystack.includes(claim)) {\n      throw new Error(\n        `${pagePath} claims \"${claim}\" against a ${Math.round(ageDays)}-day-old live-pulse snapshot; `\n        + 'derive the movement phrase from snapshot age or freeze a current pulse',\n      );\n    }\n  }\n}\n\nexport function resolveLatestLivePulseSnapshotPath(rootDir = DEFAULT_ROOT) {\n  const snapshotDir = repoPath(rootDir, RESILIENCE_SNAPSHOT_DIR);\n  const candidates = readdirSync(snapshotDir)\n    .map((filename) => ({ filename, match: filename.match(LIVE_PULSE_SNAPSHOT_RE) }))\n    .filter(({ match }) => match)\n    .sort((a, b) => b.match[1].localeCompare(a.match[1]));\n  if (candidates.length === 0) {\n    throw new Error(`No crawlable live-pulse snapshot found in ${RESILIENCE_SNAPSHOT_DIR}`);\n  }\n\n  const [{ filename, match }] = candidates;","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-crawlable-corpus.mjs#L432-L468","documentation":"assertLivePulseMovementClaim guards against stale recency claims: if the live-pulse snapshot is older than MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS and the HTML still contains one of the CII_MOVEMENT_RECENCY_CLAIMS phrases (e.g. 'last 24 hours' movement language), the build throws with the page path, claim, and snapshot age. This prevents crawlable pages from asserting fresh movement that the frozen snapshot no longer supports.","triggerScenarios":"Calling assertLivePulseMovementClaim(html, { pagePath, ageDays }) where ageDays exceeds the max and html contains any literal string from CII_MOVEMENT_RECENCY_CLAIMS.","commonSituations":"A frozen live-pulse snapshot aged past the threshold because `npm run freeze:crawlable-live-pulse` hasn't been run recently; templates copied from a page whose movement phrases were accurate at freeze time; CI running several days after content was authored.","solutions":["Run `npm run freeze:crawlable-live-pulse` to republish a current snapshot, then rebuild.","Rewrite the flagged movement phrase to derive from the actual snapshot age (e.g. 'over the past N days') instead of a fixed recency claim.","Remove or soften the recency claim if no fresh data is available.","Increase verification frequency so snapshots are refreshed within the allowed age window."],"exampleFix":"// before\n<p>Country instability movements in the last 24 hours</p>\n// after\n<p>Country instability movements as of ${snapshotDate} (snapshot ${ageDays} days old)</p>","handlingStrategy":"validation","validationCode":"if (ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS) {\n  const hits = CII_MOVEMENT_RECENCY_CLAIMS.filter((c) => html.includes(c));\n  if (hits.length) console.warn(`stale recency claims in ${pagePath}:`, hits);\n}","typeGuard":null,"tryCatchPattern":"// wrap the build step that freezes/derives claims\ntry {\n  assertLivePulseMovementClaim(html, { pagePath, ageDays });\n} catch (err) {\n  console.error(err.message);\n  process.exitCode = 1; // fail fast; re-freeze snapshot or fix copy\n}","preventionTips":["Schedule `npm run freeze:crawlable-live-pulse` on every build/deploy.","Generate movement phrases from snapshot age instead of hard-coded recency wording.","Monitor snapshot age in CI and alert before the threshold is crossed."],"tags":["build","stale-data","content-validation","live-pulse"],"backgroundTag":"stale-snapshot-data","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"}