{"record":{"id":"d5796f00babf79bb","repo":"koala73/worldmonitor","slug":"a-valid-country-code-is-required-for-a-resilience","errorCode":null,"errorMessage":"A valid country code is required for a resilience score generation","messagePattern":"A valid country code is required for a resilience score generation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/worldmonitor/resilience/v1/_shared.ts","lineNumber":1290,"sourceCode":"        imputationShare: 0,\n        dataVersion: '',\n        pillars: [],\n        schemaVersion: '1.0',\n        // Plan §U3: missing-cache fallback → not headline-eligible. A\n        // country without a successful score build can't make the\n        // PR 6 coverage gate either, so the conservative default is\n        // false even during the PR-2 \"true-by-default\" window.\n        headlineEligible: false,\n      };\n}\n\nexport async function ensureResilienceScoreGenerationCached(\n  countryCode: string,\n  reader?: ResilienceSeedReader,\n): Promise<{ score: GetResilienceScoreResponse; trace: ResilienceScoreTraceSidecar }> {\n  const normalizedCountryCode = normalizeCountryCode(countryCode);\n  if (!normalizedCountryCode) {\n    throw new Error('A valid country code is required for a resilience score generation');\n  }\n\n  await ensureResilienceScoreCached(normalizedCountryCode, reader);\n  const cached = await getCachedJson(scoreCacheKey(normalizedCountryCode)) as CachedScorePayload | null;\n  if (cached?._traceGenerationId && cached._traceCacheIdentity) {\n    const trace = await getCachedJson(\n      scoreTraceCacheKey(normalizedCountryCode, cached._traceGenerationId),\n    );\n    if (isMatchingTraceSidecar(trace, normalizedCountryCode, cached)) {\n      return {\n        score: await toPublicCachedScore(normalizedCountryCode, cached),\n        trace,\n      };\n    }\n  }\n\n  // Missing, evicted, or malformed trace references invalidate the complete\n  // generation. Rebuild score and trace together; never attach a fresh trace to","sourceCodeStart":1272,"sourceCodeEnd":1308,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/resilience/v1/_shared.ts#L1272-L1308","documentation":"Thrown by ensureResilienceScoreGenerationCached in server/worldmonitor/resilience/v1/_shared.ts when the country-code argument cannot be normalized into a 2-letter ISO 3166-1 alpha-2 code. normalizeCountryCode() trims, uppercases, and requires /^[A-Z]{2}$/; anything else (empty, null-ish, 'usa', 'us ', 'U1') yields '' and triggers this error. The generation path is strict because the cache key and trace sidecar are keyed off the normalized code.","triggerScenarios":"Calling ensureResilienceScoreGenerationCached('') , with undefined/null, with a 3-letter code like 'USA', with lowercase-only garbage that fails normalization, or with codes containing whitespace/numbers/dashes. Unlike ensureResilienceScoreCached (which returns a blank score), the *generation* wrapper throws.","commonSituations":"1) A caller passes an unvalidated user-supplied country string (e.g., from a URL query param or country name like 'Brazil'); 2) an upstream seed/manifest produces an empty code; 3) code refactors pass full country names or ISO-3 codes where ISO-2 is required.","solutions":["Validate and normalize the input first: trim, uppercase, and check /^[A-Z]{2}$/ before calling.","If you have a country name or ISO-3 code, map it to ISO-2 (lookup table) before invoking.","Ensure callers don't pass undefined when the field is optional — default to a valid code or skip the call.","For API entry points, reject invalid countryCode with a ValidationError at the request boundary, mirroring get-resilience-indicators.ts."],"exampleFix":"// before\nawait ensureResilienceScoreGenerationCached(userInput.country); // may be 'Brazil' or ''\n// after\nconst iso2 = String(userInput.country ?? '').trim().toUpperCase();\nif (!/^[A-Z]{2}$/.test(iso2)) throw new ValidationError([{ field: 'countryCode', description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code' }]);\nawait ensureResilienceScoreGenerationCached(iso2);","handlingStrategy":"validation","validationCode":"function toIso2(v) { const s = String(v ?? '').trim().toUpperCase(); return /^[A-Z]{2}$/.test(s) ? s : null; }\nconst cc = toIso2(input.country);\nif (!cc) throw new Error('countryCode must be a 2-letter ISO 3166-1 alpha-2 code');","typeGuard":"function isIso2(v: unknown): v is string {\n  return typeof v === 'string' && /^[A-Z]{2}$/.test(v.trim().toUpperCase());\n}","tryCatchPattern":"try {\n  await ensureResilienceScoreGenerationCached(cc);\n} catch (err) {\n  if (err.message.includes('A valid country code is required')) {\n    return badRequest({ field: 'countryCode', description: 'must be ISO-2' });\n  }\n  throw err;\n}","preventionTips":["Normalize (trim + uppercase) all country inputs at the boundary","Maintain a name/ISO-3 → ISO-2 lookup for user-supplied data","Never pass optional/possibly-undefined codes into the generation path","Add unit tests for '', 'usa', lowercase, and whitespace inputs"],"tags":["validation","country-code","iso3166","server"],"backgroundTag":"invalid-country-code","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}