Skip to main content

Identity & Tenancy - Domain Specification

First Introduced: MVP.0 Status: Specification Complete Last Updated: 2025-10-24


Overview

Tenancy & Identity define who owns what and who can act on behalf of whom across the platform. This is the foundational layer that governs multi-tenancy, account delegation, and user identity. The structure separates the tenant boundary (Org) from the actors (Accounts) that operate within it, ensuring clean isolation, flexible delegation, and scalable access control.


Responsibilities

This domain IS responsible for:

  • Defining organizational boundaries (multi-tenancy isolation)
  • Managing sub-tenant actors (Accounts within Orgs)
  • Global user identity (email-based, cross-org)
  • Binding users to orgs/accounts (Memberships with roles)
  • Enforcing tenant-level data isolation

This domain is NOT responsible for:

  • Permission definitions and evaluation (→ Authorization domain)
  • Session management and authentication (→ Authorization domain)
  • Resource ownership tracking (→ Individual resource domains)
  • Audit logging (→ Analytics domain)

Relationships

Depends On:

  • None (foundational domain)

Depended On By:

  • Authorization & Access - Uses Org/Account/User for permission scoping
  • Supply - Spaces/Units belong to Org + Account
  • Channels - Channel targets scoped to Org + Account
  • Payments - Financial accounts linked to Accounts
  • ALL other domains - Every entity has org_id and account_id foreign keys

Related Domains:


Core Concepts

Entity: Organization (Org)

Purpose: Top-level tenant boundary representing a business, brand, or marketplace.

Key Attributes:

  • id (UUID, primary key)
  • name (VARCHAR, required) - Display name
  • slug (VARCHAR, unique) - URL-friendly identifier
  • tier (ENUM) - Subscription tier: free | starter | professional | enterprise
  • status (ENUM) - active | suspended | deleted
  • settings (JSONB) - Org-level configuration
  • created_at, updated_at (timestamps)

Relationships:

  • Org → Account (1:*, one-to-many) - Each Org has 1+ Accounts
  • Org → Membership (1:*) - Each Membership belongs to one Org
  • Org → Role (1:*) - Roles may be org-specific or global

Lifecycle:

  • Created: During onboarding flow
  • Updated: When changing subscription tier or settings
  • Deactivated: Soft delete via status='deleted' (never hard deleted for audit)

Business Rules:

  • Every Org must have exactly one default Account (is_default=true)
  • Org slug must be globally unique
  • Deleted Orgs retain data for compliance (7 years minimum)

Entity: Account

Purpose: Sub-tenant actor within an Org representing the entity that owns/manages/operates assets (e.g., property owner, manager, marketplace, internal ops).

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • name (VARCHAR, required)
  • type (ENUM) - owner | manager | marketplace | internal
  • is_default (BOOLEAN) - True for the default account (one per Org)
  • status (ENUM) - active | suspended | deleted
  • settings (JSONB) - Account-level configuration
  • created_at, updated_at (timestamps)

Relationships:

  • Account → Org (*, many-to-one)
  • Account → Membership (1:*)
  • Account → [All actor-owned entities] (1:*) - Spaces, Units, Bookings, etc.

Lifecycle:

  • Created: Automatically on Org creation (default), or manually by admins
  • Updated: When changing type or settings
  • Deleted: Soft delete only (preserves audit trail)

Business Rules:

  • Each Org must have exactly one is_default=true Account
  • Account names must be unique within an Org
  • Cannot delete last Account in an Org
  • All actor-owned data must reference an Account

Entity: User

Purpose: Global individual identity representing a person across all Orgs.

Key Attributes:

  • id (UUID, primary key)
  • email (CITEXT, unique, required) - Case-insensitive email
  • email_verified (BOOLEAN)
  • google_sub (VARCHAR, unique) - Google SSO subject identifier
  • given_name, family_name (VARCHAR)
  • avatar_url (TEXT)
  • locale (VARCHAR, default 'en')
  • timezone (VARCHAR, default 'UTC')
  • status (ENUM) - active | suspended | deleted
  • last_login_at (TIMESTAMP)
  • created_at, updated_at (timestamps)

Relationships:

  • User → Membership (1:*, one user can belong to many Orgs/Accounts)
  • User → Session (1:*, active sessions for this user)

Lifecycle:

  • Created: On first Google SSO login (auto-provisioning)
  • Updated: Profile changes, login events
  • Deleted: Soft delete via status='deleted' (GDPR right to erasure with audit record)

Business Rules:

  • Email must be unique globally
  • Users exist independently of Orgs (can participate in multiple)
  • google_sub must be unique (one Google account = one TVL user)

Entity: Membership

Purpose: Binding of User → Org/Account with a specific Role, defining scope of authority.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, nullable, foreign key → accounts.id)
  • user_id (UUID, foreign key → users.id)
  • role_id (UUID, foreign key → roles.id)
  • status (ENUM) - active | suspended | ended
  • invited_by (UUID, foreign key → users.id)
  • invited_at, joined_at (timestamps)
  • ended_at (TIMESTAMP, nullable)
  • created_at, updated_at (timestamps)

