# Complete Access Control Stack

The Orion platform enforces access control through a **4-layer stack**. Each layer filters at a different level, and they compose together to determine what a user can see and do.

> **Think of it as:** subscription controls **WHAT** you can do, modules control **WHERE** it exists, menu config controls **WHAT's shown**, permissions control **WHO** can do it.

## Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                        INCOMING REQUEST                                      │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  LAYER 1: SUBSCRIPTION GATING                    "What you can do"          │
│                                                                              │
│  TierFeatureLimit → binary/quantitative caps per subscription tier          │
│  MerchantFeatureOverride → per-merchant exceptions                          │
│  FeatureService.check_resource_limit() → enforcement point                  │
│                                                                              │
│  Example: Free tier → 50 products max. Pro tier → unlimited.                │
│  Example: Binary feature "advanced_analytics" → on/off per tier.            │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │ allowed
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  LAYER 2: MODULE ENABLEMENT                      "Where it exists"          │
│                                                                              │
│  PlatformModule table → per-platform module on/off                          │
│  Core modules always enabled; optional modules toggled per platform         │
│  Auto-discovered from app/modules/*/definition.py                           │
│                                                                              │
│  Example: OMS platform has catalog + orders enabled.                        │
│  Example: Loyalty platform has loyalty + analytics but no inventory.        │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │ module enabled
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  LAYER 3: MENU VISIBILITY                        "What's shown"             │
│                                                                              │
│  AdminMenuConfig → opt-out model (only hidden items stored)                 │
│  Platform scope → applies to all users on platform                          │
│  User scope → personal preference (super admins only)                       │
│  MenuDiscoveryService → filtering pipeline                                  │
│                                                                              │
│  Example: Platform admin hides "code-quality" from store sidebar.           │
│  Example: Mandatory items (dashboard) cannot be hidden.                     │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │ visible
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  LAYER 4: ROLE PERMISSIONS                       "Who can do it"            │
│                                                                              │
│  Module-declared permissions via PermissionDefinition                       │
│  Role presets: owner, manager, staff, support, viewer, marketing            │
│  Per-store roles via StoreUser.role_id → Role.permissions                   │
│  Owner bypass: Merchant.owner_user_id gets all permissions                  │
│                                                                              │
│  Example: "viewer" role → can see products but not edit them.               │
│  Example: Owner sees everything, no role record needed.                     │
└──────────────────────────────────┬──────────────────────────────────────────┘
                                   │ permitted
                                   ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                          USER SEES / DOES THE THING                          │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Layer 1: Subscription Gating

Subscription gating controls **what a store can do** based on its merchant's subscription tier.

### How It Works

1. Each subscription tier (Free, Starter, Pro, Enterprise) has a set of **feature limits** defined in the `TierFeatureLimit` table.
2. Features are either **binary** (on/off) or **quantitative** (numeric cap).
3. Modules declare their billable features via `FeatureProviderProtocol` — a cross-module interface that lets each module own its feature definitions.
4. Admins can override limits per-merchant using `MerchantFeatureOverride`.

### Enforcement

```python
from app.modules.billing.services.feature_service import feature_service

# Check before allowing an action
allowed, message = feature_service.check_resource_limit(
    db=db,
    feature_code="products",
    store_id=store.id,
)
if not allowed:
    raise TierLimitExceededException(message)
```

### Key Models

| Model | Table | Purpose |
|-------|-------|---------|
| `TierFeatureLimit` | `tier_feature_limits` | Per-tier feature caps (binary/quantitative) |
| `MerchantFeatureOverride` | `merchant_feature_overrides` | Per-merchant exceptions to tier limits |

### Resolution Order

```
MerchantFeatureOverride (if exists) → TierFeatureLimit → denied
```

If a merchant has an override for a feature, it takes precedence over the tier default.

## Layer 2: Module Enablement

Module enablement controls **where functionality exists** at the platform level.

### How It Works

1. Modules are auto-discovered from `app/modules/*/definition.py`.
2. **Core modules** (core, tenancy, cms, customers, billing, payments, messaging, contracts) are always enabled.
3. **Optional modules** (catalog, orders, inventory, analytics, etc.) can be enabled/disabled per platform via the `PlatformModule` table.
4. When a module is disabled, its routes, menu items, and features are excluded from the platform.

### Key Model

| Model | Table | Purpose |
|-------|-------|---------|
| `PlatformModule` | `platform_modules` | Junction table: which modules are enabled per platform |

### Impact on Other Layers

- **Menu items** from disabled modules are automatically excluded by `MenuDiscoveryService`
- **Features** from disabled modules are not available for subscription gating
- **Permissions** from disabled modules are not shown in role management

## Layer 3: Menu Visibility

Menu visibility controls **what's shown** in the sidebar for admin and store interfaces.

### How It Works

1. Each module defines its menu items in `definition.py` using `MenuSectionDefinition` and `MenuItemDefinition`.
2. `MenuDiscoveryService` aggregates items from all enabled modules and applies filtering.
3. `AdminMenuConfig` stores **visibility overrides** using an opt-out model: all items visible by default, only hidden items stored in the database.
4. Mandatory items (`is_mandatory=True`) cannot be hidden.

### Filtering Pipeline

```
All Module Menu Items
  → Remove items from disabled modules (Layer 2)
  → Remove super_admin_only items for non-super-admins
  → Remove items hidden via AdminMenuConfig
  → Remove items requiring permissions the user lacks (Layer 4)
  = Final visible menu
