---
id: STATE-PFM-SEC-001
title: Workspace lifecycle
object: DOM-PFM-SEC-002
product_area: platform
module: security
revision: 1
status: draft
design_intent: confirmed
origin: services-platform:docs/tenants/tenant-lifecycle-plan.md#2.2 One record, two axes; the Data Job Engine
  plan §12.1 (loading, Andrew 2026-10-11)
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
- name: provisioning
  meaning: Participants are preparing the workspace. Ordinary access is blocked; HQ staff can inspect through
    HQ routes.
  provenance: observed
  source: services-platform:modules/tenants/services/provisioning/submit-tenant-provisioning.ts (Retrying)
- name: provisioning_failed
  meaning: Preparation failed. Completed participant work remains available for inspection, retry or retirement.
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:failTenantProvisioning
- name: active
  meaning: The workspace is ready for ordinary operations, subject to business status and permission checks.
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:activateProvisionedTenant
- name: loading
  meaning: An initial load is open. Customer access is blocked; data-job routes and eligible HQ staff routes
    remain available.
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:enterTenantLoading
- name: deletion_pending
  meaning: The disabled workspace has a cancellable retirement request awaiting eligibility.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/request-deletion.ts:REQUESTABLE_TENANT_LIFECYCLE_STATES
- name: deleting
  meaning: The destructive phase has started. Cancellation is refused, HQ inspection is read-only and the named
    purge job controls deletion.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/begin-destruction.ts
transitions:
- from: provisioning
  to: active
  trigger: Provisioning reaches its activation task after participant readiness checks.
  guard: The activation workflow is authoritative for this provisioning submission.
  side_effects:
  - Apply the requested business status
  - Set first activation only when absent
  - Reconcile standing access for the workspace and customer
  - Claim and attempt completion notifications
  - Invalidate availability in the current process
  who: Platform System (provisioning job)
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:activateProvisionedTenant
- from: provisioning
  to: provisioning_failed
  trigger: The provisioning failure handler or repair reconciler settles a failed or stranded job.
  guard: Change only a workspace still in provisioning.
  side_effects:
  - Claim and attempt failure notifications
  - Retain completed participant work
  who: Platform System (provisioning job, job reconciler)
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:failTenantProvisioning
- from: provisioning_failed
  to: provisioning
  trigger: Staff submit a provisioning retry.
  guard: The latest job failed and the caller has provisioning authority.
  side_effects:
  - Create a retry job with completed steps carried forward
  who: Rhythm staff (hq.tenants.provision)
  provenance: observed
  source: services-platform:modules/tenants/services/provisioning/submit-tenant-provisioning.ts (Retrying)
- from: provisioning_failed
  to: deletion_pending
  trigger: Staff request retirement after failed provisioning.
  guard: Retirement preconditions pass and business status is disabled.
  side_effects:
  - Commit request, notices and schedule job together
  - Use the never-activated waiting policy
  who: Rhythm staff (platform.tenants.deletion-request)
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/request-deletion.ts:REQUESTABLE_TENANT_LIFECYCLE_STATES
- from: active
  to: loading
  trigger: Staff open an initial load.
  guard: The workspace is in implementation; an optional reason is limited to five hundred characters.
  side_effects:
  - Retain opening actor, time and reason
  - Invalidate local availability; other processes observe their cache bound
  who: Rhythm staff (hq.data-runs.manage)
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:enterTenantLoading
- from: loading
  to: active
  trigger: Staff activate the loaded workspace.
  guard: Lifecycle is loading and the caller has activation authority.
  side_effects:
  - Preserve the existing first-activation timestamp
  - Preserve business status
  - Invalidate local availability
  who: Rhythm staff (hq.tenants.activate)
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:activateLoadedTenant
- from: loading
  to: deletion_pending
  trigger: The transition graph declares loading retirement, but no request path reaches it.
  guard: The request path accepts active or failed provisioning, while loading cannot be disabled.
  side_effects: []
  who: nobody today
  provenance: unknown
  source: FND-PFM-SEC-043
