koala73/worldmonitor · error · ConvexError
COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
Error message
COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
What it means
Thrown by referencedEvidence() (convex/companyMonitoring/admission.ts:105) when a referenced evidence row has a falsy queryVersion or a queryVersion that fails the ADMISSION_ID regex /^[A-Za-z0-9._:-]{1,128}$/. Every evidence document admitted for classification must carry a well-formed query version identifying the query that produced it.
Solutions
- Backfill the evidence documents: set a valid queryVersion (non-empty, <= 128 chars, only A-Za-z0-9._:-) via a one-off mutation
- Fix ingestion to require and validate queryVersion with the same regex before insert
- After backfill, re-run the scan so candidates reference corrected rows (old candidates still point at the bad digest)
- Add a nightly consistency check querying companyMonitoringEvidence for rows where queryVersion is missing or invalid
Example fix
// ingestion: validate queryVersion exactly like the server (prevents the error)
// before
await ctx.db.insert("companyMonitoringEvidence", { ...row, queryVersion: row.queryVersion ?? "" });
// after
const ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
function queryVersionOrThrow(value: string | undefined): string {
if (!value || !ADMISSION_ID.test(value)) {
throw new Error(`queryVersion invalid: ${JSON.stringify(value)}`);
}
return value;
}
await ctx.db.insert("companyMonitoringEvidence", { ...row, queryVersion: queryVersionOrThrow(row.queryVersion) }); Defensive patterns
Strategy: validation
Validate before calling
const ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
function hasValidQueryVersion(row: { queryVersion?: string }): boolean {
return typeof row.queryVersion === "string" && ADMISSION_ID.test(row.queryVersion);
}
// ingestion path:
if (!hasValidQueryVersion(row)) {
throw new Error(`evidence row missing valid queryVersion: ${JSON.stringify(row.queryVersion)}`);
} Type guard
function hasValidQueryVersion(row: { queryVersion?: string }): row is { queryVersion: string } {
return typeof row.queryVersion === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(row.queryVersion);
} Try / catch
try {
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);
} catch (err) {
if (err instanceof ConvexError && err.data === "COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID") {
await backfillEvidenceQueryVersion(args.companyId); // then re-scan
return;
}
throw err;
} Prevention
- Require and validate queryVersion in every evidence insert path
- Backfill legacy rows before deploying admission code that reads them
- Run a periodic query for evidence rows with missing/invalid queryVersion
- Keep query identifiers in the allowed alphabet; encode free-form labels
When it happens
Trigger: Evidence rows ingested without setting queryVersion (undefined or empty string); queryVersion populated from a raw URL or free-text label containing /, ?, spaces, or unicode; an ingestion upgrade that renamed the field; rows backfilled by a script that left it null.
Common situations: New ingestion pipelines forgetting the field; queryVersion sourced from user input or logs; partial backfills after schema evolution; providers sending query identifiers with characters outside the allowed alphabet.
Related errors
- COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID
- COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING
- COMPANY_MONITORING_ADMISSION_EVIDENCE_STALE
- COMPANY_MONITORING_EVIDENCE_REVISION_INVALID
- COMPANY_MONITORING_ _INVALID
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/27f977bb932febf1.
Report an issue: GitHub.
Appendix: source
Thrown at convex/companyMonitoring/admission.ts:105
}
const evidence = rows.map((row) => row!);
for (const row of evidence) {
if (
row.occurrenceDedupeKey !== candidate.occurrenceDedupeKey ||
(!allowExpired && (
row.state !== "active" ||
(row.expiresAt !== undefined && row.expiresAt <= now)
)) ||
(row.sourceAuthority === "verified_first_party" &&
(
row.independence !== "first_party" ||
(row.provider === "exa" && row.matchedClaimIds.length === 0)
))
) {
throw new ConvexError("COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID");
}
if (!row.queryVersion || !ADMISSION_ID.test(row.queryVersion)) {
throw new ConvexError("COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID");
}
}
const shaped = evidence.map(evidenceShape);
const evidenceSnapshotDigest = await candidateEvidenceSnapshotDigest({
selectionPolicyVersion: candidate.selectionPolicyVersion,
referenceCount: candidate.referenceCount,
referencesTruncated: candidate.referencesTruncated,
evidence: shaped,
});
if (candidate.evidenceSnapshotDigest !== evidenceSnapshotDigest) {
throw new ConvexError("COMPANY_MONITORING_ADMISSION_EVIDENCE_STALE");
}
return shaped;
}
function decisionVersions(candidate: Doc<"companyMonitoringCandidates">, modelVersion: string) {
return {
classificationSchemaVersion: COMPANY_MONITORING_CLASSIFICATION_SCHEMA_VERSION,View on GitHub (pinned to eeab0a219f)