---
id: CAP-CRM-DQ-007
title: Review duplicate groups in a shared queue
feature: FEAT-CRM-DQ-02
product_area: crm
module: data-quality
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-CRM-MRG-007
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-crm:docs/merging/experience/functional-requirements.md#DQ-2 to DQ-7 (package approved by Andrew
    2026-10-13)
  - 'services-crm:docs/merging/experience/functional-requirements.md#REV-8'
purpose: Reviewers can examine connected suggestions together and retain their place while working.
outcomes:
  - Connected records appear together for review instead of as unrelated pairs.
  - Group identity remains useful through growth and recomputation.
  - 'The product should permit safe merge selection from the review group, explaining any size bound.'
concepts:
  - CON-CRM-DQ-001
actors:
  - ACT-CRM-008
  - ACT-CRM-009
surfaces:
  - console
  - api
permissions:
  - key: crm.duplicates.read
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-DQ-007-01
    proved_by: [ATS-CRM-DQ-007-01, ATS-CRM-DQ-007-08, ATS-CRM-DQ-007-09, ATS-CRM-DQ-007-15, TST-CRM-DQ-176]
    statement: >-
      Groups contain records connected by open pairs or by dismissed pairs of the same status, with at most
      ten records and a bounded indicator when further connections exist.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/groups/group-components.spec.js#says the group it
      cut short is bounded, so a reader knows there may be more of it
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#REV-8'
  - id: BR-CAP-CRM-DQ-007-02
    proved_by: [ATS-CRM-DQ-007-02, ATS-CRM-DQ-007-08, ATS-CRM-DQ-007-10, ATS-CRM-DQ-007-11, ATS-CRM-DQ-007-12, ATS-CRM-DQ-007-15, TST-CRM-DQ-176]
    statement: >-
      A growing group retains its identity; a split assigns the old identity to the largest contribution, a
      join keeps the identity contributing most records and fewer than two remaining records removes the
      group.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/groups/group-components.spec.js#keeps the id for the
      part holding MORE of the group’s records, the other part being a new group
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#DQ-7'
  - id: BR-CAP-CRM-DQ-007-03
    proved_by: [ATS-CRM-DQ-007-03, ATS-CRM-DQ-007-09, ATS-CRM-DQ-007-15, TST-CRM-DQ-180]
    statement: >-
      Pair changes and affected group recomputation share a transaction, keeping group membership and counts
      consistent for readers.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/services/detection/duplicate-detection.integration.spec.js#grouped
      every pair it stored, the groups of a batch being written in the pass’s own transaction (§14)
    origin: none
  - id: BR-CAP-CRM-DQ-007-04
    proved_by: [ATS-CRM-DQ-007-04, ATS-CRM-DQ-007-15, TST-CRM-DQ-180]
    statement: Confirmed pairs have no group because their duplicate decision is already settled.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-groups.integration.spec.js#belongs
      to no group: a confirmed pair is a decision, not a question the queue asks
    origin: none
  - id: BR-CAP-CRM-DQ-007-05
    proved_by: [ATS-CRM-DQ-007-05, ATS-CRM-DQ-007-08, ATS-CRM-DQ-007-15, TST-CRM-DQ-176, TST-CRM-DQ-182]
    statement: >-
      Queue reads default to open groups and support dismissed status, confidence, up to ten recognized
      evidence codes, origin, detection dates and record filters, with strongest-score, newest-detection or
      size sorting; unknown evidence codes are rejected.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicate-groups.e2e.spec.js#refuses an evidence code no pair
      can carry, naming it rather than answering an empty page
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#DQ-4'
  - id: BR-CAP-CRM-DQ-007-06
    proved_by: [ATS-CRM-DQ-007-05, ATS-CRM-DQ-007-08, ATS-CRM-DQ-007-15, TST-CRM-DQ-176, TST-CRM-DQ-182]
    statement: 'Group pages default to 20 and cannot exceed 50, and a cursor from a different sort order is rejected.'
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicate-groups.e2e.spec.js#refuses a cursor answered for
      another order, rather than paging in the wrong one
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#DQ-5'
  - id: BR-CAP-CRM-DQ-007-07
    proved_by: [ATS-CRM-DQ-007-06, ATS-CRM-DQ-007-08, ATS-CRM-DQ-007-15, TST-CRM-DQ-176, TST-CRM-DQ-182]
    statement: >-
      Search matches a record-name prefix, contact names in surname-first form or an exact record number; a
      name's internal text does not match.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicate-groups.e2e.spec.js#answers only the groups the
      records of a typed prefix are in, which is the search box
    origin: none
  - id: BR-CAP-CRM-DQ-007-08
    proved_by: []
    statement: >-
      Queue groups include record numbers, names, masked summaries, creation dates, record and pair counts,
      highest score and confidence and three leading reasons; details add all group pairs.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/services/groups/duplicate-group-view.integration.spec.js#states
      no more reasons than a group may state, however much evidence its pairs carry
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#DQ-3'
  - id: BR-CAP-CRM-DQ-007-09
    proved_by: [ATS-CRM-DQ-007-07, ATS-CRM-DQ-007-15, TST-CRM-DQ-182]
    statement: >-
      Duplicate-read authority is required to list or open groups, and a group absent from the workspace
      returns not found.
    kind: permission
    enforced_at:
      - api
      - console
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicate-groups.e2e.spec.js#refuses a caller without the
      duplicates read permission
    origin: 'services-crm:docs/merging/architecture.md#9. Security and tenant isolation'
  - id: BR-CAP-CRM-DQ-007-10
    proved_by: []
    statement: >-
      The page address preserves record type, filters, order, page and selected group so reloads and shared
      links reproduce the review location.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0233.spec.ts#CRM-ACC-0233'
    origin: 'services-crm:docs/merging/experience/functional-requirements.md#DQ-2'
