{"record":{"id":"9c2c9a3e6decf9c0","repo":"koala73/worldmonitor","slug":"company-monitoring-evidence-revision-invalid","errorCode":"COMPANY_MONITORING_EVIDENCE_REVISION_INVALID","errorMessage":"COMPANY_MONITORING_EVIDENCE_REVISION_INVALID","messagePattern":"COMPANY_MONITORING_EVIDENCE_REVISION_INVALID","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/admission.ts","lineNumber":422,"sourceCode":"    companyId: string;\n    occurrenceDedupeKey: string;\n    expectedEvidenceRevision: number;\n    classificationRunId: string;\n    requestedModelVersion: string;\n    modelVersion: string;\n    modelOutput?: unknown;\n  },\n) {\n  const workerId = admissionIdentifier(args.workerId, \"ADMISSION_WORKER_ID\");\n  const leaseToken = admissionIdentifier(args.leaseToken, \"ADMISSION_LEASE\");\n  const classificationRunId = admissionIdentifier(\n    args.classificationRunId,\n    \"CLASSIFICATION_RUN_ID\",\n  );\n  const modelVersion = admissionModelVersion(args.modelVersion);\n  const requestedModelVersion = admissionModelVersion(args.requestedModelVersion);\n  if (!Number.isSafeInteger(args.expectedEvidenceRevision) || args.expectedEvidenceRevision < 1) {\n    throw new ConvexError(\"COMPANY_MONITORING_EVIDENCE_REVISION_INVALID\");\n  }\n  const replay = await ctx.db\n    .query(\"companyMonitoringAdmissionDecisions\")\n    .withIndex(\"by_replay_fence\", (q) =>\n      q\n        .eq(\"ownerAccountId\", args.ownerAccountId)\n        .eq(\"companyId\", args.companyId)\n        .eq(\"occurrenceDedupeKey\", args.occurrenceDedupeKey)\n        .eq(\"evidenceRevision\", args.expectedEvidenceRevision)\n        .eq(\"classificationRunId\", classificationRunId),\n    )\n    .unique();\n  if (replay) {\n    const submissionDigest = await fingerprint(canonicalValue({\n      requestedModelVersion,\n      modelVersion,\n      modelOutput: args.modelOutput,\n    }));","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/companyMonitoring/admission.ts#L404-L440","documentation":"Thrown by the classification admission mutation (convex/companyMonitoring/admission.ts:422) when args.expectedEvidenceRevision is not a safe integer or is less than 1. The revision identifies which version of the evidence set the classification ran against and is used as a replay-fence key, so it must be a positive integer within Number.MAX_SAFE_INTEGER.","triggerScenarios":"Passing 0 or a negative number; passing a float like 1.0 computed from arithmetic that yields 0.999...; passing NaN from parseInt(undefined) or Number(null); passing a revision larger than 2^53 - 1; serializing the revision as a string and back incorrectly.","commonSituations":"Client code computing the revision (incrementing a local counter) instead of reading candidate.evidenceRevision; a JSON pipeline converting numbers through floats; defaults like `revision ?? 0`; copy-pasting a fence/revision from a different flow that uses tokens.","solutions":["Always source expectedEvidenceRevision from candidate.evidenceRevision read in the same run — never generate it client-side","Guard before the call: Number.isSafeInteger(revision) && revision >= 1","Check for NaN-producing parsing (parseInt without radix on undefined input)","If you store revisions in your own queue, type them as number, not string, to avoid coercion surprises"],"exampleFix":"// before\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {\n  expectedEvidenceRevision: parseInt(args.revisionFromQueue),\n  ...\n});\n\n// after\nfunction evidenceRevisionOrThrow(value: unknown): number {\n  const n = typeof value === \"number\" ? value : Number(value);\n  if (!Number.isSafeInteger(n) || n < 1) {\n    throw new Error(`expectedEvidenceRevision must be a safe integer >= 1, got ${String(value)}`);\n  }\n  return n;\n}\nawait ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {\n  expectedEvidenceRevision: evidenceRevisionOrThrow(candidate.evidenceRevision),\n  ...\n});","handlingStrategy":"validation","validationCode":"function isValidEvidenceRevision(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1;\n}\nif (!isValidEvidenceRevision(args.expectedEvidenceRevision)) {\n  throw new Error(`expectedEvidenceRevision must be a safe integer >= 1, got ${String(args.expectedEvidenceRevision)}`);\n}","typeGuard":"function isValidEvidenceRevision(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1;\n}","tryCatchPattern":null,"preventionTips":["Source expectedEvidenceRevision only from candidate.evidenceRevision, never compute it client-side","Type revision fields as number end-to-end to avoid string coercion","Parse external numbers with Number() and validate before use","Reject 0/NaN defaults loudly in your own queue schema"],"tags":["convex","validation","revision","safe-integer","internal-mutation"],"backgroundTag":"revision-number-invalid","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"}