# Organizations & tenancy

**Status:** in production (single-org scope)
**Lives at:** `lib/org.ts` · `supabase/migrations/20260520120001_init.sql`

## Summary
The app is multi-tenant in the schema but currently scoped to a single
org (`bowden-works`) for all data. Users belong to one or more orgs via
`organization_members`; the time-tracking data lives under Bowden Works.

## Why
2026-05-20 — Rian is the Bowden Works owner. Adi is a Manager of Bowden
Works AND the Owner of his own org "Tingang". The Clockify-to-Toggl
bridge is a Bowden Works operation; Tingang is just the *label* Adi's
developers get folded under in the Toggl export.

## Behavior
- Three tables: `organizations`, `profiles`, `organization_members`.
- `organizations.slug` is unique. The app reads its current scope by
  looking up `slug = 'bowden-works'` (constant in `lib/org.ts`).
- `organization_members.role` is an enum: `owner | manager | member`.
- Each user has exactly one `profiles` row, keyed on `auth.users.id`.
  `profiles.is_super_admin` is a *global* (cross-org) flag.

## Constraints & edge cases
- Adi is in two orgs (Bowden Works as manager, Tingang as owner). The
  app explicitly looks up Bowden Works by slug — Adi's Tingang membership
  doesn't affect the app's data view today.
- A user without a Bowden Works membership would get an "Organization
  not found" message on every page. Today that's nobody.
- RLS policies on `organizations` and `organization_members` route
  through the `current_user_org_ids()` SECURITY DEFINER helper to avoid
  recursive policy evaluation (see decisions.md #008).
- Super admins get an additional `members_super_admin_select` policy
  that lets them read every membership (needed for the view-as switcher).

## Permissions
- Users see only orgs they're a member of.
- Super admins see all orgs and memberships (for view-as).
- Org membership rows only readable by the row's user (`members_self_select`)
  or by super admins, or by co-members of the same org
  (`members_co_member_select`, via the helper).

## Open considerations
- Single-org scope (`bowden-works`) is hardcoded in `lib/org.ts`. When
  we want multi-tenant in earnest, we'll need an org-selector in the
  nav and pass `org_id` through everywhere.
- Tingang has no data of its own yet. Eventually Adi may want to view
  his Tingang-scoped data (his team's billing, internal projects) — that
  would justify proper org switching.

## Tests
- 🟡 ORG-001 Lookup `bowden-works` by slug returns one row for Bowden Works members.
- 🟡 ORG-002 A user not in `bowden-works` sees zero rows via `getAppOrg()`.

(All RLS-dependent — integration only.)

## Changelog
- **2026-05-20** — Initial schema (orgs, profiles, memberships).
- **2026-05-21** — Added super-admin RLS policies and SECURITY DEFINER
  helpers (`current_user_is_super_admin`, `current_user_org_ids`) for
  view-as support and to fix the recursive policy bug.
