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

  1. Backfill the evidence documents: set a valid queryVersion (non-empty, <= 128 chars, only A-Za-z0-9._:-) via a one-off mutation
  2. Fix ingestion to require and validate queryVersion with the same regex before insert
  3. After backfill, re-run the scan so candidates reference corrected rows (old candidates still point at the bad digest)
  4. 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

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


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)