{"record":{"id":"ea7c3d2be42ad69e","repo":"koala73/worldmonitor","slug":"company-monitoring-model-version-invalid","errorCode":"COMPANY_MONITORING_MODEL_VERSION_INVALID","errorMessage":"COMPANY_MONITORING_MODEL_VERSION_INVALID","messagePattern":"COMPANY_MONITORING_MODEL_VERSION_INVALID","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/admission.ts","lineNumber":38,"sourceCode":"} 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>;\n    return Object.fromEntries(\n      Object.keys(row).sort().map((key) => [key, canonicalValue(row[key])]),\n    );\n  }\n  return value;\n}\n\nfunction admissionTerminalReason(decision: string) {\n  if (decision === \"publish\") return \"admitted\" as const;\n  if (decision === \"reject\") return \"rejected\" as const;","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/companyMonitoring/admission.ts#L20-L56","documentation":"Thrown by admissionModelVersion() (convex/companyMonitoring/admission.ts:38) for both the modelVersion and requestedModelVersion arguments of the classification admission mutation. A version string is rejected when it differs from its own trim() (leading/trailing whitespace) or fails /^[^\\u0000-\\u001f\\u007f]{1,200}$/u — i.e. empty, longer than 200 characters, or containing control characters.","triggerScenarios":"Passing a model version read from an env var or config file that carries a trailing \\n; passing \"\" because the env var was never set and the default is empty; a version string like \"claude-3-5-sonnet\\t20241022\"; an extremely long version built from git describe plus build metadata exceeding 200 chars.","commonSituations":"MODEL_VERSION sourced from dotenv/CI secrets that silently include newlines; a deploy that changes version format (semver to full build fingerprint) pushing length past 200; copy-pasting a version from a release page with a non-breaking space or control character; upgrade scripts that leave the variable unset.","solutions":["Trim the version and validate it against the same regex before invoking the mutation: value === value.trim() && /^[^\\u0000-\\u001f\\u007f]{1,200}$/u.test(value)","Fail fast at worker startup when the model version env var is missing or empty, so the mutation never sees \"\"","Cap the version string to a short logical tag (e.g. \"gpt-4o-2024-08-06\") instead of full build metadata","Add a preflight assertion in the worker config loader that runs the server regex on every deploy"],"exampleFix":"// before\nconst modelVersion = process.env.MODEL_VERSION; // may be \"gpt-4o\\n\" or \"\"\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, { modelVersion, ... });\n\n// after\nconst MODEL_VERSION_RE = /^[^\\u0000-\\u001f\\u007f]{1,200}$/u;\nfunction admissionModelVersion(value: string): string {\n  const trimmed = value.trim();\n  if (value !== trimmed || !MODEL_VERSION_RE.test(trimmed)) {\n    throw new Error(`MODEL_VERSION invalid: ${JSON.stringify(value)}`);\n  }\n  return trimmed;\n}\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {\n  modelVersion: admissionModelVersion(process.env.MODEL_VERSION ?? \"\"),\n  ...\n});","handlingStrategy":"validation","validationCode":"const MODEL_VERSION_RE = /^[^\\u0000-\\u001f\\u007f]{1,200}$/u;\nfunction assertModelVersion(value: string): string {\n  const trimmed = value.trim();\n  if (value !== trimmed || !MODEL_VERSION_RE.test(trimmed)) {\n    throw new Error(`model version invalid: ${JSON.stringify(value)}`);\n  }\n  return trimmed;\n}\nconst modelVersion = assertModelVersion(process.env.MODEL_VERSION ?? \"\");\nconst requestedModelVersion = assertModelVersion(config.requestedModelVersion);","typeGuard":"function isAdmissionModelVersion(value: unknown): value is string {\n  return (\n    typeof value === \"string\" &&\n    value === value.trim() &&\n    /^[^\\u0000-\\u001f\\u007f]{1,200}$/u.test(value)\n  );\n}","tryCatchPattern":"try {\n  await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);\n} catch (err) {\n  if (err instanceof ConvexError && err.data === \"COMPANY_MONITORING_MODEL_VERSION_INVALID\") {\n    failFastAtStartup(`MODEL_VERSION malformed: ${JSON.stringify(args.modelVersion)}`);\n  }\n  throw err;\n}","preventionTips":["Trim and validate the model version once at worker startup, not per request","Fail startup when the version env var is missing or empty","Use short logical version tags, not full build fingerprints","Run the server regex against the version in CI for every deploy"],"tags":["convex","validation","model-version","whitespace","internal-mutation"],"backgroundTag":"version-string-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"}