---
id: DOM-CRM-DQ-004
title: Merge
realises: CON-CRM-DQ-004
product_area: crm
module: data-quality
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-CRM-DQ-merges
lifecycle: STATE-CRM-DQ-005
custom_fields: none
readable_by:
  - SVC-CRM-ENT-entities
  - SVC-CRM-DQ-duplicates
identity: >-
  A request creates the merge identity used for job submission, change
  correlation and participant history pointers. Aliases are unique by workspace,
  record type and absorbed identity.
attributes:
  - name: plan
    kind: reference
    required: true
    computed: false
    notes: >-
      The originating plan identity deduplicates active or successful execution
      requests without a foreign key.
  - name: record type
    kind: enum
    required: true
    computed: false
    notes: The attempt concerns contacts or organizations.
  - name: survivor
    kind: reference
    required: true
    computed: false
    notes: The survivor is the participant retained by the combination.
  - name: absorbed records
    kind: collection
    required: true
    computed: false
    notes: >-
      The attempt names one to nine absorbed participants, excluding the
      survivor.
  - name: participants as captured
    kind: collection
    required: true
    computed: false
    notes: >-
      Captured names, numbers and versions let history describe participants
      after their removal.
  - name: status
    kind: enum
    required: true
    computed: false
    notes: 'Attempts progress through queued, running, succeeded or failed.'
  - name: failure
    kind: collection
    required: false
    computed: false
    notes: >-
      A failed attempt carries its classification and explanation; other
      statuses carry no failure code.
  - name: requested by and timing
    kind: collection
    required: true
    computed: false
    notes: >-
      Attribution identifies the requester; timing and claim count describe
      execution.
  - name: original values
    kind: collection
    required: false
    computed: false
    notes: >-
      Success preserves the original participant values and collections after
      absorbed records are removed.
  - name: outcome
    kind: collection
    required: false
    computed: false
    notes: >-
      Success stores the saved survivor, applied choices, reference and login
      actions, validation and write counts.
  - name: job
    kind: collection
    required: true
    computed: true
    notes: >-
      The submitted durable job observes four steps: preview check, locks,
      application changes and commit. Their outcome is reported after the
      business transaction.
  - name: absorbed identity
    kind: collection
    required: false
    computed: false
    notes: >-
      Each absorbed record retains destination, merge, time, old number, origin
      and external identity.
  - name: merge pointer and announcement
    kind: computed
    required: false
    computed: true
    notes: >-
      Participant history receives audit pointers; committed contact merges also
      write a business announcement.
relationships:
  - to: DOM-CRM-DQ-003
    cardinality: >-
      Many attempts may name one plan, with at most one active or successful
      attempt.
    ownership: >-
      Attempts retain only the plan identity and remain meaningful after plan
      removal.
    cross_service: false
  - to: DOM-CRM-ENT-001
    cardinality: A contact merge retains one contact and absorbs one to nine others.
    ownership: >-
      Entities owns ordinary contact saves and deletes, which merging calls
      after merge-specific reference work.
    cross_service: false
  - to: DOM-CRM-ENT-002
    cardinality: >-
      An organization merge retains one organization and absorbs one to nine
      others.
    ownership: >-
      Entities owns ordinary organization saves and deletes, alongside
      merge-specific reference and parent preparation.
    cross_service: false
  - to: DOM-CRM-DQ-001
    cardinality: A merge may carry multiple outside duplicate dismissals.
    ownership: >-
      The duplicates module owns the persisted review decisions updated by
      merging.
    cross_service: false
