koala73/worldmonitor · error · ValidationError

iso2 must be a 2-letter uppercase country code

Error message

iso2 must be a 2-letter uppercase country code

What it means

Validation error in runScenario: the optional iso2 parameter was provided but does not match the 2-letter uppercase ISO 3166-1 alpha-2 pattern after trimming. It is a caller-input validation guard, not a server failure; the scenario cannot be scoped to an invalid country.

Solutions

  1. Send iso2 as exactly two uppercase letters (e.g. 'US', 'GB')
  2. Omit iso2 entirely to run the scenario without a country scope
  3. Trim whitespace before sending; lowercase codes like 'us' are rejected
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/worldmonitor/scenario/v1/run-scenario.ts:44 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/ee3c6bf679d24c70. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/scenario/v1/run-scenario.ts:53

  if (!scenarioId) {
    throw new ValidationError([{ field: 'scenarioId', description: 'scenarioId is required' }]);
  }
  const template = getScenarioTemplate(scenarioId);
  if (!template) {
    throw new ValidationError([{ field: 'scenarioId', description: `Unknown scenario: ${scenarioId}` }]);
  }

  const disruptionPct = req.disruptionPct;
  if (disruptionPct !== undefined && (
    !Number.isInteger(disruptionPct) || disruptionPct < 0 || disruptionPct > 100
    || template.affectedChokepointIds.length === 0
  )) {
    throw new ValidationError([{ field: 'disruptionPct', description: 'disruptionPct must be an integer from 0 to 100 for a physical scenario' }]);
  }

  const iso2 = req.iso2 ? req.iso2.trim() : '';
  if (iso2 && !/^[A-Z]{2}$/.test(iso2)) {
    throw new ValidationError([{ field: 'iso2', description: 'iso2 must be a 2-letter uppercase country code' }]);
  }

  // Queue-depth backpressure. Raw key: worker reads it unprefixed, so we must too.
  const [depthEntry] = await runRedisPipeline([['LLEN', QUEUE_KEY]], true);
  const depth = typeof depthEntry?.result === 'number' ? depthEntry.result : 0;
  if (depth > MAX_QUEUE_DEPTH) {
    throw new ApiError(429, 'Scenario queue is at capacity, please try again later', '');
  }

  const jobId = generateJobId();
  const payload = JSON.stringify({
    jobId,
    scenarioId,
    iso2: iso2 || null,
    disruptionPct,
    enqueuedAt: Date.now(),
  });

View on GitHub (pinned to 7d06c8633d)