---
id: ATS-PFM-TNL-006
scope: CAP-PFM-TNL-006
product_area: platform
module: tenant-lifecycle
revision: 3
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  services-committees: 36129c24daeaadcdb339218c38cdeaff9861359d
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  services-submissions: 64236c19be89579ef5897aad7de3925ed3ce03bf
  services-websites: 5fc6823c69d5e6f208c21dba8c0eb3132f8bdc4c
  services-marketing: d071a159fe9782bb71ff246d5c67d5bbfe07fbfa
  rhythm-v2-docs: d318a948692343e2f0a7ecc2e11c50db179a8c95
scenarios:
  - id: ATS-PFM-TNL-006-01
    title: Client payloads cannot replace lifecycle facts
    level: api
    proves:
      - BR-CAP-PFM-TNL-006-01
    preconditions: An active workspace is available for editing.
    steps: Replace and patch with a different lifecycle value.
    expected: The requests do not move the system-controlled state.
    negative: false
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#answers 200 to a patch aimed at the
        lifecycle state, because the property is simply dropped
      - >-
        services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#leaves the lifecycle state where the
        server put it after such a patch
      - >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#ignores
        a lifecycle state a write input carries, rather than letting a client set the system axis
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#answers 200 to a patch aimed at the
      lifecycle state, because the property is simply dropped
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#answers 200 to a patch aimed at the
        lifecycle state, because the property is simply dropped
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#answers 200 to a patch aimed at the
        lifecycle state, because the property is simply dropped
    binding_note: >-
      The original test-title references contribute separate assertions from several files. No suite receives
      an implements link for the complete combined scenario; a full executable mapping remains
      FND-PFM-TNL-084.
    binding_gap: FND-PFM-TNL-084
  - id: ATS-PFM-TNL-006-02
    title: Transition policy rejects unsupported paths
    level: unit
    proves:
      - BR-CAP-PFM-TNL-006-02
    preconditions: The fixture includes every pair of lifecycle states.
    steps: Evaluate each proposed transition.
    expected: Permitted paths succeed and rejected paths identify the allowed successors.
    negative: true
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
        deletion_pending: a tenant still being built is not put up for deletion
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
        deleting, so nothing can skip the waiting period by writing the state
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
        active, and says so: a tenant being destroyed never comes back
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        deleting, the point of no return
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        loading, which is an implementation tenant opening its initial data import
      - TST-PFM-TNL-096
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
      deletion_pending: a tenant still being built is not put up for deletion
    category: transition
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
        deletion_pending: a tenant still being built is not put up for deletion
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
        deletion_pending: a tenant still being built is not put up for deletion
    binding_note: >-
      All cited checks for this scenario occur in the registered file. The link establishes inspected
      executable source rather than a passing run.
  - id: ATS-PFM-TNL-006-03
    title: State repetition is idempotent
    level: unit
    proves:
      - BR-CAP-PFM-TNL-006-03
    preconditions: Each lifecycle state is represented.
    steps: Assert a transition to the same state.
    expected: The assertion succeeds without a lifecycle change.
    negative: false
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        provisioning to provisioning, so a repeated step changes nothing and refuses nothing
      - >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        deleting to deleting, so a retried purge step is idempotent
      - TST-PFM-TNL-096
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
      provisioning to provisioning, so a repeated step changes nothing and refuses nothing
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        provisioning to provisioning, so a repeated step changes nothing and refuses nothing
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
        provisioning to provisioning, so a repeated step changes nothing and refuses nothing
    binding_note: >-
      All cited checks for this scenario occur in the registered file. The link establishes inspected
      executable source rather than a passing run.
  - id: ATS-PFM-TNL-006-04
    title: Deletion status cannot be re-enabled directly
    level: api
    proves:
      - BR-CAP-PFM-TNL-006-04
    preconditions: A workspace is deletion pending.
    steps: Attempt to set its status live.
    expected: The database rejects the cross-axis inconsistency.
    negative: true
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a status change the lifecycle forbids, by the rule the database holds
      - TST-PFM-TNL-077
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
      a status change the lifecycle forbids, by the rule the database holds
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a status change the lifecycle forbids, by the rule the database holds
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a status change the lifecycle forbids, by the rule the database holds
    binding_note: >-
      All cited checks for this scenario occur in the registered file. The link establishes inspected
      executable source rather than a passing run.
  - id: ATS-PFM-TNL-006-05
    title: Staff authority controls disabling
    level: api
    proves:
      - BR-CAP-PFM-TNL-006-05
    preconditions: Customer-admin and staff callers target the same workspace.
    steps: Attempt the disabling action with each caller.
    expected: Customer authority is refused and authorized staff can change status.
    negative: true
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not disable it,
        because cutting an association off is a staff act
      - >-
        services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not take it live
        through a replacement either
      - >-
        services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#lets Rhythm staff
        change the same status
      - TST-PFM-TNL-052
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not disable it,
      because cutting an association off is a staff act
    category: permission
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not disable it,
        because cutting an association off is a staff act
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not disable it,
        because cutting an association off is a staff act
    binding_note: >-
      All cited checks for this scenario occur in the registered file. The link establishes inspected
      executable source rather than a passing run.
  - id: ATS-PFM-TNL-006-06
    title: Production rejects disposable marking
    level: api
    proves:
      - BR-CAP-PFM-TNL-006-07
    preconditions: Production policy applies to the fixtures.
    steps: Create and update with disposable marking.
    expected: Both attempts are refused.
    negative: true
    data_needs: >-
      Use the run-owned records and caller contexts created by the cited tests; use their provider and clock
      seams only where declared.
    bound_to:
      - >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a disposable tenant in Production, where no such tenant exists
      - >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        an update that makes an existing tenant disposable in Production
      - TST-PFM-TNL-077
    provenance: test
    source: >-
      services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
      a disposable tenant in Production, where no such tenant exists
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a disposable tenant in Production, where no such tenant exists
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
        a disposable tenant in Production, where no such tenant exists
    binding_note: >-
      All cited checks for this scenario occur in the registered file. The link establishes inspected
      executable source rather than a passing run.
  - id: ATS-PFM-TNL-006-07
    title: Cancellation restores lifecycle without restoring availability
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-TNL-006-04
    interacts_with:
      - CAP-PFM-TNL-014
      - CAP-PFM-TNL-005
      - CAP-PFM-TNL-013
    preconditions: A disabled workspace has a reversible deletion request.
    steps: 'Cancel its request, read the two status axes and make an ordinary customer request.'
    expected: >-
      The request remains as cancelled history; lifecycle returns to active, business status remains disabled
      and ordinary access remains unavailable.
    negative: true
    data_needs: >-
      Create a separate run-owned workspace for each decision branch and an unaffected comparison workspace.
      Use a controlled clock, paused task or injected fault at the boundary named in the scenario.
    bound_to: []
    provenance: proposed
    source: >-
      rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-006-keep-every-workspace-s-lifecycle-state-truthful.md;
      rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-014-hold-release-or-call-off-a-deletion-before-destruction.md;
      rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-005-keep-people-and-background-work-out-of-a-workspace-that-is.md;
      rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-013-ask-for-a-workspace-to-be-deleted-after-a-waiting-period.md
    action_evidence:
      provenance: proposed
      source: >-
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-006-keep-every-workspace-s-lifecycle-state-truthful.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-014-hold-release-or-call-off-a-deletion-before-destruction.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-005-keep-people-and-background-work-out-of-a-workspace-that-is.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-013-ask-for-a-workspace-to-be-deleted-after-a-waiting-period.md
    assertion_evidence:
      provenance: proposed
      source: >-
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-006-keep-every-workspace-s-lifecycle-state-truthful.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-014-hold-release-or-call-off-a-deletion-before-destruction.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-005-keep-people-and-background-work-out-of-a-workspace-that-is.md;
        rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-013-ask-for-a-workspace-to-be-deleted-after-a-waiting-period.md
