TenantAtlas/specs/251-commercial-entitlements-billing-state/contracts/workspace-commercial-lifecycle-overlay.logical.openapi.yaml
ahmido 7ee4909212
Some checks failed
Main Confidence / confidence (push) Failing after 1m45s
feat: commercial lifecycle overlay for workspace entitlements (#292)
## 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
2026-04-28 13:39:33 +00:00

465 lines
15 KiB
YAML

openapi: 3.0.3
info:
title: TenantPilot Admin/System - Workspace Commercial Lifecycle Overlay (Conceptual)
version: 0.1.0
description: |
Conceptual contract for the commercial lifecycle overlay that follows the
existing workspace entitlement substrate from Spec 247.
NOTE: These routes are implemented as existing Filament pages, resources,
widgets, and Livewire-backed actions. Exact Livewire payload shapes are not
part of this contract. This file captures logical route boundaries, the
system/admin split, and the required 404 / 403 / business-state semantics.
servers:
- url: /admin
- url: /system
paths:
/directory/workspaces/{workspace}:
get:
summary: View read-only workspace commercial lifecycle summary in the system plane
description: |
Renders the existing system directory workspace detail page with the
effective lifecycle state, rationale, affected behavior summary, and the
reused entitlement substrate summary.
parameters:
- $ref: '#/components/parameters/WorkspaceId'
responses:
'200':
description: System workspace detail rendered
content:
text/html:
schema:
type: string
x-logical-view-model:
$ref: '#/components/schemas/SystemWorkspaceCommercialLifecycleView'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/directory/workspaces/{workspace}/actions/change-commercial-state:
post:
summary: Change the workspace commercial lifecycle state from the system plane
description: |
Conceptual contract for the confirmation-protected state-change action
on the existing system workspace detail page.
Behavior:
- Platform user with directory visibility but without the dedicated
lifecycle-manage capability: 403
- Wrong plane or non-platform actor: 404 semantics at the panel boundary
- Authorized platform user: state and rationale are written through the
existing workspace settings audit path
parameters:
- $ref: '#/components/parameters/WorkspaceId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChangeCommercialLifecycleCommand'
responses:
'204':
description: Commercial lifecycle state changed successfully
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
/onboarding/{onboardingDraft}:
get:
summary: View onboarding workflow with lifecycle-aware completion state
description: |
Renders the existing managed-tenant onboarding wizard. The completion
step must include the commercial lifecycle outcome after the underlying
entitlement substrate has been evaluated.
parameters:
- $ref: '#/components/parameters/OnboardingDraftId'
responses:
'200':
description: Onboarding wizard rendered
content:
text/html:
schema:
type: string
x-logical-view-model:
$ref: '#/components/schemas/OnboardingCommercialLifecycleView'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/onboarding/{onboardingDraft}/actions/complete:
post:
summary: Complete onboarding when entitlement, lifecycle state, and existing readiness all allow
parameters:
- $ref: '#/components/parameters/OnboardingDraftId'
responses:
'204':
description: Onboarding completed
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/BusinessStateBlocked'
/review-packs/actions/generate:
post:
summary: Generate a review pack from the current tenant context
description: |
Conceptual contract for the tenant dashboard and review-pack list start
action family.
Behavior ordering:
1. authorization
2. underlying entitlement substrate decision
3. lifecycle overlay decision
4. existing dedupe / queued-start flow when allowed
A lifecycle-blocked attempt is future-start-only in this slice: it
creates no new `ReviewPack`, creates no new `OperationRun`, emits no
queued or terminal review-pack notification, and does not affect any
review-pack work that was already queued or running.
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewPackGenerationCommand'
responses:
'202':
description: Generation accepted or deduped through the existing flow
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/BusinessStateBlocked'
/tenant-reviews/{tenantReview}/actions/export-executive-pack:
post:
summary: Export an executive pack from an existing tenant review
description: |
Conceptual contract for the review register and tenant review detail
export action family. The lifecycle overlay must block before any new
`ReviewPack` or `OperationRun` is created, emit no queued or terminal
review-pack notification for the blocked attempt, and leave any
already-created queued or running review-pack work unchanged.
parameters:
- $ref: '#/components/parameters/TenantReviewId'
responses:
'202':
description: Export accepted or deduped through the existing flow
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/BusinessStateBlocked'
/review-packs/{reviewPack}/actions/regenerate:
post:
summary: Regenerate an existing review pack
description: |
Conceptual contract for the existing review-pack detail regenerate
action. Existing confirmation and dedupe behavior remain in place when
the lifecycle overlay allows the start. A lifecycle-blocked attempt is
future-start-only: it creates no new `ReviewPack`, creates no new
`OperationRun`, emits no queued or terminal review-pack notification,
and leaves any already-created queued or running review-pack work
unchanged.
parameters:
- $ref: '#/components/parameters/ReviewPackId'
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewPackGenerationCommand'
responses:
'202':
description: Regeneration accepted or deduped through the existing flow
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/BusinessStateBlocked'
/tenant-reviews/{tenantReview}:
get:
summary: View existing tenant review while the workspace may be suspended read-only
parameters:
- $ref: '#/components/parameters/TenantReviewId'
responses:
'200':
description: Existing tenant review rendered when current RBAC allows it
content:
text/html:
schema:
type: string
x-logical-view-model:
$ref: '#/components/schemas/PreservedReadOnlyView'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/review-packs/{reviewPack}:
get:
summary: View existing review pack while the workspace may be suspended read-only
parameters:
- $ref: '#/components/parameters/ReviewPackId'
responses:
'200':
description: Existing review pack rendered when current RBAC allows it
content:
text/html:
schema:
type: string
x-logical-view-model:
$ref: '#/components/schemas/PreservedReadOnlyView'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/review-packs/{reviewPack}/download:
get:
summary: Download an already-generated review pack while the workspace may be suspended read-only
parameters:
- $ref: '#/components/parameters/ReviewPackId'
responses:
'200':
description: Existing generated pack download is still available when current RBAC allows it
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/evidence-snapshots/{evidenceSnapshot}:
get:
summary: View existing evidence snapshot while the workspace may be suspended read-only
parameters:
- $ref: '#/components/parameters/EvidenceSnapshotId'
responses:
'200':
description: Existing evidence snapshot rendered when current RBAC allows it
content:
text/html:
schema:
type: string
x-logical-view-model:
$ref: '#/components/schemas/PreservedReadOnlyView'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
parameters:
WorkspaceId:
name: workspace
in: path
required: true
schema:
type: integer
OnboardingDraftId:
name: onboardingDraft
in: path
required: true
schema:
type: integer
TenantReviewId:
name: tenantReview
in: path
required: true
schema:
type: integer
ReviewPackId:
name: reviewPack
in: path
required: true
schema:
type: integer
EvidenceSnapshotId:
name: evidenceSnapshot
in: path
required: true
schema:
type: integer
responses:
Forbidden:
description: Established-scope actor lacks the required capability
NotFound:
description: Wrong plane, non-member scope, or inaccessible record
BusinessStateBlocked:
description: Actor is otherwise authorized, but the workspace commercial state or underlying entitlement substrate blocks the requested action
content:
application/json:
schema:
$ref: '#/components/schemas/CommercialLifecycleBlockResponse'
ValidationError:
description: Submitted commercial lifecycle state change failed validation
schemas:
ChangeCommercialLifecycleCommand:
type: object
required:
- state
- reason
properties:
state:
$ref: '#/components/schemas/CommercialLifecycleState'
reason:
type: string
description: Required for every explicit lifecycle state change, including an explicit return to active_paid.
minLength: 1
maxLength: 500
CommercialLifecycleState:
type: string
enum:
- trial
- grace
- active_paid
- suspended_read_only
ReviewPackGenerationCommand:
type: object
properties:
include_pii:
type: boolean
include_operations:
type: boolean
SystemWorkspaceCommercialLifecycleView:
type: object
required:
- workspace_id
- lifecycle
- affected_behaviors
properties:
workspace_id:
type: integer
lifecycle:
$ref: '#/components/schemas/CommercialLifecycleDecision'
affected_behaviors:
type: array
items:
$ref: '#/components/schemas/CommercialLifecycleActionDecision'
entitlement_substrate:
type: object
description: Existing Spec 247 workspace entitlement summary reused for context
primary_action:
$ref: '#/components/schemas/NextAction'
nullable: true
OnboardingCommercialLifecycleView:
type: object
required:
- onboarding_draft_id
- action_decision
properties:
onboarding_draft_id:
type: integer
action_decision:
$ref: '#/components/schemas/CommercialLifecycleActionDecision'
entitlement_substrate:
type: object
nullable: true
CommercialLifecycleDecision:
type: object
required:
- state
- label
- source
- source_label
properties:
state:
$ref: '#/components/schemas/CommercialLifecycleState'
label:
type: string
source:
type: string
enum:
- default_active_paid
- workspace_setting
source_label:
type: string
description: Rendered source label from the shared lifecycle source mapping used by system detail surfaces.
rationale:
type: string
nullable: true
last_changed_at:
type: string
format: date-time
nullable: true
last_changed_by:
type: string
nullable: true
CommercialLifecycleActionDecision:
type: object
required:
- action_key
- outcome
- lifecycle_state
properties:
action_key:
type: string
enum:
- managed_tenant_activation
- review_pack_start
- review_history_read
- evidence_read
- generated_pack_read
outcome:
type: string
enum:
- allow
- warn
- block
- allow_read_only
reason_family:
type: string
nullable: true
enum:
- commercial_lifecycle
- entitlement_substrate
message:
type: string
nullable: true
lifecycle_state:
$ref: '#/components/schemas/CommercialLifecycleState'
underlying_entitlement_key:
type: string
nullable: true
CommercialLifecycleBlockResponse:
type: object
required:
- reason_family
- message
properties:
reason_family:
type: string
enum:
- commercial_lifecycle
- entitlement_substrate
lifecycle_state:
$ref: '#/components/schemas/CommercialLifecycleState'
nullable: true
message:
type: string
PreservedReadOnlyView:
type: object
required:
- read_only_access_preserved
properties:
read_only_access_preserved:
type: boolean
enum: [true]
lifecycle_state:
$ref: '#/components/schemas/CommercialLifecycleState'
message:
type: string
nullable: true
description: Optional calm explanation that the workspace is suspended read-only while current history access remains available
NextAction:
type: object
required:
- label
properties:
label:
type: string
enabled:
type: boolean
reason:
type: string
nullable: true