---
id: CAP-CRM-ENT-013
title: Set records aside and restore them
product_area: crm
module: entities
revision: 1
status: approved
product_intent: confirmed
implementation_evidence: observed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
intent_sources:
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-07'
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A16'
  - 'services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.plan.md#A8'
purpose: >-
  Administrators retain records that should leave ordinary use and restore them
  when needed.
outcomes:
  - >-
    An archived record is out of every default list and picker and cannot be
    edited.
  - >-
    Every affiliation that names an archived organization preserves it, badged
    archived.
  - Unarchive returns the record as it was.
actors:
  - ACT-CRM-001
  - ACT-CRM-003
  - ACT-CRM-004
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: crm.contacts.archive
    where_checked: api
    scope: tenant
  - key: crm.organizations.archive
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-ENT-013-01
    proved_by: [ATS-CRM-ENT-013-01, ATS-CRM-ENT-013-17, TST-CRM-ENT-786]
    statement: >-
      Archive and unarchive require the record kind’s archive permission
      independently of update permission.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      caller who may update contacts but not archive them, 403
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - id: BR-CAP-CRM-ENT-013-02
    proved_by: [ATS-CRM-ENT-013-02, TST-CRM-ENT-786, TST-CRM-ENT-787]
    statement: >-
      Archive and unarchive require the read version, reject missing or stale
      versions and reject a record locked against updates.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses an
      archive with no version, 400, naming it
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - id: BR-CAP-CRM-ENT-013-03
    proved_by: [ATS-CRM-ENT-013-03, ATS-CRM-ENT-013-11, ATS-CRM-ENT-013-16, ATS-CRM-ENT-016-08, TST-CRM-ENT-787]
    statement: >-
      Archive preserves status and collections, records its actor, time and
      optional reason up to 500 characters, advances version once and creates a
      revision; repeating the current archive state changes nothing.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: true
    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
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - id: BR-CAP-CRM-ENT-013-04
    proved_by: [ATS-CRM-ENT-013-04, ATS-CRM-ENT-013-11, TST-CRM-ENT-786, TST-CRM-ENT-787]
    statement: >-
      Unarchive clears archive information and restores the record with an
      optional reason up to 500 characters retained until its next archive.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - id: BR-CAP-CRM-ENT-013-05
    proved_by: [ATS-CRM-ENT-004-19, ATS-CRM-ENT-013-05, ATS-CRM-ENT-013-13, TST-CRM-ENT-786, TST-CRM-ENT-787]
    statement: >-
      Archived records reject ordinary replacements, patches, bulk or job edits
      and disconnected-detail actions while permitting merge writes, system
      bookkeeping and deletion.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      replacement, 409 RecordArchived
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
  - id: BR-CAP-CRM-ENT-013-06
    proved_by: [ATS-CRM-ENT-003-16, ATS-CRM-ENT-013-06, ATS-CRM-ENT-013-12, TST-CRM-ENT-786, TST-CRM-ENT-787]
    statement: >-
      Default contact and organization lists exclude archived records, support
      explicit include or archived-only selections with matching pagination and
      still return full archived records by id.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
      archived=only, and only it
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-07'
  - id: BR-CAP-CRM-ENT-013-07
    proved_by: [ATS-CRM-ENT-013-07, TST-CRM-ENT-774]
    statement: >-
      Pickers, Quick Search and newly created queries exclude archived records
      by default.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/archive.md#Lists and pickers'
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-07'
  - id: BR-CAP-CRM-ENT-013-08
    proved_by: [ATS-CRM-ENT-013-08, ATS-CRM-ENT-013-14, ATS-CRM-ENT-013-15, ATS-CRM-ENT-014-10, TST-CRM-ENT-775, TST-CRM-ENT-787]
    statement: >-
      Organization archive preserves every affiliation and its organization
      identity, adds archive information to affiliation reads and leaves
      people-list filters available with archived-contact indicators.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js#keeps
      the ended affiliation naming the organization's id, with its name and its
      archive instant
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-07'
  - id: BR-CAP-CRM-ENT-013-09
    proved_by: [ATS-CRM-ENT-013-09, TST-CRM-ENT-787]
    statement: >-
      Ordinary saves, imports and data jobs cannot write archive information;
      archive and unarchive actions own those changes.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#is
      kept through a plain save, which never writes it
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-02'
scenarios:
  - id: SC-CAP-CRM-ENT-013-01
    given: Test Duplicate is a contact nobody uses.
    when: An administrator archives it with a reason.
    then: 'It leaves Quick Search and the list, and can be unarchived from its 360.'
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0066.spec.ts#CRM-ACC-0066
  - id: SC-CAP-CRM-ENT-013-02
    given: A firm that closed years ago with former employees.
    when: It is archived.
    then: >-
      Its former employees are found under Past, each affiliation naming it,
      badged archived.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#keeps the
      former employee findable from it under Past, by the contact’s id
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-CRM-ENT-003
  - CAP-CRM-ENT-004
  - CAP-CRM-ENT-009
  - CAP-CRM-ENT-014
  - CAP-CRM-ENT-016
journeys:
  - JNY-CRM-ENT-003