scenarios:
  - id: SC-CAP-CRM-DQ-007-01
    given: A reviewer has a three-record group open.
    when: Dismissing a pair leaves a two-record component and an isolated record.
    then: The remaining group keeps the shared identity and the isolated record leaves the queue.
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/groups/group-components.spec.js#is no group at all:
      a group holds at least two records
  - id: SC-CAP-CRM-DQ-007-02
    given: Two reviewers open one group.
    when: One reviewer settles it before the other acts.
    then: The later response supplies the group identity the second reviewer follows.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0273.spec.ts#CRM-ACC-0273'
edge_cases:
  - id: SC-CAP-CRM-DQ-007-03
    given: Eleven records are connected by suggestions.
    when: Rhythm builds their groups.
    then: 'The displayed group stops at ten, reports its bound and disables Merge with an explanation.'
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0275.spec.ts#CRM-ACC-0275'
constraints: []
depends_on:
  - CAP-CRM-DQ-002
related:
  - CAP-CRM-DQ-008
  - CAP-CRM-DQ-012
journeys:
  - JNY-CRM-DQ-001
open_questions: []
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - docs/merging/architecture.md
      - docs/merging/experience/functional-requirements.md
      - modules/duplicates/db/duplicate-groups.table.ts
      - modules/duplicates/services/groups/group-components.ts
      - modules/duplicates/tests/e2e/duplicate-groups.e2e.spec.js
      - modules/duplicates/tests/integration/services/detection/duplicate-detection.integration.spec.js
      - modules/duplicates/tests/integration/services/groups/duplicate-group-view.integration.spec.js
      - modules/duplicates/tests/integration/services/persistence/duplicate-groups.integration.spec.js
      - modules/duplicates/tests/unit/services/groups/group-components.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-0233.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0273.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0275.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-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-CRM-DQ-02-01, FAS-CRM-DQ-02-02, FAS-CRM-DQ-02-04]
suites: [TST-CRM-DQ-176, TST-CRM-DQ-180, TST-CRM-DQ-182]
---

# Review duplicate groups in a shared queue

## Purpose

Reviewers can examine connected suggestions together and retain their place while working.

## How It Works

A reviewer opens the queue for contacts or organizations. Rhythm presents connected records as groups rather than requiring separate review of each pair. Confidence and other filters narrow the work, while sorting controls which groups appear first. Search matches the beginning of a name or an exact record number. Opening a group reveals its records and all constituent pairs. Group identity survives ordinary growth and keeps the largest contribution when groups split or join. Pair changes and group recalculation commit together. A group can be bounded when the full connection exceeds the review size. The address retains the filters, page and selected group for reloading or sharing. Reading requires duplicate-read authority.

## Relationships

Detection creates suggestions and review decisions reshape their groups. Group details provide an entry to merge planning when the reviewer also has merge authority.
