# Tenant Isolation Model

## V1 Isolation Guarantee

Every tenant has:
1. Its own Supabase project (separate database, auth, storage)
2. Its own environment variables (URL, keys, secrets)
3. Its own storage bucket namespace
4. Its own branding profile
5. Its own module configuration

No tenant can read, write, or infer data from another tenant.

---

## Control Plane vs Tenant Plane

```
Control Plane DB (shared, owner-only)
├── cp_tenants             — tenant registry
├── cp_tenant_environments — runtime config per tenant
├── cp_plans               — commercial plans
├── cp_features            — feature catalog
├── cp_plan_feature_entitlements
├── cp_tenant_feature_overrides
├── cp_branding_profiles
├── cp_provisioning_jobs / job_steps
├── cp_audit_events
└── cp_release_rollouts

Tenant DB (one per company, tenant-only)
├── projects / process_steps / cycle_measurements
├── shift_outputs / workshop_actions / qaf_uploads
├── assignments / consultants / planning_projects
├── assessments / assessment_responses
├── oee_records / oee_loss_categories
├── value_stream_maps
├── lsc_shifts / lsc_shift_hours / lsc_measures
├── documents / document_metadata
├── user_profiles / roles / permissions
├── email_queue / audit_log
└── (all existing tables)
```

---

## V1 Architecture: Dedicated Supabase Per Tenant

Each tenant deployment has:
```
NEXT_PUBLIC_SUPABASE_URL=https://<tenant-project>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<tenant-anon-key>
SUPABASE_SERVICE_ROLE_KEY=<tenant-service-key>
TENANT_ID=<uuid>
```

These environment variables are set per Vercel project (or deployment environment).

The `TENANT_ID` is used to:
1. Load entitlements from the control plane database
2. Load branding configuration
3. Correlate audit events with the correct tenant

---

## Control Plane Access from Tenant App

The tenant app accesses the control plane only for:
- Loading feature entitlements at startup (read-only, cached)
- Loading branding configuration (read-only, cached)

It does so via a **restricted service account** that has `SELECT` only on:
- `cp_features`
- `cp_plan_feature_entitlements`
- `cp_tenant_feature_overrides`
- `cp_branding_profiles`
- `cp_release_rollouts`

It cannot read or write any other control plane table.

Control plane credentials in tenant apps:
```
CONTROL_PLANE_SUPABASE_URL=...
CONTROL_PLANE_READONLY_KEY=...
```

---

## Row-Level Security

### Control plane tables
All `cp_*` tables have RLS enabled with a single policy:
```sql
USING (auth.role() = 'service_role')
```
Only service role access (owner portal server-side) can read/write these tables.
No anon or authenticated user can touch them.

### Tenant tables
Existing RLS policies apply within each tenant's own database.
Cross-tenant access is physically impossible (different database credentials).

---

## Isolation Test Strategy

The following must be verified by automated tests:

```
1. Tenant A credentials cannot query Tenant B's database
   → Connection string mismatch = auth error

2. Control plane tables are not readable by tenant user JWTs
   → RLS policy rejects authenticated (non-service) role

3. Entitlement cache never bleeds between requests
   → Each server request loads from correct TENANT_ID scope

4. Branding never returns another tenant's config
   → getActiveBranding(tenantId) only returns matching row

5. Support access session is time-limited and logged
   → cp_support_access_sessions.expires_at checked on every request
```

---

## Future: Shared Deployment Stamps

When the platform grows to 50+ tenants, a shared multi-tenant Supabase project becomes viable. The migration path is:

1. Add `tenant_id UUID` column to all tenant tables
2. Update all RLS policies to check `tenant_id = auth.jwt() ->> 'tenant_id'`
3. Update Supabase auth to include `tenant_id` in JWT claims
4. The entitlement and branding engines already abstract tenant identity — no change needed
5. The control plane tables remain separate and unchanged

This migration is safe because all application code accesses data through the Supabase client abstraction and permission functions — no raw SQL cross-tenant queries exist.
