---
id: CAP-CRM-ENT-014
title: Remove records and their owned information
product_area: crm
module: entities
revision: 1
status: approved
product_intent: confirmed
implementation_evidence: partially-observed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
intent_sources:
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10'
  - >-
    services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10
    amendment
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A19'
purpose: >-
  Administrators remove a contact or organization when its deletion dependencies
  permit it.
outcomes:
  - >-
    A deleted contact's collections, affiliations and roles move with it and its
    history remains readable.
  - An organization others depend on cannot be deleted.
  - A rejected delete removes nothing.
actors:
  - ACT-CRM-001
  - ACT-CRM-003
  - ACT-CRM-004
surfaces:
  - console
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: crm.contacts.delete
    where_checked: api
    scope: tenant
  - key: crm.organizations.delete
    where_checked: api
    scope: tenant
  - key: crm.contacts.bulk
    where_checked: api
    scope: tenant
  - key: crm.organizations.bulk
    where_checked: api
    scope: tenant
  - key: crm.contacts.merge
    where_checked: api
    scope: tenant
  - key: crm.organizations.merge
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-ENT-014-01
    proved_by: [ATS-CRM-ENT-014-01, ATS-CRM-ENT-014-13, TST-CRM-ENT-774, TST-CRM-ENT-775]
    statement: >-
      Contact and organization deletion requires the administrator-tier delete
      permission for that record kind and rejects standard users.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0046.spec.ts#CRM-ACC-0046
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10'
  - id: BR-CAP-CRM-ENT-014-02
    proved_by: [ATS-CRM-ENT-014-02, ATS-CRM-ENT-014-09, ATS-CRM-ENT-014-12, ATS-CRM-ENT-016-09, TST-CRM-ENT-786, TST-CRM-ENT-787]
    statement: >-
      Contact deletion transactionally removes its owned details, preferences,
      custom values, affiliations and roles, immediately releases the primary
      e-mail, preserves history and number nonreuse, and leaves subsequent id
      reads not found.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: true
    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
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10'
  - id: BR-CAP-CRM-ENT-014-03
    proved_by: [ATS-CRM-ENT-014-03, TST-CRM-ENT-787]
    statement: >-
      Deleting a spouse contact clears the surviving contact’s spouse reference
      and advances its version with system attribution.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#bumps
      the version of that contact and stamps the system, not the caller that
      deleted (M1-D6)
    origin: none
  - id: BR-CAP-CRM-ENT-014-04
    proved_by: [ATS-CRM-ENT-014-04, ATS-CRM-ENT-014-08, ATS-CRM-ENT-014-11, ATS-CRM-ENT-015-11, TST-CRM-ENT-776, TST-CRM-ENT-787, TST-CRM-ENT-789]
    statement: >-
      Any record or owned-element lock blocks deletion, identifies the blocking
      element and leaves the complete record intact.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      the delete as IllegalOperation, naming the element that is locked
    origin: none
  - id: BR-CAP-CRM-ENT-014-05
    proved_by: [ATS-CRM-ENT-002-17, ATS-CRM-ENT-013-15, ATS-CRM-ENT-014-05, ATS-CRM-ENT-014-10, TST-CRM-ENT-775, TST-CRM-ENT-786]
    statement: >-
      An organization cannot be deleted while it has child organizations or any
      current, historical or upcoming affiliation; the refusal identifies its
      dependants.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 to a delete while the contact is affiliated with it
    origin: none
  - id: BR-CAP-CRM-ENT-014-06
    proved_by: [ATS-CRM-ENT-014-06, TST-CRM-ENT-793]
    statement: >-
      Deletion detaches a contact photo or organization logo in its transaction,
      and cleanup later moves the asset to deleted storage with a 30-day expiry.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/assets/retire-entity-assets.integration.spec.js#detaches
      the photo in the delete's own transaction
    origin: none
  - id: BR-CAP-CRM-ENT-014-07
    proved_by: [ATS-CRM-ENT-014-07]
    statement: >-
      When enabled, deletion applies Platform’s associated custom-record
      ownership rules to detach, cascade or refuse within the record’s
      transaction; the disabled mode makes no such call.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/endpoint-deletions.spec.js#never
      runs a statement when it is off
    origin: >-
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10
      amendment
  - id: BR-CAP-CRM-ENT-014-08
    proved_by: []
    statement: >-
      API and bulk-job hard deletion remain available until safe delete, while
      record-page console actions offer archive.
    kind: policy
    enforced_at:
      - console
      - api
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10'
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10'
scenarios:
  - id: SC-CAP-CRM-ENT-014-01
    given: A contact whose spouse is another contact.
    when: An administrator deletes it.
    then: >-
      The contact and everything it owns are gone, the spouse's link is cleared,
      and the history is still readable.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-history.e2e.spec.js#keeps
      a deleted contact's history readable, with the record marked deleted and
      when
  - id: SC-CAP-CRM-ENT-014-02
    given: 'A firm that closed years ago, named by a contact''s ended affiliation.'
    when: An administrator deletes it.
    then: The delete is rejected; archive is the way.
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0116.spec.ts#CRM-ACC-0116
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-CRM-ENT-013
  - CAP-CRM-ENT-015
  - CAP-CRM-ENT-016
  - CAP-CRM-ENT-012
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` (delete),
    `services/persistence/contacts.ts`, `organizations.ts` (delete),
    `services/persistence/endpoint-deletions.ts`, `services/assets/*.ts`; tests
    `e2e/contacts.e2e.spec.js`, `e2e/organizations.e2e.spec.js`, `e2e/contacts-history.e2e.spec.js`,
    `integration/services/persistence/contacts.integration.spec.js`,
    `organizations.integration.spec.js`,
    `integration/services/assets/retire-entity-assets.integration.spec.js`,
    `unit/services/persistence/endpoint-deletions.spec.js`,
    `integration/db/contact-email-addresses.table.integration.spec.js`.
  - >-
    `react-console` at `da63075876b2c5f4a6af07d457bb24ea8bf4c0c7`: examined inventory
    RUL-CRM-CON-027, RUL-CRM-ORG-025, 026, 028; journeys CRM-ACC-0036, 0037, 0046, 0115, 0116, 0132,
    0209.
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-DAT-004, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-PI-001, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-PRV-003, 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: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0046.spec.ts#CRM-ACC-0046;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#bumps
      the version of that contact and stamps the system, not the caller that
      deleted (M1-D6);
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      the delete as IllegalOperation, naming the element that is locked;
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 to a delete while the contact is affiliated with it;
      services-crm:modules/entities/tests/integration/services/assets/retire-entity-assets.integration.spec.js#detaches
      the photo in the delete's own transaction;
      services-crm:modules/entities/tests/unit/services/persistence/endpoint-deletions.spec.js#never
      runs a statement when it is off;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10
    note: >-
      Operational rationale inferred from the cited behavior; no personal motive
      or historical decision is asserted.
repositories:
  - repo: react-console
    role: console
    paths:
      - 'app/(console)/console/[tenantId]/crm/contacts/'
      - acceptance/crm/playwright/journeys/contacts/CRM-ACC-0046.spec.ts
      - acceptance/crm/playwright/journeys/organizations/CRM-ACC-0116.spec.ts
  - repo: services-crm
    role: owner
    paths:
      - modules/entities/
      - docs/milestones/CRM-M1-contacts-and-organizations.md
      - modules/entities/tests/e2e/contacts-history.e2e.spec.js
      - modules/entities/tests/e2e/organizations.e2e.spec.js
      - >-
        modules/entities/tests/integration/services/assets/retire-entity-assets.integration.spec.js
      - >-
        modules/entities/tests/integration/services/persistence/contacts.integration.spec.js
      - >-
        modules/entities/tests/unit/services/persistence/endpoint-deletions.spec.js
exercised_by: [FAS-CRM-ENT-02-03, FAS-CRM-ENT-07-03, FAS-CRM-ENT-07-04]
suites: [TST-CRM-ENT-774, TST-CRM-ENT-775, TST-CRM-ENT-776, TST-CRM-ENT-786, TST-CRM-ENT-787, TST-CRM-ENT-789, TST-CRM-ENT-793]
---

# Remove records and their owned information

## Purpose

Some records must be removed from the live CRM rather than retained in archive. This capability performs hard deletion under explicit authority, checks owned-element protection and organization dependencies, and removes the record's owned information together. It explains today's deletion boundary without implying that the broader safe-delete impact preview has already shipped.

## How It Works

An authorized delete checks the parent and owned-element locks. Deleting a contact removes its details, preferences, custom values, affiliations, and role rows in the transaction, releases the primary e-mail, and leaves subsequent live reads missing. Record history and number nonreuse survive that removal. A surviving spouse loses the deleted-person reference through a separately attributed system change and version advance.

Organizations cannot be deleted while children or affiliations depend on them, including ended and future ties. Archive is available when historical dependencies make deletion inappropriate. When enabled, Platform's associated custom-record ownership rules can detach, cascade, or refuse inside the transaction; the disabled mode performs no such call.

The transaction detaches the contact photo or organization logo, and later cleanup retires the asset with its expiry. Record-page console actions offer archive, while service and bulk hard deletion remain available until safe delete. These paths are distinct from a reversible lifecycle choice.

## Example

An administrator tries to delete a closed member firm that still appears in a former employee's past affiliation. The service identifies the dependency and retains the complete organization. She archives it instead. For an unrelated contact eligible for deletion, a successful service delete removes owned entries and releases its primary address while retaining the revisions staff may inspect under history authority.

For Harbor Engineering, even an ended affiliation is a dependency. The administrator checks the named blocker rather than assuming that zero current staff makes the firm deletable. Archive retains the firm as a historical reference. For Daniel Ruiz's independently deletable contact, a locked phone still blocks deleting the whole person; the administrator needs the appropriate release before trying again. After a permitted delete she checks that the live read is missing and that the retained history remains available under its separate authority. If Daniel had a spouse link, the surviving person's next read has a new system-attributed version, so an already-open editor must recover from that change.

## Why It Works This Way

Our reading is that aggregate deletion prevents a missing parent from leaving apparently live contact details or responsibilities behind. Any element lock must block the whole operation because deleting its parent would otherwise bypass the protection. Organization dependencies include history because an ended relationship still needs the organization's identity to explain it.

Keeping revisions and number nonreuse preserves an account of what was removed without pretending the live record still exists. System attribution on a spouse update explains why another record changed during deletion. Transactional asset detachment and later retirement distinguish database success from storage cleanup. The optional ownership integration is a stated boundary, not evidence that every dependent application has a complete safe-delete policy today.

## Relationships

Set records aside and restore them provides preservation when hard deletion is unsuitable. Record locks and system protection supply blockers. History provides retained audit reads after the live record disappears. Platform files performs storage retirement, and Platform metadata supplies associated custom-record ownership rules when enabled. Data Quality owns merges; deletion does not create a merge survivor or replace that identity-change process.
