koala73/worldmonitor · error · ValidationError

Unknown chokepointId: ${chokepointId}

Error message

Unknown chokepointId: ${chokepointId}

What it means

Validation error in getMultiSectorCostShock: a chokepointId was supplied (and lowercased) but does not match any chokepoint known to the model. Like other input-shape errors it returns 400 to preserve the legacy external API contract, separating caller bugs from the intentional PRO-gate empty-200 deny path.

Source

Thrown at server/worldmonitor/supply-chain/v1/get-multi-sector-cost-shock.ts:86

  req: GetMultiSectorCostShockRequest,
): Promise<GetMultiSectorCostShockResponse> {
  const iso2 = (req.iso2 ?? '').trim().toUpperCase();
  const chokepointId = (req.chokepointId ?? '').trim().toLowerCase();
  const closureDays = clampClosureDays(req.closureDays ?? 30);

  // Input-shape errors return 400 — restoring the legacy /api/supply-chain/v1/
  // multi-sector-cost-shock contract. Empty-payload-200 is reserved for the
  // PRO-gate deny path (intentional contract shift), not for caller bugs
  // (malformed or missing fields). Distinguishing the two matters for external
  // API consumers, tests, and silent-failure detection in logs.
  if (!/^[A-Z]{2}$/.test(iso2)) {
    throw new ValidationError([{ field: 'iso2', description: 'iso2 must be a 2-letter uppercase ISO country code' }]);
  }
  if (!chokepointId) {
    throw new ValidationError([{ field: 'chokepointId', description: 'chokepointId is required' }]);
  }
  if (!CHOKEPOINT_REGISTRY.some(c => c.id === chokepointId)) {
    throw new ValidationError([{ field: 'chokepointId', description: `Unknown chokepointId: ${chokepointId}` }]);
  }

  const isPro = await isCallerPremium(ctx.request);
  if (!isPro) return emptyResponse(iso2, chokepointId, closureDays);

  // Seeder writes the products payload via raw key (no env-prefix) — read raw.
  const productsKey = `comtrade:bilateral-hs4:${iso2}:v1`;
  const [productsCache, statusCache] = await Promise.all([
    getCachedJson(productsKey, true).catch(() => null) as Promise<CountryProductsCache | null>,
    getCachedJson(CHOKEPOINT_STATUS_KEY).catch(() => null) as Promise<{ chokepoints?: ChokepointInfo[] } | null>,
  ]);

  const products = Array.isArray(productsCache?.products) ? productsCache.products : [];
  const importsByHs2 = aggregateAnnualImportsByHs2(products);
  const hasAnyImports = Object.values(importsByHs2).some(v => v > 0);
  const warRiskTier = (statusCache?.chokepoints?.find(c => c.id === chokepointId)?.warRiskTier
    ?? 'WAR_RISK_TIER_NORMAL') as WarRiskTier;

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Use a supported chokepointId from the supply-chain chokepoint reference data
  2. Verify spelling: IDs are compared after lowercasing, so mixed case is fine but unknown names are rejected
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/worldmonitor/supply-chain/v1/get-multi-sector-cost-shock.ts:86 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/c4a33716cd64fa16. Report an issue: GitHub.