---
id: STATE-PFM-MTD-004
title: Custom app lifecycle
object: DOM-PFM-MTD-004
product_area: platform
module: metadata
revision: 1
status: draft
design_intent: confirmed
origin: PLAT-M5 D15 (build custom apps), B2 (no limit yet) and B3 (refuse, name objects), services-platform:docs/milestones/PLAT-M5-custom-records.md#Decisions
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
- name: absent
  meaning: The workspace has not created an app under this key.
- name: live
  meaning: The app accepts object placement and appears in navigation when the caller can read a placed active object.
- name: archived
  meaning: The retained app is excluded from navigation and accepts neither new placement nor replacement.
transitions:
- from: absent
  to: live
  trigger: An administrator creates a custom app.
  guard: The key is valid and nonreserved, and label, icon and order satisfy their contracts.
  side_effects:
  - Store the app and audit configured fields.
  who: Workspace Administrator (platform.custom-objects.create)
  provenance: test
  source: services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
    the key of a platform application, which
- from: live
  to: live
  trigger: An administrator changes app presentation.
  guard: The app is unarchived and the supplied version matches.
  side_effects:
  - Replace label, icon and order with an audit entry.
  who: Workspace Administrator (platform.custom-objects.update)
  provenance: observed
  source: services-platform:modules/custom-objects/services/persistence/custom-apps.ts
- from: live
  to: archived
  trigger: An administrator archives the app.
  guard: The version matches and no draft or active object remains placed there.
  side_effects:
  - Retain the app and archived placements while recording the audit.
  who: Workspace Administrator (platform.custom-objects.delete)
  provenance: test
  source: services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
    to archive an app while a draft and an a
- from: archived
  to: live
  trigger: An administrator restores the app.
  guard: The app is archived and the supplied version matches.
  side_effects:
  - Clear archival so object placement can use it again.
  who: Workspace Administrator (platform.custom-objects.delete)
  provenance: observed
  source: services-platform:modules/custom-objects/services/persistence/custom-apps.ts
invalid:
- from: archived
  to: archived
  why: App archival retains identity, and archived apps must be restored before replacement or placement.
- from: live
  to: absent
  why: App archival retains identity, and archived apps must be restored before replacement or placement.
concurrency: Mutations use the current version. App archival locks its row exclusively, while placement reads it
  shared; one waits and then observes the other’s committed result.
invariants:
- INV-DOM-PFM-MTD-004-07
- INV-DOM-PFM-MTD-004-08
evidence:
- 'services-platform at ad3f6a65: modules/custom-objects/services/persistence/custom-apps.ts; db/custom-apps.table.ts;
  placement-apps.ts; docs/custom-objects.md §25'
---

# Custom app lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> absent
  absent --> live: create
  live --> live: replace
  live --> archived: archive (nothing placed in it)
  archived --> live: restore
```

## Transitions Explained

Apps organize custom categories from creation onward. Before archival, administrators must move any draft or active objects still placed there; restoration makes the app selectable again.

## What Cannot Happen

App archival retains identity, and archived apps must be restored before replacement or placement.

## Concurrency and Timing

Mutations use the current version. App archival locks its row exclusively, while placement reads it shared; one waits and then observes the other’s committed result.
