{"record":{"id":"4dbdeafd392e4250","repo":"koala73/worldmonitor","slug":"no-emails-provided","errorCode":"NO_EMAILS_PROVIDED","errorMessage":"NO_EMAILS_PROVIDED","messagePattern":"NO_EMAILS_PROVIDED","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/payments/businessSeats.ts","lineNumber":153,"sourceCode":"    if (!ownerDomain || !isCorporateDomain(ownerEmail)) {\n      throw new ConvexError({ kind: \"OWNER_DOMAIN_NOT_CORPORATE\" });\n    }\n\n    // Serialize concurrent inviteSeats / removeSeat calls for this Business\n    // subscription so the cap check below cannot be bypassed by a race.\n    await touchBusinessSeatLock(ctx, businessSub.dodoSubscriptionId, now);\n\n    const normalizedOwnerEmail = ownerEmail.toLowerCase();\n    // Dedupe: the existingByEmail check below only guards against emails\n    // that already had a grant BEFORE this call — a duplicate within the\n    // SAME args.emails array would otherwise slip past it (neither\n    // occurrence is in existingGrants yet) and create two grant rows for\n    // one invitee.\n    const emails = Array.from(\n      new Set(args.emails.map((e) => e.trim().toLowerCase()).filter((e) => e.length > 0)),\n    );\n    if (emails.length === 0) {\n      throw new ConvexError({ kind: \"NO_EMAILS_PROVIDED\" });\n    }\n    if (emails.length > MAX_SEATS) {\n      throw new ConvexError({ kind: \"TOO_MANY_EMAILS\" });\n    }\n\n    // Check existing grants for duplicates (active or pending).\n    const existingGrants = await ctx.db\n      .query(\"businessProGrants\")\n      .withIndex(\"by_businessSubscriptionId\", (q) =>\n        q.eq(\"businessSubscriptionId\", businessSub.dodoSubscriptionId),\n      )\n      .collect();\n    const existingByEmail = new Map(\n      existingGrants.map((g) => [g.inviteeEmail, g]),\n    );\n\n    // Separate new invites from duplicates before the cap check so a duplicate\n    // re-invite is idempotent even when the cap is full.","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/payments/businessSeats.ts#L135-L171","documentation":"Structured ConvexError thrown by inviteSeats when the normalized invite email list is empty after validation/deduplication. The mutation requires at least one invitee email; an empty submission is rejected before the seat-cap lock is used.","triggerScenarios":"Thrown at convex/payments/businessSeats.ts:154 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Submit at least one valid email address in the emails array","Guard the UI to disable the invite button when the input list is empty"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}