---
id: ATS-CRM-DQ-003
scope: CAP-CRM-DQ-003
product_area: crm
module: data-quality
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-DQ-003-01
    title: Rule order respects activity and record type
    level: unit
    proves:
      - BR-CAP-CRM-DQ-003-01
    preconditions: >-
      Mix contact and organization rules, an inactive rule and two qualifying
      positions.
    steps: Evaluate a matching pair.
    expected: The first active qualifying rule for its type determines the action.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#is
        decided by the first matching rule in position order, not by a later one
        that also matches
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#leaves
        out a rule that is turned off, which is what the screen’s toggle means
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#leaves
        out a rule of the other record type, so an organization rule never
        decides a contact pair
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#is
      decided by the first matching rule in position order, not by a later one
      that also matches
    category: rule
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#is
        decided by the first matching rule in position order, not by a later one
        that also matches
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#leaves
        out a rule that is turned off, which is what the screen’s toggle means
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#leaves
        out a rule of the other record type, so an organization rule never
        decides a contact pair
    binding_note: >-
      The exact unit-test references remain bound as engineering evidence; unit
      files are not executable suite artifacts.
  - id: ATS-CRM-DQ-003-02
    title: Blocking retains the review candidate
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-02
    preconditions: Configure a blocking rule that matches a pair.
    steps: Detect and refresh that pair.
    expected: The open pair remains stored under the same identity.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - TST-CRM-DQ-180
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#stores
      the candidate for a block rule too, the block acting at the create and
      never on the queue (D-32)
    category: invariant
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#stores
        the candidate for a block rule too, the block acting at the create and
        never on the queue (D-32)
      - >-
        services-crm:modules/duplicates/tests/integration/services/detection/duplicate-detection.integration.spec.js#stored
        the pair as an open candidate, so reviewers see it in the queue
      - >-
        services-crm:modules/duplicates/tests/integration/services/detection/duplicate-detection.integration.spec.js#removed
        nothing on the re-score, the block rule never hiding a pair it decided
  - id: ATS-CRM-DQ-003-03
    title: Unmatched rules leave a suggestion reviewable
    level: unit
    proves:
      - BR-CAP-CRM-DQ-003-03
    preconditions: 'First provide no applicable rules, then provide nonmatching rules.'
    steps: Evaluate the pair in both cases.
    expected: Both evaluations retain the candidate for review.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#keeps
        a pair no rule matches for review, which is detection’s default
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#keeps
        a pair for review when the tenant has no rule of this record type at all
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#keeps
      a pair no rule matches for review, which is detection’s default
    category: edge
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#keeps
        a pair no rule matches for review, which is detection’s default
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#keeps
        a pair for review when the tenant has no rule of this record type at all
    binding_note: >-
      The exact unit-test references remain bound as engineering evidence; unit
      files are not executable suite artifacts.
  - id: ATS-CRM-DQ-003-04
    title: Evidence patterns require their named agreements
    level: unit
    proves:
      - BR-CAP-CRM-DQ-003-04
    preconditions: >-
      Prepare pairs with and without e-mail, name, place, phone and domain
      agreement.
    steps: Evaluate all configured match kinds.
    expected: >-
      Each requires its stated evidence; name disagreement cannot satisfy
      name-and-postal matching and domain alone cannot satisfy name-and-domain
      matching.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#does
        not match on name and postal code when a name DISAGREES, however the
        places compare
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#matches
        on name and phone when a name agrees and a number is shared
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#does
        not match on organization name and domain when only the domain agrees
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#does
      not match on name and postal code when a name DISAGREES, however the
      places compare
    category: rule
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#does
        not match on name and postal code when a name DISAGREES, however the
        places compare
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#matches
        on name and phone when a name agrees and a number is shared
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#does
        not match on organization name and domain when only the domain agrees
    binding_note: >-
      The exact unit-test references remain bound as engineering evidence; unit
      files are not executable suite artifacts.
  - id: ATS-CRM-DQ-003-05
    title: Confidence floors use medium as the fallback
    level: unit
    proves:
      - BR-CAP-CRM-DQ-003-05
    preconditions: 'Supply explicit, absent and unrecognized minimum bands.'
    steps: Evaluate pairs at and below their floors.
    expected: The exact floor qualifies; absent and unknown values use medium.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#matches
        a pair scoring exactly the rule’s band, which is the floor and not a
        step above it
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#holds
        a rule naming no band to the default band, which is what the column
        defaults to
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#holds
        a rule naming a band this release does not know to the default band,
        rather than matching nothing
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#matches
      a pair scoring exactly the rule’s band, which is the floor and not a step
      above it
    category: boundary
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#matches
        a pair scoring exactly the rule’s band, which is the floor and not a
        step above it
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#holds
        a rule naming no band to the default band, which is what the column
        defaults to
      - >-
        services-crm:modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js#holds
        a rule naming a band this release does not know to the default band,
        rather than matching nothing
    binding_note: >-
      The exact unit-test references remain bound as engineering evidence; unit
      files are not executable suite artifacts.
  - id: ATS-CRM-DQ-003-06
    title: Invalid replacements preserve the existing rule
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-06
    preconditions: >-
      Read a rule and prepare stale, versionless, mismatched, invalid-action and
      locked replacements.
    steps: Submit each replacement.
    expected: Every invalid edit is refused and the stored rule remains unchanged.
    negative: true
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - TST-CRM-DQ-182
      - TST-CRM-DQ-180
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#refuses
      the version that has moved on (409)
    category: concurrency
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#refuses
        the version that has moved on (409)
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#refuses
        an update that sends no version at all (400)
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#refuses
        a body whose id is not the rule the path names (400)
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#refuses
        a rule whose action is not one of the three (400)
      - >-
        services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
        to change a rule locked against updates
  - id: ATS-CRM-DQ-003-07
    title: Names and ordering positions are validated
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-07
    preconditions: Prepare two configuration rules.
    steps: 'Try a duplicate position, out-of-range positions and a blank name.'
    expected: Each invalid input is rejected.
    negative: true
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - TST-CRM-DQ-180
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
      the place another rule already holds
    category: validation
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
        the place another rule already holds
      - >-
        services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
        a place outside the order a tenant may have
      - >-
        services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
        a blank name
  - id: ATS-CRM-DQ-003-08
    title: Configuration authority is separate from review
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-09
    preconditions: 'Prepare a duplicate reader, reviewer and configuration administrator.'
    steps: Request rule reads and updates as each caller.
    expected: Only the caller with configuration authority succeeds.
    negative: true
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - TST-CRM-DQ-182
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#answers
      403 to a reviewer reading the rules, which are the tenant’s configuration
    category: permission
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#answers
        403 to a reviewer reading the rules, which are the tenant’s
        configuration
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#answers
        403 to a reader changing a rule
      - >-
        services-crm:modules/duplicates/tests/e2e/duplicates.e2e.spec.js#lets a
        caller who may configure duplicates read the rules
  - id: ATS-CRM-DQ-003-09
    title: Moving rule order changes creation behavior
    level: browser
    proves:
      - BR-CAP-CRM-DQ-003-01
      - BR-CAP-CRM-DQ-003-02
    preconditions: Start with the seeded settings.
    steps: >-
      Place a warning before the blocking rule, create the matching contact,
      restore the order and repeat.
    expected: The first create succeeds with a review pair and the second is blocked.
    negative: false
    data_needs: The existing scenario setup supplies the required records.
    bound_to:
      - TST-CRM-DQ-176
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0244.spec.ts#CRM-ACC-0244
    category: interaction
    execution_sources:
      - >-
        react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0244.spec.ts#CRM-ACC-0244
      - >-
        react-console:acceptance/crm/playwright/journeys/merging/CRM-ACC-0240.spec.ts#CRM-ACC-0240
  - id: ATS-CRM-DQ-003-10
    title: Two rules cannot hold the same position
    category: invariant
    level: api
    proves:
      - INV-DOM-CRM-DQ-001-11
    preconditions: Two rules in one workspace at different positions.
    steps: Try moving the second rule to the first rule’s position.
    expected: The update is refused and the original ordering remains.
    negative: false
    data_needs: >-
      Records, versions and caller contexts owned by the run; transaction
      failure injection when the invariant concerns rollback.
    bound_to:
      - TST-CRM-DQ-180
    source: >-
      services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
      the place another rule already holds
    design_sources:
      - DOM-CRM-DQ-001
    execution_sources:
      - >-
        services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#refuses
        the place another rule already holds
    provenance: test
  - id: ATS-CRM-DQ-003-11
    title: Provisioning preserves an edited workspace rule
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-08
    preconditions: 'A workspace with no default rules, followed by an edited default.'
    steps: >-
      Provision defaults, edit one, provision again and inspect all rule
      identities and versions.
    expected: >-
      The default set exists and repeat provisioning retains the edit and its
      version.
    negative: true
    data_needs: Run-owned records and caller permissions that distinguish the two cases.
    source: >-
      services-crm:modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js#leaves
      the tenant’s own name, switch and action exactly as the tenant left them
    design_sources:
      - CAP-CRM-DQ-003
    bound_to: []
    provenance: proposed
    execution_sources: []
  - id: ATS-CRM-DQ-003-12
    title: Configuration reaches draft checks and later re-checks
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-DQ-003-01
      - BR-CAP-CRM-DQ-003-02
      - BR-CAP-CRM-DQ-003-03
      - BR-CAP-CRM-DQ-003-04
      - BR-CAP-CRM-DQ-003-05
      - BR-CAP-CRM-DQ-003-06
      - BR-CAP-CRM-DQ-003-07
      - BR-CAP-CRM-DQ-003-09
      - BR-CAP-CRM-DQ-003-08
    exercises:
      - CAP-CRM-DQ-003
      - CAP-CRM-DQ-004
      - CAP-CRM-DQ-005
    workflow_sources:
      - WF-CRM-DQ-001
      - WF-CRM-DQ-002
      - WF-CRM-DQ-003
      - WF-CRM-DQ-009
    preconditions: >-
      Run-owned contact and organization records, the relevant staff permissions
      and the related state described by the sequence.
    steps: >-
      Move a qualifying warning ahead of a blocking rule, check a draft, then
      restore the order and request a re-check of saved records.
    expected: >-
      Creation advice follows each configured order while the re-check refreshes
      existing review work.
    negative: false
    data_needs: >-
      An isolated workspace with related records and fixed timestamps; owned
      participant failure injection where requested.
    source: >-
      CAP-CRM-DQ-003; CAP-CRM-DQ-004; CAP-CRM-DQ-005; WF-CRM-DQ-001;
      WF-CRM-DQ-002; WF-CRM-DQ-003; WF-CRM-DQ-009
    note: >-
      This cumulative seam scenario remains proposed until one executable
      sequence establishes all of its outcomes.
    bound_to: []
    provenance: proposed
    workflow_coverage_note: >-
      The workflows identify the handoffs under review. Separate assertions
      leave recovery and failure ordering unproved; the capability coverage
      finding records that limit.
    execution_sources: []
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - modules/duplicates/tests/e2e/duplicates.e2e.spec.js
      - >-
        modules/duplicates/tests/integration/services/detection/duplicate-detection.integration.spec.js
      - >-
        modules/duplicates/tests/integration/services/persistence/duplicate-rules.integration.spec.js
      - >-
        modules/duplicates/tests/unit/services/detection/duplicate-rule-actions.spec.js
    note: >-
      Bindings identify authored proof; empty bindings remain proposed and
      findings record known limitations.
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0240.spec.ts
      - acceptance/crm/playwright/journeys/merging/CRM-ACC-0244.spec.ts
    note: >-
      Bindings identify authored proof; empty bindings remain proposed and
      findings record known limitations.
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-CRM-DQ-054'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  failure: >-
    No complete scenario currently isolates this category; the finding lists the
    remaining proof obligation.
