{"record":{"id":"a17d9113da528ca2","repo":"koala73/worldmonitor","slug":"conflict-seed-unavailable","errorCode":null,"errorMessage":"Conflict seed unavailable","messagePattern":"Conflict seed unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"api/embed/map-frame.ts","lineNumber":78,"sourceCode":"/** Build the generated handler context for an in-process source read. */\nfunction handlerContext(req: Request) {\n  return { request: req, pathParams: {}, headers: Object.fromEntries(req.headers) };\n}\n\nfunction buildSources(req: Request): EmbedMapFrameSources {\n  const ctx = handlerContext(req);\n  // Each upstream is called with ITS OWN defaults — zeros mean \"handler\n  // default\" across these generated requests. Forwarding a caller-supplied\n  // window or page size here would hand a stolen credential the knobs this\n  // endpoint exists to remove.\n  return {\n    listConflicts: async () => {\n      // Isolate this source's fallback signal from the other parallel readers.\n      const conflictReq = new Request(req);\n      const response = await listAcledEvents(handlerContext(conflictReq), { start: 0, end: 0, pageSize: 0, cursor: '', country: '' });\n      if (drainResponseHeaders(conflictReq)?.['X-No-Cache']) {\n        markNoCacheResponse(req);\n        throw new Error('Conflict seed unavailable');\n      }\n      return response.events;\n    },\n    listEarthquakes: async () =>\n      (await listEarthquakes(ctx, { minMagnitude: 0, start: 0, end: 0, pageSize: 0, cursor: '' })).earthquakes,\n    listNaturalEvents: async () => (await listNaturalEvents(ctx, { days: 30 })).events,\n    listProtests: async () =>\n      (await listUnrestEvents(ctx, {\n        country: '',\n        start: 0,\n        end: 0,\n        pageSize: 0,\n        cursor: '',\n        // The handler reads neither the bbox nor minSeverity; these are here\n        // only to satisfy the generated request type. Kept at their zero values\n        // so that if it ever starts reading them, the embed asks for no filter\n        // rather than silently inheriting one.\n        minSeverity: 'SEVERITY_LEVEL_UNSPECIFIED',","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/embed/map-frame.ts#L60-L96","documentation":"buildSources' listConflicts seed reader calls the internal ACLED listAcledEvents handler and, if the response is flagged X-No-Cache (the handler's signal that upstream data could not be fetched and no cached fallback exists), it throws 'Conflict seed unavailable'. The map-frame embed aborts rather than rendering a stale or empty conflict layer.","triggerScenarios":"Rendering /api/embed/map-frame while the ACLED upstream fails or is rate-limited and no cached conflict data exists, causing listAcledEvents to respond with X-No-Cache. Only affects the conflicts layer; earthquakes/natural events have separate readers.","commonSituations":"ACLED API key missing or expired; ACLED rate limit hit on a busy embed; upstream outage during a cold cache; Redis cache eviction leaving no seed.","solutions":["Verify the ACLED upstream/API key is valid and within rate limits","Warm the conflict cache (seed job) so the embed can serve cached data","Retry the embed after the ACLED upstream recovers — the failure is per-request","Optionally degrade the embed UI to hide the conflicts layer instead of failing the whole frame"],"exampleFix":"// before\nconst frame = await fetch('/api/embed/map-frame'); // 500 on conflict-seed failure\n// after\nconst res = await fetch('/api/embed/map-frame');\nif (!res.ok) renderFrameWithoutConflicts(await res.text().catch(() => ''));","handlingStrategy":"fallback","validationCode":"// before rendering the embed, confirm the conflict feed is warm\nconst health = await fetch('/api/health'); // check the acled/conflict standalone health key first","typeGuard":"null","tryCatchPattern":"try { frame = await renderMapFrame(); } catch (e) { if (e.message === 'Conflict seed unavailable') return renderMapFrameWithoutConflicts(); throw e; }","preventionTips":["Keep ACLED credentials valid and monitor their rate-limit headroom","Schedule cache seed/refresh jobs so a cold cache never coincides with upstream failure","Per-layer degradation: let one data source failing not kill the whole embed","Alert on X-No-Cache headers from the ACLED handler"],"tags":["upstream","cache","acled","embed"],"backgroundTag":"upstream-api-error","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"}