---
id: ATS-CRM-ENT-005
scope: CAP-CRM-ENT-005
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-005-01
    title: Three canonical e-mail slots
    level: browser
    proves:
      - BR-CAP-CRM-ENT-005-01
      - INV-DOM-CRM-ENT-001-06
    preconditions: No additional setup is required.
    steps: >-
      Save four addresses; save "Ada@Example.com "; save an invalid one; save a
      blank one.
    expected: Fourth rejected; stored canonical; invalid rejected; blank dropped.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      a fourth e-mail address
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a fourth e-mail address
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a fourth e-mail address
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a fourth e-mail address
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#stores
        the e-mail address lower-cased with its whitespace removed
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#drops
        a blank e-mail address
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0015.spec.ts#CRM-ACC-0015
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-02
    title: One primary e-mail per active contact; released by inactive and deceased
    level: browser
    proves:
      - BR-CAP-CRM-ENT-005-02
      - INV-DOM-CRM-ENT-001-05
    preconditions: >-
      Prepare contacts holding primary and secondary addresses in several
      statuses.
    steps: >-
      Write a second primary; a secondary copy; take a deceased contact's
      address; reactivate the deceased.
    expected: Rejected; accepted; accepted; rejected naming the address.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        a second contact the same primary address on insert
      - >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#accepts
        another contact’s primary address as a secondary address (slot 2)
      - >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#releases
        the primary address of a deceased contact: another contact may hold it
      - >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
      - >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#keeps
        the primary address of a contact marked duplicate reserved
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0016.spec.ts#CRM-ACC-0016
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-03
    title: 'One phone per type, one preferred, default by the workspace’s order'
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-03
      - INV-DOM-CRM-ENT-001-07
    preconditions: No additional setup is required.
    steps: Save two numbers of a type; two preferred; none preferred.
    expected: Rejected; rejected; first in order preferred.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      two numbers of the same type
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two preferred numbers
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        phone numbers in the read order, the preferred one the first in the
        tenant’s order
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        400 for a phone number type the tenant does not have
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-04
    title: 'US numbers formatted, others preserved, blanks dropped'
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-04
    preconditions: No additional setup is required.
    steps: 'Save "202-555-0143", a UK number and a blank number.'
    expected: Formatted; as typed; dropped.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats
      a valid US number, drops a blank one and marks the default preferred
      number
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats
        a valid US number, drops a blank one and marks the default preferred
        number
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats
        a valid US number, drops a blank one and marks the default preferred
        number
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats
        a valid US number, drops a blank one and marks the default preferred
        number
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#formats
        a US number of any type, now that no type is the legacy
        `other_phone_numbers`
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-05
    title: 'Addresses: one per type, one preferred, never seasonal, promoted'
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-05
    preconditions: No additional setup is required.
    steps: >-
      Save addresses with none marked; a preferred seasonal one; remove an
      organization's preferred address.
    expected: First non-seasonal preferred; rejected; next promoted.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks
      the first address of the tenant’s display order as preferred, never one
      with a seasonal period
    category: transition
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks
        the first address of the tenant’s display order as preferred, never one
        with a seasonal period
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks
        the first address of the tenant’s display order as preferred, never one
        with a seasonal period
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks
        the first address of the tenant’s display order as preferred, never one
        with a seasonal period
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a preferred address with a seasonal period
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#promotes
        the next address of the tenant’s display order to preferred
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        400 for a body with two addresses of one type
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-06
    title: Countries cleaned or rejected; coordinates bounded
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-06
    preconditions: No additional setup is required.
    steps: 'Save country "america", country "Earth", latitude 91.'
    expected: '"US"; 400 naming country; 400 naming latitude.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
      an unknown country, naming the address’s country
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        an unknown country, naming the address’s country
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        an unknown country, naming the address’s country
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#accepts
        a country by name and stores its code
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        an unknown country, naming the address’s country
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for an address latitude above 90, naming the field
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        an unknown country on an address, naming the address’s country
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-07
    title: Organizations-only types rejected on a contact
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-07
      - INV-DOM-CRM-ENT-001-13
    preconditions: Prepare an address type and a phone type marked for organizations.
    steps: >-
      Add an address and a phone of those types to a contact; save a contact
      whose stored address type became organizations-only.
    expected: 400 naming the type field each; the second saved.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
      a new address of an address type marked for organizations only, naming its
      type field
    category: edge
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        a new address of an address type marked for organizations only, naming
        its type field
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        a new address of an address type marked for organizations only, naming
        its type field
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#refuses
        a contact address of a type marked for organizations (400), naming the
        type field (CRM-M1 decision M1-D8)
      - >-
        services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#refuses
        a contact phone number of a type marked for organizations (400), naming
        the type field (CRM-M1 decision M1-D8)
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
        a contact saveable after the type of an address it holds became
        organizations-only
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-08
    title: 'Seasons are recurring month and day, inclusive, never overlapping'
    level: browser
    proves:
      - BR-CAP-CRM-ENT-005-08
      - INV-DOM-CRM-ENT-001-08
    preconditions: No additional setup is required.
    steps: Save 12-01..03-31; a calendar date; Feb 29; two seasons sharing a day.
    expected: Accepted crossing the year; rejected; accepted; rejected naming both.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-786
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
      overlapping seasons naming both addresses
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
        overlapping seasons naming both addresses
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
        overlapping seasons naming both addresses
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#crosses
        December and January
      - >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
        a calendar date, a day the month lacks and a malformed value
      - >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#takes
        a Feb 29 end as Feb 28 in a year without one, so Feb 28 is the last day
      - >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
        overlapping seasons naming both addresses
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#refuses
        calendar dates with a 400 naming the field
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0067.spec.ts#CRM-ACC-0067
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-09
    title: the mailing address follows the season only when marked
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-09
    preconditions: Prepare a contact with a preferred and a seasonal address.
    steps: 'Read the mailing address in and out of season, marked and unmarked.'
    expected: The seasonal one only in season and marked; else the preferred.
    negative: false
    data_needs: Use workspace date in and out of season.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-788
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
      seasonal address in season when the contact marks it, with the workspace
      date
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        seasonal address in season when the contact marks it, with the workspace
        date
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        seasonal address in season when the contact marks it, with the workspace
        date
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        seasonal address in season when the contact marks it, with the workspace
        date
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        preferred address in season when the contact has not marked the season
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        preferred address out of season
      - >-
        services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#is
        the regular preferred address in season when the contact does not mark
        the seasonal one
    binding_scope: >-
      contributions: the cited executable assertions collectively support only
      the observations they contain; additional matrix and journey checks remain
      proposed.
  - id: ATS-CRM-ENT-005-10
    title: Reject a conflicting primary email without saving other changes
    category: failure
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-02
      - BR-CAP-CRM-ENT-004-07
    preconditions: Two active people reserve different primary addresses.
    steps: >-
      Change the second person’s primary address to the first person’s address
      while also changing its notes.
    expected: >-
      The refusal leaves the second person’s address and notes as previously
      saved.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-11
    title: Keep a corrected contact point under its existing identity
    category: invariant
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-10
      - BR-CAP-CRM-ENT-004-05
    preconditions: A person has one typed address and one typed phone.
    steps: >-
      Correct each value using its current element identity; read both
      collections.
    expected: The corrected values are returned under the original element identities.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      the second address into the type the first left, and the preferred flag
      with it
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-12
    title: Create a person with usable contact points
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-001-01
      - BR-CAP-CRM-ENT-005-01
    preconditions: A valid name and three distinct email values are prepared.
    steps: >-
      Create the person with all three addresses and read the ordered
      collection.
    expected: >-
      The contact is created once and the saved contact points retain their
      declared order.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
      400 for a contact with neither a first nor a last name, naming the rule;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      a fourth e-mail address
    exercises:
      - CAP-CRM-ENT-001
      - CAP-CRM-ENT-005
    workflows:
      - WF-CRM-ENT-001
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
        400 for a contact with neither a first nor a last name, naming the rule;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a fourth e-mail address
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
        400 for a contact with neither a first nor a last name, naming the rule;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        a fourth e-mail address
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-13
    title: Save organizational contact points with the body
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-01
      - BR-CAP-CRM-ENT-005-03
    preconditions: A named organization has two usable phone purposes.
    steps: Create it with one phone in each purpose and read its contact points.
    expected: The organization and its typed phones are readable as one saved aggregate.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
      a name of 201 characters, naming name;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      two numbers of the same type
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-005
    workflows:
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-14
    title: Correct a contact point without replacing its identity
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-05
      - BR-CAP-CRM-ENT-005-10
    preconditions: A person’s address has a known element identity and version.
    steps: Patch its city and read the collection and record version.
    expected: >-
      The city changes under the original element identity and the aggregate
      advances once.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      the second address into the type the first left, and the preferred flag
      with it;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
    workflows:
      - WF-CRM-ENT-002
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it;
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it;
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-15
    title: Source removal leaves a person’s detail for review
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-10
      - BR-CAP-CRM-ENT-006-06
    preconditions: A person’s work address is linked to an organization address.
    steps: Remove the source address and read the person’s address and version.
    expected: >-
      The person retains the last value as disconnected information needing
      verification and its version advances.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#turns
      the linked work address into a disconnected row holding its last value,
      not a copy (D-25)
    exercises:
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-006
    workflows:
      - WF-CRM-ENT-001
      - WF-CRM-ENT-002
      - WF-CRM-ENT-003
      - WF-CRM-ENT-010
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9;
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#turns
        the linked work address into a disconnected row holding its last value,
        not a copy (D-25)
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9;
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#turns
        the linked work address into a disconnected row holding its last value,
        not a copy (D-25)
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-16
    title: Retain a held purpose after its applicability changes
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-005-07
      - BR-CAP-CRM-ENT-018-05
    preconditions: A person holds an address purpose that currently applies to people.
    steps: >-
      Change the purpose to organizations only; save the person with its
      existing address; try assigning that purpose to another person.
    expected: The held address remains saveable; the new person assignment is refused.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
      a contact saveable after the type of an address it holds became
      organizations-only;
      services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers
      only the types usable on organizations when the query asks for them
    exercises:
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-018
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
        a contact saveable after the type of an address it holds became
        organizations-only;
        services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers
        only the types usable on organizations when the query asks for them
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
        a contact saveable after the type of an address it holds became
        organizations-only;
        services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers
        only the types usable on organizations when the query asks for them
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-17
    title: Remove an owned contact point explicitly
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-05
      - BR-CAP-CRM-ENT-005-05
    preconditions: >-
      A person has an unlocked preferred address and another nonseasonal
      address.
    steps: >-
      Submit the address collection without the preferred entry and read it
      again.
    expected: >-
      The omitted address is removed and the surviving eligible address supplies
      the preferred choice.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
      the collection
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
        the collection
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
        the collection
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-005-18
    title: Exercise every declared authority independently
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-07
      - BR-CAP-CRM-ENT-003-09
    preconditions: >-
      An eligible record and configuration fixture exist for each operation; the
      caller can reach only the run’s workspace.
    steps: >-
      For each declared permission, execute its matching operation with that
      permission, then without it while retaining the other required grants.
      Read affected records after every refused mutation.
    expected: >-
      The authorized case reaches the operation; the missing-grant case is
      refused and preserves saved data. Record access alone does not authorize
      the other declared actions.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:aws/crm.service.ts:ApplicationPermissions;
      services-crm:modules/entities/aws/api/entities.routes.ts
    permission_keys:
      - crm.contacts.update
      - crm.organizations.update
      - crm.contacts.read
      - crm.address-types.read
      - crm.phone-number-types.read
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:aws/crm.service.ts:ApplicationPermissions;
        services-crm:modules/entities/aws/api/entities.routes.ts
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:aws/crm.service.ts:ApplicationPermissions;
        services-crm:modules/entities/aws/api/entities.routes.ts
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
evidence:
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    a fourth e-mail address
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#stores
    the e-mail address lower-cased with its whitespace removed
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#drops
    a blank e-mail address
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0015.spec.ts#CRM-ACC-0015
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
    to make an inactive contact active while another contact holds its primary
    address
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
    a second contact the same primary address on insert
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#accepts
    another contact’s primary address as a secondary address (slot 2)
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#releases
    the primary address of a deceased contact: another contact may hold it
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#keeps
    the primary address of a contact marked duplicate reserved
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0016.spec.ts#CRM-ACC-0016
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    two numbers of the same type
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    two preferred numbers
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    phone numbers in the read order, the preferred one the first in the tenant’s
    order
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    400 for a phone number type the tenant does not have
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats
    a valid US number, drops a blank one and marks the default preferred number
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#formats
    a US number of any type, now that no type is the legacy
    `other_phone_numbers`
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks
    the first address of the tenant’s display order as preferred, never one with
    a seasonal period
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    a preferred address with a seasonal period
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#promotes
    the next address of the tenant’s display order to preferred
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    400 for a body with two addresses of one type
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
    an unknown country, naming the address’s country
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#accepts
    a country by name and stores its code
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for
    an address latitude above 90, naming the field
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    an unknown country on an address, naming the address’s country
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
    a new address of an address type marked for organizations only, naming its
    type field
  - >-
    services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#refuses a
    contact address of a type marked for organizations (400), naming the type
    field (CRM-M1 decision M1-D8)
  - >-
    services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#refuses
    a contact phone number of a type marked for organizations (400), naming the
    type field (CRM-M1 decision M1-D8)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
    a contact saveable after the type of an address it holds became
    organizations-only
  - >-
    services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
    overlapping seasons naming both addresses
  - >-
    services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#crosses
    December and January
  - >-
    services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#refuses
    a calendar date, a day the month lacks and a malformed value
  - >-
    services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#takes
    a Feb 29 end as Feb 28 in a year without one, so Feb 28 is the last day
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#refuses
    calendar dates with a 400 naming the field
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0067.spec.ts#CRM-ACC-0067
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    seasonal address in season when the contact marks it, with the workspace
    date
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    preferred address in season when the contact has not marked the season
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    preferred address out of season
  - >-
    services-crm:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js#is
    the regular preferred address in season when the contact does not mark the
    seasonal one
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
    the second address into the type the first left, and the preferred flag with
    it
  - >-
    services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
    400 for a contact with neither a first nor a last name, naming the rule;
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    a fourth e-mail address
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    a name of 201 characters, naming name;
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    two numbers of the same type
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
    the second address into the type the first left, and the preferred flag with
    it;
    services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
  - >-
    services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9;
    services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#turns
    the linked work address into a disconnected row holding its last value, not
    a copy (D-25)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves
    a contact saveable after the type of an address it holds became
    organizations-only;
    services-crm:modules/entities/tests/e2e/phone-number-types.e2e.spec.js#answers
    only the types usable on organizations when the query asks for them
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
    the collection
  - >-
    services-crm:aws/crm.service.ts:ApplicationPermissions;
    services-crm:modules/entities/aws/api/entities.routes.ts
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-CRM-ENT-053'
  invariant: covered
  interaction: covered
