---
id: CAP-CRM-DQ-006
title: Reuse a contact during portal registration
feature: FEAT-CRM-DQ-01
product_area: crm
module: data-quality
revision: 1
status: draft
product_intent: proposed
implementation_evidence: partially-observed
supersedes:
  - CAP-CRM-MRG-013
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources: []
purpose: Portal registration can suggest an existing contact and preserve declined matches for staff review.
outcomes:
  - Registration can offer an existing contact to a returning member.
  - Declined suggestions remain available for later staff review.
concepts:
  - CON-CRM-DQ-001
actors:
  - ACT-CRM-010
  - ACT-CRM-005
  - ACT-CRM-008
surfaces:
  - portal
  - job
permissions: []
rules:
  - id: BR-CAP-CRM-DQ-006-01
    statement: >-
      Portal candidate selection returns no more than three contacts scoring at least 50, ordered by shared
      e-mail first, then score and contact number.
    kind: calculation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#answers
      the three best contacts above the floor, the e-mail match first and a tie in contact-number order, with
      what Platform masks
    origin: none
  - id: BR-CAP-CRM-DQ-006-02
    statement: >-
      Candidates exclude contacts linked to a workspace portal user, deleted contacts and contacts belonging
      to another workspace.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#never
      offers a contact a portal user of the tenant already links
    origin: none
  - id: BR-CAP-CRM-DQ-006-03
    statement: >-
      Only shared e-mail or phone information can reach portal candidate selection; a registration sharing
      neither returns no suggestions.
    kind: calculation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#offers
      nothing for a sign-up that shares no e-mail address and no number with any contact
    origin: none
  - id: BR-CAP-CRM-DQ-006-04
    statement: Portal candidates use the same contact scores and evidence as the staff-side scorer.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#scores
      every contact of the neighbourhood with the score and the reasons the TypeScript scorer gives it
    origin: none
  - id: BR-CAP-CRM-DQ-006-05
    statement: >-
      When suggestions are declined, contact creation applies ordinary conventions, makes the registration
      e-mail primary and its phone the preferred mobile, and attributes the write to portal registration.
    kind: lifecycle
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#writes
      the contact with a UUIDv7 id, the next number, the names trimmed and computed and the stamps of
      portal-sign-up
    origin: none
  - id: BR-CAP-CRM-DQ-006-06
    statement: >-
      Creation stores an open portal-sign-up pair with the best declined contact, retaining at least low
      confidence and an explanation of the rejection.
    kind: lifecycle
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#records
      the declined pair, open, with origin portal_sign_up and the score and reasons the scorer gives it
    origin: none
  - id: BR-CAP-CRM-DQ-006-07
    statement: >-
      Detection may refresh and group portal-sign-up pairs but preserves their origin and does not retire
      them.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#keeps
      the declined pair through detection pass over the new contact, which groups it
    origin: none
  - id: BR-CAP-CRM-DQ-006-08
    statement: A repeated registration within one minute returns its existing contact without another write.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#answers
      the same contact for the same sign-up retried within a minute, and writes nothing more
    origin: none
  - id: BR-CAP-CRM-DQ-006-09
    statement: >-
      Creation rejects an e-mail already reserved as an active contact's primary address and rejects
      registration without either first or last name.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#refuses
      an address that is already the primary address of a contact it did not just create
    origin: none
  - id: BR-CAP-CRM-DQ-006-10
    statement: >-
      Only Platform registration may call the workspace candidate and create functions; the CRM application
      role is denied that access.
    kind: permission
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#refuses
      CRM's own application role, which is not granted it
    origin: none
  - id: BR-CAP-CRM-DQ-006-11
    statement: A declined contact that has been absorbed by a merge resolves to its survivor.
    kind: policy
    enforced_at:
      - database
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/merging/duplicate-detection.md#15. Portal sign-up (the `portal_sign_up` origin)'
    origin: none
scenarios:
  - id: SC-CAP-CRM-DQ-006-01
    given: A member provides a new e-mail address and a mobile already held by a contact.
    when: Platform requests registration suggestions.
    then: >-
      The existing contact appears among at most three candidates, with member-facing masking supplied by
      Platform.
    provenance: test
    source: >-
      services-crm:modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js#ranks
      the neighbourhood as the scores say: 70 for the nickname with the address and the mobile, 50 for the
      name and the number, 38 for an initial
  - id: SC-CAP-CRM-DQ-006-02
    given: A member rejects the offered contacts.
    when: Registration creates their contact.
    then: The new record and best rejected candidate form an open review pair.
    provenance: unknown
    source: FND-CRM-DQ-031
edge_cases: []
constraints:
  - The pinned development environment provides no customer-facing portal registration flow.
depends_on:
  - CAP-CRM-DQ-002
related:
  - CAP-CRM-DQ-007
journeys: []
open_questions: []
evidence:
  - repository: services-crm
    source_sha: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
    paths:
      - docs/merging/duplicate-detection.md
      - modules/duplicates/tests/integration/db/portal-sign-up-contacts.integration.spec.js
    note: 'Static examination connects rule claims to implementation, declared checks and the cited tests.'
preconditions: []
postconditions: []
controls: [TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-005, TC-PI-001, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Reuse a contact during portal registration

## Purpose

Portal registration can suggest an existing contact and preserve declined matches for staff review.

## How It Works

Platform asks CRM for contacts that resemble the registration details. The candidate functions operate within the selected workspace. Suggestions require shared e-mail or phone information and enough evidence to meet the medium confidence floor. Contacts already linked to a portal account are excluded. The best three suggestions are ordered with an e-mail match first. Platform handles the member-facing masking. A member who declines the suggestions can receive a newly created contact. That create uses ordinary contact conventions and records the best declined suggestion as a review pair. A short retry window avoids duplicate creates from repeated submissions. These CRM functions provide backend evidence; a complete portal experience remains outside this examination.

## Relationships

Platform owns registration and login identity. CRM supplies candidate selection and contact creation, while duplicate review handles the pair left by a declined suggestion.
