---
id: STATE-CRM-ENT-001
title: Contact record lifecycle
object: DOM-CRM-ENT-001
product_area: crm
module: entities
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: new
    meaning: The requested identity has no stored contact yet.
    entry_conditions: No live identity exists yet.
    allowed_actions:
      - Submit a validated create request.
    terminal: false
    reversible: false
    provenance: observed
    source: >-
      services-crm at 7ea48a41: modules/entities/db/contacts.table.ts,
      archive-columns.ts; services/persistence/contacts.ts (createContact,
      updateContact, archiveContact, unarchiveContact, deleteContact);
      docs/archive.md.
    experience: A form draft has no allocated record number or history.
  - name: active
    meaning: >-
      Normal contact status, eligible to reserve primary email and receive
      automatic roles.
    entry_conditions: The stored status is active.
    allowed_actions:
      - Read
      - edit if not archived or fully locked
      - archive
      - delete if permitted.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made
    experience: Ordinary selection can include the person.
  - name: inactive
    meaning: >-
      The record and existing relationships remain, while primary email
      reservation and new automatic-role eligibility are released.
    entry_conditions: A create or save records inactive status.
    allowed_actions:
      - Read
      - edit under ordinary guards
      - restore active status if primary email is available.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES
    experience: >-
      The person remains readable with existing ties; ordinary selectable lists
      exclude them.
  - name: deceased
    meaning: >-
      The person is recorded as having died, with the same reservation and
      assignment exclusions as inactive status.
    entry_conditions: A create or save records deceased status.
    allowed_actions:
      - Read
      - correct permitted information under ordinary guards.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES
    experience: The header can show deceased presentation; existing relationships remain.
  - name: duplicate
    meaning: >-
      The record is marked duplicate but still reserves primary email and
      retains active-status role eligibility.
    entry_conditions: A create or save records duplicate status.
    allowed_actions:
      - Read
      - edit under ordinary guards
      - use separately authorized Data Quality work.
    terminal: false
    reversible: true
    provenance: observed
    source: 'services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES'
    experience: >-
      The record retains email reservation and eligibility associated with
      active status.
  - name: archived
    meaning: >-
      An independent archive flag preserves status and data, excludes ordinary
      selection and prevents general editing.
    entry_conditions: >-
      A dedicated archive action records archive metadata while retaining
      status.
    allowed_actions:
      - Read directly
      - inspect permitted history
      - unarchive
      - delete subject to delete guards.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status
    experience: >-
      Archive badges and reasons explain why ordinary edit and selection are
      unavailable.
  - name: deleted
    meaning: >-
      The live record and owned rows are removed; retained revisions and the
      allocated number survive.
    entry_conditions: A delete transaction removes the live owner and owned rows.
    allowed_actions:
      - Read retained history with history authority.
    terminal: true
    reversible: false
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading
    experience: Live reads find no record; there is no restore action.
