Skip to main content

Tenancy model

MUZE is a shared-database, row-level multi-tenant system. All clients (for example Boxer, Clicks) share the same PostgreSQL database and the same application instance. Tenant isolation is enforced at the application layer: every query includes a clientId filter. There is no database-level row-level security (RLS) and no separate schemas or databases per client. MUZE enforces boundaries at two levels:
  1. URL level: ScopeGuard verifies the user can access the entity referenced in the URL
  2. Query level: service methods filter database queries to the user’s scope
The query level is not automated; it is enforced by convention in each service and covered by code review. See Data scoping for the implementation patterns.

How client scoping works

Every tenant-scoped model includes a clientId foreign key:
When a request arrives, the authorization layer resolves the user’s scope from their UserRole record. Every subsequent database query filters by it.

Tenant-scoped models

User, UserRole, and Session are global: a user can hold roles across clients.

What each role can and cannot see

An HR user is assigned to exactly one client and can see:
  • All stores within their client
  • All employees in their client’s stores
  • All orders from their client’s employees
  • All entitlement rule sets for their client
They cannot see data from other clients, even though it exists in the same tables. A Store Manager is assigned to exactly one store and can see their store’s employees and orders. They cannot see other stores’ data, even within the same client.

Potential risks and mitigations