---
id: CAP-CRM-DQ-012
title: Prepare a merge and choose its survivor
feature: FEAT-CRM-DQ-03
product_area: crm
module: data-quality
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-CRM-MRG-014
  - CAP-CRM-MRG-022
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - >-
    services-crm:docs/merging/conflict-resolution.md#8. Which record the system recommends keeping (decided by
    Andrew 2026-10-13)
  - >-
    services-crm:docs/merging/experience/functional-requirements.md#SUR-1 to SUR-4, GEN-4, MAN-4 (package
    approved by Andrew 2026-10-13)
purpose: Administrators can assemble the records to combine and choose the identity that remains.
outcomes:
  - A complete participant set is reviewed in one plan rather than successive pairwise choices.
  - Survivor suggestions prioritize continuity while retaining administrator choice.
  - 'Stored addresses and API listings support resumption, and overdue open plans expire.'
concepts:
  - CON-CRM-DQ-003
actors:
  - ACT-CRM-009
surfaces:
  - console
  - api
permissions:
  - key: crm.contacts.merge
    where_checked: api
    scope: tenant
  - key: crm.contacts.read
    where_checked: api
    scope: tenant
  - key: crm.organizations.merge
    where_checked: api
    scope: tenant
  - key: crm.organizations.read
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-DQ-012-01
    statement: >-
      A merge plan requires two to ten distinct records of one type and a survivor among them; invalid counts,
      repeats and outside survivors are rejected, missing records return not found and absorbed records report
      their survivor.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/persistence/record-merge-plans.integration.spec.js#refuses
      more records than a merge takes
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
    examples:
      positive: 'For example, four records of one member are planned as one merge.'
      negative: 'By comparison, eleven records are refused; the plan states the limit of ten.'
  - id: BR-CAP-CRM-DQ-012-02
    statement: >-
      All plan operations require the relevant type's read and merge permissions, deny cross-type authority
      and reject callers who can merge neither type before looking up a plan.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#refuses the plan to a caller who may merge
      contacts but not organizations
    origin: 'services-crm:docs/merging/architecture.md#9. Security and tenant isolation'
  - id: BR-CAP-CRM-DQ-012-03
    statement: >-
      Survivor recommendation chooses the sole portal-linked participant, then the sole integration-addressed
      participant, then the uniquely most referenced participant, then the oldest; organizations omit the
      login criterion.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/unit/services/planning/survivor-recommendation.spec.js#keeps the
      participant the most rows outside the merge name, when no identity separates them
    origin: >-
      services-crm:docs/merging/conflict-resolution.md#8. Which record the system recommends keeping (decided
      by Andrew 2026-10-13)
  - id: BR-CAP-CRM-DQ-012-04
    statement: >-
      A requested participant remains survivor even when the recommendation differs; without an explicit
      survivor, planning uses the recommendation.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/survivor-recommendation.integration.spec.js#leaves
      the survivor the request named alone, the recommendation being advisory
    origin: >-
      services-crm:docs/merging/conflict-resolution.md#8. Which record the system recommends keeping (decided
      by Andrew 2026-10-13)
  - id: BR-CAP-CRM-DQ-012-05
    statement: >-
      Recommendations explain their reasons using record names and numbers, mark an age-only choice as weak
      and never favor recency or completeness.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/unit/services/planning/survivor-recommendation.spec.js#ignores how
      complete a record is: the fuller participant is not preferred
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#SUR-3'
  - id: BR-CAP-CRM-DQ-012-06
    statement: >-
      Participant summaries include creation and modification times, named status and type, login and
      integration presence, inbound reference counts and the information each contributes.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/participant-summary.integration.spec.js#says
      what it carries, by business concept and in the words staff read them by
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#SUR-2'
  - id: BR-CAP-CRM-DQ-012-07
    statement: >-
      Merge-authorized callers read service limits of two to ten participants, 100,000 related rows and
      100,000 organization employees; the console obtains those limits from the service.
    kind: policy
    enforced_at:
      - api
    must_prove: false
    provenance: test
    source: 'services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#answers the numbers the engine itself enforces'
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#GEN-4'
  - id: BR-CAP-CRM-DQ-012-08
    statement: >-
      Participant or survivor changes recompute the plan, carry decisions for surviving questions and report
      discarded decisions while enforcing at least two participants.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/unit/services/planning/merge-decisions.spec.js#is dropped and reported
      when the plan is simply read again after the data moved on
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#SUR-4'
  - id: BR-CAP-CRM-DQ-012-09
    statement: >-
      Unfinished plans with decisions-needed, ready or blocked status list one type, default to the caller's
      plans and sort by latest change with 25 default and 50 maximum per page; editing does not transfer
      ownership.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-plan-lists.integration.spec.js#answers
      the reader’s own open plans, the one touched last at the top
    origin: none
  - id: BR-CAP-CRM-DQ-012-10
    statement: >-
      A five-minute sweep expires open plans seven days after their last change, preventing further changes or
      execution while leaving executed plans unchanged.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-plan-expiry.integration.spec.js#expires
      a blocked plan too, a plan nobody cleared being as abandoned as one nobody decided
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-012-11
    statement: >-
      Any workspace caller with both read and merge permissions for the plan's type may read and edit it
      regardless of its creator.
    kind: permission
    enforced_at:
      - api
    must_prove: false
    provenance: observed
    source: 'services-crm:modules/merges/aws/api/merges.routes.ts:requireAccessToPlan'
    origin: none
  - id: BR-CAP-CRM-DQ-012-12
    statement: >-
      Once created, the console replaces the launcher with the plan's own address so the administrator can
      leave and resume it.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0274.spec.ts#CRM-ACC-0274'
    origin: none
