---
id: CAP-CRM-DQ-013
title: Resolve differences in the merge preview
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-015
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-crm:docs/milestones/CRM-M4-duplicates-and-merge.md#D-33'
  - 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - >-
    services-crm:docs/merging/experience/functional-requirements.md#CNF-1 to CNF-10, INT-1, PRV-3 (package
    approved by Andrew 2026-10-13)
purpose: Administrators can decide the survivor's values and recognize changes that require renewed review.
outcomes:
  - Questions contain actual participant values rather than invented replacements.
  - Version checks and source tracking prevent silent decision overwrite or reuse after value changes.
  - The product should make every preview explanation readable without internal identifiers.
concepts:
  - CON-CRM-DQ-003
actors:
  - ACT-CRM-009
surfaces:
  - console
  - api
permissions:
  - key: crm.contacts.merge
    where_checked: api
    scope: tenant
  - key: crm.organizations.merge
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-DQ-013-01
    proved_by: [ATS-CRM-DQ-013-01, ATS-CRM-DQ-013-22]
    statement: >-
      Questions appear only for distinct values the survivor cannot retain together after ordinary save
      normalization; identical values cause no question and a sole supplied value fills an empty field.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/unit/services/planning/contact-fields.spec.js#is no conflict when only
      one participant holds a value, which the survivor then takes
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-013-02
    proved_by: [ATS-CRM-DQ-013-01, ATS-CRM-DQ-013-22]
    statement: >-
      Merge choices come from participating records or an optional-field choice to retain no value; staff
      cannot type a new value into a merge.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/unit/services/planning/contact-fields.spec.js#offers clearing the
      field as well as each value held
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#CNF-7'
  - id: BR-CAP-CRM-DQ-013-03
    proved_by: [ATS-CRM-DQ-013-02, ATS-CRM-DQ-013-22, TST-CRM-DQ-181]
    statement: >-
      Defaults prefer the survivor's value, then a sole value, majority value, most recently changed field and
      most recently changed record, with the deciding criterion explained.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/default-basis-and-recency.integration.spec.js#is
      the majority, with the records that agree counted, where more records hold one value than any other
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#CNF-3'
  - id: BR-CAP-CRM-DQ-013-04
    proved_by: [ATS-CRM-DQ-013-03, ATS-CRM-DQ-013-11, ATS-CRM-DQ-013-22, TST-CRM-DQ-183]
    statement: >-
      Required unanswered questions are open, accepted recommendations are defaulted and explicit answers are
      resolved; execution requires no open question or blocker.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#refuses the execution request as a plan that is
      not ready
    origin: 'services-crm:docs/merging/README.md#Status (architecture and plan approved by Andrew 2026-10-11)'
  - id: BR-CAP-CRM-DQ-013-05
    proved_by: [ATS-CRM-DQ-013-04, ATS-CRM-DQ-013-10, ATS-CRM-DQ-013-22, TST-CRM-DQ-176, TST-CRM-DQ-183]
    statement: >-
      Answers are merged by question and remain until explicitly cleared, while a request that answers and
      clears the same question is rejected.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#keeps the first answer when the second request
      answers only the other conflict
    origin: none
  - id: BR-CAP-CRM-DQ-013-06
    proved_by: [ATS-CRM-DQ-013-05, ATS-CRM-DQ-013-22, TST-CRM-DQ-181, TST-CRM-DQ-183]
    statement: >-
      Unknown questions and unavailable choices are rejected without saving; previously stored answers
      invalidated by changed records are instead dropped and reported.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#refuses it as an invalid resolution rather than
      storing a decision nobody can apply
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#CNF-10'
  - id: BR-CAP-CRM-DQ-013-07
    proved_by: [ATS-CRM-DQ-013-06, ATS-CRM-DQ-013-10, ATS-CRM-DQ-013-22, TST-CRM-DQ-176, TST-CRM-DQ-181, TST-CRM-DQ-183]
    statement: >-
      Changes require the current plan version, and an outdated version is refused without overwriting another
      administrator's work.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-plan-decisions.integration.spec.js#leaves
      both decisions on the plan, at the version it was refused against
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#INT-1'
  - id: BR-CAP-CRM-DQ-013-08
    proved_by: [ATS-CRM-DQ-013-07, ATS-CRM-DQ-013-22, TST-CRM-DQ-181]
    statement: >-
      A value choice retains its source record; if that record changes the value, the plan shows and retains
      the new source value but requires confirmation or a replacement answer before execution.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/approval-binding.integration.spec.js#shows
      the record’s new value as what the merge would take, and proposes it as the choice to confirm
    origin: 'services-crm:docs/milestones/CRM-M4-duplicates-and-merge.md#D-33'
  - id: BR-CAP-CRM-DQ-013-09
    proved_by: []
    statement: >-
      The service records answer author, time and source record, retaining the original attribution when the
      same answer is resubmitted unchanged.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/merge-outcome.integration.spec.js#keeps
      the reviewer’s choice with what it was, who made it, and the record it was taken from
    origin: none
  - id: BR-CAP-CRM-DQ-013-10
    proved_by: [ATS-CRM-DQ-013-08, ATS-CRM-DQ-013-22, TST-CRM-DQ-181]
    statement: >-
      The product should describe plan values with workspace labels, archive indicators, record names and
      numbers and readable counts rather than internal references or storage terms.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: documented
    source: 'services-crm:docs/merging/experience/functional-requirements.md'
    origin: >-
      services-crm:docs/merging/api.md#4. Merge plans (lookup values in the workspace’s words, Andrew
      2026-10-14)
  - id: BR-CAP-CRM-DQ-013-11
    proved_by: [ATS-CRM-DQ-013-21, ATS-CRM-DQ-013-22]
    statement: >-
      Competing values and collection items show their last field change from history when available,
      otherwise the record creation time.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#says when each competing value last changed, so
      a reader can see which is freshest (gap G9)
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#CNF-4'
  - id: BR-CAP-CRM-DQ-013-12
    proved_by: [ATS-CRM-DQ-013-09, ATS-CRM-DQ-013-12, ATS-CRM-DQ-013-13, ATS-CRM-DQ-013-14, ATS-CRM-DQ-013-15, ATS-CRM-DQ-013-16, ATS-CRM-DQ-013-22, TST-CRM-DQ-183]
    statement: >-
      Explicit validation returns a preview recomputed from current records, and reading an open plan likewise
      refreshes it when records have changed.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#answers the validation with the plan as it is
      now, ready to execute
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#PRV-3'
  - id: BR-CAP-CRM-DQ-013-13
    proved_by: []
    statement: >-
      Plan fields include visibility markers so future field restrictions can omit hidden choices and preserve
      survivor defaults.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/merging/architecture.md#9. Security and tenant isolation'
    origin: none
