---
id: CAP-CRM-DQ-018
title: Resolve identities absorbed by a merge
feature: FEAT-CRM-DQ-05
product_area: crm
module: data-quality
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - CAP-CRM-MRG-020
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-crm:docs/merging/decisions.md#D3 (decided 2026-10-11: option 1)'
  - 'services-crm:docs/milestones/CRM-M4-duplicates-and-merge.md#D-20'
  - >-
    services-crm:docs/merging/experience/functional-requirements.md#IND-6, SUR-5 (package approved by Andrew
    2026-10-13)
purpose: Old references can identify the survivor and cannot recreate an absorbed record identity.
outcomes:
  - Older references identify their destination rather than silently recreating an absorbed identity.
  - Absorbed identities cannot be reassigned to new same-type workspace records.
  - Console navigation explains the destination of an older record link.
concepts:
  - CON-CRM-DQ-004
actors:
  - ACT-CRM-004
  - ACT-CRM-002
  - ACT-CRM-005
surfaces:
  - api
  - console
permissions:
  - key: crm.merges.read
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-DQ-018-01
    proved_by: [ATS-CRM-DQ-018-01, ATS-CRM-DQ-018-09, ATS-CRM-DQ-018-18, TST-CRM-DQ-181]
    statement: >-
      Each deleted participant receives one alias atomically with its merge, retaining survivor, merge, time,
      original number, origin and external identity.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/db/merged-record-aliases.table.integration.spec.js#refuses
      a second alias of the same merged id, so following one is never a choice
    origin: 'services-crm:docs/merging/decisions.md#D3 (decided 2026-10-11: option 1)'
  - id: BR-CAP-CRM-DQ-018-02
    proved_by: [ATS-CRM-DQ-018-02, ATS-CRM-DQ-018-10, ATS-CRM-DQ-018-18, TST-CRM-DQ-181]
    statement: >-
      When a survivor is absorbed later, its inbound aliases redirect to the new survivor while preserving
      their original merge references, allowing direct resolution.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/persistence/merged-record-aliases.integration.spec.js#leaves
      C holding both aliases, so the survivor can say what it is made of
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-018-03
    proved_by: [ATS-CRM-DQ-018-03, ATS-CRM-DQ-018-18, TST-CRM-DQ-181]
    statement: >-
      An absorbed identity cannot be reused for its record type in the same workspace, but remains independent
      of identities in other workspaces or record types.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/db/merged-id-reuse.integration.spec.js#refuses the contact
      a client asks to create under it, which is the path a chosen id arrives by
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-018-04
    proved_by: [ATS-CRM-DQ-018-04, ATS-CRM-DQ-018-18, TST-CRM-DQ-183]
    statement: >-
      Alias lookup returns survivor, merge and time for either type with merge-read authority, and returns not
      found for identities never absorbed.
    kind: policy
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#says where an id the merge took away went, and
      through which merge
    origin: 'services-crm:docs/merging/decisions.md#D3 (decided 2026-10-11: option 1)'
  - id: BR-CAP-CRM-DQ-018-05
    proved_by: [ATS-CRM-DQ-018-05, ATS-CRM-DQ-018-18, TST-CRM-DQ-181, TST-CRM-DQ-183]
    statement: >-
      Alias reads and writes remain within their workspace; another workspace's absorbed identity returns not
      found.
    kind: invariant
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merged-records.e2e.spec.js#answers 404 in this tenant for an id
      merged away in another, whose merge is not ours to disclose
    origin: none
  - id: BR-CAP-CRM-DQ-018-06
    proved_by: [ATS-CRM-DQ-018-06, ATS-CRM-DQ-018-18, TST-CRM-DQ-181]
    statement: Lookups using an absorbed record's number or origin identity resolve its survivor through the alias.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/persistence/merged-record-aliases.queries.integration.spec.js#answers
      the alias of the record that had that number, whole
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-018-07
    proved_by: [ATS-CRM-DQ-018-07, ATS-CRM-DQ-018-12, ATS-CRM-DQ-018-13, ATS-CRM-DQ-018-18, TST-CRM-DQ-181]
    statement: >-
      The application cannot delete or rewrite aliases except to redirect survivors, and workspace purge
      removes them with the workspace.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/db/merged-record-aliases.table.integration.spec.js#may not
      erase an alias, which would make a merged id answerable and reusable again
    origin: 'services-crm:docs/merging/decisions.md#D4 (decided 2026-10-11: option 1)'
  - id: BR-CAP-CRM-DQ-018-08
    proved_by: [ATS-CRM-DQ-018-08, ATS-CRM-DQ-018-18, TST-CRM-DQ-176]
    statement: >-
      Old console record links navigate to the survivor with a notice identifying both record numbers, while
      stale explorer, recent, bookmark, search and saved-view references resolve or report failure.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0267.spec.ts#CRM-ACC-0267'
    origin: 'services-crm:docs/milestones/CRM-M4-duplicates-and-merge.md#D-20'
  - id: BR-CAP-CRM-DQ-018-09
    proved_by: [ATS-CRM-DQ-018-11]
    statement: >-
      The product should keep alias destinations live; hard-deleting a survivor currently leaves unresolved
      destinations.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: unknown
    source: FND-CRM-DQ-022
    origin: 'services-crm:docs/merging/architecture.md#14.1 Non-negotiable invariants'
  - id: BR-CAP-CRM-DQ-018-10
    proved_by: []
    statement: >-
      The product should expose survivor resolution to other applications' reports so retained absorbed
      identities can display the surviving name.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: unknown
    source: OQ-CRM-DQ-003
    origin: none
