{"record":{"id":"92248de8ab0a429b","repo":"koala73/worldmonitor","slug":"bls-canonical-key-series-req-seriesid","errorCode":null,"errorMessage":"${BLS_CANONICAL_KEY} series ${req.seriesId}","messagePattern":"(.+?) series (.+?)","errorType":"exception","errorClass":"SeedUnavailableError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/economic/v1/get-bls-series.ts","lineNumber":70,"sourceCode":"): Promise<GetBlsSeriesResponse> {\n  if (!req.seriesId) return { series: undefined };\n  if (!KNOWN_SERIES_IDS.has(req.seriesId)) return { series: undefined };\n\n  const seeded = await readRequiredSeed(BLS_CANONICAL_KEY, value => {\n    const data = value as { series?: unknown } | null;\n    return Array.isArray(data?.series) ? (data.series as unknown[]) : undefined;\n  });\n\n  // A known series absent from — or malformed within — a valid seed is\n  // unavailable, not empty: the seeder refuses a partial cohort, so this only\n  // fires on drift between the published ids and the contract, or on a\n  // corrupted envelope. Name the series so the 503 log does not blame the\n  // healthy canonical key.\n  const series = seeded.find(\n    (entry): entry is BlsSeries => isServableSeries(entry) && entry.seriesId === req.seriesId,\n  );\n  if (!series) {\n    throw new SeedUnavailableError(`${BLS_CANONICAL_KEY} series ${req.seriesId}`);\n  }\n\n  const limit = normalizeLimit(req.limit);\n  const obs = series.observations;\n  const sliced = obs.length > limit ? obs.slice(-limit) : obs;\n\n  return { series: { ...series, observations: sliced } };\n}\n","sourceCodeStart":52,"sourceCodeEnd":79,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/economic/v1/get-bls-series.ts#L52-L79","documentation":"getBlsSeries serves BLS series from a Redis seed cache. If no servable (valid-envelope) series matching req.seriesId is found in the seed, it throws SeedUnavailableError naming BLS_CANONICAL_KEY and the series — deliberately distinguishing seed-cache unavailability/corruption from errors blamed on a healthy canonical key (per #3707 context).","triggerScenarios":"Requesting a seriesId absent from the seed cache; the seed Redis key holding a corrupt/unparseable envelope so isServableSeries fails for every entry; Redis seed never populated (worker down, seed job skipped).","commonSituations":"Typo'd or retired series ID; expired/evicted Redis seed data; seed-meta writer failing so the envelope is stale or malformed; fresh deployment before the first seed run.","solutions":["Verify the seriesId exists in the seed (inspect the Redis seed key / seed-meta)","Run the seed worker to (re)populate BLS_CANONICAL_KEY","Check server logs for a corrupt envelope and repair the seed entry","Retry after seeding; if it persists, treat the seed as unavailable and surface a 503 for this series"],"exampleFix":"// before\nconst data = await getBlsSeries({ seriesId: 'CUUR0000SA0' }); // seed empty -> SeedUnavailableError\n// after\nawait runBlsSeedWorker(); // repopulate BLS_CANONICAL_KEY first\nconst data = await getBlsSeries({ seriesId: 'CUUR0000SA0' });","handlingStrategy":"try-catch","validationCode":"const meta = await redis.get(`seed-meta:${BLS_CANONICAL_KEY}`);\nif (!meta) throw new Error('BLS seed not populated');","typeGuard":"const isServable = (entry) => entry != null && typeof entry === 'object' && typeof entry.seriesId === 'string' && Array.isArray(entry.observations);","tryCatchPattern":"try { return await getBlsSeries(req); } catch (e) { if (e instanceof SeedUnavailableError) { log.warn('BLS seed unavailable', { seriesId: req.seriesId }); return respond503(); } throw e; }","preventionTips":["Ensure the BLS seed worker runs before serving traffic","Monitor seed-meta:<key> freshness and alert on staleness","Validate seed envelopes at write time (isServableSeries)","Keep a known-good series ID list in tests to detect seed drift"],"tags":["cache","redis","seed","availability"],"backgroundTag":"seed-cache-unavailable","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}