76 lines
7.3 KiB
Markdown
76 lines
7.3 KiB
Markdown
# Research: Plans, Entitlements & Billing Readiness
|
|
|
|
**Date**: 2026-04-27
|
|
**Branch**: `247-plans-entitlements-billing-readiness`
|
|
|
|
## Decision 1: Persist workspace commercial truth in existing `workspace_settings`
|
|
|
|
- **Decision**: Store the first-slice workspace commercial truth through explicit `WorkspaceSetting` keys in an `entitlements` domain, reusing `SettingsRegistry`, `SettingsResolver`, and `SettingsWriter`.
|
|
- **Rationale**: The repo already has validated, audited workspace-scoped settings persistence and a singleton workspace settings page. Reusing that path keeps the slice narrow, keeps audit behavior consistent, and avoids inventing a billing or account persistence model.
|
|
- **Alternatives considered**:
|
|
- New `plans`, `subscriptions`, or `customer_accounts` tables: rejected because the spec explicitly forbids broad billing/account scope.
|
|
- One nested JSON blob for all entitlement fields: rejected because explicit keys better fit existing page save/reset patterns, validation, and audit attribution.
|
|
|
|
## Decision 2: Keep the plan-profile catalog code-owned and bounded
|
|
|
|
- **Decision**: Represent plan-profile defaults as a small code-owned catalog with one code-owned default profile and bounded named profile identifiers, not as operator-editable data.
|
|
- **Rationale**: The first slice needs deterministic defaults when no workspace-specific selection exists, but it does not need a management UI, a billing backoffice, or a pricing model. Code-owned defaults are the narrowest current-release truth.
|
|
- **Alternatives considered**:
|
|
- Database-backed plan catalog: rejected because there is no current product workflow for editing plans.
|
|
- External billing/provider sync: rejected because the spec explicitly excludes payment providers and subscription lifecycle work.
|
|
|
|
## Decision 3: Introduce one bounded `WorkspaceEntitlementResolver`
|
|
|
|
- **Decision**: Add one bounded resolver that projects effective entitlement decisions from plan defaults, workspace overrides, override rationale, and current usage.
|
|
- **Rationale**: Existing settings helpers resolve raw setting values but do not answer the operator question the feature actually needs: what is the effective value, where did it come from, why is it overridden, what is current usage, and may this action proceed now?
|
|
- **Alternatives considered**:
|
|
- Rebuild the logic independently on `WorkspaceSettings`, `ManagedTenantOnboardingWizard`, and each review-pack entry surface: rejected because it would immediately create wording drift and inconsistent enforcement.
|
|
- Extend `SettingsResolver` to absorb entitlement-specific usage logic: rejected because that would over-specialize a generic settings utility.
|
|
|
|
## Decision 4: Keep hard enforcement at the existing mutation and run-start boundaries
|
|
|
|
- **Decision**: Enforce onboarding entitlement in `ManagedTenantOnboardingWizard::canCompleteOnboarding()` and `completeOnboarding()`, and enforce review-pack entitlement inside `ReviewPackService::generate()` and `generateFromReview()`, while UI surfaces render the same decision state ahead of action execution.
|
|
- **Rationale**: Review-pack generation already fans out through several Filament actions, but those surfaces converge on `ReviewPackService`. Putting hard enforcement at the service boundary prevents bypass. Onboarding completion is already owned by the wizard page and should remain there.
|
|
- **Alternatives considered**:
|
|
- UI-only disabling on each action surface: rejected because it would not protect direct Livewire action execution.
|
|
- A second cross-cutting action framework for entitlement checks: rejected because the slice only needs one bounded business decision path, not a new platform hook system.
|
|
|
|
## Decision 5: Preserve explicit RBAC versus business-state semantics
|
|
|
|
- **Decision**: Keep 404 for non-members and wrong-plane actors, keep 403 for members missing capability, and model entitlement denial as a visible business-state block for otherwise authorized actors.
|
|
- **Rationale**: The repo constitution already distinguishes membership isolation from capability denial. Entitlements are neither. Treating entitlement denial as 403 or 404 would erase the operator-visible truth this slice exists to provide.
|
|
- **Alternatives considered**:
|
|
- Hide blocked actions completely: rejected because the spec requires operator-visible rationale.
|
|
- Return 403 for entitlement denial: rejected because it conflates product policy with authorization.
|
|
|
|
## Decision 6: Keep system visibility read-only on the existing workspace directory page
|
|
|
|
- **Decision**: Expose the resolved plan profile, entitlement values, source, and last-changed attribution on `App\Filament\System\Pages\Directory\ViewWorkspace` and its existing Blade view, with no system-plane mutation control.
|
|
- **Rationale**: Platform support needs visibility into current workspace commercial truth, but introducing a second mutation plane would immediately create duplicate truth and cross-plane drift.
|
|
- **Alternatives considered**:
|
|
- New system resource or admin-like settings page: rejected because the first slice is explicitly read-only on `/system`.
|
|
- Linking support users back to `/admin` without any local visibility: rejected because it keeps support dependent on plane switching and tribal knowledge.
|
|
|
|
## Decision 7: Keep review-pack shared OperationRun UX unchanged when entitled
|
|
|
|
- **Decision**: Preserve existing `OperationUxPresenter`, `OperationRunLinks`, dedupe behavior, and queued background generation semantics whenever review-pack generation is entitled.
|
|
- **Rationale**: The feature is about whether generation is allowed, not about rebuilding review-pack run UX. The right insertion point is before run creation, not inside the shared run lifecycle.
|
|
- **Alternatives considered**:
|
|
- Localize new review-pack blocked/queued UX per surface: rejected because the repo already centralizes the run-start UX.
|
|
- Add a new entitlement-specific notification family: rejected because blocked attempts should stop quietly with truthful local action messaging and no new run.
|
|
|
|
## Decision 8: Prove the slice with focused Sail/Pest unit and feature coverage only
|
|
|
|
- **Decision**: Cover the new resolver/profile defaults with unit tests and prove settings, onboarding, review-pack gating, and system visibility with focused feature tests run through Sail.
|
|
- **Rationale**: The business risk is decision correctness and action enforcement, not browser layout or broad workflow orchestration. Unit plus feature lanes are enough to prove the slice without dragging in heavy-governance or browser cost.
|
|
- **Alternatives considered**:
|
|
- Browser tests: rejected because no browser-only interaction or layout risk is introduced.
|
|
- Heavy-governance suite expansion: rejected because the scope is bounded and local to existing surfaces.
|
|
|
|
## Decision 9: Leave Filament panel registration, global search, and assets unchanged
|
|
|
|
- **Decision**: Do not add panels, providers, global-search resources, or new Filament asset registrations as part of this slice.
|
|
- **Rationale**: The feature is workspace-first entitlement truth inside existing admin and system surfaces. Filament infrastructure changes would widen scope without helping the first release.
|
|
- **Alternatives considered**:
|
|
- New commercial panel or system sub-panel: rejected because the slice reuses current surfaces.
|
|
- Asset-backed custom billing UI components: rejected because native Filament components and the existing system Blade page are sufficient. |