previous_approval:
  revision: 1
  approved_by: Andrew
  approved_at: '2026-10-22T00:00:00.000Z'
transition_coverage:
  - state_model: STATE-CRM-ENT-006
    from: owned
    to: removed
    scenarios:
      - ATS-CRM-ENT-005-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-006-shared-contact-detail-source.md:transitions
coverage_notes:
  concurrency:
    reason: >-
      No detailed concurrency scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-053
coverage_findings:
  - FND-CRM-ENT-053
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.contacts.update
    where_checked: api
    scenario: ATS-CRM-ENT-005-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md
  - key: crm.organizations.update
    where_checked: api
    scenario: ATS-CRM-ENT-005-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md
  - key: crm.contacts.read
    where_checked: api
    scenario: ATS-CRM-ENT-005-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md
  - key: crm.address-types.read
    where_checked: api
    scenario: ATS-CRM-ENT-005-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md
  - key: crm.phone-number-types.read
    where_checked: api
    scenario: ATS-CRM-ENT-005-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md
related_coverage:
  - capability: CAP-CRM-ENT-001
    scenarios:
      - ATS-CRM-ENT-005-12
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md:related
  - capability: CAP-CRM-ENT-004
    scenarios:
      - ATS-CRM-ENT-005-14
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md:related
  - capability: CAP-CRM-ENT-006
    scenarios:
      - ATS-CRM-ENT-005-15
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md:related
  - capability: CAP-CRM-ENT-018
    scenarios:
      - ATS-CRM-ENT-005-16
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-005-keep-the-e-mails-phone-numbers-and-addresses-of-contacts-and.md:related
examination_evidence:
  provenance: documented
  source: 'rhythm-v2-docs:specs/crm/entities/reports/EXAMINATION-2026-10-23.md'
  heading_conflict: FND-CRM-ENT-075
