---
load: on-demand
relevant-skills:
  - fluent-use-case-discover
  - fluent-feature-plan
  - fluent-rule-scaffold
  - fluent-workflow-builder
  - fluent-mystique-builder
---

# Fluent Commerce -- Roles, Permissions & Context Model

## Three-Layer Access Control

| Layer | Where enforced | Example |
|-------|---------------|---------|
| **Permission-based** | Platform (API, Transition API, GraphQL) | FULFILMENT_TRANSITION required to trigger events |
| **Role-based (custom)** | Custom rule code (queries `me.roles`) | APPEASEMENT_TIER_1 gets $50 limit |
| **Manifest-based** | Client-side Mystique UI (`hasRole` helper) | Hide admin page from non-ADMIN |

## Permissions

Atomic capabilities assigned to roles. Platform enforces automatically.

**Naming**: `{ENTITY}_{ACTION}` -- e.g., ORDER_VIEW, FULFILMENT_TRANSITION, SETTING_CREATE.

**170+ permissions** covering all entity types. Common groups:
- CRUD: _VIEW, _CREATE, _UPDATE, _EDIT (legacy alias)
- Transitions: _TRANSITION (ORDER, FULFILMENT, WAVE, ARTICLE, CONSIGNMENT)
- Special: GRAPHQL_VIEW, GRAPHQL_SAVE, PLUGIN_INSTALL, PLUGIN_UPLOAD

## Context Model

**Roles are containers. Context is set at user-role assignment time.**

```
User -> UserRole -> Role (name) + contexts[] (contextType + contextId)
```

| contextType | contextId | Scope |
|-------------|-----------|-------|
| `ACCOUNT` | Account name/ID | Account-wide (admin, API, Rubix users) |
| `RETAILER` | Retailer ID (numeric) | Retailer-scoped (operational users) |
| `AGENT` | Location ID (numeric) | Location-scoped (Store app associates) |

A user can have the same role with different contexts simultaneously.

## User Types

| Type | Purpose | Context pattern |
|------|---------|----------------|
| `API` | Service accounts, Rubix users | ACCOUNT-level |
| `RETAILER` | Human users (OMS/admin) | RETAILER-level |
| `AGENT` | Location users (Store app) | AGENT-level |

## Custom Role Checking in Rules

Rubix user executes rules, NOT the clicking user. To know WHO clicked:

1. Manifest adds `me { username }` to page data query
2. User action passes `actionedBy` with `defaultValue: "{{me.username}}"`
3. Rule receives via `@EventAttribute(name = "actionedBy")`
4. Rule queries: `{ user(username: $actionedBy) { roles { role { name } } } }`
5. Rule checks role name and enforces business logic

**Label-only roles**: Custom roles with zero permissions, exist purely for code to check against. Example: APPEASEMENT_TIER_1 has no permissions but rules check for it.

## Feature Design Questions

### User actions
- Who can click this button? -> determines `modules[]` + role-gating
- Does backend need to know WHO clicked? -> add `actionedBy` attribute
- Different limits per role? -> custom label-only role + rule checking
- Hide button for some roles? -> manifest `hasRole` or custom component `useAuth()`

### Pages/views
- Admin-only page? -> manifest `hasRole` on route
- Data filtered by location? -> Store app auto-scopes by AGENT context
- New app or existing? -> existing = modify fragment; new = new manifest root + orchestrationAlias

### Rules
- Check permissions? -> usually no, platform handles via Transition API
- Check roles? -> yes if business logic varies by role
- Rubix user permissions? -> needs GRAPHQL + ADMIN at minimum

## GraphQL Schema

```graphql
# me.roles is a flat array, NOT a connection
# { me { roles { role { name } contexts { contextType contextId } } } }

type UserRole {
  role: Role!            # { name, permissions[] }
  contexts: [UserRoleContext!]!  # flat array
}

type UserRoleContext {
  contextType: String!   # ACCOUNT | RETAILER | AGENT
  contextId: String!
}
```

## Pre-Flight Discovery Queries

```graphql
{ roles(first: 100) { edges { node { id name permissions { name } } } } }
{ users(first: 100) { edges { node { username type status primaryRetailer { id ref } roles { role { name } contexts { contextType contextId } } } } } }
```

Save snapshots to `accounts/<PROFILE>/knowledge/client/roles-and-permissions.md`.
