# Research: Billing & Subscription Truth Layer v1 **Date**: 2026-05-04 **Branch**: `274-billing-subscription-truth` ## Decision 1: Use a dedicated workspace-owned subscription table, not more workspace-setting keys - **Decision**: Persist current subscription truth in a new `workspace_subscriptions` table with one current row per workspace. - **Rationale**: Subscription truth now has its own lifecycle, date fields, audit trail, and future sync seam. Keeping it inside `WorkspaceSetting` would continue to blur fallback lifecycle truth, plan settings, and current subscription truth into one settings bucket. - **Alternatives considered**: - More keys under `entitlements.*`: rejected because the record now has independent lifecycle meaning and no longer behaves like a small plan override. - Broad customer-account or billing model: rejected because the current release needs one current subscription record only, not a full billing domain. ## Decision 2: Keep `WorkspaceCommercialLifecycleResolver` as the only runtime gate - **Decision**: `WorkspaceCommercialLifecycleResolver` remains the one gate consulted by onboarding and review-pack surfaces. - **Rationale**: Specs 247 and 251 already proved the current gate shape. Introducing direct subscription checks on onboarding or review-pack surfaces would create a second gate and immediate drift. - **Alternatives considered**: - Direct subscription checks on each surface: rejected because that would duplicate lifecycle logic and blur business-state versus entitlement-state reasoning. - Leaving lifecycle fully manual even after adding a subscription record: rejected because it would leave two competing commercial truths alive at once. ## Decision 3: Subscription truth becomes the upstream lifecycle source when present - **Decision**: When a workspace has a current subscription record, lifecycle state is derived from that record. When no subscription record exists, the current manual lifecycle overlay remains the fallback source. - **Rationale**: This keeps current behavior stable for untouched workspaces while letting new subscription-backed workspaces stop relying on manual lifecycle state. - **Alternatives considered**: - Force every workspace to get a subscription record immediately: rejected because the repo already has live manual lifecycle semantics and the narrow slice should not require a bulk migration workflow. - Add subscription truth as read-only evidence without feeding lifecycle: rejected because it would preserve duplicated truths and force operators to reconcile them manually. ## Decision 4: Use one current record and audit as history - **Decision**: V1 stores one current subscription record per workspace and relies on `AuditLog` for change history. - **Rationale**: The current operator problem is understanding the current posture and driving the current lifecycle gate, not browsing historical subscription revisions in-product. - **Alternatives considered**: - Multi-row history table or event ledger: rejected because the current slice would become a billing-history feature rather than a source-of-truth follow-through. - No history beyond the row itself: rejected because auditability is required for commercial truth changes. ## Decision 5: Keep the current mutation surface on the system workspace detail page - **Decision**: Reuse `ViewWorkspace` for subscription mutation and keep `WorkspaceSettings` read-only. - **Rationale**: Specs 247 and 251 already separated admin-plane self-understanding from platform-plane commercial control. This slice should preserve that separation. - **Alternatives considered**: - Add an admin-plane subscription form: rejected because it would create a second commercial control plane. - Add a new system billing page: rejected because the existing workspace detail page is already the commercial-truth drilldown. ## Decision 6: No automatic timers, provider sync, or invoice logic in v1 - **Decision**: Period dates are visible and testable, but the product does not auto-transition subscription state, sync from an external system, or introduce invoices in this slice. - **Rationale**: The current need is durable commercial truth and one shared runtime source, not an automation engine. - **Alternatives considered**: - Webhook or provider sync placeholders: rejected because they would widen the slice into provider-boundary work. - Automatic transitions when trial or period end passes: rejected because the repo has no current scheduling or operator-review contract for that behavior. ## Final Research Outcome - Current-release truth requires a dedicated current subscription record. - Runtime gating remains on the existing lifecycle resolver. - The implementation should stay on the current system and admin surfaces. - No provider, invoice, portal, or automation work is needed for this slice.