# Security Model (CL2) ## Tenancy model - Tenant boundary is `agency_id`. - Every business table row is scoped to an agency. - RLS helper functions enforce scope using `auth.uid()` against `agency_members`: - `public.is_agency_member(agency_id)` - `public.is_agency_admin(agency_id)` (`owner` or `admin`) ## Role model - `owner`: full admin permissions in agency - `admin`: same operational permissions as owner for managed resources - `member`: read-mostly on sensitive resources, no admin writes ## Write restrictions by table - `agencies` - `INSERT`: any authenticated user (supports first-time onboarding) - `UPDATE/DELETE`: admin only - `agency_members` - `SELECT`: own membership rows - `INSERT`: onboarding self-owner membership, or admin adding members - `UPDATE/DELETE`: admin (delete also allows self-leave) - `clients` - member-readable and member-writable (chosen for MVP collaboration speed) - `plesk_instances` - member-readable, admin-writable - `plesk_subscriptions` - member-readable, admin-writable - `plesk_domains` - member-readable, admin-writable - `actions_log` - member-readable, admin insert/update - `billing_accounts` - member-readable, admin-writable - `entitlements` - member-readable, admin-writable ## Onboarding compatibility Policies are designed so a newly authenticated user can: 1. insert first `agencies` row, 2. insert first `agency_members` row as `owner` for `auth.uid()`, 3. then operate normally under tenant-scoped access. This supports the server-side auto-bootstrap flow used by `getServerAgencyContextOrRedirect()`.