scenarios:
  - id: SC-CAP-CRM-DQ-013-01
    given: A plan contains two unresolved questions.
    when: Successive updates answer a different question each time.
    then: Both choices remain in the planned survivor.
    provenance: test
    source: >-
      services-crm:modules/merges/tests/e2e/merges.e2e.spec.js#plans the survivor with both chosen values,
      neither request having undone the other
  - id: SC-CAP-CRM-DQ-013-02
    given: An administrator chooses a participant's job title and approves the preview.
    when: That participant receives another job title before execution.
    then: Execution rejects the stale preview and shows the old and new source values for renewed confirmation.
    provenance: test
    source: >-
      services-crm:modules/merges/tests/integration/services/merges/approval-binding.integration.spec.js#refuses
      the approved execution as plan_stale, naming the old value and the new one
edge_cases:
  - id: SC-CAP-CRM-DQ-013-03
    given: Two administrators hold a plan affected by a participant edit.
    when: One read refreshes the stored preview before the other saves a decision.
    then: The older-version decision is rejected and its author reloads the plan.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0250.spec.ts#CRM-ACC-0250'
constraints: []
depends_on:
  - CAP-CRM-DQ-012
related:
  - CAP-CRM-DQ-014
  - CAP-CRM-DQ-016
journeys:
  - JNY-CRM-DQ-002
  - JNY-CRM-DQ-003
open_questions: []
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - docs/merging/README.md
      - docs/merging/api.md
      - docs/merging/architecture.md
      - docs/merging/experience/functional-requirements.md
      - docs/milestones/CRM-M4-duplicates-and-merge.md
      - modules/merges/services/planning/merge-engine.ts
      - modules/merges/services/planning/plan-changes.ts
      - modules/merges/tests/e2e/merges.e2e.spec.js
      - modules/merges/tests/integration/services/merges/approval-binding.integration.spec.js
      - modules/merges/tests/integration/services/merges/default-basis-and-recency.integration.spec.js
      - modules/merges/tests/integration/services/merges/merge-outcome.integration.spec.js
      - modules/merges/tests/integration/services/merges/merge-plan-decisions.integration.spec.js
      - modules/merges/tests/unit/services/planning/contact-fields.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-0250.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]
exercised_by: [FAS-CRM-DQ-03-01, FAS-CRM-DQ-03-02, FAS-CRM-DQ-03-03, FAS-CRM-DQ-03-05]
suites: [TST-CRM-DQ-176, TST-CRM-DQ-181, TST-CRM-DQ-183]
---

# Resolve differences in the merge preview

## Purpose

Administrators can decide the survivor's values and recognize changes that require renewed review.

## How It Works

Rhythm compares the participating values using normal save conventions. A gap can be filled automatically; incompatible values become questions. The plan supplies a recommended answer and explains its basis. Administrators choose among existing values and may clear an optional field. Each answer affects its own question without removing other decisions. Updates require the plan version the administrator read. An invalid choice or an outdated version is refused without a partial save. A chosen value follows its source record, and a later source edit requires confirmation of the new value. Reading an open plan recomputes it against current records and can update the stored version. Staff can explicitly refresh before confirmation. Field-visibility flags prepare for future restrictions but do not establish that field-level permissions already exist.

## Relationships

Participant selection supplies the compared records. Contact and organization combination supply automatic results. Execution checks that the material preview still matches the approval.
