Some checks failed
Main Confidence / confidence (push) Failing after 1m45s
## Summary - add the bounded workspace commercial lifecycle overlay from spec 251 on top of the existing entitlement substrate - expose audited commercial state inspection and mutation on the system workspace detail surface - gate onboarding activation and review-pack start actions through the shared lifecycle decision while preserving suspended read-only access to existing review, evidence, and generated-pack history - add focused Pest coverage plus the spec/plan/tasks/data-model/contract artifacts for the feature ## Validation - targeted Pest unit and feature lanes for lifecycle resolution, system-plane mutation, onboarding gating, review-pack enforcement, download preservation, customer review workspace access, and evidence snapshot access - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - integrated browser smoke on the system workspace detail and the preserved read-only review/evidence/review-pack surfaces ## Notes - branch: `251-commercial-entitlements-billing-state` - base: `dev` - commit: `606e9760` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #292
170 lines
8.8 KiB
Markdown
170 lines
8.8 KiB
Markdown
# Data Model: Commercial Entitlements and Billing-State Maturity
|
|
|
|
**Date**: 2026-04-28
|
|
**Branch**: `251-commercial-entitlements-billing-state`
|
|
|
|
## Overview
|
|
|
|
This slice adds no new table. Persisted truth stays in existing `workspace_settings` rows, while the commercial lifecycle overlay and action-family outcomes remain derived.
|
|
|
|
## Persisted Truth
|
|
|
|
### 1. Workspace Commercial Lifecycle Setting Aggregate
|
|
|
|
**Persistence**: Existing `App\Models\WorkspaceSetting` rows
|
|
**Ownership**: Workspace-owned
|
|
**Scope**: One workspace, no new tenant-owned or platform-owned persistence
|
|
|
|
The slice reuses explicit settings keys under the existing `entitlements` domain.
|
|
|
|
| Setting key | Type | Nullable | Validation | Notes |
|
|
|-------------|------|----------|------------|-------|
|
|
| `entitlements.commercial_lifecycle_state` | string | yes | when present, must be one of `trial`, `grace`, `active_paid`, `suspended_read_only` | `null` means the workspace has never been explicitly set and resolves to the implicit default `active_paid` |
|
|
| `entitlements.commercial_lifecycle_reason` | string | yes | required on every explicit lifecycle state change; trimmed; max 500 chars | Operator-entered rationale shown on system and contextual admin surfaces |
|
|
|
|
**Write rules**:
|
|
|
|
- Lifecycle mutation happens from the system plane only and updates state plus rationale together through the existing workspace settings write/audit path.
|
|
- The future `Change commercial state` action is confirmation-protected and requires explicit rationale for every explicit lifecycle transition, including an explicit return to `active_paid`.
|
|
- Once a platform operator explicitly sets `active_paid`, that remains a stored state like the other three values. `null` is reserved for untouched workspaces only.
|
|
|
|
**Relationships**:
|
|
|
|
- `workspace_settings.workspace_id` anchors lifecycle truth to the workspace.
|
|
- `workspace_settings.updated_by_user_id` remains the attribution source for state change metadata.
|
|
|
|
## Existing Substrate Truth Reused
|
|
|
|
### 2. Workspace Entitlement Substrate Summary
|
|
|
|
**Persistence**: Existing Spec 247 workspace entitlement settings + code-owned plan-profile catalog
|
|
**Owner**: `WorkspaceEntitlementResolver`
|
|
|
|
This slice does not remodel the substrate. It reuses:
|
|
|
|
- `plan_profile`
|
|
- `managed_tenant_activation_limit`
|
|
- `review_pack_generation_enabled`
|
|
- substrate rationale/source/current-usage metadata
|
|
|
|
The lifecycle overlay may warn or restrict after substrate resolution, but it must never expand access beyond what the substrate already allows.
|
|
|
|
## Code-Owned Truth
|
|
|
|
### 3. Commercial Lifecycle State Catalog Entry
|
|
|
|
**Persistence**: none, code-owned
|
|
**Ownership**: Product/runtime configuration
|
|
**Scope**: current release only
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `id` | string | yes | Stable internal identifier stored in `entitlements.commercial_lifecycle_state` |
|
|
| `label` | string | yes | Operator-facing state label |
|
|
| `description` | string | yes | Short explanation for system detail and contextual messaging |
|
|
| `onboarding_outcome` | string | yes | `allow` or `block` |
|
|
| `review_pack_start_outcome` | string | yes | `allow`, `warn`, or `block` |
|
|
| `preserves_read_only_history` | bool | yes | Whether existing review/evidence/generated-pack consumption remains explicitly preserved |
|
|
| `is_default` | bool | yes | Exactly one default entry: `active_paid` |
|
|
|
|
**Behavior matrix**:
|
|
|
|
| State | Onboarding activation | Review-pack starts | Existing review/evidence/download access |
|
|
|-------|-----------------------|--------------------|------------------------------------------|
|
|
| `trial` | allow | allow | allow |
|
|
| `active_paid` | allow | allow | allow |
|
|
| `grace` | block | warn (start still allowed) | allow |
|
|
| `suspended_read_only` | block | block | allow |
|
|
|
|
## Derived Truth
|
|
|
|
### 4. Effective Commercial Lifecycle Decision
|
|
|
|
**Persistence**: none, derived at runtime
|
|
**Owner**: bounded `WorkspaceCommercialLifecycleResolver`
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `workspace_id` | int | yes | Workspace being evaluated |
|
|
| `state` | string | yes | Effective lifecycle state |
|
|
| `label` | string | yes | Operator-facing label |
|
|
| `source` | string | yes | `default_active_paid` or `workspace_setting`; any rendered source label must come from one shared mapping |
|
|
| `rationale` | string | no | Explicit operator rationale when source is `workspace_setting` |
|
|
| `last_changed_at` | datetime | no | Derived from the most recent lifecycle-related `WorkspaceSetting` row |
|
|
| `last_changed_by` | string | no | Derived actor attribution |
|
|
| `entitlement_summary` | object | yes | Existing Spec 247 substrate summary reused for support/context |
|
|
| `action_decisions` | object | yes | Per-action-family outcomes described below |
|
|
|
|
### 5. Commercial Lifecycle Action Decision
|
|
|
|
**Persistence**: none, derived at runtime
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `action_key` | string | yes | One of `managed_tenant_activation`, `review_pack_start`, `review_history_read`, `evidence_read`, `generated_pack_read` |
|
|
| `outcome` | string | yes | `allow`, `warn`, `block`, or `allow_read_only` |
|
|
| `reason_family` | string | no | `commercial_lifecycle`, `entitlement_substrate`, or `null` when fully allowed |
|
|
| `message` | string | no | Operator-safe explanation or warning |
|
|
| `lifecycle_state` | string | yes | Effective state that produced the action decision |
|
|
| `underlying_entitlement_key` | string | no | Present for onboarding/review-pack start decisions to preserve substrate traceability |
|
|
|
|
**Decision ordering rules**:
|
|
|
|
- The substrate entitlement decision runs first.
|
|
- If the substrate already blocks the action, the lifecycle overlay must not replace that reason.
|
|
- If the substrate allows the action, the lifecycle overlay may warn or block according to the state matrix.
|
|
- Authorization is not part of this derived decision; 404 and 403 semantics remain outside and happen earlier.
|
|
|
|
## Supporting Derived View Models
|
|
|
|
### 6. System Workspace Commercial Lifecycle View Model
|
|
|
|
**Persistence**: none
|
|
**Consumer**: `App\Filament\System\Pages\Directory\ViewWorkspace`
|
|
|
|
Contains:
|
|
|
|
- effective lifecycle state, label, rationale, and last-change attribution
|
|
- the two in-scope action-family outcomes
|
|
- the reused entitlement substrate summary for support context
|
|
- the one dominant mutation affordance metadata for `Change commercial state`
|
|
|
|
### 7. Contextual Admin Lifecycle Gate View Models
|
|
|
|
**Persistence**: none
|
|
**Consumers**: `ManagedTenantOnboardingWizard`, review-pack entry surfaces, and suspended read-only history surfaces
|
|
|
|
Contains:
|
|
|
|
- the immediate action-family outcome (`allow`, `warn`, `block`, or `allow_read_only`)
|
|
- one operator-safe explanation
|
|
- enough substrate context to keep lifecycle blocks distinct from underlying entitlement blocks
|
|
|
|
## Derived Query Dependencies
|
|
|
|
| Need | Source | Notes |
|
|
|------|--------|-------|
|
|
| Underlying plan-profile and entitlement truth | `WorkspaceEntitlementResolver` | Remains the canonical substrate |
|
|
| Lifecycle last-change attribution | existing `workspace_settings.updated_by_user_id` and timestamps | Derived from lifecycle-related rows only |
|
|
| Active managed-tenant usage | existing tenant/workspace runtime truth | Reused from the substrate summary |
|
|
| Existing review/history/evidence/download availability | existing review pack, review, evidence snapshot, and RBAC truth | No new persistence needed |
|
|
| Review-pack no-run proof | existing `review_packs` and `operation_runs` tables | Used only in tests to prove blocked starts do not write new run state |
|
|
|
|
## State Transitions
|
|
|
|
There is no new table-backed lifecycle entity. State changes are explicit workspace-setting transitions plus audit entries.
|
|
|
|
| From | To | Trigger | Consequence |
|
|
|------|----|---------|-------------|
|
|
| `null` (implicit default) | any explicit state | platform operator saves lifecycle state on the system detail page | workspace now has explicit commercial posture, rationale, and attribution |
|
|
| `trial` | `grace` | platform operator state change | new managed-tenant activation blocks; review-pack starts remain allowed with warning |
|
|
| `grace` | `suspended_read_only` | platform operator state change | onboarding and new review-pack starts block; history/evidence/download remain available |
|
|
| `suspended_read_only` | `active_paid` | platform operator state change | future starts again defer to underlying entitlement truth |
|
|
| any explicit state | another explicit state | platform operator state change | previous state is replaced; audit history preserves the transition trail |
|
|
|
|
## Boundaries Explicitly Preserved
|
|
|
|
- No new billing/customer/subscription entity exists.
|
|
- No new automated timers, expiry jobs, renewal reminders, or scheduled transitions are introduced.
|
|
- No new broad suspension contract is added for unrelated mutable surfaces.
|
|
- Existing read-only review/evidence/generated-pack access remains governed by current RBAC and redaction rules. |