## Summary - add explicit workspace closure and tenant removal lifecycle truth with a bounded `WorkspaceLifecycleService` - surface closure and removal posture across admin/system pages, chooser recovery, and canonical historical viewers - block new review-pack and operation starts for closed workspaces or removed tenants while preserving memberships, audit, and history - add focused Pest coverage plus the Spec 292 artifacts for the implemented slice ## Testing - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/System/Directory/ViewWorkspaceClosureTest.php tests/Feature/System/Ops/ClosedWorkspaceHistoricalAccessTest.php tests/Feature/Filament/Resources/Workspaces/WorkspaceClosureStatusTest.php tests/Feature/Filament/Resources/TenantResource/TenantWorkspaceRemovalTest.php tests/Feature/Filament/Pages/WorkspaceContextClosureRecoveryTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - manual integrated-browser smoke for admin tenant remove/restore plus chooser recovery and system workspace close/reopen Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #337
370 lines
48 KiB
Markdown
370 lines
48 KiB
Markdown
# Feature Specification: Workspace & Tenant Closure Lifecycle v1
|
|
|
|
**Feature Branch**: `292-workspace-tenant-closure`
|
|
**Created**: 2026-05-07
|
|
**Status**: Approved for implementation
|
|
**Input**: User description: "Promote the lifecycle taxonomy follow-through into one bounded runtime slice that closes workspaces, removes tenants from workspaces, and defines explicit suspended read-only versus closed behavior without introducing purge flows."
|
|
|
|
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
|
|
|
- **Problem**: TenantPilot already has partial lifecycle truth for tenant operability, archived workspaces, and subscription-driven suspended read-only posture, but it still has no explicit product truth for deliberately closing a workspace or removing a tenant from a workspace while preserving history.
|
|
- **Today's failure**: Operators can archive records or lose access implicitly through membership or context drift, but they cannot make an explicit, auditable closure decision. `archived`, `suspended read-only`, selector invalidation, and historical access are still separate behaviors rather than one bounded closure contract.
|
|
- **User-visible improvement**: Platform and workspace operators can close or reopen workspaces and remove or restore tenants with explicit confirmation, clear read-only behavior, consistent chooser recovery, and preserved historical viewers instead of relying on implicit 404s or overloaded archive semantics.
|
|
- **Smallest enterprise-capable version**: Add explicit closure truth on `Workspace`, explicit removed-from-workspace truth on `ManagedEnvironment`, reuse existing admin and system Filament surfaces, keep history readable, block new mutations and starts where required, and stop short of export, purge, or billing-provider workflows.
|
|
- **Explicit non-goals**: No hard delete, no purge engine, no export-before-delete flow, no retention executor, no customer self-serve billing portal, no payment-provider integration, no membership auto-deletion, no new panel, no new global-search resource, no broad lifecycle engine, and no reopening of the full Spec 262 taxonomy package.
|
|
- **Permanent complexity imported**: One bounded closure/removal truth on existing records, one bounded write orchestration path, explicit close/reopen and remove/restore action surfaces, chooser and context recovery rules, and focused audit plus feature-test coverage. No new standalone closure table or workflow engine is introduced.
|
|
- **Why now**: Spec 262 deliberately reserved `Workspace & Tenant Closure Lifecycle v1` as the next runtime follow-through after the taxonomy-first package, and the repo now has enough real substrate in tenant operability, commercial lifecycle, audit, and workspace context handling to implement it safely.
|
|
- **Why not local**: Closure and removal semantics affect workspace selection, tenant selection, tenant-bound routes, canonical run viewers, action gating, audit, and platform/admin surfaces together. A local page fix would preserve ambiguity and drift.
|
|
- **Approval class**: Core Enterprise
|
|
- **Red flags triggered**: New lifecycle truth, cross-plane UI impact, and destructive-like action semantics. Defense: the slice stays explicitly bounded, reuses existing surfaces and audit paths, adds no purge or export behavior, and resists a generic lifecycle framework.
|
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 1 | Wiederverwendung: 2 | **Gesamt: 10/12**
|
|
- **Decision**: approve
|
|
|
|
## Spec Scope Fields *(mandatory)*
|
|
|
|
- **Scope**: workspace + tenant-bound + canonical-view
|
|
- **Primary Routes**:
|
|
- `/system/directory/workspaces`
|
|
- `/system/directory/workspaces/{workspace}`
|
|
- `/system/directory/tenants/{tenant}`
|
|
- `/system/ops/runs/{run}`
|
|
- `/admin/workspaces`
|
|
- `/admin/workspaces/{record}`
|
|
- `/admin/choose-workspace`
|
|
- `/admin/choose-tenant`
|
|
- `/admin/settings/workspace`
|
|
- existing tenant-management resource pages backed by `apps/platform/app/Filament/Resources/TenantResource.php`
|
|
- existing tenant-context routes under `/admin/t/{tenant}/...`
|
|
- **Data Ownership**:
|
|
- Workspace closure truth remains workspace-owned and lives on the existing `Workspace` record.
|
|
- Tenant removal truth remains workspace-owned on the existing `ManagedEnvironment` record and does not create cross-workspace sharing.
|
|
- Workspace memberships, tenant memberships, audit logs, evidence, review artifacts, and `OperationRun` history remain owned by their current records and are preserved in place.
|
|
- This feature does not introduce new historical ledgers, export bundles, or purge artifacts.
|
|
- **RBAC**:
|
|
- Authorization planes involved: platform `/system` for workspace close and reopen; admin `/admin` for workspace read-only visibility, chooser recovery, and tenant remove or restore within the current workspace.
|
|
- Non-members or actors outside the relevant workspace or tenant scope receive deny-as-not-found (`404`).
|
|
- Actors who are in scope but lack the required capability receive forbidden (`403`).
|
|
- Canonical record viewers continue to authorize off record ownership and entitlement, not off remembered workspace or tenant context.
|
|
|
|
For canonical-view specs, the spec MUST define:
|
|
|
|
- **Default filter behavior when tenant-context is active**: Closed workspaces and removed tenants must never become the remembered active context. Canonical viewers may still render historically linked records when entitlement allows, even if the current remembered context was cleared.
|
|
- **Explicit entitlement checks preventing cross-tenant leakage**: Canonical viewers must validate workspace ownership of the record, referenced-tenant ownership where applicable, active actor entitlement to the workspace and referenced tenant, and route legitimacy independent of remembered selector state. Closed or removed lifecycle flags are product posture, not authorization shortcuts.
|
|
|
|
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
|
|
|
|
- **Cross-cutting feature?**: yes
|
|
- **Interaction class(es)**: status messaging, badges, header actions, row or detail actions, chooser recovery messaging, canonical detail viewers, and audit-backed lifecycle copy
|
|
- **Systems touched**: `WorkspaceContext`, chooser pages, tenant operability, commercial lifecycle summaries, admin and system detail pages, audit logging, and canonical run or tenant viewers
|
|
- **Existing pattern(s) to extend**: `WorkspaceCommercialLifecycleResolver`, `TenantOperabilityService`, `BadgeCatalog` and `BadgeRenderer`, current audit-log infrastructure, current Filament action surfaces, and existing chooser or context-recovery flows
|
|
- **Shared contract / presenter / builder / renderer to reuse**: `WorkspaceContext`, `TenantOperabilityService`, `BadgeCatalog` and `BadgeRenderer`, `WorkspaceAuditLogger`, tenant audit logging, current Filament detail and table action patterns, and the current system-directory plus admin resource surfaces
|
|
- **Why the existing shared path is sufficient or insufficient**: The repo already has the shared chooser, audit, and status paths needed for bounded runtime delivery. What is missing is the explicit closure and removed-from-workspace truth those shared paths must consume.
|
|
- **Allowed deviation and why**: none. This feature must not create a parallel lifecycle language or a page-local closure vocabulary.
|
|
- **Consistency impact**: `Suspended read-only`, `Closed`, `Removed from workspace`, `Archived`, and `Provider missing` must remain distinct meanings with consistent badges, copy, and blocked-action explanations.
|
|
- **Review focus**: Reviewers must verify that workspace closure and tenant removal reuse the existing shared status, chooser, audit, and canonical viewer seams instead of introducing one-off page logic.
|
|
|
|
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
|
|
|
|
- **Touches OperationRun start/completion/link UX?**: yes, by lifecycle gating and canonical historical viewing only
|
|
- **Shared OperationRun UX contract/layer reused**: existing shared `OperationRun` start UX and current canonical monitoring surfaces remain authoritative
|
|
- **Delegated start/completion UX behaviors**: blocked starts in closed workspaces or removed-tenant contexts must happen before enqueue, create no new run, and keep current `View run` or canonical-detail links unchanged for already-existing history
|
|
- **Local surface-owned behavior that remains**: system and admin surfaces own only the close/reopen and remove/restore inputs plus the impact summary shown before confirmation
|
|
- **Queued DB-notification policy**: unchanged; this feature introduces no new queued notification family
|
|
- **Terminal notification path**: unchanged; no new `OperationRun` lifecycle is introduced
|
|
- **Exception required?**: none
|
|
|
|
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
|
|
|
|
N/A - no shared provider or platform boundary is broadened here. Workspace closure and tenant removal are workspace-owned lifecycle concerns and must not be modeled as provider state.
|
|
|
|
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
|
|
|
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
|
|---|---|---|---|---|---|---|
|
|
| System workspace detail close or reopen controls | yes | Native Filament | header actions, badges, audit-backed detail summaries | page, detail | no | n/a |
|
|
| Admin workspace detail read-only closure summary | yes | Native Filament | status messaging, summary sections | page, detail | no | n/a |
|
|
| Managed tenant list and detail remove or restore controls | yes | Native Filament | row/detail actions, badges, lifecycle explanations | page, detail | no | n/a |
|
|
| Workspace and tenant chooser recovery after closure or removal | yes | Native Filament + global context shell | navigation, shell recovery, selector messaging | shell, page | no | special shell handling only |
|
|
| Canonical run and tenant viewers for removed or closed history | yes | Native Filament | shared detail family, historical evidence visibility | detail | no | no new mutation surface |
|
|
|
|
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
|
|
|
|
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|
|
|---|---|---|---|---|---|---|---|
|
|
| System workspace detail | Primary Decision Surface | Decide whether a workspace should be closed or reopened | Current posture, impact summary, closure reason, one dominant action | Audit history, affected tenant counts, related historical records | Primary because platform users make the closure decision here | Follows workspace governance, not storage internals | Removes guesswork about what closure changes and what remains readable |
|
|
| Managed tenant list and detail | Primary Decision Surface | Decide whether a tenant remains in the active workspace set | Tenant posture, removal impact, one dominant remove or restore action | Related memberships, historical runs, audit trail | Primary because workspace operators govern tenant presence here | Aligns to tenant-management workflow | Avoids overloading archive and implicit chooser disappearance |
|
|
| Admin workspace detail | Secondary Context Surface | Understand why the workspace is read-only or unavailable for active selection | Workspace posture, read-only explanation, next allowed inspection path | Closure reason, timestamps, linked history | Not primary because the admin plane does not own closure mutation | Supports post-decision inspection inside the workspace family | Keeps one clear explanation instead of repeating blockers across pages |
|
|
| Workspace and tenant chooser recovery | Secondary Context Surface | Recover from a cleared or invalid remembered context | Why the prior context is invalid and one next step | Optional timestamps or posture detail only if needed | Not primary because the user is recovering from an already-made decision | Follows operator recovery flow | Replaces silent redirects with one clear explanation |
|
|
| Canonical run and historical viewers | Tertiary Evidence / Diagnostics Surface | Inspect historical evidence after closure or removal | Historical record remains readable plus lifecycle badge on related workspace or tenant | Linked audit trail, tenant or workspace metadata | Not primary because the feature does not ask operators to decide here | Keeps evidence and history accessible without reopening active context | Prevents false not-found errors when context changed |
|
|
|
|
## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*
|
|
|
|
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|
|
|---|---|---|---|---|---|---|---|
|
|
| System workspace detail | support-platform | Current posture, closure reason summary, impact, close or reopen action | Related tenant counts, membership posture, recent activity | Full audit events and internal IDs | `Close workspace` or `Reopen workspace` | Raw audit payloads and low-level IDs remain secondary | One top summary states the posture once; lower sections add evidence only |
|
|
| Managed tenant list and detail | operator-MSP, support-platform | Tenant posture, removal explanation, remove or restore action | Related memberships, lifecycle timestamps, affected history counts | Raw audit details and internal identifiers | `Remove tenant` or `Restore tenant` | Raw audit details stay secondary or support-only | The tenant posture chip and summary line are the single visible source of truth |
|
|
| Admin workspace detail | operator-MSP | Workspace posture and what remains readable | Commercial-state detail, closure timestamps, guidance to history surfaces | Platform-only internal detail stays hidden | `Review workspace history` | Platform-only internal detail stays hidden | Read-only explanation appears once in the summary region |
|
|
| Workspace and tenant chooser recovery | operator-MSP | Why the context was cleared and which chooser or page to use next | Minimal recovery detail only | None | `Choose workspace` or `Choose tenant` | Internal route or entitlement diagnostics stay hidden | Recovery page states the blocker once and points to one next action |
|
|
| Canonical run and historical viewers | operator-MSP, support-platform | Historical record remains readable and the related workspace or tenant posture | Linked workspace and tenant metadata, timestamps, linked audit | Raw identifiers and platform-only debug metadata | `Review history` | Raw metadata remains secondary | The record summary owns the lifecycle note so deeper sections do not restate it |
|
|
|
|
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
|
|
|
|
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
| System workspace detail | Detail / Header Actions | Governance detail | Close or reopen workspace | Route-record detail | n/a | Secondary detail links stay inside sections | Detail header only with confirmation | `/system/directory/workspaces` | `/system/directory/workspaces/{workspace}` | Workspace identity, posture badge | Workspace | Open, suspended read-only, or closed posture plus impact | none |
|
|
| Admin workspace detail | Detail / Context Summary | Workspace management detail | Review closure posture and history | Route-record detail | n/a | Related links inside sections | none on admin plane for closure | `/admin/workspaces` | `/admin/workspaces/{record}` | Workspace identity, posture badge | Workspace | Read-only explanation and next allowed path | none |
|
|
| Managed tenant list | List / Table / More | Registry list | Inspect tenant and decide remove or restore | Full-row click | required | `More` for remove or restore and rare actions | `More` only | `/admin/tenants` | `/admin/tenants/{tenant}` | Workspace context, tenant posture | Tenant | Active versus removed posture | none |
|
|
| Managed tenant detail | Detail / Header Actions | Governance detail | Remove or restore tenant | Route-record detail | n/a | Secondary lifecycle links inside sections | Detail header or `More` with confirmation | `/admin/tenants` | `/admin/tenants/{tenant}` | Workspace identity, tenant posture | Tenant | Removed-from-workspace state and impact | none |
|
|
| Workspace and tenant chooser recovery | Shell / Recovery | Global-context shell | Recover to a valid current context | Single recovery card / chooser list | chooser row click only | None beyond recovery guidance | none | `/admin/choose-workspace` or `/admin/choose-tenant` | same as collection | Current workspace and tenant context validity | Workspace / Tenant | Why the previous context is invalid | global-context-shell |
|
|
| Canonical run and historical viewers | Detail / Evidence | Historical detail | Inspect history without reopening active context | Route-record detail | n/a | Secondary related links inside sections | none | `/system/ops/runs` or current historical collection | `/system/ops/runs/{run}` and related detail routes | Workspace or tenant posture badges | Run / Historical record | Historical legitimacy plus related lifecycle note | shared-detail-family |
|
|
|
|
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
|
|
|
|
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
| System workspace detail | Platform operator | Decide whether workspace should be closed or reopened | Governance detail | Should this workspace remain operable? | Workspace posture, closure reason, impact, what remains readable | Full audit trail, internal IDs, related history counts | commercial posture, closure posture | TenantPilot only | Close workspace, Reopen workspace | Close workspace |
|
|
| Admin workspace detail | Workspace owner or manager | Understand why current workspace is read-only or unavailable for active context | Workspace management detail | What does this posture change for my workspace work? | Workspace posture, read-only explanation, history access path | Timestamps and related summary counts | commercial posture, closure posture | None on this surface | Review workspace history | None |
|
|
| Managed tenant list and detail | Workspace owner | Decide whether tenant remains part of the workspace's active operating set | Governance list/detail | Should this tenant stay operable in this workspace? | Tenant posture, removal explanation, effect on selectors and actions | Membership and historical record details | tenant lifecycle, removed-from-workspace posture | TenantPilot only | Remove tenant, Restore tenant | Remove tenant |
|
|
| Workspace and tenant chooser recovery | Workspace member | Recover from invalid remembered context | Global-context shell | Which valid workspace or tenant should I use now? | Recovery explanation and one next chooser action | Minimal recovery detail only | context validity, closure or removal posture | None | Choose workspace, Choose tenant | None |
|
|
| Canonical run and historical viewers | Workspace operator or platform operator | Inspect history after closure or removal | Historical detail | Is this record still legitimate and what lifecycle posture does it reflect? | Record summary plus related workspace or tenant posture | Audit links and related metadata | record status, workspace or tenant posture | None | Review history | None |
|
|
|
|
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
|
|
|
- **New source of truth?**: yes - explicit workspace closed truth and explicit tenant removed-from-workspace truth on existing records
|
|
- **New persisted entity/table/artifact?**: yes - new persisted lifecycle fields on `Workspace` and `ManagedEnvironment`; no new standalone entity or table family beyond targeted columns
|
|
- **New abstraction?**: yes, one bounded write-orchestration service if needed for close/reopen and remove/restore audit-safe mutations; no generic lifecycle framework
|
|
- **New enum/state/reason family?**: yes - explicit closed and removed-from-workspace posture with audit reason text and derived blocked-action consequences
|
|
- **New cross-domain UI framework/taxonomy?**: no
|
|
- **Current operator problem**: the product cannot currently make a deliberate closure or removal decision without misusing archive semantics, implicit 404s, or commercial suspension language.
|
|
- **Existing structure is insufficient because**: `archived_at` on workspaces and the subscription resolver's suspended-read-only posture do not encode an explicit closure decision, and current tenant lifecycle does not cover workspace-specific removal while preserving history.
|
|
- **Narrowest correct implementation**: add bounded fields on existing records, derive read-only and selector behavior from those fields plus current commercial posture, reuse current surfaces, and keep mutations inside one bounded orchestration seam.
|
|
- **Ownership cost**: migration work, model and middleware touch points, Filament surface updates, audit-log additions, and focused feature-test maintenance.
|
|
- **Alternative intentionally rejected**: overloading archive or suspended-read-only semantics was rejected because it would preserve ambiguity; a generic lifecycle engine or new closure table was rejected because current-release truth only needs bounded fields and shared-surface reuse.
|
|
- **Release truth**: current-release truth
|
|
|
|
### Compatibility posture
|
|
|
|
This feature assumes a pre-production environment.
|
|
|
|
Backward compatibility, migration shims, historical alias states, and compatibility-specific tests are out of scope unless implementation reveals a concrete current-release blocker.
|
|
|
|
Canonical replacement of ambiguous archive or suspension meanings is preferred over preserving overloaded semantics.
|
|
|
|
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
|
|
|
- **Test purpose / classification**: Feature
|
|
- **Validation lane(s)**: fast-feedback, confidence
|
|
- **Why this classification and these lanes are sufficient**: The slice changes Filament surfaces, middleware or context recovery, audit-backed mutations, and canonical viewer legitimacy. Focused feature coverage is the narrowest honest proof because route, policy, and page behavior matter more than isolated unit indirection.
|
|
- **New or expanded test families**: focused feature coverage for system directory, admin workspace and tenant resources, chooser recovery, and canonical run or historical viewers
|
|
- **Fixture / helper cost impact**: moderate but bounded; test setup needs explicit workspace membership, tenant membership, commercial posture, and closure or removal state, but no new heavy provider or browser defaults
|
|
- **Heavy-family visibility / justification**: none; browser coverage is not required for the initial proof path
|
|
- **Special surface test profile**: standard-native-filament, global-context-shell, shared-detail-family
|
|
- **Standard-native relief or required special coverage**: functional-core plus state-contract coverage is required; browser or heavy-governance coverage is out of scope unless implementation proves a shell-only gap that feature tests cannot honestly prove
|
|
- **Reviewer handoff**: reviewers must verify that close/reopen and remove/restore remain confirmation-protected and audit-backed, that chooser recovery clears invalid context explicitly, that blocked start or mutate paths do not create runs, and that canonical historical viewers stay accessible when entitlement allows
|
|
- **Budget / baseline / trend impact**: none expected beyond one feature-local increase in admin and system feature coverage
|
|
- **Escalation needed**: none
|
|
- **Active feature PR close-out entry**: Guardrail
|
|
- **Planned validation commands**:
|
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/System/Directory/ViewWorkspaceClosureTest.php tests/Feature/System/Ops/ClosedWorkspaceHistoricalAccessTest.php`
|
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/Resources/Workspaces/WorkspaceClosureStatusTest.php tests/Feature/Filament/Resources/TenantResource/TenantWorkspaceRemovalTest.php tests/Feature/Filament/Pages/WorkspaceContextClosureRecoveryTest.php`
|
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
|
|
|
|
## User Scenarios & Testing *(mandatory)*
|
|
|
|
### User Story 1 - Close a workspace without losing history (Priority: P1)
|
|
|
|
As a platform operator, I need to close and later reopen a workspace explicitly, so that I can stop active operations and mutations while preserving readable history and audit evidence.
|
|
|
|
**Why this priority**: Workspace closure is the core enterprise-trust behavior missing from the current product. Without it, every related lifecycle action still depends on implicit archive or access loss.
|
|
|
|
**Independent Test**: Can be fully tested by closing a workspace from the system directory, confirming chooser and action gating update, verifying historical viewers remain readable for entitled actors, and reopening the workspace to restore normal operability.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** a platform actor is authorized to govern a workspace, **When** the actor closes the workspace with confirmation and a reason, **Then** the workspace becomes non-selectable as current context, in-scope active mutations are blocked, and the closure decision is written to audit.
|
|
2. **Given** a workspace is closed, **When** an entitled actor opens a historical run or workspace detail viewer, **Then** the record remains readable with explicit closed posture and no false not-found error.
|
|
3. **Given** a workspace is closed, **When** the platform actor reopens it, **Then** the workspace becomes selectable again and existing memberships plus historical records remain intact.
|
|
|
|
---
|
|
|
|
### User Story 2 - Remove a tenant from a workspace without deleting tenant history (Priority: P1)
|
|
|
|
As a workspace owner, I need to remove and later restore a tenant from the active workspace set, so that the tenant stops appearing as an operable context without destroying its history, memberships, or canonical records.
|
|
|
|
**Why this priority**: Workspace operators need a bounded lifecycle action smaller than deletion and more explicit than archive or hidden selector disappearance.
|
|
|
|
**Independent Test**: Can be fully tested by removing a tenant from the tenant-management surface, confirming chooser and tenant-context routes stop treating it as operable, verifying canonical historical viewers still render, and restoring the tenant.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** an owner is authorized for tenant governance, **When** the owner removes a tenant from the workspace with confirmation and a reason, **Then** the tenant no longer appears in choose-tenant or tenant-context start flows and the action is written to audit.
|
|
2. **Given** a tenant was removed from the workspace, **When** an entitled actor opens a canonical run or historical detail page that references that tenant, **Then** the record remains readable and the tenant is labeled removed from workspace.
|
|
3. **Given** a tenant is removed from the workspace, **When** the owner restores it, **Then** the tenant becomes selectable and operable again without recreating memberships or historical records.
|
|
|
|
---
|
|
|
|
### User Story 3 - Distinguish suspended read-only, closed, and removed clearly (Priority: P2)
|
|
|
|
As an operator, I need workspace and tenant posture to explain whether I am blocked because the workspace is commercially suspended, explicitly closed, or the tenant was removed from the workspace, so that I know what next action is legitimate.
|
|
|
|
**Why this priority**: Clear posture language prevents operators from treating commercial billing state, workspace closure, and tenant removal as the same event.
|
|
|
|
**Independent Test**: Can be fully tested by rendering admin and system surfaces plus chooser recovery for a suspended-read-only workspace, a closed workspace, and a removed tenant and confirming each posture has distinct copy, badge semantics, and blocked-action behavior.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** a workspace is commercially suspended but not closed, **When** the actor opens admin or system summaries, **Then** the posture reads suspended read-only and not closed.
|
|
2. **Given** a workspace is closed, **When** the actor lands on chooser or current-context recovery surfaces, **Then** the UI explains that the prior workspace is closed and offers one valid next action instead of a silent redirect.
|
|
3. **Given** a tenant is removed from the workspace, **When** the actor visits tenant management or historical detail surfaces, **Then** the tenant is labeled removed from workspace and not archived or provider missing.
|
|
|
|
### Edge Cases
|
|
|
|
- A user lands on `/admin` with a remembered current workspace that was closed after the last session; the product must clear the invalid context and route through an explicit recovery path.
|
|
- A remembered current tenant was removed from the workspace while the workspace itself remains open; the tenant context must be cleared without breaking workspace-scoped pages.
|
|
- A workspace is already `SUSPENDED_READ_ONLY` from the commercial resolver and then becomes explicitly closed; the product must show both truths without collapsing them into one ambiguous blocker.
|
|
- A removed tenant or closed workspace is still referenced by `OperationRun`, audit, evidence, or review artifacts; canonical viewers must stay readable when entitlement allows.
|
|
- A workspace close or tenant removal action is attempted while a last-owner or active-membership guard would otherwise make the result unsafe; the feature must preserve current owner-guard behavior and surface a clear failure reason.
|
|
- A reopen or restore action is performed after the remembered context was cleared; the product must not silently reactivate old context without the chooser or explicit selection flow confirming it.
|
|
|
|
## Requirements *(mandatory)*
|
|
|
|
**Constitution alignment (required):** This feature introduces no Microsoft Graph calls. It does introduce destructive-like lifecycle mutations, so close/reopen and remove/restore must be previewed, confirmation-protected, audit-logged, authorization-checked server-side, and covered by focused tests. No purge or hard-delete behavior is allowed in this slice.
|
|
|
|
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** The feature adds bounded persisted lifecycle truth on existing records because current release truth now requires a deliberate closure posture distinct from archive and commercial suspension. A generic lifecycle engine, registry, or new closure history ledger is explicitly out of scope. The narrowest correct implementation is to add fields on existing records, derive blocking behavior from those fields plus current commercial posture, and keep write orchestration bounded.
|
|
|
|
**Constitution alignment (XCUT-001):** The feature reuses existing chooser, context, badge, audit, and canonical-viewer seams. It must not introduce page-local status vocabularies or a parallel action language for closure and removal.
|
|
|
|
**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** System workspace detail and managed tenant detail are the primary decision surfaces. Chooser recovery and historical viewers remain secondary or tertiary surfaces. Default-visible content must explain posture and one next action; deeper audit and raw identifiers stay secondary.
|
|
|
|
**Constitution alignment (PROV-001):** This slice is platform-core lifecycle work. Provider presence remains a separate lifecycle dimension from Spec 261 and must not be reused to model closure or removal.
|
|
|
|
**Constitution alignment (TEST-GOV-001):** Proof stays in focused feature coverage for native Filament surfaces, chooser recovery, and canonical viewers. No heavy-governance or browser lane should be introduced unless implementation reveals a shell-only gap that feature tests cannot honestly prove.
|
|
|
|
**Constitution alignment (OPS-UX):** The feature does not add a new `OperationRun` type. It does require that blocked starts in closed workspaces or removed-tenant contexts fail before enqueue and that existing canonical run viewers remain authoritative and readable when entitlement allows.
|
|
|
|
**Constitution alignment (OPS-UX-START-001):** Existing start surfaces continue to rely on the shared `OperationRun` start UX path when allowed. Close or remove posture only changes whether a start is permitted; it must not introduce local queued toast, dedupe, or notification semantics.
|
|
|
|
**Constitution alignment (RBAC-UX):** Platform and admin planes remain separated. Workspace non-members, tenant non-members, and wrong-plane actors get `404`; in-scope actors lacking capability get `403`. Destructive-like actions must use `->requiresConfirmation()` and server-side authorization. Closed or removed posture is never a substitute for an authorization decision.
|
|
|
|
**Constitution alignment (OPS-EX-AUTH-001):** This feature does not change auth-handshake behavior.
|
|
|
|
**Constitution alignment (BADGE-001):** Workspace and tenant posture badges must be centralized. `Suspended read-only`, `Closed`, and `Removed from workspace` must not fall back to ad hoc labels or local color choices.
|
|
|
|
**Constitution alignment (UI-FIL-001):** All changed admin and system surfaces remain native Filament pages or resources. No local card system, no ad hoc status-color system, and no fake row interactivity may be introduced. One dominant action per primary decision surface is required.
|
|
|
|
**Constitution alignment (UI-NAMING-001):** Primary operator-facing verbs are `Close workspace`, `Reopen workspace`, `Remove tenant`, and `Restore tenant`. `Workspace` and `Tenant` are the real target objects and must remain stable across buttons, modals, audit prose, and notifications.
|
|
|
|
**Constitution alignment (DECIDE-001):** This feature adds explicit human-in-the-loop lifecycle decisions. The default experience must become calmer and clearer by separating active context, read-only closure posture, and historical evidence rather than adding more ambiguous lifecycle states.
|
|
|
|
**Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001 / HDR-001):** Changed surfaces must preserve exactly one primary inspect/open model, keep destructive actions in header or `More`, avoid redundant view actions, and keep chooser recovery and historical viewers focused on one immediate question.
|
|
|
|
**Constitution alignment (ACTSURF-001 - action hierarchy):** Workspace close/reopen and tenant remove/restore must not compete with navigation. Primary decision surfaces keep one dominant action; rare or adjacent lifecycle actions stay grouped.
|
|
|
|
**Constitution alignment (OPSURF-001):** Every changed surface must show whether the blocker is commercial suspension, explicit closure, or tenant removal. Mutation scope must stay legible: all lifecycle mutations in this slice are TenantPilot-only and do not mutate Microsoft tenants.
|
|
|
|
**Constitution alignment (UI-SEM-001 / LAYER-001 / TEST-TRUTH-001):** The feature must map existing domain truth directly to UI posture without adding a new presentation taxonomy or semantic wrapper layer. Tests should prove user-facing business consequences and access behavior, not thin indirection.
|
|
|
|
**Constitution alignment (Filament Action Surfaces):** System workspace detail, admin workspace detail, managed tenant list and detail, chooser recovery, and any touched historical viewers must satisfy the action-surface contract described in the matrix below.
|
|
|
|
**Constitution alignment (UX-001 - Layout & Information Architecture):** Existing Filament layouts remain in force. Any added summary or warning content must fit current Main/Aside or detail-section layouts, keep one primary action, and preserve native empty-state and badge semantics.
|
|
|
|
### Functional Requirements
|
|
|
|
- **FR-001**: The system MUST model workspace closure as explicit product truth distinct from `archived_at` and distinct from commercial `SUSPENDED_READ_ONLY` posture.
|
|
- **FR-002**: Authorized platform actors MUST be able to close and reopen a workspace through an explicit confirmation flow that captures a human-readable reason and writes an audit event.
|
|
- **FR-003**: A closed workspace MUST stop being selectable as the current workspace in `/admin/choose-workspace` and MUST clear any remembered tenant context tied to that workspace.
|
|
- **FR-004**: A closed workspace MUST block the in-scope tenant start surface and the in-scope state-changing admin surfaces touched by this slice while preserving entitled read access to management and historical viewers.
|
|
- **FR-005**: The admin plane MUST show read-only closure posture for an affected workspace without exposing a second closure-mutation plane.
|
|
- **FR-006**: The system MUST model tenant removal from workspace as explicit product truth distinct from tenant archive and distinct from provider-missing lifecycle.
|
|
- **FR-007**: Authorized workspace actors MUST be able to remove and restore a tenant from the workspace through explicit confirmation flows that capture a reason and write audit events.
|
|
- **FR-008**: A removed tenant MUST stop being selectable in `/admin/choose-tenant` and MUST stop being a valid tenant-context route target under `/admin/t/{tenant}/...`.
|
|
- **FR-009**: A removed tenant MUST remain inspectable on tenant-management and canonical historical viewer surfaces when the actor remains entitled to the workspace and tenant history.
|
|
- **FR-010**: Canonical historical viewers in scope for this v1, specifically system run viewers and system workspace or tenant historical detail viewers, MUST remain viewable when they reference a closed workspace or removed tenant and entitlement still allows access.
|
|
- **FR-011**: The system MUST preserve existing workspace-membership and tenant-membership records during close/reopen and remove/restore flows in this v1 slice; no membership purge or recreation workflow is allowed.
|
|
- **FR-012**: The product MUST distinguish `Suspended read-only`, `Closed`, and `Removed from workspace` with separate badge semantics, copy, and blocked-action explanations.
|
|
- **FR-013**: Chooser and context-recovery surfaces MUST explain why a remembered context was cleared and MUST offer one valid next action rather than silently redirecting.
|
|
- **FR-014**: Authorization on affected surfaces MUST preserve `404` for non-members or wrong-plane actors and `403` for in-scope actors missing capability.
|
|
- **FR-015**: Lifecycle mutations in this feature MUST be labeled consistently as `Close workspace`, `Reopen workspace`, `Remove tenant`, and `Restore tenant` across buttons, modals, notifications, and audit prose.
|
|
- **FR-016**: Close/reopen and remove/restore actions MUST be TenantPilot-only mutations; no Microsoft tenant or provider mutation is performed in this slice.
|
|
- **FR-017**: Blocked starts caused by closed workspaces or removed tenants on the in-scope tenant start surface MUST not create `OperationRun` records, local blocked-run substitutes, or new notification families.
|
|
- **FR-018**: The feature MUST preserve current owner-guard and membership-safety behavior rather than bypassing it through lifecycle mutations.
|
|
- **FR-019**: This feature MUST not widen discovery: existing global-search and list entitlement rules remain unchanged, and no new discovery path may leak closed or removed records to actors who are not already entitled to inspect them.
|
|
- **FR-020**: The feature MUST not add purge, export-before-delete, retention execution, or billing-provider logic.
|
|
|
|
## UI Action Matrix *(mandatory when Filament is changed)*
|
|
|
|
If this feature adds or modifies any Filament Resource / RelationManager / Page, fill out the matrix below.
|
|
|
|
For each surface, list the exact action labels, whether they are destructive (confirmation? typed confirmation?), RBAC gating (capability + enforcement helper), whether the mutation writes an audit log, and any exemption or exception used.
|
|
|
|
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
| System workspace detail | `apps/platform/app/Filament/System/Pages/Directory/ViewWorkspace.php` | `Close workspace` or `Reopen workspace` | Route-record detail | none | none | n/a | `Close workspace` or `Reopen workspace` | n/a | yes | Platform capability only; confirmation required; no second control plane on `/admin` |
|
|
| Admin workspace detail | `apps/platform/app/Filament/Resources/Workspaces/Pages/ViewWorkspace.php` | none added in this slice | Route-record detail | none | none | n/a | none for closure on admin plane | Save/cancel unchanged where edit already exists | no direct mutation | Read-only summary only |
|
|
| Managed tenant list | `apps/platform/app/Filament/Resources/TenantResource.php` list page | none beyond current list controls | Full-row click to tenant detail | current safe shortcuts only; `Remove tenant` or `Restore tenant` lives in `More` | none in this slice | existing tenant-creation CTA stays unchanged | n/a | n/a | yes for remove/restore | Destructive-like action grouped to `More` |
|
|
| Managed tenant detail | `apps/platform/app/Filament/Resources/TenantResource.php` view page | `Remove tenant` or `Restore tenant` plus existing safe navigation | Route-record detail | none | none | n/a | `Remove tenant` or `Restore tenant` | Save/cancel unchanged where edit already exists | yes | Owner or equivalent capability only; confirmation required |
|
|
| Workspace chooser | `apps/platform/app/Filament/Pages/ChooseWorkspace.php` | none | chooser row or card select | `Choose workspace` | none | existing empty-state CTA only if no valid workspace exists | n/a | n/a | no | Closed workspaces excluded from selectable set |
|
|
| Tenant chooser | `apps/platform/app/Filament/Pages/ChooseTenant.php` | none | chooser row or card select | `Choose tenant` for active tenants only | none | existing empty-state CTA only if no valid tenant exists | n/a | n/a | no | Removed tenants excluded from selectable set |
|
|
|
|
### Key Entities *(include if feature involves data)*
|
|
|
|
- **Workspace closure truth**: Explicit lifecycle posture on `Workspace` that marks a workspace as closed or reopened and records the reason and actor without deleting the workspace.
|
|
- **Tenant removal truth**: Explicit lifecycle posture on `ManagedEnvironment` that marks a tenant as removed from the active workspace set or restored without deleting the tenant record.
|
|
- **Remembered workspace and tenant context**: Convenience selection state that must be cleared when closure or removal makes it invalid.
|
|
- **Canonical historical viewer in scope**: Existing system run viewers plus system workspace and tenant detail viewers that remain readable even when active context becomes invalid.
|
|
|
|
## Success Criteria *(mandatory)*
|
|
|
|
### Measurable Outcomes
|
|
|
|
- **SC-001**: In validation scenarios, 100% of close/reopen and remove/restore actions show explicit posture and impact summary with no ambiguous fallback to `Archived`.
|
|
- **SC-002**: In validation scenarios, 0 closed workspaces and 0 removed tenants appear as selectable choices in the normal workspace and tenant choosers.
|
|
- **SC-003**: In validation scenarios, 100% of entitled system run viewers and in-scope system historical detail viewers remain accessible when they reference a closed workspace or removed tenant.
|
|
- **SC-004**: In validation scenarios, 100% of blocked actions explain whether the blocker is `Suspended read-only`, `Closed`, or `Removed from workspace`.
|
|
- **SC-005**: In validation scenarios, 100% of lifecycle mutations covered by this slice write audit events that include actor, prior state, new state, timestamp, and reason.
|
|
|
|
## Summary
|
|
|
|
Workspace closure and tenant removal are the next runtime follow-through after the lifecycle taxonomy foundation from Spec 262. The feature does not attempt deletion, purge, or export. It introduces one explicit workspace-level closure truth, one explicit tenant removed-from-workspace truth, and the minimum surface, chooser, and canonical-view behavior needed to make those truths operable and auditable.
|
|
|
|
The key product distinction is that commercial suspension, explicit workspace closure, tenant removal from workspace, tenant archive, and provider-missing lifecycle are not the same thing. This feature productizes the first two missing runtime decisions without reopening the broader lifecycle package.
|
|
|
|
## Goals
|
|
|
|
- Let platform users close and reopen workspaces deliberately and auditably.
|
|
- Let workspace owners remove and restore tenants from the active workspace set without deleting history.
|
|
- Make chooser, context-recovery, and canonical-view behavior explicit when closure or removal invalidates active context.
|
|
- Keep suspended read-only posture distinct from explicit closure and tenant removal.
|
|
- Reuse current Filament, audit, chooser, and canonical-view seams rather than introducing a new lifecycle framework.
|
|
|
|
## Non-Goals
|
|
|
|
- No purge, export-before-delete, or retention-execution workflow.
|
|
- No hard delete or membership-deletion cascade.
|
|
- No payment-provider or billing-portal work.
|
|
- No reopening of broader provider lifecycle or retention taxonomy work.
|
|
- No new panel, global-search resource, or custom UI system.
|
|
|
|
## Assumptions
|
|
|
|
- `WorkspaceCommercialLifecycleResolver` remains the source of commercial suspension truth and is not replaced by closure logic.
|
|
- Workspace memberships and tenant memberships remain the entitlement backbone even when a workspace is closed or a tenant is removed.
|
|
- Existing canonical historical viewers already have legitimate workspace identity and can keep rendering when lifecycle posture changes.
|
|
- Close/reopen and remove/restore are TenantPilot-only mutations in this v1.
|
|
|
|
## Risks
|
|
|
|
- Closure and removal could be mistakenly folded into existing archive semantics if the implementation does not keep badges, copy, and blocked-action explanations distinct.
|
|
- Chooser recovery could regress into silent redirects if the feature does not explicitly handle cleared context.
|
|
- Canonical historical viewers could accidentally inherit active-context requirements and start returning false not-found errors.
|
|
- The slice could widen into purge, export, or billing-provider logic unless the follow-up boundaries stay explicit.
|
|
|
|
## Follow-Up Work
|
|
|
|
- Export-before-delete and retention/purge governance remain separate follow-up specs.
|
|
- Any future customer self-service offboarding or billing-driven closure workflow remains a separate commercial or portal slice.
|
|
- Broader lifecycle alignment for other governed objects remains outside this workspace and tenant closure follow-through.
|
|
|
|
## Final Direction
|
|
|
|
The intended runtime contract is: a suspended workspace stays readable but commercially blocked, a closed workspace becomes non-selectable and read-only for entitled actors, and a removed tenant stops being an active workspace context without losing historical legitimacy. The implementation should add only the truth and behavior needed to make those distinctions real. |