permission_coverage:
  - key: crm.duplicates.configure
    scenario_ids:
      - ATS-CRM-DQ-003-08
    finding: FND-CRM-DQ-054
proof_matrix:
  - rule: BR-CAP-CRM-DQ-003-01
    scenarios:
      - ATS-CRM-DQ-003-01
      - ATS-CRM-DQ-003-09
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-02
    scenarios:
      - ATS-CRM-DQ-003-02
      - ATS-CRM-DQ-003-09
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-03
    scenarios:
      - ATS-CRM-DQ-003-03
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-04
    scenarios:
      - ATS-CRM-DQ-003-04
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-05
    scenarios:
      - ATS-CRM-DQ-003-05
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-06
    scenarios:
      - ATS-CRM-DQ-003-06
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-07
    scenarios:
      - ATS-CRM-DQ-003-07
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-08
    scenarios:
      - ATS-CRM-DQ-003-11
      - ATS-CRM-DQ-003-12
  - rule: BR-CAP-CRM-DQ-003-09
    scenarios:
      - ATS-CRM-DQ-003-08
      - ATS-CRM-DQ-003-12
interaction_coverage:
  related:
    - CAP-CRM-DQ-004
    - CAP-CRM-DQ-005
  workflow_sources:
    - WF-CRM-DQ-001
    - WF-CRM-DQ-002
    - WF-CRM-DQ-003
    - WF-CRM-DQ-009
  scenarios:
    - ATS-CRM-DQ-003-09
    - ATS-CRM-DQ-003-12
