Skip to main content

Multi-Tenancy

Tenant Column

Every tenant-scoped table includes a user_id column:

-- Current (single-user mode)
CREATE TABLE secrets (
id TEXT PRIMARY KEY,
realm_id TEXT NOT NULL REFERENCES realms(id),
-- realm already has user_id, so tenant isolation is through realm ownership
...
);

Store Layer Enforcement

The store layer enforces tenant isolation:

// WRONG — no tenant filter
func (s *Store) ListSecrets(ctx context.Context, realmID uuid.UUID) ([]Secret, error)

// RIGHT — realm ownership already enforced by ResolveRealm, but store validates
func (s *Store) ListSecrets(ctx context.Context, realmID uuid.UUID, env string) ([]Secret, error)

Realm Ownership as Tenant Boundary

Realm ownership is the tenant boundary. ResolveRealm() ensures a user can only access their own realms. All queries downstream use realm_id as the scope — never a direct user_id filter on data tables.

Future org_id Migration

When organizations land (future), the migration adds org_id to the realms table and adjusts ResolveRealm() to filter by org membership instead of direct user ownership.