# Feature Entitlement Model

## Overview

Feature access in Supplier Pulse is decided by four independent layers evaluated in order. All four must grant access for a feature to be available.

---

## The Four Layers

```
Request: Can user U of tenant T use feature F?

Layer 1: PLAN ENTITLEMENT
  Is F included in T's current plan?
  → NO → ACCESS_DENIED (upgrade required)
  → YES ↓

Layer 2: TENANT FEATURE OVERRIDE
  Has the platform owner explicitly disabled F for T?
  → DISABLED → ACCESS_DENIED
  Has the platform owner explicitly enabled F for T (even if not in plan)?
  → ENABLED → continue to Layer 3
  → NOT_SET → continue to Layer 3 with plan result

Layer 3: ROLE PERMISSION
  Does user U's role grant permission for this specific action on F?
  → NO → ACCESS_DENIED
  → YES ↓

Layer 4: OPERATIONAL TOGGLE
  Is there an active KILL_SWITCH for F?
  → YES → ACCESS_DENIED
  → NO → ACCESS_GRANTED
```

---

## Feature Definitions

Features are permanent catalog entries. They do not change with code deployments.

```typescript
type FeatureCode =
  | 'MODULE_CALENDAR'
  | 'MODULE_LSC'
  | 'MODULE_OEE'
  | 'MODULE_VSM'
  | 'MODULE_ASSESSMENT'
  | 'MODULE_REPORTING'
  | 'MODULE_REPOSITORY'
  | 'MODULE_PROJECTS'
  // Sub-features
  | 'ASSESSMENT_EXPORT_PDF'
  | 'ASSESSMENT_I18N'
  | 'OEE_TREND_CHARTS'
  | 'REPORTING_ADVANCED'
  | 'BRANDING_CUSTOM'
  | 'SSO_SAML'
  | 'API_ACCESS'
```

---

## Plan Catalog

| Feature                | Starter | Professional | Enterprise |
|------------------------|---------|--------------|------------|
| MODULE_PROJECTS        | ✓       | ✓            | ✓          |
| MODULE_CALENDAR        | ✓       | ✓            | ✓          |
| MODULE_REPOSITORY      | ✓       | ✓            | ✓          |
| MODULE_REPORTING       | basic   | ✓            | ✓          |
| MODULE_LSC             | —       | ✓            | ✓          |
| MODULE_OEE             | —       | ✓            | ✓          |
| MODULE_ASSESSMENT      | —       | ✓            | ✓          |
| MODULE_VSM             | —       | ✓            | ✓          |
| ASSESSMENT_EXPORT_PDF  | —       | ✓            | ✓          |
| ASSESSMENT_I18N        | —       | ✓            | ✓          |
| REPORTING_ADVANCED     | —       | —            | ✓          |
| BRANDING_CUSTOM        | —       | —            | ✓          |
| SSO_SAML               | —       | —            | ✓          |
| API_ACCESS             | —       | —            | ✓          |

---

## Entitlement Engine API

```typescript
// Server-side check (API routes, server components)
import { checkEntitlement, EntitlementResult } from '@/lib/entitlement/engine'

const result = await checkEntitlement({
  tenantId: 'uuid',
  feature: 'MODULE_OEE',
  userId: 'uuid',         // optional — skips role check if omitted
  permission: 'view_oee', // optional — the specific permission to check
})

// result.granted: boolean
// result.reason: 'plan_missing' | 'tenant_disabled' | 'role_denied' | 'kill_switch' | 'granted'
```

---

## Client-Side Hook

```typescript
// Client components
import { useEntitlement } from '@/lib/entitlement/client-hook'

function OeeNavItem() {
  const oeeEnabled = useEntitlement('MODULE_OEE')
  if (!oeeEnabled) return null
  return <NavItem href="/oee" label="OEE Analyse" />
}
```

---

## Enforcement Points

Features must be blocked at all three planes:

### 1. Navigation / UI
- Nav items hidden when module not entitled
- Module pages redirect to `/unauthorized` if accessed directly
- Checked via `useEntitlement()` hook or server-side `checkEntitlement()`

### 2. API Routes
Every `/api/` handler for a module feature calls `requireEntitlement()`:
```typescript
export async function GET(req: NextRequest) {
  await requireEntitlement(req, 'MODULE_OEE')
  // ... handler logic
}
```
Returns `403 Forbidden` if not entitled.

### 3. Data Access
Modules with sensitive data have database-level enforcement via:
- RLS policies (per-table)
- Background jobs check entitlement before running

---

## Caching Strategy

Entitlements are loaded from the control plane database and cached:
- Per-tenant cache with 5-minute TTL
- Invalidated when owner changes entitlements via Owner Portal API
- Cache key: `entitlements:{tenantId}`
- Implementation: in-memory Map (single instance) or Redis for multi-instance

---

## Operational Toggles vs Plan Entitlements

| Property         | Plan Entitlement       | Operational Toggle        |
|------------------|------------------------|---------------------------|
| Lifetime         | Long-lived (months)    | Short-lived (hours/days)  |
| Purpose          | Commercial             | Emergency / rollout       |
| Set by           | Billing Admin          | Platform Owner / Admin    |
| Override         | Tenant override allowed| Overrides everything      |
| Audit required   | Yes                    | Yes (mandatory)           |

These are modeled in **separate tables** (`cp_plan_feature_entitlements` vs `cp_release_rollouts`) and evaluated separately in the entitlement engine.
