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: tenant-owned queries include a role/store scope, aclientId filter, or both.
There is no database-level row-level security (RLS) and no separate schemas or databases per client.
MUZE enforces boundaries at two levels:
- URL level:
ScopeGuardverifies the user can access the entity referenced in the URL - Query level: service methods filter database queries to the user’s scope
How client scoping works
Every tenant-scoped model includes aclientId foreign key:
UserRole record. Every subsequent database query filters by it.
For Muze Admin and Super Admin, the global client selector supplies an optional
clientId to client-scoped APIs. The resolver treats it as a narrowing filter and never replaces the caller’s role or store scope. Aggregate manager order, approval, and employee views use this filter. Their React Query keys also include the active client, and they do not reuse rows from the previous client while the new request is loading.
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