koala73/worldmonitor · error · ConvexError

COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING

COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING

Error message

COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING

What it means

Thrown by referencedEvidence() (convex/companyMonitoring/admission.ts:86) when at least one fingerprint in candidate.referenceEvidenceFingerprints has no row in companyMonitoringEvidence under the by_account_company_fingerprint index (ownerAccountId + companyId + evidenceFingerprint). The admission flow requires every referenced evidence document to still exist at classification time.

Solutions

  1. Treat it as a terminal signal for that run: abandon the candidate and let the next scan cycle build a new candidate against existing evidence
  2. Check whether a purge ran concurrently (companyMonitoringEvidence deletions, purgeGeneration/purgePhase on the company) and re-run the scan after purge completes
  3. If fingerprints never resolve even after re-scan, verify fingerprint() inputs are canonicalized identically at write and reference time
  4. Make retention sweeps skip evidence still referenced by pending candidates (check candidate existence before delete)

Example fix

// worker: handle missing evidence without crashing the loop
// before
const result = await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);

// after
try {
  const result = await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);
} catch (err) {
  if (err instanceof ConvexError && err.data === "COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING") {
    logger.warn("evidence purged before admission; dropping run", { runId: args.classificationRunId });
    return; // next scan rebuilds the candidate
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// If you control the purge path, skip evidence still referenced by a pending candidate:
const pending = await ctx.db
  .query("companyMonitoringCandidates")
  .withIndex("by_account_company_occurrence", (q) =>
    q.eq("ownerAccountId", row.ownerAccountId).eq("companyId", row.companyId).eq("occurrenceDedupeKey", row.occurrenceDedupeKey),
  )
  .unique();
if (pending?.state === "pending_classification") {
  return; // evidence still needed for admission; defer purge
}

Try / catch

try {
  const result = await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);
} catch (err) {
  if (err instanceof ConvexError && err.data === "COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING") {
    logger.warn("evidence gone before admission; discarding run", { runId: args.classificationRunId });
    return; // terminal for this run; next scan rebuilds
  }
  throw err;
}

Prevention

When it happens

Trigger: A purge job (purgeCompanyEvidenceBatch) deleted evidence rows between candidate creation and classification submission; evidence was written with a different fingerprint canonicalization than the candidate references; evidence belonged to a different companyId or ownerAccountId due to an id mix-up; manual deletion of evidence docs.

Common situations: Company removal/pause racing an in-flight classification worker; a deployment changing the fingerprint algorithm so old candidates reference fingerprints that no new rows carry; retention/TTL sweeps deleting expired evidence while a slow model call is in flight.

Related errors


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

Appendix: source

Thrown at convex/companyMonitoring/admission.ts:86

    new Set(candidate.referenceEvidenceFingerprints).size !==
      candidate.referenceEvidenceFingerprints.length ||
    candidate.referenceCount < candidate.referenceEvidenceFingerprints.length
  ) {
    throw new ConvexError("COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID");
  }
  const rows = await Promise.all(candidate.referenceEvidenceFingerprints.map((evidenceFingerprint) =>
    ctx.db
      .query("companyMonitoringEvidence")
      .withIndex("by_account_company_fingerprint", (q) =>
        q
          .eq("ownerAccountId", candidate.ownerAccountId)
          .eq("companyId", candidate.companyId)
          .eq("evidenceFingerprint", evidenceFingerprint),
      )
      .unique()
  ));
  if (rows.some((row) => !row)) {
    throw new ConvexError("COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING");
  }
  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)) {

View on GitHub (pinned to eeab0a219f)