---

# Set the response to duplicate matches: Capability Test Specification

## Coverage

The rule matrix names the authored checks. A covered category means that a scenario exists; it does not report a passing execution. The coverage notes and findings identify missing checks, while execution sources retain the exact assertions inspected.

| Required Rule | Scenarios |
|---|---|
| `BR-CAP-CRM-DQ-003-01` | `ATS-CRM-DQ-003-01`, `ATS-CRM-DQ-003-09`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-02` | `ATS-CRM-DQ-003-02`, `ATS-CRM-DQ-003-09`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-03` | `ATS-CRM-DQ-003-03`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-04` | `ATS-CRM-DQ-003-04`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-05` | `ATS-CRM-DQ-003-05`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-06` | `ATS-CRM-DQ-003-06`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-07` | `ATS-CRM-DQ-003-07`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-08` | `ATS-CRM-DQ-003-11`, `ATS-CRM-DQ-003-12` |
| `BR-CAP-CRM-DQ-003-09` | `ATS-CRM-DQ-003-08`, `ATS-CRM-DQ-003-12` |

## Interactions With Related Capabilities

Place a warning before the blocking rule, create the matching contact, restore the order and repeat. Move a qualifying warning ahead of a blocking rule, check a draft, then restore the order and request a re-check of saved records.

These sequences test the handoffs named in the interaction coverage record. A proposed sequence remains unbound until an executable run checks its combined outcome; component assertions alone leave that obligation open.
