koala73/worldmonitor · error · ConvexError

NOT_OWNER

NOT_OWNER

Error message

NOT_OWNER

What it means

Structured ConvexError thrown by removeSeat when the grant exists but its ownerUserId does not match the signed-in user. Seat removal is owner-only; a non-owner attempting to revoke someone else's seat is refused even if the grant id is valid.

Solutions

  1. Only the original inviter may remove a seat; sign in as that account
  2. If ownership legitimately changed, have support transfer the Business subscription first
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/payments/businessSeats.ts:424 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/5832250e28c71891. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/businessSeats.ts:420

      })),
    };
  },
});

/**
 * Owner-only removal of a single seat. Revokes the grant and recomputes the
 * invitee's entitlement.
 */
export const removeSeat = mutation({
  args: { grantId: v.id("businessProGrants") },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);
    const grant = await ctx.db.get(args.grantId);
    if (!grant) {
      throw new ConvexError({ kind: "GRANT_NOT_FOUND" });
    }
    if (grant.ownerUserId !== userId) {
      throw new ConvexError({ kind: "NOT_OWNER" });
    }
    if (grant.status !== "pending" && grant.status !== "accepted") {
      return { ok: true as const, status: "already_inactive" as const };
    }

    const now = Date.now();
    // Serialize with inviteSeats via the per-Business-subscription lock row.
    await touchBusinessSeatLock(ctx, grant.businessSubscriptionId, now);

    await ctx.db.patch(args.grantId, { status: "revoked" });

    if (grant.inviteeUserId) {
      await ctx.runMutation(
        internal.payments.subscriptionHelpers.recomputeEntitlementForUser,
        { userId: grant.inviteeUserId, eventTimestamp: now },
      );
    }
    return { ok: true as const, status: "revoked" as const };

View on GitHub (pinned to 7d06c8633d)