transitions:
  - from: new
    to: active
    trigger: Create a contact with active status or the default status.
    guard: >-
      Contact create permission, a first or last name, valid references and
      details; duplicate-block override needs its separate permission and
      reason.
    side_effects:
      - allocate a contact number
      - maintain spouse reciprocity
      - assign eligible primary-affiliation roles
      - apply or defer default links
      - attach files
      - capture revision and event
    who: An authorized creator or integration
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: new
    to: inactive
    trigger: Create an inactive contact.
    guard: The ordinary create checks still apply.
    side_effects:
      - release primary email reservation
      - omit automatic-role assignment
    who: An authorized creator or import
    provenance: inferred
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
    note: >-
      Initial status behavior follows the shared status lists and the
      persistence eligibility checks; no separate product decision is inferred.
  - from: new
    to: deceased
    trigger: Create a deceased contact.
    guard: Use the same validation and authorization as other contact creates.
    side_effects:
      - do not reserve primary email
      - do not assign automatic roles
    who: An authorized creator or import
    provenance: inferred
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
    note: >-
      Initial status behavior follows the shared status lists and the
      persistence eligibility checks; no separate product decision is inferred.
  - from: new
    to: duplicate
    trigger: Create a contact marked duplicate.
    guard: 'Apply create validation, duplicate blocking and any required override.'
    side_effects:
      - allocate number and persist aggregate
      - retain primary email reservation
      - apply eligible automatic roles
      - capture revision and change event
    who: An authorized creator or integration
    provenance: observed
    source: 'services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES'
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: active
    to: inactive
    trigger: Save a change from active to inactive.
    guard: >-
      Contact update permission, current read version, no archive and no full
      lock.
    side_effects:
      - release email reservation
      - exclude from contact selection
      - retain existing relationships and roles
    who: An authorized editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: active
    to: deceased
    trigger: Save deceased status.
    guard: 'The update, version, archive and lock checks apply.'
    side_effects:
      - release primary email and automatic-role eligibility
      - show deceased status in the console header
    who: An authorized editor
    provenance: observed
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: inactive
    to: active
    trigger: Save active status after inactivity or death.
    guard: Ordinary save checks plus availability of the primary email reservation.
    side_effects:
      - reserve primary email
      - restore picker eligibility
    who: An authorized editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: active
    to: duplicate
    trigger: Save duplicate status.
    guard: Satisfy the normal versioned-save checks.
    side_effects:
      - change status without releasing primary email
    who: An authorized editor or data-quality process
    provenance: observed
    source: 'services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES'
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: active
    to: archived
    trigger: Archive a contact in any status.
    guard: >-
      Archive permission, matching version and no full lock; optional reason is
      limited to 500 characters.
    side_effects:
      - set archive actor and timestamp
      - advance version
      - capture revision and event
      - remove from default selection
    who: An authorized archive operator
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: archived
    to: active
    trigger: Unarchive while retaining the stored contact status.
    guard: Archive permission and current version; a full lock blocks the action.
    side_effects:
      - clear archive stamps
      - retain unarchive reason
      - advance version and capture revision
    who: An authorized archive operator
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: active
    to: deleted
    trigger: 'Delete a contact, including an archived record.'
    guard: >-
      Delete permission; no owner or element deletion lock and no Rhythm
      protection.
    side_effects:
      - cascade owned details and relationships
      - clear reciprocal spouse with system attribution
      - release email reservation
      - detach photo
      - apply enabled owner-deletion policy
      - retain deletion revision
    who: An authorized delete operator or bulk job
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
  - from: archived
    to: deleted
    trigger: Delete an archived contact.
    guard: Deletion checks apply independently of archive.
    side_effects:
      - remove live aggregate and retain history
    who: An authorized delete operator
    provenance: documented
    source: 'services-crm:docs/archive.md#While archived'
    audit_event: >-
      A changed contact transaction captures an attributed aggregate revision
      and change event. Archive actions also retain actor, time and reason. A
      recognized creation replay adds no new creation revision.
invalid:
  - from: archived
    to: inactive
    why: Archive prevents ordinary status editing until the contact is unarchived.
    response: >-
      A general status save on an archived record returns the archive refusal;
      staff must use unarchive before editing status.
  - from: inactive
    to: active
    why: >-
      Reactivation fails if another active or duplicate contact reserves this
      primary email.
    response: >-
      Reactivation can return a primary-email collision refusal. The existing
      inactive record remains unchanged.
  - from: deleted
    to: active
    why: >-
      No restore action exists. Creating again with an unused identity allocates
      a new number, while earlier revisions retain their subject identity.
    response: >-
      A deleted identity has no restore action. A live read returns
      record-not-found; an authorized fresh create is a separate creation and
      spends another number.
  - from: new
    to: archived
    why: >-
      Creation cannot set archive metadata; archiving requires its own
      operation.
    response: >-
      Supplied archive metadata is not a create shortcut. The route refuses
      unsupported writable fields or ignores read-only echoes; only the archive
      action establishes the flag.
concurrency: >-
  Versioned saves and archive actions compare the submitted version while
  holding the contact row lock. Deletion has no version argument. System changes
  advance the version and can invalidate an open editor. Recognized creates with
  matching fingerprints return the previously created contact when retries
  compete.
