# 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 |