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
- 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
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
- 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
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
- COMPANY_MONITORING_ _INVALID
- COMPANY_MONITORING_MODEL_VERSION_INVALID
- userId is required
- COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID
- COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING
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)