invariants:
  - INV-DOM-CRM-ENT-001-04
  - INV-DOM-CRM-ENT-001-05
  - INV-DOM-CRM-ENT-033-01
  - INV-DOM-CRM-ENT-033-11
  - INV-DOM-CRM-ENT-033-12
evidence:
  - >-
    services-crm at 7ea48a41: modules/entities/db/contacts.table.ts,
    archive-columns.ts; services/persistence/contacts.ts (createContact,
    updateContact, archiveContact, unarchiveContact, deleteContact);
    docs/archive.md.
automatic: []
ai_may_recommend: false
ai_may_execute: false
permissions_by_state:
  - state: new
    action: create
    permission: crm.contacts.create
  - state: active
    action: read
    permission: crm.contacts.read
  - state: active
    action: update
    permission: crm.contacts.update
  - state: active
    action: archive
    permission: crm.contacts.archive
  - state: active
    action: delete
    permission: crm.contacts.delete
  - state: active
    action: history
    permission: crm.contacts.history
  - state: inactive
    action: read
    permission: crm.contacts.read
  - state: inactive
    action: update
    permission: crm.contacts.update
  - state: inactive
    action: archive
    permission: crm.contacts.archive
  - state: inactive
    action: delete
    permission: crm.contacts.delete
  - state: inactive
    action: history
    permission: crm.contacts.history
  - state: deceased
    action: read
    permission: crm.contacts.read
  - state: deceased
    action: update
    permission: crm.contacts.update
  - state: deceased
    action: archive
    permission: crm.contacts.archive
  - state: deceased
    action: delete
    permission: crm.contacts.delete
  - state: deceased
    action: history
    permission: crm.contacts.history
  - state: duplicate
    action: read
    permission: crm.contacts.read
  - state: duplicate
    action: update
    permission: crm.contacts.update
  - state: duplicate
    action: archive
    permission: crm.contacts.archive
  - state: duplicate
    action: delete
    permission: crm.contacts.delete
  - state: duplicate
    action: history
    permission: crm.contacts.history
  - state: archived
    action: read
    permission: crm.contacts.read
  - state: archived
    action: archive
    permission: crm.contacts.archive
  - state: archived
    action: delete
    permission: crm.contacts.delete
  - state: archived
    action: history
    permission: crm.contacts.history
  - state: deleted
    action: history
    permission: crm.contacts.history
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Purpose
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: Ownership and Authority
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: The Lifecycle
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: States
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: Transitions Explained
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: What Cannot Happen
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: Concurrency and Timing
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/db/contacts.table.ts:RELEASED_CONTACT_STATUSES;
      services-crm:modules/entities/db/contacts.table.ts:CONTACT_STATUSES;
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:docs/archive.md#While archived
  - section: Permissions by State
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/AUTHORIZATION.md#Role Entitlement Matrix'
  - section: Purpose
    provenance: inferred
    source: >-
      services-crm at 7ea48a41: modules/entities/db/contacts.table.ts,
      archive-columns.ts; services/persistence/contacts.ts (createContact,
      updateContact, archiveContact, unarchiveContact, deleteContact);
      docs/archive.md.
    note: >-
      The explicitly labeled rationale interprets the separation of state
      dimensions and the existing trigger and guard rows.
  - section: Invariants
    provenance: inferred
    source: >-
      INV-DOM-CRM-ENT-001-04; INV-DOM-CRM-ENT-001-05; INV-DOM-CRM-ENT-033-01;
      INV-DOM-CRM-ENT-033-11; INV-DOM-CRM-ENT-033-12
    note: >-
      Protection explanations interpret the unchanged domain-object invariants;
      no lifecycle policy is added.
  - section: Invariants
    provenance: inferred
    source: "services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts; services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts; services-crm:modules/entities/services/persistence/record-locks.ts"
    note: "The rationale interprets this artifact’s sourced invariants and linked capability rules; it does not claim a recorded product decision."
---

# Contact record lifecycle

## Purpose

This lifecycle explains when a person remains available for ordinary CRM work and how the person’s business status affects record behavior. An association can retain a former member or deceased person without treating that person as a new identity. Archive supplies a separate preservation decision; deletion removes the live aggregate.

