{"record":{"id":"3594e6ecb008ccf5","repo":"koala73/worldmonitor","slug":"owner-email-unavailable","errorCode":"OWNER_EMAIL_UNAVAILABLE","errorMessage":"OWNER_EMAIL_UNAVAILABLE","messagePattern":"OWNER_EMAIL_UNAVAILABLE","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/payments/businessSeats.ts","lineNumber":125,"sourceCode":"  return grants.filter((g) => {\n    if (g.status === \"accepted\") return true;\n    if (g.status === \"pending\" && g.expiresAt > at) return true;\n    return false;\n  }).length;\n}\n\n/**\n * Owner invites up to 4 teammates at any corporate email domain. Pending\n * invites count against the cap; each gets a single-use HMAC token emailed via Resend.\n */\nexport const inviteSeats = mutation({\n  args: { emails: v.array(v.string()) },\n  handler: async (ctx, args) => {\n    const userId = await requireUserId(ctx);\n    const identity = await resolveUserIdentity(ctx);\n    const ownerEmail = identity?.email?.trim();\n    if (!ownerEmail) {\n      throw new ConvexError({ kind: \"OWNER_EMAIL_UNAVAILABLE\" });\n    }\n\n    const now = Date.now();\n    const businessSub = await getCoveringBusinessSubscription(ctx, userId, now);\n    if (!businessSub) {\n      throw new ConvexError({ kind: \"OWNER_NOT_BUSINESS\" });\n    }\n\n    const ownerDomain = extractDomain(ownerEmail);\n    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();","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/payments/businessSeats.ts#L107-L143","documentation":"Structured ConvexError thrown by the inviteSeats mutation when the seat-owning user's Clerk identity has no email address available. Business seat invites are keyed to the owner's corporate email domain, so without a resolvable owner email the invite flow cannot proceed past its first guard.","triggerScenarios":"Thrown at convex/payments/businessSeats.ts:126 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Ensure the signed-in user's Clerk profile has a primary email address","Re-authenticate so a fresh identity token with email claims is available","If email is deliberately withheld, that account cannot own Business seat invites by design"],"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"}