> SUPERSEDED 2026-05-26 — Single-tenant BMW conversion (refactor/remove-control-plane). Historical record only.

# ADR 002: Control Plane Tables Colocated in Tenant Supabase Project

## Status
Accepted

## Date
2026-04-05

## Context

The control plane requires a persistent database for: tenant registry, provisioning jobs, plans, feature entitlements, branding profiles, and audit logs.

Two options were considered for where to host this database:

**Option A: Dedicated Supabase project for the control plane**
A separate Supabase project (separate database, auth, storage) that is exclusively used by the owner portal.

**Option B: `cp_` prefixed tables in the existing Supabase project**
Colocate control plane tables in the same Supabase project used by the existing application, distinguished by a `cp_` table prefix.

## Decision

**Option B — `cp_` prefixed tables in the existing Supabase project**.

## Rationale

1. **One connection pool**: The existing Supabase project already has Auth, Storage, and an established connection. Adding tables is zero infrastructure overhead.

2. **RLS is sufficient isolation**: Control plane tables have RLS policies restricted to `auth.role() = 'service_role'` only. No tenant user JWT can read or write any `cp_*` table, regardless of where the tables live.

3. **Schema discipline enforces boundaries**: The `cp_` prefix is a naming convention that makes cross-domain queries obvious and reviewable. Any query touching `cp_` tables in a tenant context is immediately suspicious.

4. **Operational simplicity**: One Supabase project means one set of credentials, one dashboard, one migration file, one backup target.

5. **Migration path is safe**: If a dedicated control plane database is required later, all `cp_*` tables can be extracted to a new Supabase project with a schema dump. The application code reads `CONTROL_PLANE_SUPABASE_URL` and `CONTROL_PLANE_READONLY_KEY` env vars — switching these targets the new project with no code changes.

## Consequences

- All `cp_*` tables must have RLS enabled with `USING (auth.role() = 'service_role')`.
- The supabase client used for control plane operations must always use the service role key, never the anon key.
- Tenant apps that need read-only access to entitlement/branding data use a restricted `CONTROL_PLANE_READONLY_KEY` with SELECT-only permissions on specific tables.
- Migration file `supabase/migrations/supabase-migration-control-plane.sql` manages all `cp_*` table definitions.

## Rejected Alternative

**Option A** was rejected because: additional Supabase project billing, separate auth configuration, more environment variables per deployment, more operational surface area. Revisit if: compliance requires physical data separation, or scale requires dedicated compute.