```

### Scope

| Scope | Who it affects | Frontend |
|-------|----------------|----------|
| Platform | All platform admins and stores on that platform | Admin + Store |
| User | Individual super admin | Admin only |

### Key Model

| Model | Table | Purpose |
|-------|-------|---------|
| `AdminMenuConfig` | `admin_menu_configs` | Visibility overrides per platform or user |

## Layer 4: Role Permissions

Role permissions control **who can do what** at the store level.

### How It Works

1. Each module declares its permissions in `definition.py` using `PermissionDefinition`:

    ```python
    PermissionDefinition(
        id="products.view",
        label_key="catalog.permission.products_view",
        description_key="catalog.permission.products_view_desc",
        category="products",
    )
    ```

2. `PermissionDiscoveryService` aggregates permissions from all modules.
3. Roles are collections of permission IDs, stored per-store in the `roles` table.
4. Store team members are linked to roles via `StoreUser.role_id`.

### Owner Bypass

Store owners (`Merchant.owner_user_id`) automatically receive **all permissions** without needing a role record. Ownership is checked via `User.is_owner_of(store_id)`.

### Role Presets

The system provides 5 preset roles with predefined permission sets:

| Preset | Description | Permission Count |
|--------|-------------|-----------------|
| `manager` | Full operational access | ~23 |
| `staff` | Day-to-day operations | ~10 |
| `support` | Customer service focus | ~6 |
| `viewer` | Read-only access | ~6 |
| `marketing` | Marketing and customer data | ~7 |

Preset roles are created automatically on first access. Store owners can also create custom roles with any combination of the available permissions via the role editor UI at `/store/{store_code}/team/roles`.

### Custom Role Management

Store owners can create, edit, and delete custom roles via:
- **Store UI:** `/store/{store_code}/team/roles` (Alpine.js permission matrix)
- **Store API:** `POST/PUT/DELETE /api/v1/store/team/roles`
- **Admin UI:** `/admin/store-roles` (with Tom Select store picker)
- **Admin API:** `GET/POST/PUT/DELETE /api/v1/admin/store-roles?store_id=X`

The **Permission Catalog API** (`GET /api/v1/store/team/permissions/catalog`) returns all permissions grouped by category with labels and descriptions for the UI.

Admin access is scoped: **super admins** can manage any store, while **platform admins** can only manage stores within their assigned platforms (validated via `StorePlatform` table).

### Enforcement Points

| Where | How | Pattern |
|-------|-----|---------|
| **API routes** | `require_store_permission("products.view")` | FastAPI `Depends()` — returns 403 JSON |
| **Page routes** | `require_store_page_permission("products.view")` | FastAPI `Depends()` — redirects to no-access page |
| **Sidebar menu** | `MenuItemDefinition.requires_permission` | Items hidden if user lacks permission |
| **Template UI** | `window.USER_PERMISSIONS` | Alpine.js `x-show` for button/element hiding |

### Key Models

| Model | Table | Purpose |
|-------|-------|---------|
| `Role` | `roles` | Permission set per store (preset or custom) |
| `StoreUser` | `store_users` | Links user to store with role assignment |

### Per-Store Flexibility

A user can have **different roles in different stores**. For example, a user might be a `manager` in Store A but a `viewer` in Store B, because `StoreUser` records are per-store.

## How the Layers Interact

### Example: Store team member tries to view products

```
1. SUBSCRIPTION: Does the merchant's tier include "products" feature?
   → Yes (tier allows up to 200 products) → Continue
   → No → "Upgrade your plan to access products"

2. MODULE: Is the catalog module enabled on this platform?
   → Yes (OMS platform has catalog enabled) → Continue
   → No → Products section doesn't exist at all

3. MENU VISIBILITY: Is the products menu item visible?
   → Yes (not hidden in AdminMenuConfig) → Show in sidebar
   → No → Item hidden from sidebar (but URL still works)

4. PERMISSIONS: Does the user's role include "products.view"?
   → Yes (user has "staff" role with products.view) → Allow access
   → No → 403 Forbidden / redirect to no-access page
```

### Key Distinction

- **Layers 1-3** are about **platform/store configuration** — they define the environment
- **Layer 4** is about **individual user authorization** — it defines what each person can do within that environment

## Related Documentation

- [Authentication & RBAC](auth-rbac.md) — JWT auth, user roles, enforcement methods
- [Store RBAC](../backend/store-rbac.md) — Custom role CRUD, permission catalog API, admin role management
- [Menu Management](menu-management.md) — Menu discovery, visibility config, AdminMenuConfig
- [Module System](module-system.md) — Module architecture, auto-discovery, classification
- [Feature Gating](../implementation/feature-gating-system.md) — Tier-based feature limits
