{"record":{"id":"e4df8b4aa190f224","repo":"koala73/worldmonitor","slug":"no-digest-input-feeds-are-available","errorCode":null,"errorMessage":"No digest input feeds are available","messagePattern":"No digest input feeds are available","errorType":"exception","errorClass":"McpSourceUnavailableError","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/analysis-tools.ts","lineNumber":178,"sourceCode":"    items: { type: 'string' },\n    description: 'Required cache keys that were missing or unreadable; their contribution is not treated as quiet.',\n  },\n  failed_inputs: {\n    type: 'array',\n    items: { type: 'string' },\n    description: 'Subset of unavailable_inputs whose Redis read failed rather than returning a genuine miss.',\n  },\n} as const;\n\ntype AnalysisFreshness = Awaited<ReturnType<typeof readCachesWithFreshness>>['freshness'];\n\nfunction requireAnyInput(\n  payloads: unknown[],\n  freshness: AnalysisFreshness,\n  message: string,\n): void {\n  if (payloads.every((value) => value === null)) {\n    throw new McpSourceUnavailableError(\n      message,\n      freshness.unavailable_inputs,\n      freshness.failed_inputs,\n    );\n  }\n}\n\nfunction resolveLimit(raw: unknown, fallback: number): number {\n  if (raw === undefined || raw === null) return fallback;\n  const parsed = Math.round(Number(raw));\n  if (!Number.isFinite(parsed)) return fallback;\n  if (parsed <= 0) return Number.POSITIVE_INFINITY;\n  return parsed;\n}\n\nexport const ANALYSIS_TOOLS: ToolDef[] = [\n  {\n    name: 'get_signal_convergence',","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/analysis-tools.ts#L160-L196","documentation":"Thrown by requireAnyInput() inside the get_alert_digest MCP tool. This tool sweeps seven seeded domain caches (CII risk scores, military surges, cable health, infra outages, temporal anomalies, thermal escalation, shipping stress) and if ALL seven returned null the digest has no data to report, so it throws McpSourceUnavailableError. The weekly view also reads military:surges:history:v1 but that does not participate in the requireAnyInput gate. This is a total-source-outage signal, not a quiet day — a quiet day has data with no threshold trips.","triggerScenarios":"Calling get_alert_digest (either view) when every one of the seven domain caches is null in Redis — a total seeder outage, a Redis flush, or a multi-region Redis failure. Distinguished from a legitimately quiet day because quiet days still have populated caches with zero trips; this error means the caches themselves are absent.","commonSituations":"Post-deploy cold start before any of the seven seeders has run; Redis data eviction (memory pressure evicting all volatile keys); a misconfigured REDIS_URL pointing at an empty instance; a cascading seeder failure where the seed pipeline is entirely stopped.","solutions":["Check GET /api/health and confirm the seven seed-meta keys are present and recent: seed-meta:intelligence:risk-scores, seed-meta:military-surges, seed-meta:cable-health, seed-meta:infra:outages, seed-meta:temporal:anomalies, seed-meta:thermal:escalation, seed-meta:supply_chain:shipping_stress.","Retry after the seeder cadence elapses (most feeds seed on a 30-45 minute cycle); transient stampedes resolve on TTL refresh.","If persistent, run the relevant seed scripts manually and verify they write seed-meta:* keys.","Verify Redis connectivity and that the Upstash REST URL/token env vars are correct for the edge runtime."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Check all seven digest feed health before calling get_alert_digest\nconst health = await fetch('/api/health').then(r => r.json());\nconst feeds = ['intelligence:risk-scores','military-surges','cable-health','infra:outages','temporal:anomalies','thermal:escalation','supply_chain:shipping_stress'];\nconst seeded = feeds.filter(k => health.seedMeta?.[`seed-meta:${k}`]);\nif (seeded.length === 0) {\n  throw new Error('All alert-digest feeds unseeded; retry after seeder cycle');\n}","typeGuard":"function isMcpSourceUnavailableError(e: unknown): e is { unavailableInputs: string[]; failedInputs: string[] } & Error {\n  return e instanceof Error && (e as any).name === 'McpSourceUnavailableError';\n}","tryCatchPattern":"try {\n  const digest = await callMcpTool('get_alert_digest', {});\n} catch (e) {\n  if (isMcpSourceUnavailableError(e)) {\n    // Total outage across 7 feeds — retry after seeder cadence\n    await backoffRetry(60_000);\n  } else throw e;\n}","preventionTips":["Set up health-check alerts on GET /api/health for the seven digest seed-meta keys.","Do not treat this error as a quiet day — quiet days return 200 with empty tripped arrays, not this throw.","Cache the last successful digest response as a fallback during transient outages."],"tags":["mcp","redis","source-unavailable","cache-miss","alert-digest"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}