---
id: WF-PFM-SEC-004
title: Purge a retired workspace
trigger: The retirement watcher commits the destructive boundary and creates its purge job.
product_area: platform
module: security
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
implements:
- CAP-PFM-SEC-004
moves:
- STATE-PFM-SEC-001
- STATE-PFM-SEC-003
crosses:
- SVC-PFM-SEC-tenants
- SVC-PFM-SEC-security
idempotency: Removal batches converge on absence and retries carry completed steps forward. A failure consequence
  applies only to the executing request that still names that job. Completed-request task replay can finish
  notices or ledger writing without reopening deletion.
concurrency: Tasks recheck the named job and deleting lifecycle, with an exception for completed-request finalization
  replay after the workspace row is gone. Each batch commits independently; locks are not held across the entire
  purge.
hops:
- n: 1
  actor_or_service: Purge job (publish)
  action: Publish the purge plan and compare it with live participant manifests.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (PublishStepDefinition)
  seam: state_machine
  on_failure: Fail the executing request through its consequence when the plan differs.
  left_inconsistent: The workspace remains unavailable without participant removal yet.
  observer: Job events expose the mismatch.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (header)
- n: 2
  actor_or_service: Purge job (flush the authorizer)
  action: Request authorizer-cache flushing as acceleration of the existing expiry bounds.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (FlushAuthorizer)
  seam: state_machine
  on_failure: Apply task retries; ordinary availability is already denied.
  left_inconsistent: Cached decisions and previously signed contexts retain their expiry limits.
  observer: The purge job records flush work and failures.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (header)
- n: 3
  actor_or_service: Each participant, in deletion order
  action: Remove participant records in bounded batches and delete correctly tagged portal pool resources.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-operations.ts
  seam: state_machine
  on_failure: Apply task retries; exhaustion fails the job and its executing request.
  left_inconsistent: The workspace remains partly destroyed and unavailable.
  observer: Participant steps, logs and inventories identify remaining work.
  provenance: observed
  source: services-platform:modules/tenants/services/lifecycle/participants.ts (participant table)
- n: 4
  actor_or_service: Purge job (verify)
  action: Verify all participant inventories and repeat the removal round when counts remain, up to three rounds.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (VerifyPurge)
  seam: state_machine
  on_failure: Fail after the configured verification-round budget.
  left_inconsistent: Unremoved or recreated resources remain visible in inventory.
  observer: The job records residual counts.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (header)
- n: 5
  actor_or_service: Purge job (finalize)
  action: Remove workspace, seed and resource rows, complete the request with counts, then finish external
    notices and ledger writing.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-workflow.ts (FinalizePurge)
  seam: state_machine
  on_failure: Task replay can finish external work. A later failure does not mark a completed request failed.
  left_inconsistent: Database completion can coexist with a missing ledger object, weakening restore detection
    until recovery.
  observer: Request counts, job outcome and ledger storage expose the gap.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/deletion-ledger.ts (header)
- n: 6
  actor_or_service: Failure consequence (failure task or reconciler)
  action: Apply the executing-request failure consequence with a safe summary and failure notice.
  anchor: services-platform:modules/tenants/services/deletion/tenant-purge-failure.ts
  seam: state_machine
  on_failure: Use configured failure-task retries and later reconciliation where applicable.
  left_inconsistent: Partial removal remains awaiting a forward retry; completed requests are unchanged.
  observer: Request state and the failure notice expose settlement.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/tenant-purge-failure.ts (header)
- n: 7
  actor_or_service: Operator (retry or restore re-apply)
  action: Retry a failed request with a new authoritative job, or reapply ledger-recorded deletions after a
    database restore.
  anchor: services-platform:modules/tenants/services/deletion/operator-recovery.ts:retryTenantPurge; reapply-deletions.ts
  seam: sync
  on_failure: Refuse request retry unless failed; restore reapplication handles workspaces independently.
  left_inconsistent: Restored records remain until reapplication runs, and missing ledger entries require separate
    recovery.
  observer: Operator recovery reads and job monitoring show progress.
  provenance: observed
  source: services-platform:modules/tenants/services/deletion/reapply-deletions.ts (header)
branches:
- Portal pool purge refuses resources without the expected workspace tag and leaves them for an operator.
waits:
- Large purges run as many short batched invocations; the execution owns the loop and configured retry limits.
evidence:
- 'services-platform at ad3f6a65: modules/tenants/services/deletion/tenant-purge-workflow.ts, tenant-purge-operations.ts,
  tenant-purge-failure.ts, operator-recovery.ts, deletion-ledger.ts, reapply-deletions.ts; modules/security/services/onboarding/portal-pool-removal.ts;
  services/lifecycle/participants.ts.'
examined_at: '2026-10-23'
examiner: codex
controls: [TC-AVL-001, TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-DAT-006, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-003, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-MON-003, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Purge a retired workspace

## What It Does

Purge proceeds only under the recorded deletion authority and compensates forward. Each participant owns removal and inventory for its resources, and the final verification can repeat the purge round within its configured limit.

## The Flow

```mermaid
flowchart TD
    H1["Publish purge plan"]
    H2["Request cache flush"]
    H3["Remove participant batches"]
    H4["Verify zero inventory"]
    H5["Complete database deletion and external ledger"]
    H6["Settle executing-request failure"]
    H7["Retry or reapply"]
    H1 --> H2
    H2 --> H3
    H3 -->|more batches| H3
    H3 -->|participants done| H4
    H4 -->|counts remain and budget remains| H3
    H4 -->|zero| H5
    H1 -. failure .-> H6
    H2 -. failure .-> H6
    H3 -. failure .-> H6
    H4 -. exhausted .-> H6
    H5 -. executing-request failure only .-> H6
    H5 -->|external completion replay| H5
    H6 -->|operator retry| H7
    H7 --> H1
```

The diagram separates the relevant success, alternative and failure paths. The numbered hop rows retain the detailed authority checks, delegated work and retry limits.

## When Something Fails

Database completion precedes the external ledger write. The job can therefore fail after the request says completed. Task replay can finish the ledger, while request-specific retry requires failed state; recovery after replay exhaustion needs an explicit operator procedure.

Removal batches converge on absence and retries carry completed steps forward. A failure consequence applies only to the executing request that still names that job. Completed-request task replay can finish notices or ledger writing without reopening deletion.

Tasks recheck the named job and deleting lifecycle, with an exception for completed-request finalization replay after the workspace row is gone. Each batch commits independently; locks are not held across the entire purge.

## Open Design Questions

The recorded findings retain any stronger planning expectation, missing writer or unproved recovery path. Observed behavior does not approve those outstanding product choices.