Our reading is that keeping these dimensions separate avoids using one label to answer incompatible questions. Inactive is a business status, while archived controls availability for ordinary discovery and editing. A duplicate still has its own current record until Data Quality performs a merge. None of these labels grants permission to read or change the person.

## Ownership and Authority

Authorized creators choose an initial business status. Editors submit later status changes through contact save, while archive operators use dedicated archive and unarchive actions and delete operators request hard removal. Integrations remain subject to the operation’s grants and record guards. System work can change the version and make an editor’s read stale.

The lifecycle does not call a model to recommend or execute transitions. Data Quality owns merge decisions and survivor routing rather than this lifecycle inventing a new automatic identity policy. The CRM authorization model identifies the roles that hold each operation grant, and contact safeguard checks further restrict their use.

## The Lifecycle


```mermaid
stateDiagram-v2
  state "Stored contact status" as status {
    [*] --> active
    active --> inactive: status save
    inactive --> active: status save, email available
    active --> deceased: status save
    active --> duplicate: status save
    duplicate --> active: status save
  }
  state "Archive flag (independent of status)" as archiveFlag {
    [*] --> live
    live --> archived: archive action
    archived --> live: unarchive action
  }
  status --> deleted: authorized hard delete
  deleted --> [*]
```

## States

### new

The requested identity has no stored contact yet. Entry requires this condition: No live identity exists yet. Available behavior is Submit a validated create request.. A form draft has no allocated record number or history.

### active

Normal contact status, eligible to reserve primary email and receive automatic roles. Entry requires this condition: The stored status is active. Available behavior is Read; edit if not archived or fully locked; archive; delete if permitted.. Ordinary selection can include the person.

### inactive

The record and existing relationships remain, while primary email reservation and new automatic-role eligibility are released. Entry requires this condition: A create or save records inactive status. Available behavior is Read; edit under ordinary guards; restore active status if primary email is available.. The person remains readable with existing ties; ordinary selectable lists exclude them.

### deceased

The person is recorded as having died, with the same reservation and assignment exclusions as inactive status. Entry requires this condition: A create or save records deceased status. Available behavior is Read; correct permitted information under ordinary guards.. The header can show deceased presentation; existing relationships remain.

### duplicate

The record is marked duplicate but still reserves primary email and retains active-status role eligibility. Entry requires this condition: A create or save records duplicate status. Available behavior is Read; edit under ordinary guards; use separately authorized Data Quality work.. The record retains email reservation and eligibility associated with active status.

### archived

An independent archive flag preserves status and data, excludes ordinary selection and prevents general editing. Entry requires this condition: A dedicated archive action records archive metadata while retaining status. Available behavior is Read directly; inspect permitted history; unarchive; delete subject to delete guards.. Archive badges and reasons explain why ordinary edit and selection are unavailable.

### deleted

The live record and owned rows are removed; retained revisions and the allocated number survive. Entry requires this condition: A delete transaction removes the live owner and owned rows. Available behavior is Read retained history with history authority.. Live reads find no record; there is no restore action. This row’s lifecycle ends here; a new request or new record is separate work.

## Transitions Explained

### new → active

An authorized creator or integration starts this change when the following occurs: Create a contact with active status or the default status. The transition checks this boundary: Contact create permission, a first or last name, valid references and details; duplicate-block override needs its separate permission and reason. The accompanying work is allocate a contact number; maintain spouse reciprocity; assign eligible primary-affiliation roles; apply or defer default links; attach files; capture revision and event. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### new → inactive

An authorized creator or import starts this change when the following occurs: Create an inactive contact. The transition checks this boundary: The ordinary create checks still apply. The accompanying work is release primary email reservation; omit automatic-role assignment. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### new → deceased

An authorized creator or import starts this change when the following occurs: Create a deceased contact. The transition checks this boundary: Use the same validation and authorization as other contact creates. The accompanying work is do not reserve primary email; do not assign automatic roles. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### new → duplicate

