Self-registration provisions no tenant, membership, or role
Register.tsx calls only supabase.auth.signUp({ email, password, data: { full_name } }).
It creates no company, no company_memberships row, and no role.
Membership/role are provisioned out of band, through paths that are invisible from the signup screen:
- a Postgres trigger
handle_new_user()creates aprofilesrow (name copied from auth metadata) — but not a membership; - the
setup-companyedge function creates a company + asuper_adminmembership, but only via the onboarding flow, and it rejects users who already have any membership (one-company-per-user at signup); - the
invite-useredge function adds a user to an existing company with a role.
So a user who simply signs up authenticates successfully but has no
primaryCompanyId — and the whole app keys off that. AdminLayout / Dashboard /
OrgAuditLog compute companyId as null and the user is stuck in empty / "select a
company" states forever, with no on-screen hint that a backend step is missing.
Related silent dependencies: audit actor names are read back from profiles.full_name
(OrgAuditLog), so if the handle_new_user trigger isn't installed, audit logs show raw
UUIDs. Email confirmation is assumed enabled in Supabase (Register unconditionally tells
the user to check their email); password strength is a client-only length < 6 check.
The tacit assumption: the Supabase project has specific triggers and auth settings configured in the console, none of which live in the repo.
What breaks: provisioning the database from the repo alone yields an app where signup appears to work but produces unusable accounts.
Rebuild implication
Make provisioning explicit and server-owned: signup → create/join tenant → assign role, as one auditable flow. Don't rely on console-configured triggers that aren't in source control (see also phantom deliverables table).