> ## Documentation Index
> Fetch the complete documentation index at: https://system.muzemus.online/llms.txt
> Use this file to discover all available pages before exploring further.

# Client isolation

> How MUZE isolates data between retail clients in a shared database

## 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](/security/data-scoping) for the implementation patterns.

## How client scoping works

Every tenant-scoped model includes a `clientId` foreign key:

```prisma theme={null}
model Store {
  id       String @id @default(cuid())
  name     String
  clientId String
  client   Client @relation(fields: [clientId], references: [id])

  @@index([clientId])
}
```

When a request arrives, the authorization layer resolves the user's scope from their `UserRole` record. Every subsequent database query filters by it.

| Role           | Scope source                                       | Data visibility     |
| -------------- | -------------------------------------------------- | ------------------- |
| SUPER\_ADMIN   | Bypasses scope checks                              | All clients         |
| MUZE\_ADMIN    | Bypasses scope checks; `clientId` param may narrow | All clients         |
| HR             | `userRole.clientId`                                | One assigned client |
| STORE\_MANAGER | `userRole.storeId`                                 | One assigned store  |
| EMPLOYEE       | No API access                                      | None                |

## Tenant-scoped models

| Model                                                         | Scoping            |
| ------------------------------------------------------------- | ------------------ |
| `Region`, `Department`, `Store`, `UniformCategory`, `Product` | Direct `clientId`  |
| `EntitlementRuleSet`                                          | Direct `clientId`  |
| `Employee`                                                    | Via store          |
| `Order`, `StoreOrder`                                         | Via store/employee |

`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

| Risk                            | Mitigation                                                                                                       |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Forgotten clientId filter       | `ScopeGuard` catches URL-level violations; service-layer filters are convention-based and covered by code review |
| Admin accidentally over-scoping | Service methods check `request.isAdmin` before applying or relaxing clientId filters                             |
| Cross-client data in reports    | Reports resolve the client from the user's roles before generating any query                                     |
| CSV import across clients       | Imports are scoped to the importer's client; every imported row is assigned to it                                |
