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
- 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
- Check whether a purge ran concurrently (companyMonitoringEvidence deletions, purgeGeneration/purgePhase on the company) and re-run the scan after purge completes
- If fingerprints never resolve even after re-scan, verify fingerprint() inputs are canonicalized identically at write and reference time
- 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
- Make retention/purge jobs check for pending candidates before deleting evidence
- Keep model latency and retry budgets short relative to evidence retention windows
- Use identical canonicalization when computing fingerprints at write and reference time
- Alert when this code fires repeatedly — it indicates a purge/admission race worth fixing
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
- COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID
- Account changed while creating the embed key. Try again.
- Account changed while revoking the embed key. Try again.
- COMPANY_MONITORING_ADMISSION_EVIDENCE_STALE
- COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
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)