Relationships:

  • Membership → Org (*, many-to-one)
  • Membership → Account (*, many-to-one, optional for org-wide)
  • Membership → User (*, many-to-one)
  • Membership → Role (*, many-to-one)

Lifecycle:

  • Created: When user is invited or joins an Org
  • Updated: When role changes or status updated
  • Ended: Set ended_at timestamp (never deleted for audit)

Business Rules:

  • account_id = NULL means Org-wide access
  • account_id set means access limited to that Account
  • Cannot have duplicate active Memberships (same user, org, account)
  • Membership with status='ended' retained for audit trail

Workflows

Workflow: Org Creation

  1. User signs up via Google SSO
  2. Create Organization record with status='active', tier='free'
  3. Auto-create default Account with type='owner', is_default=true, name='<Org.name> (Default)'
  4. Create Membership binding creator User to Org with role='admin', account_id=NULL (org-wide)
  5. Return success with Org ID and redirect to dashboard

Postconditions:

  • Org has exactly 1 Account (default)
  • Creator User has admin role across entire Org
  • All future data will be scoped to this org_id

Workflow: Invite User to Account

  1. Admin selects Account to invite user into
  2. Enter user email and role
  3. Create Membership record with status='pending', invited_by=<admin_user_id>
  4. Send invitation email with magic link
  5. User clicks link
  6. If user exists: Update Membership to status='active', set joined_at
  7. If new user: Create User via Google SSO, then activate Membership
  8. Return success

Postconditions:

  • User can access resources in specified Account
  • Permissions determined by assigned Role
  • Invitation audit trail preserved

Business Rules

  1. Org Isolation: All queries MUST filter by org_id (enforced via Row-Level Security policies)
  2. Default Account: Every Org must have exactly one is_default=true Account
  3. Account Ownership: All actor-owned entities (Spaces, Units, Bookings, etc.) MUST have account_id foreign key
  4. Email Uniqueness: User email must be globally unique (case-insensitive)
  5. Soft Deletes Only: Orgs, Accounts, Users never hard deleted (audit compliance)
  6. Membership Retention: Ended Memberships retained indefinitely for audit trail
  7. Org-wide vs. Account-level: Membership with account_id=NULL grants access to ALL Accounts in Org

Implementation Notes

MVP Scope (MVP.0)

Included:

  • Organizations, Accounts, Users, Memberships entities
  • Google SSO integration (create User on first login)
  • Default Account auto-creation
  • Admin role assignment to creator
  • Org isolation via org_id filtering (enforced in application layer)

Deferred:

  • Row-Level Security (RLS) policies defined but not enforced (enable in MVP.1)
  • Multi-org user switching (user locked to first Org in MVP)
  • Account hierarchy (parent-child Accounts) - V1.0+
  • Directory integration (SAML SSO, SCIM) - V1.1+

Database Indexes

Critical for performance:

CREATE INDEX idx_accounts_org_id ON accounts(org_id);
CREATE UNIQUE INDEX idx_accounts_org_default ON accounts(org_id, is_default) WHERE is_default = true;
CREATE UNIQUE INDEX idx_users_email ON users(LOWER(email));
CREATE UNIQUE INDEX idx_users_google_sub ON users(google_sub);
CREATE INDEX idx_memberships_user_org ON memberships(user_id, org_id);
CREATE INDEX idx_memberships_account ON memberships(account_id) WHERE account_id IS NOT NULL;

Constraints

Enforce data integrity:

-- Organizations
ALTER TABLE organizations ADD CONSTRAINT orgs_slug_valid CHECK (slug ~ '^[a-z0-9-]+$');

-- Accounts
ALTER TABLE accounts ADD CONSTRAINT accounts_org_name_unique UNIQUE (org_id, name);
ALTER TABLE accounts ADD CONSTRAINT accounts_org_default_unique UNIQUE (org_id, is_default) DEFERRABLE INITIALLY DEFERRED;

-- Memberships
ALTER TABLE memberships ADD CONSTRAINT memberships_unique_active UNIQUE (user_id, org_id, account_id) WHERE status = 'active';

Future Enhancements

V1.0: Account Hierarchy

  • Add parent_account_id to Accounts table
  • Support management companies with sub-accounts
  • Permissions inheritance from parent to child

V1.1: Multi-Org User Switching

  • UI for users to switch between Orgs
  • Session scoped to active Org + Account
  • Recent Org list for quick switching

V1.2: Directory Integration

  • SAML SSO for enterprise customers
  • SCIM for user provisioning
  • Group-based role assignment

V2.0: Federated Identity

  • Support for external identity providers
  • Cross-org identity federation
  • Delegated authentication

Physical Schema

See 001_initial_schema.sql for complete CREATE TABLE statements.

Summary:

  • 4 tables: organizations, accounts, users, memberships
  • 12+ indexes for query performance
  • 8+ constraints for data integrity
  • Row-Level Security policies defined (not enforced in MVP)