koala73/worldmonitor · error · ConvexError

COMPANY_MONITORING_EVIDENCE_REVISION_INVALID

COMPANY_MONITORING_EVIDENCE_REVISION_INVALID

Error message

COMPANY_MONITORING_EVIDENCE_REVISION_INVALID

What it means

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.

Solutions

  1. Always source expectedEvidenceRevision from candidate.evidenceRevision read in the same run — never generate it client-side
  2. Guard before the call: Number.isSafeInteger(revision) && revision >= 1
  3. Check for NaN-producing parsing (parseInt without radix on undefined input)
  4. If you store revisions in your own queue, type them as number, not string, to avoid coercion surprises

Example fix

// before
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {
  expectedEvidenceRevision: parseInt(args.revisionFromQueue),
  ...
});

// after
function evidenceRevisionOrThrow(value: unknown): number {
  const n = typeof value === "number" ? value : Number(value);
  if (!Number.isSafeInteger(n) || n < 1) {
    throw new Error(`expectedEvidenceRevision must be a safe integer >= 1, got ${String(value)}`);
  }
  return n;
}
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {
  expectedEvidenceRevision: evidenceRevisionOrThrow(candidate.evidenceRevision),
  ...
});
Defensive patterns

Strategy: validation

Validate before calling

function isValidEvidenceRevision(value: unknown): value is number {
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
}
if (!isValidEvidenceRevision(args.expectedEvidenceRevision)) {
  throw new Error(`expectedEvidenceRevision must be a safe integer >= 1, got ${String(args.expectedEvidenceRevision)}`);
}

Type guard

function isValidEvidenceRevision(value: unknown): value is number {
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

Thrown at convex/companyMonitoring/admission.ts:422

    companyId: string;
    occurrenceDedupeKey: string;
    expectedEvidenceRevision: number;
    classificationRunId: string;
    requestedModelVersion: string;
    modelVersion: string;
    modelOutput?: unknown;
  },
) {
  const workerId = admissionIdentifier(args.workerId, "ADMISSION_WORKER_ID");
  const leaseToken = admissionIdentifier(args.leaseToken, "ADMISSION_LEASE");
  const classificationRunId = admissionIdentifier(
    args.classificationRunId,
    "CLASSIFICATION_RUN_ID",
  );
  const modelVersion = admissionModelVersion(args.modelVersion);
  const requestedModelVersion = admissionModelVersion(args.requestedModelVersion);
  if (!Number.isSafeInteger(args.expectedEvidenceRevision) || args.expectedEvidenceRevision < 1) {
    throw new ConvexError("COMPANY_MONITORING_EVIDENCE_REVISION_INVALID");
  }
  const replay = await ctx.db
    .query("companyMonitoringAdmissionDecisions")
    .withIndex("by_replay_fence", (q) =>
      q
        .eq("ownerAccountId", args.ownerAccountId)
        .eq("companyId", args.companyId)
        .eq("occurrenceDedupeKey", args.occurrenceDedupeKey)
        .eq("evidenceRevision", args.expectedEvidenceRevision)
        .eq("classificationRunId", classificationRunId),
    )
    .unique();
  if (replay) {
    const submissionDigest = await fingerprint(canonicalValue({
      requestedModelVersion,
      modelVersion,
      modelOutput: args.modelOutput,
    }));

View on GitHub (pinned to eeab0a219f)