---

# Maintain reliable contact points
## Coverage

The category map separates specified cases from recorded gaps. A proposed case describes proof to implement; a binding identifies the assertions supplied by an existing executable file. The permission and transition ledgers record the individual checks needed for the capability.

| Required rule | Scenario or finding |
|---|---|
| `BR-CAP-CRM-ENT-005-01` | `ATS-CRM-ENT-005-01`, `ATS-CRM-ENT-005-12` |
| `BR-CAP-CRM-ENT-005-02` | `ATS-CRM-ENT-005-02`, `ATS-CRM-ENT-005-10` |
| `BR-CAP-CRM-ENT-005-03` | `ATS-CRM-ENT-005-03`, `ATS-CRM-ENT-005-13` |
| `BR-CAP-CRM-ENT-005-04` | `ATS-CRM-ENT-005-04` |
| `BR-CAP-CRM-ENT-005-05` | `ATS-CRM-ENT-005-05`, `ATS-CRM-ENT-005-17` |
| `BR-CAP-CRM-ENT-005-06` | `ATS-CRM-ENT-005-06` |
| `BR-CAP-CRM-ENT-005-07` | `ATS-CRM-ENT-005-07`, `ATS-CRM-ENT-005-16` |
| `BR-CAP-CRM-ENT-005-08` | `ATS-CRM-ENT-005-08` |
| `BR-CAP-CRM-ENT-005-09` | `ATS-CRM-ENT-005-09` |

## Interactions With Related Capabilities

The interaction scenarios exercise establish a person’s CRM record; save corrections without overwriting another change; share organization contact points with affiliated people; organize the workspace’s record classifications. They change one part of a prepared record and inspect the dependent outcome. Unbound interactions remain proposed rather than implying an existing end-to-end implementation.

## Source Evidence

The frontmatter pins the repositories and identifies the source anchors for each scenario. Bindings record inspected assertions, while unbound cases remain proposed. Source inspection supplies no execution result or product approval.
