D16 — Email-first tenant discovery (central) (RESOLVED)
Architecture decision record, relocated verbatim from the retired single-file
docs/tenancy/decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.
Residual security risk (tracked): this endpoint is unauthenticated and returns tenant uuid + name for a probed email, so it is an email-enumeration / tenant-identity disclosure vector — accepted as the cost of email-first discovery, mitigated by Turnstile +
throttle:10,1. Options to harden are tracked indocs/tracking/security-hardening.md.
The client SPA does not know its tenant up front. Following the major-SaaS pattern, login is
email-first: the SPA submits the email to a CENTRAL endpoint that resolves which tenant(s)
the client user belongs to, then sends the chosen tenant as X-Tenant for login.
POST /api/tenant-discovery(routes/api.php,TenantDiscoveryController) runs in the CENTRAL / BYPASSRLS context (wrapped intenancy()->central()), so it can searchclient_usersacross all tenants — an RLS-scoped query could not. Returns{ "tenants": [{ "id", "name" }] }(a list, since an email may exist in several tenants — the SPA shows a picker; one match → auto-proceed).- Central API routing is now enabled (
bootstrap/app.php→api: routes/api.php, prefixapi). Central cross-tenant endpoints live inroutes/api.php; tenant-scoped endpoints stay inroutes/tenant.php(identified + RLS). - SECURITY: the endpoint inherently discloses email→tenant membership (true of any email-first
flow). It is strictly rate-limited (
throttle:10,1). See follow-up in §3.