evidence:
  - >-
    services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#answers 200 to a patch aimed at the
    lifecycle state, because the property is simply dropped
  - >-
    services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#refuses
    deletion_pending: a tenant still being built is not put up for deletion
  - >-
    services-platform:modules/tenants/tests/unit/services/lifecycle/lifecycle-transitions.spec.js#allows
    provisioning to provisioning, so a repeated step changes nothing and refuses nothing
  - >-
    services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
    a status change the lifecycle forbids, by the rule the database holds
  - >-
    services-platform:modules/tenants/tests/e2e/tenant-deletion-requests.e2e.spec.js#may not disable it,
    because cutting an association off is a staff act
  - >-
    services-platform:modules/tenants/tests/integration/services/persistence/tenants.integration.spec.js#refuses
    a disposable tenant in Production, where no such tenant exists
  - >-
    rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-006-keep-every-workspace-s-lifecycle-state-truthful.md;
    rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-014-hold-release-or-call-off-a-deletion-before-destruction.md;
    rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-005-keep-people-and-background-work-out-of-a-workspace-that-is.md;
    rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-013-ask-for-a-workspace-to-be-deleted-after-a-waiting-period.md
coverage:
  rule: 'gap: FND-PFM-TNL-071'
  transition: 'gap: FND-PFM-TNL-071'
  validation: 'gap: FND-PFM-TNL-071'
  permission: covered
  edge: 'gap: FND-PFM-TNL-071'
  failure: 'gap: FND-PFM-TNL-071'
  boundary: covered
  concurrency: 'gap: FND-PFM-TNL-071'
  invariant: covered
  interaction: 'gap: FND-PFM-TNL-071'
