{"record":{"id":"12b4ff0ff18754e8","repo":"koala73/worldmonitor","slug":"no-event-feeds-are-available-for-exposure-enrichme","errorCode":null,"errorMessage":"No event feeds are available for exposure enrichment","messagePattern":"No event feeds are available for exposure enrichment","errorType":"exception","errorClass":"McpSourceUnavailableError","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/analysis-tools.ts","lineNumber":181,"sourceCode":"      stale: evaluated.stale || unavailableInputs.length > 0,\n      unavailable_inputs: unavailableInputs,\n      failed_inputs: failedInputs,\n    },\n  };\n}\n\nconst ANALYSIS_CACHE_STATUS_PROPERTIES = {\n  unavailable_inputs: {\n    type: 'array',\n    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 {","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/mcp/registry/analysis-tools.ts#L163-L199","documentation":"McpSourceUnavailableError thrown by requireAnyInput (api/mcp/registry/analysis-tools.ts:173) for exposure enrichment: the event feeds the enrichment pass needs are all null, so the tool cannot correlate exposures with events. Typed fields expose unavailable_inputs (genuine misses) and failed_inputs (Redis errors) for downstream classification.","triggerScenarios":"Calling the exposure-enrichment analysis tool when every event-feed cache key reads null — event producer down, keys expired mid-cycle, seeds never run, or Redis read failures across all inputs.","commonSituations":"Event ingestion pipeline paused. Fresh environment without event seeds. Upstash degradation. Timing gap right after deploy before the first event publish.","solutions":["Check error.data.failed_inputs for Redis-side failures vs unavailable_inputs for unseeded keys","Verify event feed freshness on /api/health","Retry after the event producer's next run","Run the event feed seed script if keys are missing in this environment"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const health = await fetch('https://<host>/api/health').then(r => r.json());\nif (eventFeedKeys.some(k => health?.datasets?.[k]?.fresh === false)) defer('event feeds stale');","typeGuard":"function isSourceUnavailable(e) {\n  return e?.name === 'McpSourceUnavailableError' || Array.isArray(e?.data?.unavailable_inputs);\n}","tryCatchPattern":"try {\n  await client.callTool(exposureEnrichmentTool, args);\n} catch (e) {\n  if (isSourceUnavailable(e)) {\n    return e.data?.failed_inputs?.length ? retryWithBackoff(call, 3) : retryAfter(eventProducerCadence);\n  }\n  throw e;\n}","preventionTips":["Run enrichment after event-producer publish windows, not on a fixed clock","Branch on failed_inputs (Redis) vs unavailable_inputs (producer) in one shared catch helper","Surface partial results when only enrichment fails, instead of dropping the base analysis"],"tags":["mcp","analysis","redis","data-source","events"],"backgroundTag":"data-source-unavailable","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}