{"record":{"id":"d839a7dbb5727dbf","repo":"koala73/worldmonitor","slug":"company-monitoring-field-invalid","errorCode":null,"errorMessage":"COMPANY_MONITORING_${field}_INVALID","messagePattern":"COMPANY_MONITORING_(.+?)_INVALID","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/admission.ts","lineNumber":28,"sourceCode":"  COMPANY_MONITORING_RETRY_POLICY,\n  COMPANY_MONITORING_SOURCE_POLICY_VERSION,\n  evaluateCompanyMonitoringClassifierTransportFailure,\n  evaluateCompanyMonitoringClassification,\n} from \"../../scripts/lib/company-monitoring-classification.mjs\";\nimport { fingerprint, randomFence } from \"./_shared\";\nimport { assertValidCandidateState } from \"./validators\";\nimport {\n  companyMonitoringCandidateEvidenceSnapshotDigest as candidateEvidenceSnapshotDigest,\n  companyMonitoringEvidenceShape as evidenceShape,\n} from \"./admissionSnapshot\";\n\nconst ADMISSION_LEASE_MS = 5 * 60 * 1000;\nconst ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;\nconst ADMISSION_MODEL_VERSION = /^[^\\u0000-\\u001f\\u007f]{1,200}$/u;\n\nfunction admissionIdentifier(value: string, field: string) {\n  if (!ADMISSION_ID.test(value)) {\n    throw new ConvexError(`COMPANY_MONITORING_${field}_INVALID`);\n  }\n  return value;\n}\n\nfunction admissionModelVersion(value: string) {\n  if (\n    value !== value.trim() ||\n    !ADMISSION_MODEL_VERSION.test(value)\n  ) {\n    throw new ConvexError(\"COMPANY_MONITORING_MODEL_VERSION_INVALID\");\n  }\n  return value;\n}\n\nfunction canonicalValue(value: unknown): unknown {\n  if (Array.isArray(value)) return value.map(canonicalValue);\n  if (value && typeof value === \"object\") {\n    const row = value as Record<string, unknown>;","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/companyMonitoring/admission.ts#L10-L46","documentation":"Thrown by admissionIdentifier() in the company-monitoring classification admission mutation (convex/companyMonitoring/admission.ts:28). Worker-supplied identifiers are interpolated into the message, so the actual codes are COMPANY_MONITORING_ADMISSION_WORKER_ID_INVALID, COMPANY_MONITORING_ADMISSION_LEASE_INVALID, and COMPANY_MONITORING_CLASSIFICATION_RUN_ID_INVALID. An id is rejected unless it matches /^[A-Za-z0-9._:-]{1,128}$/ (non-empty, max 128 chars, restricted alphabet).","triggerScenarios":"Calling the internal admission mutation with a workerId, leaseToken, or classificationRunId that is empty, longer than 128 characters, or contains characters outside A-Z a-z 0-9 . _ : - . Concrete hits: a run id built as `run/${uuid}` (slash), a lease token copied from a log with a trailing newline, a workerId like \"worker (pod-1)\" with spaces, or an undefined value coerced to the string \"undefined\".","commonSituations":"Worker code composing ids from free-form template strings or external payloads; copying lease tokens out of logs/JSON with whitespace; a dependency upgrade changing id alphabets (e.g. base64 ids containing + or =); concatenating fields until the id exceeds 128 chars.","solutions":["Sanitize the id right before the call: trim whitespace and re-encode into the allowed alphabet (e.g. base64url with + -> -, / -> _, padding stripped)","Generate workerId/classificationRunId from the same helper the server uses (randomFence in convex/companyMonitoring/_shared.ts) so alphabets can never diverge","If an upstream id is longer than 128 chars or uses a wider alphabet, hash it (sha256 hex) before sending","Add a unit test asserting your worker's id generator output always matches /^[A-Za-z0-9._:-]{1,128}$/"],"exampleFix":"// before\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {\n  workerId: `worker/${process.env.POD_NAME}`,\n  classificationRunId: `run-${crypto.randomUUID()}`,\n  ...\n});\n\n// after\nconst ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;\nfunction toAdmissionId(value: string): string {\n  const encoded = value.trim().replace(/[^A-Za-z0-9._:-]/g, \"-\").slice(0, 128);\n  if (!ADMISSION_ID.test(encoded)) throw new Error(`unencodable id: ${value}`);\n  return encoded;\n}\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {\n  workerId: toAdmissionId(`worker-${process.env.POD_NAME}`),\n  classificationRunId: toAdmissionId(`run-${crypto.randomUUID()}`),\n  ...\n});","handlingStrategy":"validation","validationCode":"const ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;\nfunction toAdmissionId(value: string, field: string): string {\n  const cleaned = value.trim().replace(/[^A-Za-z0-9._:-]/g, \"-\").slice(0, 128);\n  if (!ADMISSION_ID.test(cleaned)) {\n    throw new Error(`${field} cannot be encoded to a valid admission id: ${JSON.stringify(value)}`);\n  }\n  return cleaned;\n}\n// before the mutation:\nconst workerId = toAdmissionId(rawWorkerId, \"workerId\");\nconst leaseToken = toAdmissionId(rawLeaseToken, \"leaseToken\");\nconst classificationRunId = toAdmissionId(rawRunId, \"classificationRunId\");","typeGuard":"function isAdmissionIdentifier(value: unknown): value is string {\n  return typeof value === \"string\" && /^[A-Za-z0-9._:-]{1,128}$/.test(value);\n}","tryCatchPattern":"try {\n  await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);\n} catch (err) {\n  if (err instanceof ConvexError && /^COMPANY_MONITORING_.*_INVALID$/.test(String(err.data))) {\n    logger.error(\"admission argument rejected\", { code: err.data, args: redactIds(args) });\n    return; // do not retry: fix the id generation first\n  }\n  throw err;\n}","preventionTips":["Generate worker ids with the server's own helper (randomFence) so alphabets match by construction","Never build ids from unvalidated external input; encode or hash first","Assert the id regex in worker startup self-checks before any classification runs","Keep a unit test locking your id generator to the allowed alphabet and 128-char cap"],"tags":["convex","validation","internal-mutation","worker","identifier"],"backgroundTag":"identifier-validation-failed","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}