invariants:
  - id: INV-DOM-CRM-DQ-004-01
    statement: >-
      Each plan permits at most one queued, running or successful attempt while
      retaining earlier failures.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-settlement.integration.spec.js#answers
      both requests the same merge
  - id: INV-DOM-CRM-DQ-004-02
    statement: A merge names a survivor and at least one different absorbed record.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-crm:modules/merges/db/record-merges.table.ts:record_merges_merged_ids_check
  - id: INV-DOM-CRM-DQ-004-03
    statement: >-
      Terminal status requires a finish instant, and failure status alone
      requires a failure code.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-crm:modules/merges/db/record-merges.table.ts:record_merges_failure_check
  - id: INV-DOM-CRM-DQ-004-04
    statement: >-
      All planned business changes commit in one transaction or roll back
      together.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/approval-binding.integration.spec.js#fails
      the merge Stale inside its transaction rather than moving a task nobody
      approved
  - id: INV-DOM-CRM-DQ-004-05
    statement: Overlapping merges cannot both absorb the same participant successfully.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/overlapping-merges.integration.spec.js#lets
      exactly one of the two merges happen
  - id: INV-DOM-CRM-DQ-004-06
    statement: >-
      Alias uniqueness and transactional insertion give each absorbed identity a
      single destination record.
    enforced_at:
      - service
      - database
    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
  - id: INV-DOM-CRM-DQ-004-07
    statement: Further merging repoints existing aliases directly to the new survivor.
    enforced_at:
      - service
    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
  - id: INV-DOM-CRM-DQ-004-08
    statement: >-
      Database insertion guards refuse reuse of an absorbed identity for that
      workspace and record type.
    enforced_at:
      - database
    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
  - id: INV-DOM-CRM-DQ-004-09
    statement: >-
      Application grants prevent alias deletion and changes other than survivor
      redirection.
    enforced_at:
      - database
    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
  - id: INV-DOM-CRM-DQ-004-10
    statement: >-
      Application grants prevent deleting merge history retained for the
      workspace lifetime.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/db/record-merges.table.integration.spec.js#may
      not erase a merge, which holds the only copy left of the records it
      deleted
  - id: INV-DOM-CRM-DQ-004-11
    statement: >-
      Success writes merge audit pointers for each participant within the
      business transaction.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merged-from-history.integration.spec.js#says
      who merged which record into which, and when: the merge pointer
  - id: INV-DOM-CRM-DQ-004-12
    statement: >-
      A contact-merged event is persisted with the successful transaction and
      cannot survive its rollback.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/business-events.e2e.spec.js#hands
      the subscriber the envelope of crm.contact.merged, about the survivor, in
      the merge’s tenant
  - id: INV-DOM-CRM-DQ-004-13
    statement: >-
      Job uniqueness permits at most one job per merge, and workspace jobs
      require customer scope; accepted execution creates the job with the
      attempt.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/merges/db/record-merge-jobs.table.ts:record_merge_jobs_merge_uidx,
      record_merge_jobs_customer_id_check;
      modules/merges/services/merges/merge-plans.ts:queueTheMergeOfThisPlan
  - id: INV-DOM-CRM-DQ-004-14
    statement: >-
      The workflow retries eligible transient task errors, while a settled
      business failure becomes a refusal.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-crm:modules/merges/services/jobs/record-merge-job.ts:recordMergeWorkflow
physical:
  - record_merges
  - record_merge_jobs
  - record_merge_job_steps
  - record_merge_job_events
  - merged_record_aliases
evidence:
  - >-
    services-crm at a8d1364f: modules/merges/db/record-merges.table.ts,
    record-merge-jobs.table.ts, merged-record-aliases.table.ts;
    services/merges/merge-execution.ts, merge-settlement.ts, merge-events.ts,
    merge-records.ts; services/jobs/*.ts;
    services/aliases/merged-record-resolution.ts;
    aws/api/merged-record-routes.ts; docs/merging/architecture.md §5, §7, §8,
    decisions.md D1 to D4, D8.
---

# Merge

## What It Models

A merge combines the requested execution and its permanent history in one model. The durable job, retained originals, aliases, participant history pointers and contact announcement explain both the work and its eventual result.

## Identity and Attributes

Captured participant labels allow history to remain readable after deletion. Successful attempts retain originals and final values; failed attempts retain request context and their reason without a successful outcome. The running claim is inside the business transaction and is not a separately committed progress marker.

## Relationships

One plan can produce successive failed attempts before a successful merge. Ordinary survivor saves and absorbed deletions use Entities persistence, while merge-owned functions move references and prepare related rows. Outside dismissals remain owned by duplicate persistence.

## Invariants

Storage checks constrain status, required failure data, active-attempt uniqueness and alias reuse. Service execution supplies atomicity, overlapping-merge locks and the committed audit and event writes.

## How It Is Stored and Shared

The merges module owns history, job records and aliases in CRM. Export includes history and aliases; privileged workspace purge removes them. Job roots omit workspace row policies for monitoring and reconciliation, while their customer scope remains mandatory for workspace jobs.

## Open Design Questions

A later ordinary deletion can leave alias destinations missing. Individual removal of retained originals and organization-merge announcements still need implementation or product resolution, and report consumers do not yet have full survivor-name resolution.