scenarios:
  - id: SC-CAP-CRM-DQ-012-01
    given: Four records describe one member and exactly one has a portal login.
    when: An administrator creates a plan without selecting a survivor.
    then: The login-linked record is decisively recommended to avoid relinking the login.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0248.spec.ts#CRM-ACC-0248'
  - id: SC-CAP-CRM-DQ-012-02
    given: An open plan has received no changes for seven days.
    when: The expiry sweep evaluates it.
    then: Its status becomes expired and it no longer appears among unfinished plans.
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-plan-expiry.integration.spec.js#expires
      the plan whose seven days are up, which is what takes it off the reader’s list
edge_cases:
  - id: SC-CAP-CRM-DQ-012-03
    given: An administrator retains contact-merge authority but loses contact-read authority.
    when: They attempt contact and organization planning.
    then: Contact planning fails while authorized organization planning remains available.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0285.spec.ts#CRM-ACC-0285'
constraints: []
depends_on: []
related:
  - CAP-CRM-DQ-010
  - CAP-CRM-DQ-013
  - CAP-CRM-DQ-016
journeys:
  - JNY-CRM-DQ-002
open_questions:
  - OQ-CRM-DQ-001
  - OQ-CRM-DQ-004
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - docs/merging/README.md
      - docs/merging/architecture.md
      - docs/merging/conflict-resolution.md
      - docs/merging/experience/functional-requirements.md
      - modules/merges/aws/api/merges.routes.ts
      - modules/merges/db/record-merge-plans.table.ts
      - modules/merges/services/planning/survivor-recommendation.ts
      - modules/merges/tests/e2e/merges.e2e.spec.js
      - modules/merges/tests/integration/services/merges/merge-plan-expiry.integration.spec.js
      - modules/merges/tests/integration/services/merges/merge-plan-lists.integration.spec.js
      - modules/merges/tests/integration/services/merges/participant-summary.integration.spec.js
      - modules/merges/tests/integration/services/merges/survivor-recommendation.integration.spec.js
      - modules/merges/tests/integration/services/persistence/record-merge-plans.integration.spec.js
      - modules/merges/tests/unit/services/planning/merge-decisions.spec.js
      - modules/merges/tests/unit/services/planning/survivor-recommendation.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-0248.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0274.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0285.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-005, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Prepare a merge and choose its survivor

## Purpose

Administrators can assemble the records to combine and choose the identity that remains.

## How It Works

An administrator selects two to ten contacts or two to ten organizations. The service verifies distinct participants and rejects an unavailable or absorbed record. Planning requires both read and merge authority for the selected type. Rhythm recommends a survivor using login, integration, reference and age signals in order. The administrator may keep that recommendation or choose another participant. Each record has a summary of its identity, dates and related information. Changing participants or survivor recomputes the plan and retains decisions that still apply. The plan has a stable console address so work can resume later. Administrators can list unfinished plans through the API, normally their own. Abandoned open plans expire after seven days, but the stored working copies are not deleted. Another administrator with the necessary permissions may edit the same plan; the product decision about that shared access remains open.

## Relationships

Decision review fills out the plan. Contact and organization combination determine its result. Execution consumes the approved preview rather than a fresh selection of records.
