# Data Model: Plans, Entitlements & Billing Readiness

**Date**: 2026-04-27  
**Branch**: `247-plans-entitlements-billing-readiness`

## Overview

This slice adds no new table. Persisted truth stays in existing `workspace_settings` rows, while plan defaults and effective entitlement decisions remain derived.

## Persisted Truth

### 1. Workspace Entitlement Settings Aggregate

**Persistence**: Existing `App\Models\WorkspaceSetting` rows  
**Ownership**: Workspace-owned  
**Scope**: One workspace, no tenant-owned persistence, no system-plane mutation

The slice reuses explicit settings keys under an `entitlements` domain.

| Setting key | Type | Nullable | Validation | Notes |
|-------------|------|----------|------------|-------|
| `entitlements.plan_profile` | string | yes | must match a code-owned plan-profile identifier when present | `null` means use the code-owned default profile |
| `entitlements.managed_tenant_limit_override_value` | int | yes | integer, `>= 0` | Explicit override for onboarding activation limit |
| `entitlements.managed_tenant_limit_override_reason` | string | yes | required when the paired override value is present; trimmed; max 500 chars | Operator-entered rationale shown on admin and system surfaces |
| `entitlements.review_pack_generation_override_value` | bool | yes | boolean | Explicit override for whether new `Generate pack`, `Regenerate`, and `Export executive pack` actions are allowed |
| `entitlements.review_pack_generation_override_reason` | string | yes | required when the paired override value is present; trimmed; max 500 chars | Operator-entered rationale shown on admin and system surfaces |

**Write rules**:

- Saving the section may update several `WorkspaceSetting` rows in one page submission, but each row continues to use the existing `SettingsWriter` audit path.
- Resetting an override clears both the override value and its rationale, returning effective truth to the selected plan profile or code-owned default profile.
- Lowering the managed-tenant limit below current usage does not mutate tenant records; it only changes future activation eligibility.

**Relationships**:

- `workspace_settings.workspace_id` anchors all persisted truth to a workspace.
- `workspace_settings.updated_by_user_id` remains the attribution source for last change metadata.

## Code-Owned Truth

### 2. Workspace Plan Profile Catalog Entry

**Persistence**: none, code-owned  
**Ownership**: Product/runtime configuration  
**Scope**: first-slice only

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `id` | string | yes | Stable internal identifier stored in `entitlements.plan_profile` |
| `label` | string | yes | Operator-facing plan profile label on settings and system surfaces |
| `description` | string | yes | Concise explanation of what the profile allows |
| `managed_tenant_limit_default` | int | yes | Default active managed-tenant activation limit |
| `review_pack_generation_default` | bool | yes | Default allow/block state for new review-pack generation |
| `is_default` | bool | yes | Exactly one profile is the code-owned fallback when no workspace setting exists |

**Rules**:

- The catalog is intentionally bounded to the first slice and must not grow into a broader entitlement matrix in this feature.
- The catalog is not operator-editable and is not a contract, invoice, or subscription record.

## Derived Truth

### 3. Effective Workspace Entitlement Decision

**Persistence**: none, derived at runtime  
**Owner**: bounded `WorkspaceEntitlementResolver`

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `workspace_id` | int | yes | Workspace being evaluated |
| `plan_profile_id` | string | yes | Effective profile after applying the code-owned default fallback |
| `key` | string | yes | One of the two first-slice entitlement keys |
| `effective_value` | int or bool | yes | Final value after plan defaults plus any workspace override |
| `source` | string | yes | `plan_profile_default` or `workspace_override` |
| `rationale` | string | no | Override reason when source is `workspace_override`; otherwise optional plan-profile description |
| `current_usage` | int | no | Active managed-tenant count for the limit-based key; `null` for the boolean key |
| `remaining_capacity` | int | no | Derived only for the limit-based key |
| `is_blocked` | bool | yes | Whether the current action should stop for business-state reasons |
| `block_reason` | string | no | Operator-facing explanation used on onboarding and review-pack surfaces when blocked |
| `last_changed_at` | datetime | no | Derived from the most recent entitlement-related `WorkspaceSetting` row if present |
| `last_changed_by` | string | no | Derived actor attribution for settings and system visibility |

**Key catalog**:

| Entitlement key | Value type | Used by |
|-----------------|------------|---------|
| `managed_tenant_activation_limit` | int | `ManagedTenantOnboardingWizard` completion eligibility and summary |
| `review_pack_generation_enabled` | bool | `ReviewPackService`, tenant dashboard widget, review register, tenant review view, review-pack list/detail actions |

**Behavior rules**:

- `managed_tenant_activation_limit` compares `current_usage` to the effective limit and blocks only future onboarding activation.
- `review_pack_generation_enabled=false` blocks new generate, regenerate, and executive-pack export attempts before `ReviewPack` or `OperationRun` creation.
- Existing review-pack downloads and already-generated artifacts remain outside this entitlement decision.

## Supporting Derived View Models

### 4. Workspace Entitlement Section Read Model

**Persistence**: none  
**Consumer**: `App\Filament\Pages\Settings\WorkspaceSettings`

Contains:

- effective plan profile label and description
- both entitlement decisions
- editable override values plus rationale inputs
- current managed-tenant usage summary
- last changed attribution for the `entitlements` domain

### 5. System Workspace Entitlement Summary Read Model

**Persistence**: none  
**Consumer**: `App\Filament\System\Pages\Directory\ViewWorkspace` and `resources/views/filament/system/pages/directory/view-workspace.blade.php`

Contains:

- read-only effective plan profile label
- both entitlement decisions with source and rationale
- last changed attribution
- current managed-tenant usage summary

## Derived Query Dependencies

| Need | Source | Notes |
|------|--------|-------|
| Active managed-tenant usage | existing tenant/workspace runtime truth | Count active managed tenants for the current workspace only; no persisted counter needed |
| Last change attribution | existing `workspace_settings.updated_by_user_id` and timestamps | Derived from entitlement-related settings rows only |
| Review-pack run creation proof | existing `review_packs` and `operation_runs` behavior | Used only in tests to prove blocked attempts create no new run |

## State Transitions

No new persisted lifecycle state is introduced.

Derived runtime states for the limit-based entitlement:

| State | Trigger | Consequence |
|-------|---------|-------------|
| `within_limit` | `current_usage < effective_value` | Onboarding completion may proceed if all other existing checks pass |
| `at_limit` | `current_usage >= effective_value` | Future onboarding completion is blocked with a truthful reason |
| `over_limit_after_lowering` | Workspace limit is lowered below current usage | Existing tenants stay active; future onboarding completion remains blocked until usage or limit changes |

Derived runtime states for the review-pack entitlement:

| State | Trigger | Consequence |
|-------|---------|-------------|
| `enabled` | effective boolean value is `true` | Existing review-pack start flow proceeds unchanged |
| `disabled` | effective boolean value is `false` | New generate/regenerate/export attempts block before run creation |