tenant_id.
Four database roles
Application code picks a role by which session factory it uses
(
core/database/session.py) — there’s no single shared connection string
that “just happens” to see everything. BYPASSRLS is confined to a
deliberately small platform connection pool.
Fail-closed by design
seo_app has a sentinel default for app.current_tenant_id:
SET LOCAL app.current_tenant_id = ? doesn’t see
every tenant’s data — it sees zero rows, because no real tenant has
that UUID. This is verified by a restore test: even a fresh database
restored from backup preserves the fail-closed default, and cross-tenant
isolation is asserted directly in the test suite (a tenant A JWT cannot
read tenant B’s data, checked against the real database, not mocked).
Transactional tenant context (ADR-003)
SET LOCAL only takes effect for the duration of the current transaction
— so tenant context is always set inside an explicit transaction, per
request. This closes a real class of bug: SET (without LOCAL) on a
pooled connection can leak tenant context to the next request that
happens to reuse the same physical connection from the pool. SET LOCAL
inside a transaction cannot leak past COMMIT/ROLLBACK.
Super admin (ADR-005)
Platform-level operations (cross-tenant support, billing, ops) go throughseo_platform’s BYPASSRLS, gated by the super_admin role — a
deliberately separate access path, not “tenant_id NULL” sprinkled through
tenant-facing code. super_admin access tokens are additionally tracked
in a Redis denylist (revocable before expiry), unlike ordinary sessions
which rely on the session-active check.
Tenant-scoped resources
Beyond row data, isolation extends to:- Memory — session and long-term memory are namespaced per tenant (Redis key prefix, RLS on PostgreSQL tables)
- Rate limits and budgets — per-tenant token buckets and
monthly_budget_usdenforced pre-dispatch - Feature flags — per-tenant overrides on top of platform defaults
- API keys and sessions — scoped to one tenant (or
NULLforsuper_admin, never both)