koala73/worldmonitor · error · Error
Select exactly one scorecard bloc preset or custom member li
Error message
Select exactly one scorecard bloc preset or custom member list.
What it means
resolveBlocSelection in server/worldmonitor/scorecard/v1/_bloc-presets.ts enforces XOR selection: a caller must provide exactly one of a named bloc preset OR a custom member list. If both are provided (preset plus non-empty members), or neither is provided (no preset and empty members), it throws this error. Presets come from SCORECARD_BLOC_PRESETS (USMCA, EU27, ASEAN, BRICS, etc.); custom lists bypass the preset table entirely.
Source
Thrown at server/worldmonitor/scorecard/v1/_bloc-presets.ts:30
id: 'EU27',
label: 'European Union',
members: ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'],
},
BRICS: { id: 'BRICS', label: 'BRICS', members: ['BR', 'RU', 'IN', 'CN', 'ZA', 'SA', 'EG', 'AE', 'ET', 'ID', 'IR'] },
GCC: { id: 'GCC', label: 'Gulf Cooperation Council', members: ['AE', 'BH', 'KW', 'OM', 'QA', 'SA'] },
ASEAN: { id: 'ASEAN', label: 'ASEAN', members: ['BN', 'KH', 'ID', 'LA', 'MY', 'MM', 'PH', 'SG', 'TH', 'TL', 'VN'] },
NATO: {
id: 'NATO',
label: 'NATO',
members: ['AL', 'BE', 'BG', 'CA', 'HR', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IT', 'LV', 'LT', 'LU', 'ME', 'NL', 'MK', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'TR', 'GB', 'US'],
},
};
export function resolveBlocSelection(input: { preset?: string; members?: string[] }): ScorecardBlocSelection {
const preset = String(input.preset || '').trim();
const members = Array.isArray(input.members) ? input.members : [];
if (Boolean(preset) === (members.length > 0)) {
throw new Error('Select exactly one scorecard bloc preset or custom member list.');
}
if (preset) {
if (!(preset in SCORECARD_BLOC_PRESETS)) throw new Error(`Unknown scorecard bloc preset: ${preset}`);
return SCORECARD_BLOC_PRESETS[preset as ScorecardBlocPreset];
}
if (members.length < 2 || members.length > 30) throw new Error('Custom scorecard blocs require 2-30 members.');
if (members.some((member) => !/^[A-Z]{2}$/.test(member))) {
throw new Error('Custom scorecard bloc members must be uppercase ISO-2 codes.');
}
if (new Set(members).size !== members.length) throw new Error('Custom scorecard bloc members must be unique.');
if (members.some((member) => !isInRankableUniverse(member))) {
throw new Error('Custom scorecard bloc members must belong to the public rankable country universe.');
}
const sorted = [...members].sort();
return { id: `custom:${sorted.join('-')}`, label: sorted.join(' + '), members: sorted };
}
View on GitHub (pinned to 9361220cc0)
Solutions
- Send exactly one field: either { preset: '<name>' } or { members: [...] }, never both.
- If both are set client-side, clear the preset when the user picks custom members (and vice versa) before submitting.
- If no selection is intended, don't call the bloc scorecard at all rather than passing empty input.
- Guard with a pre-check: Boolean(preset) !== (members.length > 0) must hold before calling.
Example fix
// before
resolveBlocSelection({ preset: 'EU27', members: ['DE', 'FR'] }); // throws
// after
const input = members.length > 0 ? { members } : { preset };
resolveBlocSelection(input); Defensive patterns
Strategy: validation
Validate before calling
function canSubmitBlocSelection(sel) {
const preset = String(sel.preset || '').trim();
const count = Array.isArray(sel.members) ? sel.members.length : 0;
return Boolean(preset) !== (count > 0); // exactly one of the two
} Try / catch
try {
const bloc = await getBlocScorecard(selection);
} catch (err) {
if (err.message.includes('Select exactly one scorecard bloc preset or custom member list')) {
resetSelectionUi(); return badRequest('choose either a preset or custom members, not both/neither');
}
throw err;
} Prevention
- Clear preset when members are chosen and vice versa in the UI
- Treat whitespace-only preset as empty before submitting
- Never send members: [] alongside a preset
- Assert XOR selection in a unit test on the request builder
When it happens
Trigger: Calling getBlocScorecardWithReader / resolveBlocSelection with: (a) { preset: 'EU27', members: ['DE','FR'] } — both supplied; (b) { } or { preset: '', members: [] } — nothing selected; (c) { preset: ' ' } (whitespace-only trims to empty) with no members; (d) { members: [] } only.
Common situations: 1) A UI form keeps a previously selected preset in hidden state while the user also builds a custom list, sending both; 2) a script passes an empty object because its variables were unset; 3) whitespace-only preset strings that look set but trim to ''; 4) API clients send members: [] explicitly, which is treated as 'not selected' and combined with a preset — that is fine — but sending members plus preset is not.
Related errors
- Unknown scorecard bloc preset: ${preset}
- Custom scorecard blocs require 2-30 members.
- Custom scorecard bloc members must be unique.
- Custom scorecard bloc members must belong to the public rank
- Custom scorecard bloc members must be uppercase ISO-2 codes.
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/4c1cd738e7bccb90.
Report an issue: GitHub.