koala73/worldmonitor · error · ValidationError
Invalid scorecard bloc selection.
Error message
Invalid scorecard bloc selection.
What it means
getBlocScorecard validates the bloc request via resolveBlocSelection, which enforces: exactly one of preset or members, preset must be one of USMCA/EU27/BRICS/GCC/ASEAN/NATO, custom members must be 2-30 unique uppercase ISO-2 codes from the rankable universe. Any resolveBlocSelection throw is wrapped in a ValidationError naming the offending field ('preset' or 'members').
Source
Thrown at server/worldmonitor/scorecard/v1/get-bloc-scorecard.ts:26
// @ts-expect-error — JS module, no declaration file
import { captureSilentError } from '../../../../api/_sentry-edge.js';
import { markNoStoreFallbackResponse } from '../../../_shared/response-headers';
import { resolveBlocSelection } from './_bloc-presets';
import { asFiveFactorSnapshot, readFiveFactorSnapshot, type ScorecardSnapshotReader } from './_read-snapshot';
import { toPublicBlocScorecard } from './_response';
import { scoreBloc } from './_score-bloc';
import type { CountryScorecardEvidence } from './_types';
export async function getBlocScorecardWithReader(
ctx: ServerContext,
req: GetBlocScorecardRequest,
reader: ScorecardSnapshotReader,
): Promise<GetBlocScorecardResponse> {
let selection;
try {
selection = resolveBlocSelection(req);
} catch (error) {
throw new ValidationError([{
field: req.preset ? 'preset' : 'members',
description: error instanceof Error ? error.message : 'Invalid scorecard bloc selection.',
}]);
}
let snapshotValue: unknown;
try {
snapshotValue = await reader(selection.members);
} catch (error) {
console.warn('[scorecard] snapshot read failed operation=get-bloc-scorecard', error instanceof Error ? error.message : 'unknown');
void captureSilentError(error, { tags: { route: 'scorecard/get-bloc-scorecard', step: 'snapshot-read' } });
return markNoStoreFallbackResponse(ctx.request, {
unavailable: true,
unavailableReason: 'scorecard-snapshot-unavailable',
});
}
const snapshot = asFiveFactorSnapshot(snapshotValue);
if (!snapshot) {
return markNoStoreFallbackResponse(ctx.request, {View on GitHub (pinned to 9361220cc0)
Solutions
- Send exactly one of preset or members, not both
- Use a preset from SCORECARD_BLOC_PRESETS: USMCA, EU27, BRICS, GCC, ASEAN, NATO (case-sensitive)
- For custom blocs, send 2-30 unique uppercase ISO-2 codes that are in the rankable universe
- Normalize client input: trim, uppercase, dedupe, and filter members before calling
Example fix
// before
getBlocScorecard({ preset: 'EU', members: ['DE'] })
// after
getBlocScorecard({ preset: 'EU27' })
// or custom:
getBlocScorecard({ members: ['DE', 'FR'] }) Defensive patterns
Strategy: validation
Validate before calling
const PRESETS = ['USMCA','EU27','BRICS','GCC','ASEAN','NATO'];
function validBlocInput(req) {
const hasPreset = Boolean(String(req.preset || '').trim());
const members = Array.isArray(req.members) ? req.members : [];
if (hasPreset === (members.length > 0)) return false;
if (hasPreset) return PRESETS.includes(req.preset.trim());
const uniq = [...new Set(members.map((m) => String(m).trim().toUpperCase()))];
return uniq.length >= 2 && uniq.length <= 30 && uniq.every((m) => /^[A-Z]{2}$/.test(m));
} Type guard
function isBlocRequest(req) {
return typeof req === 'object' && req !== null &&
(typeof req.preset === 'string' || Array.isArray(req.members));
} Try / catch
try {
const res = await getBlocScorecard(req);
} catch (e) {
if (e.name === 'ValidationError' && (e.field === 'preset' || e.field === 'members')) {
// surface e.description, correct the bloc selection
} else throw e;
} Prevention
- Always send exactly one of preset or members
- Use preset names copied from SCORECARD_BLOC_PRESETS, not ad-hoc abbreviations
- Uppercase, dedupe, and rankable-universe-filter custom members client-side
- Keep member lists between 2 and 30 entries
When it happens
Trigger: Passing both preset and members; passing neither; unknown preset string (e.g. 'EU' or 'brics' lowercase); custom members with <2 or >30 entries; non-uppercase or non-2-letter member codes; duplicate members; countries outside the rankable universe.
Common situations: Client UI sending both a preset and a stale members array; hardcoded preset names that drift from SCORECARD_BLOC_PRESETS; lowercase country codes from user input; including countries like 'HK' or 'TW' that are not in the rankable universe.
Related errors
- countryCode must be a 2-letter ISO 3166-1 alpha-2 code
- chokepointId must be a canonical chokepoint id
- iso2 must be a 2-letter uppercase ISO country code
- get_intel_timeline requires at least one of domain ("conflic
- INCOMPATIBLE_DELIVERY
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/56b9eb672be0e80b.
Report an issue: GitHub.