- from: active
  to: deletion_pending
  trigger: Staff request retirement of an active workspace.
  guard: Disabled status, no locks or sandboxes, allowed environment, typed confirmation, reason and no open
    request are required.
  side_effects:
  - Commit request, notices and schedule job together
  - Calculate eligibility from the request-time policy
  - Invalidate local availability
  who: Rhythm staff (platform.tenants.deletion-request)
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/request-deletion.ts:requestTenantDeletion
- from: deletion_pending
  to: active
  trigger: Staff cancel before destructive work begins.
  guard: The scheduled or held request wins the shared locks before the point of no return.
  side_effects:
  - Keep business status disabled
  - Record the cancellation notice
  who: Rhythm staff (platform.tenants.deletion-cancel)
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/deletion-holds.ts
- from: deletion_pending
  to: deleting
  trigger: The schedule executes its point-of-no-return task.
  guard: Eligibility and gating notices pass; no hold, locks or sandboxes remain; requester authority is re-evaluated.
  side_effects:
  - Mark the request executing
  - Create its purge job atomically
  who: Platform System (retirement schedule job)
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/begin-destruction.ts
- from: active
  to: deleting
  trigger: An operator reapplies deletion after a database restore.
  guard: The external deletion ledger supplies authority; the script defaults to dry run.
  side_effects:
  - Set disabled business status with deleting lifecycle
  - Use the explicit restore exception to the transition graph
  who: Operator (restore runbook)
  provenance: observed
  source: services-platform:modules/tenants/services/persistence/tenants.ts:forceTenantDeletingAfterRestore
invalid:
- from: deleting
  to: active
  why: Destruction may already have removed data, so purge failure never makes the workspace operational again.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
    services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
    docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
- from: active
  to: provisioning
  why: Initialization can supply later participant defaults without re-entering provisioning.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
    services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
    docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
- from: provisioning
  to: deletion_pending
  why: A running provisioning attempt must finish or fail before retirement can be requested.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
    services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
    docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
- from: deletion_pending
  to: loading
  why: An outstanding retirement request cannot open an initial load.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
    services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
    docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
- from: active
  to: provisioning_failed
  why: A late provisioning failure consequence cannot undo committed activation.
  provenance: observed
  source: 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
    services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
    docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
concurrency: Lifecycle services use locked or conditional updates to protect state changes. Cancellation and
  the destructive boundary compete for the same request/workspace locks. Repeating a lifecycle target preserves
  state but does not necessarily preserve version or modification time. The operator restore path bypasses
  the ordinary graph. Availability in other warm processes can remain cached for thirty seconds.
invariants:
- INV-DOM-PFM-SEC-002-03
- INV-DOM-PFM-SEC-002-04
- INV-DOM-PFM-SEC-002-07
evidence:
- 'services-platform at ad3f6a65: modules/tenants/services/lifecycle/lifecycle-transitions.ts; services/persistence/tenants.ts;
  services/provisioning/*; services/deletion/*; services/access/tenant-availability.ts; db/tenants.table.ts;
  docs/tenants/tenant-lifecycle-plan.md §2.2, §4, §5.'
examined_at: '2026-10-23'
examiner: codex
---

# Workspace lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
    state "Provisioning" as provisioning
    state "Provisioning Failed" as provisioning_failed
    state "Active" as active
    state "Loading" as loading
    state "Deletion Pending" as deletion_pending
    state "Deleting" as deleting
    provisioning --> active
    provisioning --> provisioning_failed
    provisioning_failed --> provisioning
    provisioning_failed --> deletion_pending
    active --> loading
    loading --> active
    active --> deletion_pending
    deletion_pending --> active
    deletion_pending --> deleting
    active --> deleting
```

The diagram summarizes the observed transition rows; declared edges without an observed writer remain outside the operational graph.

## Transitions Explained

The structured transitions describe ordinary readiness and destruction changes and the explicit restore exception. Business status is a separate model; activation after a load preserves it and the stored first-activation time.

The loading-to-retirement edge is declared but unreachable through the request operation. It remains an unknown implementation path with a finding. Failure handling preserves partial work and permits forward retry; notification claims prevent repeated dispatch claims but do not guarantee delivery.

## What Cannot Happen

Destruction may already have removed data, so purge failure never makes the workspace operational again.

Initialization can supply later participant defaults without re-entering provisioning.

A running provisioning attempt must finish or fail before retirement can be requested.

An outstanding retirement request cannot open an initial load.

A late provisioning failure consequence cannot undo committed activation.

## Concurrency and Timing

Lifecycle services use locked or conditional updates to protect state changes. Cancellation and the destructive boundary compete for the same request/workspace locks. Repeating a lifecycle target preserves state but does not necessarily preserve version or modification time. The operator restore path bypasses the ordinary graph. Availability in other warm processes can remain cached for thirty seconds.
