TenantAtlas/specs/247-plans-entitlements-billing-readiness/data-model.md
ahmido e222845a36
Some checks failed
Main Confidence / confidence (push) Failing after 53s
247: plans entitlements billing readiness (#287)
Automated commit and PR created by Copilot per user request.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #287
2026-04-27 17:35:04 +00:00

148 lines
7.8 KiB
Markdown

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