koala73/worldmonitor · error · Error
invalid ${kind} logical ID
Error message
invalid ${kind} logical ID What it means
Thrown by assertLogicalId() when a value does not match the strict logical-ID format for its kind. Each kind (company, claim, event, impact, evidence) must be '<prefix><ULID>' where prefix is cm_company_, cm_claim_, cm_event_, cm_impact_, or cm_evidence_ and ULID is exactly 26 uppercase Crockford-base32 characters ([0-9A-HJKMNP-TV-Z], so no I, L, O, U).
Source
Thrown at shared/company-monitoring-contract.ts:351
export function assertCompanyMonitoringAccountContext(
context: { ownerAccountId?: string } | null | undefined,
): string {
const ownerAccountId = context?.ownerAccountId;
if (typeof ownerAccountId !== 'string' || !ownerAccountId.trim()) {
throw new Error('account context is required');
}
if (ownerAccountId !== ownerAccountId.trim() || !ACCOUNT_ID.test(ownerAccountId)) {
throw new Error('account context is invalid');
}
return ownerAccountId;
}
export type CompanyMonitoringLogicalIdKind = keyof typeof LOGICAL_ID_PREFIXES;
export function assertLogicalId(kind: CompanyMonitoringLogicalIdKind, value: string): string {
const pattern = new RegExp(`^${LOGICAL_ID_PREFIXES[kind]}${ULID}$`);
if (typeof value !== 'string' || !pattern.test(value)) throw new Error(`invalid ${kind} logical ID`);
return value;
}
export interface CompanyMonitoringConfidenceAxes {
attribution: number;
occurrenceTruth: number;
materialImpact: number;
overall: number;
}
export function validateConfidenceAxes(
axes: CompanyMonitoringConfidenceAxes,
): CompanyMonitoringConfidenceAxes {
for (const field of ['attribution', 'occurrenceTruth', 'materialImpact', 'overall'] as const) {
const value = axes?.[field];
if (!Number.isFinite(value) || value < 0 || value > 1) {
throw new Error(`${field} must be between 0 and 1`);
}View on GitHub (pinned to eeab0a219f)
Solutions
- Use IDs exactly as issued by the company-monitoring API — never re-serialize, lowercase, or trim them
- When generating, produce an uppercase Crockford ULID (standard ulid package output) and prepend the exact kind prefix: 'cm_company_' + ulid()
- Pre-test with the same pattern before the call: new RegExp('^cm_company_[0-9A-HJKMNP-TV-Z]{26}$').test(value)
Example fix
// before
assertLogicalId('company', company.id); // id was lowercased by a URL segment -> throws
// after
// Preserve canonical case end-to-end; if a round-trip is unavoidable, restore it:
const canonical = restoreCrockfordCase(company.id); // re-uppercase, map i/l/o/u if needed
assertLogicalId('company', canonical); Defensive patterns
Strategy: type-guard
Validate before calling
const ULID = '[0-9A-HJKMNP-TV-Z]{26}';
const PREFIXES = { company: 'cm_company_', claim: 'cm_claim_', event: 'cm_event_', impact: 'cm_impact_', evidence: 'cm_evidence_' } as const;
function isValidLogicalId(kind: keyof typeof PREFIXES, value: string): boolean {
return new RegExp(`^${PREFIXES[kind]}${ULID}$`).test(value);
} Type guard
function isCompanyLogicalId(value: unknown): value is string {
return typeof value === 'string' && /^cm_company_[0-9A-HJKMNP-TV-Z]{26}$/.test(value);
} Try / catch
catch (e) { if (e instanceof Error && e.message.includes('logical ID')) { refetchCanonicalIds(); } else throw e; } Prevention
- Pass server-issued IDs through verbatim — no lowercasing, trimming, or case-insensitive storage
- If IDs travel through URLs, re-canonicalize case on the way out
- Generate with the ulid package and prepend the exact kind prefix
When it happens
Trigger: Passing a bare ULID without the cm_company_ prefix; a lowercase or otherwise non-canonical ULID (e.g. generated with a library that emits lowercase); passing an ID of the wrong kind (an cm_event_ ID where a company kind is asserted); a truncated 25-character ID or a UUID from another system.
Common situations: Client code generating IDs with a different ULID library or normalization (case-folding in a URL path or query param is a classic); storing IDs in a database column with case-insensitive collation; copy-pasting a claim ID into a company field; hand-written test fixtures with placeholder IDs like 'test-company-1'.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- COMPANY_MONITORING_${field}_INVALID
- Unknown chokepoint ID: ${invalidCp}
- batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} row
- batch rows must share one clientImportId
- batch ordinals must be contiguous from 0
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/b4b7d0b0ca97c0d7.
Report an issue: GitHub.