An authorized creator or integration starts this change when the following occurs: Create a contact marked duplicate. The transition checks this boundary: Apply create validation, duplicate blocking and any required override. The accompanying work is allocate number and persist aggregate; retain primary email reservation; apply eligible automatic roles; capture revision and change event. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### active → inactive

An authorized editor starts this change when the following occurs: Save a change from active to inactive. The transition checks this boundary: Contact update permission, current read version, no archive and no full lock. The accompanying work is release email reservation; exclude from contact selection; retain existing relationships and roles. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### active → deceased

An authorized editor starts this change when the following occurs: Save deceased status. The transition checks this boundary: The update, version, archive and lock checks apply. The accompanying work is release primary email and automatic-role eligibility; show deceased status in the console header. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### inactive → active

An authorized editor starts this change when the following occurs: Save active status after inactivity or death. The transition checks this boundary: Ordinary save checks plus availability of the primary email reservation. The accompanying work is reserve primary email; restore picker eligibility. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### active → duplicate

An authorized editor or data-quality process starts this change when the following occurs: Save duplicate status. The transition checks this boundary: Satisfy the normal versioned-save checks. The accompanying work is change status without releasing primary email. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### active → archived

An authorized archive operator starts this change when the following occurs: Archive a contact in any status. The transition checks this boundary: Archive permission, matching version and no full lock; optional reason is limited to 500 characters. The accompanying work is set archive actor and timestamp; advance version; capture revision and event; remove from default selection. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### archived → active

An authorized archive operator starts this change when the following occurs: Unarchive while retaining the stored contact status. The transition checks this boundary: Archive permission and current version; a full lock blocks the action. The accompanying work is clear archive stamps; retain unarchive reason; advance version and capture revision. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### active → deleted

An authorized delete operator or bulk job starts this change when the following occurs: Delete a contact, including an archived record. The transition checks this boundary: Delete permission; no owner or element deletion lock and no Rhythm protection. The accompanying work is cascade owned details and relationships; clear reciprocal spouse with system attribution; release email reservation; detach photo; apply enabled owner-deletion policy; retain deletion revision. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

### archived → deleted

An authorized delete operator starts this change when the following occurs: Delete an archived contact. The transition checks this boundary: Deletion checks apply independently of archive. The accompanying work is remove live aggregate and retain history. A changed contact transaction captures an attributed aggregate revision and change event. Archive actions also retain actor, time and reason. A recognized creation replay adds no new creation revision.

## What Cannot Happen

A general status save on an archived record returns the archive refusal; staff must use unarchive before editing status.

Reactivation can return a primary-email collision refusal. The existing inactive record remains unchanged.

A deleted identity has no restore action. A live read returns record-not-found; an authorized fresh create is a separate creation and spends another number.

Supplied archive metadata is not a create shortcut. The route refuses unsupported writable fields or ignores read-only echoes; only the archive action establishes the flag.

## Permissions by State

The state rows reference grants from the CRM authorization model. Read access is distinct from mutation, and history requires the corresponding history and read checks. Administrator and Power User hold administrative record actions; ordinary Standard User grants include applicable read and update operations, while Read Only grants do not authorize edits.

Archived records remain directly readable, but ordinary update is blocked. Archive authority permits restoration subject to the dedicated guards. Deleted records have only retained history rather than another ordinary update state.

## Concurrency and Timing

A saved contact may acquire a new version through spouse maintenance, disconnection or automatic-role work before a coordinator finishes editing it. The old read version therefore conflicts even if no colleague touched the visible title. Save and archive compare versions while holding the owner row lock.

Deletion is a separate unversioned action, so this model does not imply compare-and-delete safety. A stable client identity and matching validated create fingerprint support contact creation replay; a create without that identity cannot use replay recognition. Counter allocation can leave a gap when later transactional validation fails.

## Invariants

Our reading is that keeping archive separate from contact status protects the meaning of both. Reopening a deceased contact does not make that person active, and an inactive person is not automatically archived. A staff correction still needs current-version and lock checks so a lifecycle action cannot discard another editor’s work. Retained revisions explain committed changes and deletion without requiring the live contact to survive. They do not make hard deletion equivalent to reversible archive.
