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

  1. Use IDs exactly as issued by the company-monitoring API — never re-serialize, lowercase, or trim them
  2. When generating, produce an uppercase Crockford ULID (standard ulid package output) and prepend the exact kind prefix: 'cm_company_' + ulid()
  3. 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

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


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