open_questions: []
feature: FEAT-CRM-ENT-07
concepts:
  - CON-CRM-ENT-001
  - CON-CRM-ENT-002
  - CON-CRM-ENT-003
evidence:
  - >-
    `services-crm` at `7ea48a414c42eade10d60753b8bcc323b1dd726f`: examined
    `aws/api/contacts.routes.ts`, `organizations.routes.ts` (archive, unarchive),
    `archive.schemas.ts`, `archive-api-mapper.ts`, `services/persistence/contacts.ts`,
    `organizations.ts` (archive region), `db/archive-columns.ts`, `queries/contacts.query.ts`,
    `docs/archive.md`; tests `e2e/archive.e2e.spec.js`,
    `integration/services/persistence/contacts.archive.integration.spec.js`,
    `organizations.archive.integration.spec.js`.
  - >-
    `react-console` at `da63075876b2c5f4a6af07d457bb24ea8bf4c0c7`: examined journeys CRM-ACC-0036,
    0066, 0144, 0147.
approved_by: Andrew
approved_at: 2026-10-22
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Works This Way
    provenance: inferred
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      caller who may update contacts but not archive them, 403;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses an
      archive with no version, 400, naming it;
      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/organizations.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      replacement, 409 RecordArchived;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
      archived=only, and only it; services-crm:docs/archive.md#Lists and
      pickers;
      services-crm:modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js#keeps
      the ended affiliation naming the organization's id, with its name and its
      archive instant;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#is
      kept through a plain save, which never writes it
    note: >-
      Operational rationale inferred from the cited behavior; no personal motive
      or historical decision is asserted.
repositories:
  - repo: services-crm
    role: owner
    paths:
      - modules/entities/
      - docs/archive.md
      - modules/entities/tests/e2e/archive.e2e.spec.js
      - >-
        modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js
      - >-
        modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js
  - repo: react-console
    role: console
    paths:
      - 'app/(console)/console/[tenantId]/crm/contacts/'
      - acceptance/crm/playwright/journeys/contacts/CRM-ACC-0066.spec.ts
exercised_by: [FAS-CRM-ENT-01-03, FAS-CRM-ENT-02-03, FAS-CRM-ENT-07-02, FAS-CRM-ENT-07-03, FAS-CRM-ENT-07-04]
suites: [TST-CRM-ENT-774, TST-CRM-ENT-775, TST-CRM-ENT-786, TST-CRM-ENT-787]
---

# Set records aside and restore them

## Purpose

An obsolete contact or closed organization may still explain earlier association work. This capability sets the record aside without destroying that context and lets an authorized administrator restore it later. It separates ordinary availability from business status, so archiving a body does not rewrite what its status or relationships mean.

## How It Works

Archive and unarchive are separate authorized actions, each using the version read and respecting update locks. Archive records the actor, time, and optional reason, advances the version, and creates a revision while leaving status and collections intact. Asking for the state already present has no effect. Restoration clears archive information and can retain its own reason until a later archive.

Archived records disappear from default lists and ordinary selection, but an explicit archived selection and a direct identity read remain available. Ordinary edits, imports, and disconnected-detail actions are refused while system bookkeeping, merge work, and deletion have their stated exceptions. An archived organization retains every affiliation; relationship reads identify its archive state rather than erasing its name or history. Ordinary saves cannot set archive metadata themselves.

## Example

Harbor Engineering closes while three former employees still have ended affiliations to it. An administrator archives the organization with a reason. Staff no longer find it in ordinary selection, but an existing link opens the retained record and its Past people view. If the firm reopens, the administrator reads the current version and restores it, preserving the old connections rather than rebuilding them.

The administrator first reads Harbor Engineering's current version and records Closed after final contract as the archive reason. If she has update permission alone, the lifecycle action is refused; ordinary editing authority does not imply archive authority. A full update lock also prevents archive until properly released. After success, a direct read still returns the firm and its retained affiliation context, while the ordinary list excludes it. An archived-only selection finds it for retention work. If the administrator repeats the archive while it is already archived, the request does not manufacture another state change. Restoration uses the explicit unarchive action rather than editing its status.

## Why It Works This Way

Our reading is that archive preserves an explanation of the past while changing the record's availability for present work. Keeping status independent prevents a closed organization's archive operation from inventing a new business status. Dedicated permission and version checks make this a deliberate lifecycle action rather than a side effect of an ordinary correction.

Blocking ordinary edits stabilizes a record set aside for retention, while explicit reads preserve its usefulness as a historical reference. Retaining affiliations avoids rewriting former employees' records to disguise the organization's closure. No-op behavior prevents retries from manufacturing revisions. Dedicated archive actions prevent integrations from bypassing those safeguards by writing the archive fields in an otherwise ordinary payload.

## Relationships

Contact and Organization retain their identities and owned information through this operation. Locate people and organizations applies archive to ordinary discovery, including Platform pickers and Quick Search. Affiliations and Roles exposes retained connections. Record locks constrain lifecycle actions, and Inspect a record’s changes over time explains them. Remove records and their owned information remains a separate destructive capability.
