{"record":{"id":"b4b7d0b0ca97c0d7","repo":"koala73/worldmonitor","slug":"invalid-kind-logical-id","errorCode":null,"errorMessage":"invalid ${kind} logical ID","messagePattern":"invalid (.+?) logical ID","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"shared/company-monitoring-contract.ts","lineNumber":351,"sourceCode":"\nexport function assertCompanyMonitoringAccountContext(\n  context: { ownerAccountId?: string } | null | undefined,\n): string {\n  const ownerAccountId = context?.ownerAccountId;\n  if (typeof ownerAccountId !== 'string' || !ownerAccountId.trim()) {\n    throw new Error('account context is required');\n  }\n  if (ownerAccountId !== ownerAccountId.trim() || !ACCOUNT_ID.test(ownerAccountId)) {\n    throw new Error('account context is invalid');\n  }\n  return ownerAccountId;\n}\n\nexport type CompanyMonitoringLogicalIdKind = keyof typeof LOGICAL_ID_PREFIXES;\n\nexport function assertLogicalId(kind: CompanyMonitoringLogicalIdKind, value: string): string {\n  const pattern = new RegExp(`^${LOGICAL_ID_PREFIXES[kind]}${ULID}$`);\n  if (typeof value !== 'string' || !pattern.test(value)) throw new Error(`invalid ${kind} logical ID`);\n  return value;\n}\n\nexport interface CompanyMonitoringConfidenceAxes {\n  attribution: number;\n  occurrenceTruth: number;\n  materialImpact: number;\n  overall: number;\n}\n\nexport function validateConfidenceAxes(\n  axes: CompanyMonitoringConfidenceAxes,\n): CompanyMonitoringConfidenceAxes {\n  for (const field of ['attribution', 'occurrenceTruth', 'materialImpact', 'overall'] as const) {\n    const value = axes?.[field];\n    if (!Number.isFinite(value) || value < 0 || value > 1) {\n      throw new Error(`${field} must be between 0 and 1`);\n    }","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/shared/company-monitoring-contract.ts#L333-L369","documentation":"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).","triggerScenarios":"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.","commonSituations":"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'.","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)"],"exampleFix":"// before\nassertLogicalId('company', company.id); // id was lowercased by a URL segment -> throws\n\n// after\n// Preserve canonical case end-to-end; if a round-trip is unavoidable, restore it:\nconst canonical = restoreCrockfordCase(company.id); // re-uppercase, map i/l/o/u if needed\nassertLogicalId('company', canonical);","handlingStrategy":"type-guard","validationCode":"const ULID = '[0-9A-HJKMNP-TV-Z]{26}';\nconst PREFIXES = { company: 'cm_company_', claim: 'cm_claim_', event: 'cm_event_', impact: 'cm_impact_', evidence: 'cm_evidence_' } as const;\nfunction isValidLogicalId(kind: keyof typeof PREFIXES, value: string): boolean {\n  return new RegExp(`^${PREFIXES[kind]}${ULID}$`).test(value);\n}","typeGuard":"function isCompanyLogicalId(value: unknown): value is string {\n  return typeof value === 'string' && /^cm_company_[0-9A-HJKMNP-TV-Z]{26}$/.test(value);\n}","tryCatchPattern":"catch (e) { if (e instanceof Error && e.message.includes('logical ID')) { refetchCanonicalIds(); } else throw e; }","preventionTips":["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"],"tags":["validation","identifier","ulid","company-monitoring"],"backgroundTag":"invalid-identifier-format","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}