# Flattened User Permissions Design

## Problem

Authorization checks require joining User → UserRole → Role → RolePermission → Permission. This is slow and complex. We want permissions denormalized directly on the User record for fast lookups.

## Solution

Add a `permissions` field to User that contains a flattened array of permission keys. Recompute automatically when role/permission assignments change.

## Data Model

```typescript
// User.permissions
permissions: string[] | null; // e.g., ["orders:read", "orders:write", "users:read"]
```

## Recompute Function

`src/lib/recomputeUserPermissions.ts`:

- Query: User → UserRole → RolePermission → Permission
- Deduplicate permission keys
- Update User.permissions field
- Return computed array

## Trigger Points

| Command                  | Affected Users           | Approach     |
| ------------------------ | ------------------------ | ------------ |
| assignRoleToUser         | Single user              | Synchronous  |
| revokeRoleFromUser       | Single user              | Synchronous  |
| assignPermissionToRole   | All users with that role | Asynchronous |
| revokePermissionFromRole | All users with that role | Asynchronous |

## Async Executors

For role-permission changes (which affect multiple users), executors handle recomputation asynchronously:

- `recomputeOnRolePermissionCreated` - Triggers on RolePermission create
- `recomputeOnRolePermissionDeleted` - Triggers on RolePermission delete

Located in `src/executor/recomputeOnRolePermissionChange.ts`.

## Trade-offs

- **Pro**: Fast authorization (single field read, no joins)
- **Pro**: Simple permission checks (`user.permissions.includes("orders:read")`)
- **Pro**: Single-user operations remain fast (synchronous)
- **Pro**: Multi-user operations don't block requests (asynchronous)
- **Con**: Storage overhead (denormalized data)
- **Con**: Eventual consistency for role-permission changes
