{"record":{"id":"6f5d38cf8bfb8b6c","repo":"koala73/worldmonitor","slug":"chokepointid-must-be-a-canonical-chokepoint-id","errorCode":null,"errorMessage":"chokepointId must be a canonical chokepoint id","messagePattern":"chokepointId must be a canonical chokepoint id","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/supply-chain/v1/get-chokepoint-dependencies.ts","lineNumber":31,"sourceCode":"  chokepointDependencyShardKey,\n  resolvePageSize,\n  enforceDependencyRedistributionPolicy,\n  hasCurrentRedistributionPolicy,\n  isMatchingShard,\n  locateEntityShard,\n  mapChokepointDependency,\n  type RawVulnerabilityCohort,\n  type RawChokepointShard,\n  stringValue,\n} from './_vulnerability-projection';\n\nexport async function getChokepointDependencies(\n  ctx: ServerContext,\n  req: GetChokepointDependenciesRequest,\n): Promise<GetChokepointDependenciesResponse> {\n  const chokepointId = (req.chokepointId || '').trim().toLowerCase();\n  if (!/^[a-z0-9_-]{1,80}$/.test(chokepointId)) {\n    throw new ValidationError([{ field: 'chokepointId', description: 'chokepointId must be a canonical chokepoint id' }]);\n  }\n\n  const persistedPayload = await getCachedJson(VULNERABILITY_COHORT_KEY, true)\n    .catch(() => null) as RawVulnerabilityCohort | null;\n  const payload = hasCurrentRedistributionPolicy(persistedPayload) ? persistedPayload : null;\n  let chokepoint = payload?.chokepoints?.[chokepointId];\n  let shardUnavailable = false;\n  if (payload && !payload.chokepoints) {\n    const located = locateEntityShard(\n      payload,\n      payload.chokepointIds,\n      chokepointId,\n      chokepointDependencyShardKey,\n    );\n    if (located.status === 'unavailable') {\n      shardUnavailable = true;\n    } else if (located.status === 'read') {\n      const shard = await getCachedJson(located.key, true)","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/supply-chain/v1/get-chokepoint-dependencies.ts#L13-L49","documentation":"getChokepointDependencies normalizes chokepointId to lowercase and requires it to match /^[a-z0-9_-]{1,80}$/ (a canonical chokepoint id like 'suez' or 'strait-of-hormuz'). Non-matching ids throw this ValidationError before any cache lookup.","triggerScenarios":"Passing display names ('Suez Canal'), ids with spaces or special characters, empty/missing chokepointId, or ids longer than 80 characters.","commonSituations":"UI passing human-readable labels instead of canonical ids; ids sourced from free text; uppercase ids (regex is lowercase-only after toLowerCase, but non-ASCII or symbols fail); stale ids from an old seed dataset.","solutions":["Use a canonical lowercase id (e.g. 'suez', 'panama', 'strait-of-hormuz') from the chokepoint seed/reference data","Sanitize: trim, lowercase, replace spaces with '-' before calling","Validate with /^[a-z0-9_-]{1,80}$/ on the client","Check the chokepoint exists in the vulnerability cohort payload before calling"],"exampleFix":"// before\nawait getChokepointDependencies({ chokepointId: 'Suez Canal' })\n// after\nconst id = String(raw).trim().toLowerCase().replace(/\\s+/g, '-');\nif (!/^[a-z0-9_-]{1,80}$/.test(id)) throw new Error('invalid chokepoint id');\nawait getChokepointDependencies({ chokepointId: id })","handlingStrategy":"validation","validationCode":"function isValidChokepointId(v) {\n  const id = String(v || '').trim().toLowerCase();\n  return /^[a-z0-9_-]{1,80}$/.test(id);\n}","typeGuard":"function isCanonicalChokepointId(v) {\n  return typeof v === 'string' && /^[a-z0-9_-]{1,80}$/.test(v);\n}","tryCatchPattern":"try {\n  return await getChokepointDependencies({ chokepointId });\n} catch (e) {\n  if (e.name === 'ValidationError' && e.field === 'chokepointId') {\n    return null;\n  }\n  throw e;\n}","preventionTips":["Source chokepoint ids from the canonical reference/seed data, never display labels","Normalize ids (trim, lowercase, dashes for spaces) at the UI boundary","Keep ids under 80 characters and ASCII-only","Validate the id exists in the cohort payload for a better UX than the throw"],"tags":["validation","input-validation","supply-chain"],"backgroundTag":"invalid-request-parameter","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}