{"record":{"id":"12a355c7516c6a8d","repo":"koala73/worldmonitor","slug":"grant-not-found","errorCode":"GRANT_NOT_FOUND","errorMessage":"GRANT_NOT_FOUND","messagePattern":"GRANT_NOT_FOUND","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/payments/businessSeats.ts","lineNumber":421,"sourceCode":"        createdAt: g.createdAt,\n        acceptedAt: g.acceptedAt ?? null,\n        expiresAt: g.expiresAt,\n      })),\n    };\n  },\n});\n\n/**\n * Owner-only removal of a single seat. Revokes the grant and recomputes the\n * invitee's entitlement.\n */\nexport const removeSeat = mutation({\n  args: { grantId: v.id(\"businessProGrants\") },\n  handler: async (ctx, args) => {\n    const userId = await requireUserId(ctx);\n    const grant = await ctx.db.get(args.grantId);\n    if (!grant) {\n      throw new ConvexError({ kind: \"GRANT_NOT_FOUND\" });\n    }\n    if (grant.ownerUserId !== userId) {\n      throw new ConvexError({ kind: \"NOT_OWNER\" });\n    }\n    if (grant.status !== \"pending\" && grant.status !== \"accepted\") {\n      return { ok: true as const, status: \"already_inactive\" as const };\n    }\n\n    const now = Date.now();\n    // Serialize with inviteSeats via the per-Business-subscription lock row.\n    await touchBusinessSeatLock(ctx, grant.businessSubscriptionId, now);\n\n    await ctx.db.patch(args.grantId, { status: \"revoked\" });\n\n    if (grant.inviteeUserId) {\n      await ctx.runMutation(\n        internal.payments.subscriptionHelpers.recomputeEntitlementForUser,\n        { userId: grant.inviteeUserId, eventTimestamp: now },","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/payments/businessSeats.ts#L403-L439","documentation":"Thrown by the `removeSeat` mutation when `ctx.db.get(args.grantId)` returns null — the supplied ID does not resolve to a `businessProGrants` document. The grant may never have existed, may have been deleted, or the ID is not a valid Convex document ID for that table.","triggerScenarios":"Calling `removeSeat({ grantId })` with an ID that is absent from `businessProGrants`, malformed, or belongs to a different table. Commonly hit when the UI shows a grant that another flow already revoked/deleted.","commonSituations":"Stale UI listing a grant removed elsewhere; copy/paste error in the grantId; race where a scheduled lapse job revoked the grant between page load and the remove click.","solutions":["Verify the grantId is a valid `businessProGrants` document ID that still exists before calling removeSeat","Refresh the seat list in the UI before presenting the remove action","Treat GRANT_NOT_FOUND as idempotent success (the seat is already gone) and reconcile the UI"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await convex.mutation(api.payments.businessSeats.removeSeat, { grantId });\n} catch (err) {\n  if (err.data?.kind === 'GRANT_NOT_FOUND') {\n    // idempotent: seat already gone — sync UI and treat as success\n  } else if (err.data?.kind === 'NOT_OWNER') {\n    // not authorized to remove\n  } else { throw err; }\n}","preventionTips":["Fetch the seat list before rendering a remove control so the grantId is current","Treat GRANT_NOT_FOUND as idempotent success in the UI","Guard removeSeat calls behind ownership checks in the UI"],"tags":["payments","business-seats","grants","convex"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}