{"record":{"id":"10c7e1ae01f2f8fd","repo":"koala73/worldmonitor","slug":"invalid-company-monitoring-cohort","errorCode":null,"errorMessage":"INVALID_COMPANY_MONITORING_COHORT","messagePattern":"INVALID_COMPANY_MONITORING_COHORT","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/orchestration.ts","lineNumber":294,"sourceCode":"  args: { ownerAccountId: string; source: Source; companyIds: string[] },\n  mode: \"strict\" | \"missing_only\" = \"strict\",\n) {\n  const now = Date.now();\n  const account = await ctx.db\n    .query(\"companyMonitoringAccounts\")\n    .withIndex(\"by_logicalAccountId\", (q) => q.eq(\"logicalAccountId\", args.ownerAccountId))\n    .unique();\n  if (!account || account.lifecycle !== \"entitled\" || account.terminalReason) {\n    throw new ConvexError(\"COMPANY_MONITORING_ACCOUNT_INACTIVE\");\n  }\n  requireProviderClaimPolicy(account, args.source);\n\n  const requestedCompanyIds = [...new Set(args.companyIds)].sort();\n  if (\n    requestedCompanyIds.length === 0 ||\n    requestedCompanyIds.length > COMPANY_MONITORING_SCAN_COHORT_LIMIT\n  ) {\n    throw new ConvexError(\"INVALID_COMPANY_MONITORING_COHORT\");\n  }\n\n  const rows = await Promise.all(requestedCompanyIds.map(async (companyId) => {\n    const [company, obligation] = await Promise.all([\n      ctx.db\n        .query(\"companyMonitoringCompanies\")\n        .withIndex(\"by_account_companyId\", (q) =>\n          q.eq(\"ownerAccountId\", args.ownerAccountId).eq(\"companyId\", companyId),\n        )\n        .unique(),\n      ctx.db\n        .query(\"companyMonitoringScanObligations\")\n        .withIndex(\"by_account_company_source\", (q) =>\n          q\n            .eq(\"ownerAccountId\", args.ownerAccountId)\n            .eq(\"companyId\", companyId)\n            .eq(\"source\", args.source),\n        )","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/companyMonitoring/orchestration.ts#L276-L312","documentation":"Thrown by scheduleAccountWorkHandler when the deduplicated requestedCompanyIds list is empty or longer than COMPANY_MONITORING_SCAN_COHORT_LIMIT (25). Scan cohorts are bounded so a single work item's obligations and provider query stay within lease and budget limits.","triggerScenarios":"Calling account-work scheduling with no company ids or with more than 25 distinct company ids in one call.","commonSituations":"A 'scan all' action that does not chunk by cohort limit; an empty list from a filter that matched nothing; cohort limit lowered after code paths were written against a larger value.","solutions":["Chunk company ids into cohorts of at most COMPANY_MONITORING_SCAN_COHORT_LIMIT (25) and schedule one work item per chunk.","Skip scheduling when the list is empty.","Read the limit from the exported constant rather than hard-coding 25."],"exampleFix":"// before\nawait schedule({ ownerAccountId, source, companyIds: allIds });\n\n// after\nconst LIMIT = 25;\nfor (let i = 0; i < allIds.length; i += LIMIT) {\n  await schedule({ ownerAccountId, source, companyIds: allIds.slice(i, i + LIMIT) });\n}","handlingStrategy":"validation","validationCode":"const LIMIT = COMPANY_MONITORING_SCAN_COHORT_LIMIT;\nconst unique = [...new Set(companyIds)];\nif (unique.length === 0 || unique.length > LIMIT) {\n  throw new Error(\"cohort out of range\");\n}","typeGuard":"function cohortValid(ids: string[], limit = 25): boolean {\n  const u = new Set(ids);\n  return u.size > 0 && u.size <= limit;\n}","tryCatchPattern":null,"preventionTips":["Chunk company ids into cohorts of COMPANY_MONITORING_SCAN_COHORT_LIMIT.","Skip scheduling for empty lists."],"tags":["convex","validation","limits","scheduling","company-monitoring"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}