scenarios:
  - id: SC-CAP-CRM-DQ-018-01
    given: 'A record merges into a second record, which later merges into a third.'
    when: A client resolves the first identity.
    then: Its alias points directly to the third record while retaining the first merge reference.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0261.spec.ts#CRM-ACC-0261'
  - id: SC-CAP-CRM-DQ-018-02
    given: An integration submits creation with an absorbed contact identity.
    when: The create reaches identity validation.
    then: >-
      That contact create fails, while the same identity remains independent for organizations and other
      workspaces.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0268.spec.ts#CRM-ACC-0268'
edge_cases: []
constraints: []
depends_on:
  - CAP-CRM-DQ-016
related:
  - CAP-CRM-ENT-003
  - CAP-CRM-ENT-014
  - CAP-CRM-ENT-016
journeys:
  - JNY-CRM-DQ-006
open_questions:
  - OQ-CRM-DQ-003
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - docs/merging/README.md
      - docs/merging/architecture.md
      - docs/merging/decisions.md
      - docs/merging/experience/functional-requirements.md
      - docs/milestones/CRM-M4-duplicates-and-merge.md
      - modules/merges/db/merged-record-aliases.table.ts
      - modules/merges/services/persistence/merged-record-aliases.ts
      - modules/merges/tests/e2e/merged-records.e2e.spec.js
      - modules/merges/tests/e2e/merges.e2e.spec.js
      - modules/merges/tests/integration/db/merged-id-reuse.integration.spec.js
      - modules/merges/tests/integration/db/merged-record-aliases.table.integration.spec.js
      - modules/merges/tests/integration/services/persistence/merged-record-aliases.integration.spec.js
      - >-
        modules/merges/tests/integration/services/persistence/merged-record-aliases.queries.integration.spec.js
    note: 'Static examination connects rule claims to implementation, declared checks and the cited tests.'
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0261.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0267.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0268.spec.ts
    note: 'Static examination connects rule claims to implementation, declared checks and the cited tests.'
preconditions: []
postconditions: []
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-CRM-DQ-04-05, FAS-CRM-DQ-05-01, FAS-CRM-DQ-05-02, FAS-CRM-DQ-05-03, FAS-CRM-DQ-05-04, FAS-CRM-DQ-05-05]
suites: [TST-CRM-DQ-176, TST-CRM-DQ-181, TST-CRM-DQ-183]
---

# Resolve identities absorbed by a merge

## Purpose

Old references can identify the survivor and cannot recreate an absorbed record identity.

## How It Works

An absorbed record leaves an alias in the merge transaction. The alias retains its original number and external identity alongside the survivor and merge details. When that survivor is merged again, earlier aliases point directly to the new survivor. Clients therefore need only one resolution step. The database rejects creation using an absorbed identity of the same type in the same workspace. The alias lookup requires merge-read authority. Number and origin lookups can also find the record through its alias. Old console links navigate to the survivor with an explanation. Hard deletion of a survivor currently leaves a dangling destination, which remains a defect. The product should expose alias information to reporting applications so their retained historical references can show the survivor.

## Relationships

Execution writes and updates aliases. Entities owns merged-record responses on record routes. Merge history uses absorbed identities to include earlier outcomes.
