koala73/worldmonitor · error

${pagePath} claims "${claim}" against a ${Math.round(ageDays

Error message

${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

What it means

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.

Solutions

  1. Run `npm run freeze:crawlable-live-pulse` to republish a current snapshot, then rebuild.
  2. 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.
  3. Remove or soften the recency claim if no fresh data is available.
  4. Increase verification frequency so snapshots are refreshed within the allowed age window.

Example fix

// before
<p>Country instability movements in the last 24 hours</p>
// after
<p>Country instability movements as of ${snapshotDate} (snapshot ${ageDays} days old)</p>
Defensive patterns

Strategy: validation

Validate before calling

if (ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS) {
  const hits = CII_MOVEMENT_RECENCY_CLAIMS.filter((c) => html.includes(c));
  if (hits.length) console.warn(`stale recency claims in ${pagePath}:`, hits);
}

Try / catch

// wrap the build step that freezes/derives claims
try {
  assertLivePulseMovementClaim(html, { pagePath, ageDays });
} catch (err) {
  console.error(err.message);
  process.exitCode = 1; // fail fast; re-freeze snapshot or fix copy
}

Prevention

When it happens

Trigger: Calling assertLivePulseMovementClaim(html, { pagePath, ageDays }) where ageDays exceeds the max and html contains any literal string from CII_MOVEMENT_RECENCY_CLAIMS.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/66d09218d7a2a736. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-crawlable-corpus.mjs:450

  };
}

export function livePulseMovementClaimLastmod(capturedAt, now = Date.now()) {
  const ageDays = livePulseSnapshotAgeDays(capturedAt, now);
  if (!(ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS)) return null;
  const capturedAtMs = Date.parse(`${String(capturedAt || '').slice(0, 10)}T00:00:00Z`);
  if (!Number.isFinite(capturedAtMs)) return null;
  return new Date(
    capturedAtMs + MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS * 86_400_000,
  ).toISOString().slice(0, 10);
}

export function assertLivePulseMovementClaim(html, { pagePath, ageDays }) {
  if (!(ageDays > MAX_TWENTY_FOUR_HOUR_MOVEMENT_CLAIM_AGE_DAYS)) return;
  const haystack = String(html || '');
  for (const claim of CII_MOVEMENT_RECENCY_CLAIMS) {
    if (haystack.includes(claim)) {
      throw new Error(
        `${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',
      );
    }
  }
}

export function resolveLatestLivePulseSnapshotPath(rootDir = DEFAULT_ROOT) {
  const snapshotDir = repoPath(rootDir, RESILIENCE_SNAPSHOT_DIR);
  const candidates = readdirSync(snapshotDir)
    .map((filename) => ({ filename, match: filename.match(LIVE_PULSE_SNAPSHOT_RE) }))
    .filter(({ match }) => match)
    .sort((a, b) => b.match[1].localeCompare(a.match[1]));
  if (candidates.length === 0) {
    throw new Error(`No crawlable live-pulse snapshot found in ${RESILIENCE_SNAPSHOT_DIR}`);
  }

  const [{ filename, match }] = candidates;

View on GitHub (pinned to 7d06c8633d)