coverage_notes:
  transition: >-
    The listed scenarios sample lifecycle changes. Complete transition, guard and side-effect proof is still
    owed for the state models named in the coverage finding.
  interaction: >-
    The added interaction is proposed. Related capabilities not included in that scenario remain named in the
    coverage finding.
transition_obligations:
  - state_model: STATE-PFM-TNL-001
    transition_n: 1
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/persistence/tenants.ts:activateProvisionedTenant'
  - state_model: STATE-PFM-TNL-001
    transition_n: 2
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: >-
      services-platform:modules/tenants/services/provisioning/provision-tenant-failure.ts:applyTenantProvisioningFailure
  - state_model: STATE-PFM-TNL-001
    transition_n: 3
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: >-
      services-platform:modules/tenants/services/provisioning/submit-tenant-provisioning.ts:retryTenantProvisioning
  - state_model: STATE-PFM-TNL-001
    transition_n: 4
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/deletion/request-deletion.ts:requestTenantDeletion'
  - state_model: STATE-PFM-TNL-001
    transition_n: 5
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/persistence/tenants.ts:enterTenantLoading'
  - state_model: STATE-PFM-TNL-001
    transition_n: 6
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/deletion/request-deletion.ts:requestTenantDeletion'
  - state_model: STATE-PFM-TNL-001
    transition_n: 7
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/persistence/tenants.ts:activateLoadedTenant'
  - state_model: STATE-PFM-TNL-001
    transition_n: 8
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/lifecycle/lifecycle-transitions.ts:SUCCESSORS'
  - state_model: STATE-PFM-TNL-001
    transition_n: 9
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/deletion/deletion-holds.ts:cancelTenantDeletion'
  - state_model: STATE-PFM-TNL-001
    transition_n: 10
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/deletion/begin-destruction.ts:beginTenantDestruction'
  - state_model: STATE-PFM-TNL-001
    transition_n: 11
    coverage: 'gap: FND-PFM-TNL-071'
    provenance: observed
    source: 'services-platform:modules/tenants/services/persistence/tenants.ts:forceTenantDeletingAfterRestore'
permission_obligations:
  - key: platform.tenants.manage-status
    where_checked: api
    scenario_examples:
      - ATS-PFM-TNL-006-05
    coverage: 'gap: FND-PFM-TNL-071'
    note: 'Examples do not establish the complete key-by-key grant, revoked-grant and staff-context matrix.'
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/platform/tenant-lifecycle/planning/capabilities/CAP-PFM-TNL-006-keep-every-workspace-s-lifecycle-state-truthful.md
evidence_note: >-
  The test-specific convention lists an Evidence body section, but the corpus checker rejects it. Sources and
  verification limits remain in frontmatter so this artifact conforms without changing the checker.
---
# Preserve valid workspace lifecycle states: Capability Test Specification

## Coverage

The category map separates defined scenarios from missing proof. A covered category has a scenario; a binding records inspected test assertions and carries no execution result. State-model completeness and untested interactions remain explicit gaps.

| Rule | Proved by |
|---|---|
| `BR-CAP-PFM-TNL-006-01` | `ATS-PFM-TNL-006-01` |
| `BR-CAP-PFM-TNL-006-02` | `ATS-PFM-TNL-006-02` |
| `BR-CAP-PFM-TNL-006-03` | `ATS-PFM-TNL-006-03` |
| `BR-CAP-PFM-TNL-006-04` | `ATS-PFM-TNL-006-04`, `ATS-PFM-TNL-006-07` |
| `BR-CAP-PFM-TNL-006-05` | `ATS-PFM-TNL-006-05` |
| `BR-CAP-PFM-TNL-006-07` | `ATS-PFM-TNL-006-06` |

## Interactions With Related Capabilities

Cancellation restores lifecycle without restoring availability. The combined scenario sets up the stated boundary, changes the upstream condition and observes its effect on the dependent outcome. Its steps and assertions are proposed; component tests do not establish this combined execution.

| Related capability | Combined coverage |
|---|---|
| `CAP-PFM-TNL-002` | Gap: `FND-PFM-TNL-071` |
| `CAP-PFM-TNL-005` | `ATS-PFM-TNL-006-07` (proposed) |
| `CAP-PFM-TNL-011` | Gap: `FND-PFM-TNL-071` |
| `CAP-PFM-TNL-013` | `ATS-PFM-TNL-006-07` (proposed) |
| `CAP-PFM-TNL-014` | `ATS-PFM-TNL-006-07` (proposed) |
| `CAP-PFM-TNL-016` | Gap: `FND-PFM-TNL-071` |
