Problem
Two paths can mutate organizations.plan_id and seats_limit:
- Webhook path — Stripe webhook → Edge Function →
apply_billing_subscription (service_role)
- RPC path —
update_organization_plan (granted to authenticated, admin check only)
The RPC path is intentional for mock billing, but in production Stripe mode an org admin can still call it directly via PostgREST, potentially putting Postgres plan state out of sync with Stripe.
Impact
- Plan/seat state in Postgres can diverge from Stripe subscription
- Entitlements (seat limits, email quotas) may not match what customer is actually paying for
- Hard to detect unless monitoring is in place
Proposed fix
Option A: Revoke EXECUTE on update_organization_plan from authenticated when Stripe is the billing provider (requires app-level enforcement or a DB flag).
Option B: Add a guard inside update_organization_plan that checks organization_billing.provider; if Stripe, reject the call.
Option C: Document as intentional design for mixed providers and accept the risk.
Files
supabase/migrations/0008_org_billing_plan.sql
supabase/migrations/0017_update_organization_plan_seats.sql
supabase/migrations/0019_guard_seats_limit_override.sql
docs/adr/0002-billing-multi-provider.md
Problem
Two paths can mutate
organizations.plan_idandseats_limit:apply_billing_subscription(service_role)update_organization_plan(granted toauthenticated, admin check only)The RPC path is intentional for mock billing, but in production Stripe mode an org admin can still call it directly via PostgREST, potentially putting Postgres plan state out of sync with Stripe.
Impact
Proposed fix
Option A: Revoke
EXECUTEonupdate_organization_planfromauthenticatedwhen Stripe is the billing provider (requires app-level enforcement or a DB flag).Option B: Add a guard inside
update_organization_planthat checksorganization_billing.provider; if Stripe, reject the call.Option C: Document as intentional design for mixed providers and accept the risk.
Files
supabase/migrations/0008_org_billing_plan.sqlsupabase/migrations/0017_update_organization_plan_seats.sqlsupabase/migrations/0019_guard_seats_limit_override.sqldocs/adr/0002-billing-multi-provider.md