---
id: ATS-CRM-ENT-003
scope: CAP-CRM-ENT-003
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-003-01
    title: Lists page by number with a cursor and refuse bad paging
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-01
    preconditions: Prepare five contacts.
    steps: 'Walk the list with limit=2; send limit 0, 101 and a made-up cursor.'
    expected: 'Three pages, the last without a cursor; 400 for each bad value.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
      for a cursor it did not answer
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a cursor it did not answer
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a cursor it did not answer
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        contacts that follow the cursor the first page gave
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers no
        cursor on the last page
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a limit above the range
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a cursor it did not answer
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        the rest of the tenant organizations after that cursor
    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-003-02
    title: 'Name search contains, both orders, limited'
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-02
    preconditions: Prepare contacts named Ada Lovelace and others.
    steps: 'Search "lovelace, ada", spaces only, and 101 characters.'
    expected: Match; no filter; rejected.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-795
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#matches
      "Lovelace, Ada" too: the name is searched in both of the orders a contact
      is written in
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#matches
        "Lovelace, Ada" too: the name is searched in both of the orders a
        contact is written in
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#matches
        "Lovelace, Ada" too: the name is searched in both of the orders a
        contact is written in
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#matches
        "Lovelace, Ada" too: the name is searched in both of the orders a
        contact is written in
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        the whole tenant for a search of nothing but spaces: an empty search is
        no search
      - >-
        services-crm:modules/entities/tests/integration/services/api/list-pages.integration.spec.js#refuses
        a search longer than the hundred characters the parameter takes, as
        ObjectInvalid
    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-003-03
    title: the organization filter is "a affiliation in effect today"
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-03
    preconditions: >-
      Prepare four affiliations to one organization: two current, one ended, one
      upcoming.
    steps: List contacts filtered by that organization.
    expected: 'The two current ones, primary or not.'
    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.queries.integration.spec.js#answers
      a contact whose affiliation to the organization is not its primary one
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        a contact whose affiliation to the organization is not its primary one
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        a contact whose affiliation to the organization is not its primary one
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        only the contacts with an affiliation to that organization effective
        today, in number order
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        a contact whose affiliation to the organization is not its primary one
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
        out a contact whose affiliation to the organization has ended
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
        out a contact whose affiliation to the organization begins after today
    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-003-04
    title: >-
      the people beneath an organization follow affiliations in effect today
      down the hierarchy
    level: browser
    proves:
      - BR-CAP-CRM-ENT-003-04
    preconditions: >-
      Prepare a hierarchy with contacts tied primarily and non-primarily, ended
      and upcoming.
    steps: List the descendants of the top.
    expected: >-
      Every current affiliation at any depth once; ended and upcoming left out;
      [] for an unknown id.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
      a contact whose affiliation in effect to an organization of the walk is
      not its primary one (M2-D3)
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
        out a contact whose affiliation to the organization has not started
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
        out a contact whose affiliation to the organization has ended
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#answers
        an empty list for an organization the tenant does not have
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
    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-003-05
    title: Pickers leave out inactive and deceased contacts
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-05
    preconditions: 'Prepare active, duplicate, inactive and deceased contacts.'
    steps: 'List with selectable=true, false and a bad value.'
    expected: Active and duplicate only; every contact; 400.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only
      the contacts a picker may offer: not the inactive, not the deceased
    category: edge
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the contacts a picker may offer: not the inactive, not the deceased
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the contacts a picker may offer: not the inactive, not the deceased
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the contacts a picker may offer: not the inactive, not the deceased
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a selectable that is neither true nor false
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
        out the inactive and the deceased contacts, and keeps the active and the
        duplicate ones, in number order
    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-003-06
    title: workspace isolation on every route
    level: browser
    proves:
      - BR-CAP-CRM-ENT-003-07
    preconditions: Prepare the same contact id in two tenants.
    steps: Read through each workspace; read with a context not covering the path.
    expected: Each workspace its own contact; 403.
    negative: true
    data_needs: Use two tenants.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-774
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
      other tenant its own contact for that id, to a context that covers it
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        other tenant its own contact for that id, to a context that covers it
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        other tenant its own contact for that id, to a context that covers it
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
        other tenant its own contact for that id, to a context that covers it
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a body that chooses its own tenant
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0048.spec.ts#CRM-ACC-0048
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0133.spec.ts#CRM-ACC-0133
    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-003-07
    title: a merged-away id returns 410 with the survivor
    level: browser
    proves:
      - BR-CAP-CRM-ENT-003-08
    preconditions: Prepare two contacts merged.
    steps: 'Read, replace and delete the old id; read a random id.'
    expected: >-
      410 with survivor_id on each; 404 for the random id; the console is saved
      on the survivor.
    negative: true
    data_needs: Use a merge executed by an administrator.
    bound_to:
      - TST-CRM-ENT-774
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0050.spec.ts#CRM-ACC-0050
    category: edge
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0050.spec.ts#CRM-ACC-0050
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0050.spec.ts#CRM-ACC-0050
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0050.spec.ts#CRM-ACC-0050
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0130.spec.ts#CRM-ACC-0130
    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-003-08
    title: Application access is enforced before record keys
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-09
    preconditions: Prepare a caller holding every CRM key.
    steps: >-
      Call a CRM route with access denied, then with access never granted, then
      allowed.
    expected: 403 twice; then served.
    negative: true
    data_needs: Use enforce mode.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
      refused where the assignment DENIES crm.application.access, though the
      route itself is served
    category: permission
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused where the assignment DENIES crm.application.access, though the
        route itself is served
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused where the assignment DENIES crm.application.access, though the
        route itself is served
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused where the assignment DENIES crm.application.access, though the
        route itself is served
      - >-
        services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused where the grants simply never name the access key, as a custom
        role written without it leaves them
      - >-
        services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
        served it once the access key is allowed beside the same read key
    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-003-09
    title: a staff slice over every workspace is rejected at the boundary
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-10
    preconditions: Prepare a Rhythm staff slice.
    steps: Call a representative route of every module.
    expected: 403 at the workspace boundary.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses
      every one of them to a staff slice reaching every tenant, 403 at the
      tenant boundary
    category: permission
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses
        every one of them to a staff slice reaching every tenant, 403 at the
        tenant boundary
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses
        every one of them to a staff slice reaching every tenant, 403 at the
        tenant boundary
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses
        every one of them to a staff slice reaching every tenant, 403 at the
        tenant boundary
    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-003-10
    title: >-
      Read-only staff can open and browse, and a contacts-only reader still sees
      the organization by name
    level: browser
    proves:
      - BR-CAP-CRM-ENT-003-06
      - BR-CAP-CRM-ENT-003-12
    preconditions: >-
      Prepare a read-only user and a user who may read contacts but not
      organizations.
    steps: Open a contact 360 and its affiliations.
    expected: >-
      Every detail displayed with no write actions; the affiliation names its
      organization.
    negative: false
    data_needs: Use personas acceptance-readonly and a contacts-only role.
    bound_to:
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064
    category: permission
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0045.spec.ts#CRM-ACC-0045
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064
    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-003-11
    title: 'the journey: the people beneath an organization'
    level: browser
    proves:
      - AS-JNY-CRM-ENT-005-01
      - AS-JNY-CRM-ENT-005-02
    preconditions: >-
      Prepare a national body, a chapter and contacts tied primarily,
      non-primarily, ended and upcoming.
    steps: Run JNY-CRM-ENT-005.
    expected: >-
      The list filter and the descendant listing agree; ended and upcoming
      affiliations never count.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-776
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0143.spec.ts#CRM-ACC-0143
    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-003-12
    title: Read a projected record with display links
    category: rule
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-06
      - BR-CAP-CRM-ENT-003-13
    preconditions: A person has a photo and a document value visible to the reader.
    steps: >-
      Read the person’s identity, photo and document fields using a projection;
      inspect the returned links.
    expected: >-
      The response contains the requested fields and identity, a public photo
      link and an attachment download link with the specified lifetime.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/display-links.e2e.spec.js#answers
      a presigned GET that lasts 15 minutes and downloads as an attachment for a
      document custom field
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/display-links.e2e.spec.js#answers
        a presigned GET that lasts 15 minutes and downloads as an attachment for
        a document custom field
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/display-links.e2e.spec.js#answers
        a presigned GET that lasts 15 minutes and downloads as an attachment for
        a document custom field
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-003-13
    title: Find the newly established person
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-001-02
      - BR-CAP-CRM-ENT-003-06
    preconditions: A person request uses a distinctive last name.
    steps: 'Create the person, search by that name and open the saved record.'
    expected: >-
      Search identifies the saved person and the opened record carries the saved
      identity.
    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#builds
      the full name from every part in order, trimmed and singly spaced;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only
      the fields `?fields=` named, with the id
    exercises:
      - CAP-CRM-ENT-001
      - CAP-CRM-ENT-003
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds
        the full name from every part in order, trimmed and singly spaced;
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the fields `?fields=` named, with the id
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds
        the full name from every part in order, trimmed and singly spaced;
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the fields `?fields=` named, with the id
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-003-14
    title: Move a branch and rediscover its people
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-07
      - BR-CAP-CRM-ENT-003-04
    preconditions: >-
      Two roots, a child organization and a current affiliate under the child
      exist.
    steps: >-
      Move the child to the other root; read its ancestry and discover people
      below each root.
    expected: >-
      The child’s new ancestry and the two root populations agree with the moved
      structure.
    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.integration.spec.js#answers
      the descendant, never saved again, its ancestry under the new parent;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
      a contact whose affiliation in effect to an organization of the walk is
      not its primary one (M2-D3)
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-003
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-003-15
    title: Find a person through a secondary tie
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-03
      - BR-CAP-CRM-ENT-009-04
    preconditions: >-
      A person has a primary tie to one organization and a current secondary tie
      to another.
    steps: Filter people by the secondary organization and open the matching person.
    expected: >-
      The person appears once through that current secondary tie while retaining
      the separate primary designation.
    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.queries.integration.spec.js#answers
      a contact whose affiliation to the organization is not its primary one;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      two affiliations newly marked primary at once
    exercises:
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-009
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        a contact whose affiliation to the organization is not its primary one;
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two affiliations newly marked primary at once
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
        a contact whose affiliation to the organization is not its primary one;
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two affiliations newly marked primary at once
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-003-16
    title: Archive changes discovery without losing direct reads
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-05
      - BR-CAP-CRM-ENT-013-06
    preconditions: A live person can be found in an ordinary list.
    steps: >-
      Archive the person; list ordinarily and with archive inclusion; open it by
      identity.
    expected: >-
      The ordinary list omits it, the explicit archived selection includes it
      and the direct read retains its saved information.
    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.e2e.spec.js#answers only
      the contacts a picker may offer: not the inactive, not the deceased;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
      archived=only, and only it
    exercises:
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-013
    workflows:
      - WF-CRM-ENT-011
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the contacts a picker may offer: not the inactive, not the
        deceased;
        services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
        archived=only, and only it
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers
        only the contacts a picker may offer: not the inactive, not the
        deceased;
        services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
        archived=only, and only it
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-003-17
    title: Exercise every declared authority independently
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-07
      - BR-CAP-CRM-ENT-003-09
      - BR-CAP-CRM-ENT-003-10
      - BR-CAP-CRM-ENT-003-11
    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.read
      - crm.organizations.read
      - crm.contacts.query
      - crm.organizations.query
      - crm.application.access
    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/e2e/contacts.e2e.spec.js#answers 400 for
    a cursor it did not answer
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    contacts that follow the cursor the first page gave
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers no
    cursor on the last page
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for
    a limit above the range
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    the rest of the tenant organizations after that cursor
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#matches
    "Lovelace, Ada" too: the name is searched in both of the orders a contact is
    written in
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
    the whole tenant for a search of nothing but spaces: an empty search is no
    search
  - >-
    services-crm:modules/entities/tests/integration/services/api/list-pages.integration.spec.js#refuses
    a search longer than the hundred characters the parameter takes, as
    ObjectInvalid
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
    a contact whose affiliation to the organization is not its primary one
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
    only the contacts with an affiliation to that organization effective today,
    in number order
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
    out a contact whose affiliation to the organization has ended
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
    out a contact whose affiliation to the organization begins after today
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
    a contact whose affiliation in effect to an organization of the walk is not
    its primary one (M2-D3)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
    out a contact whose affiliation to the organization has not started
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#answers
    an empty list for an organization the tenant does not have
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0189.spec.ts#CRM-ACC-0189
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only
    the contacts a picker may offer: not the inactive, not the deceased
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for
    a selectable that is neither true nor false
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#leaves
    out the inactive and the deceased contacts, and keeps the active and the
    duplicate ones, in number order
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the
    other tenant its own contact for that id, to a context that covers it
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for
    a body that chooses its own tenant
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0048.spec.ts#CRM-ACC-0048
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0133.spec.ts#CRM-ACC-0133
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0050.spec.ts#CRM-ACC-0050
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0130.spec.ts#CRM-ACC-0130
  - >-
    services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
    refused where the assignment DENIES crm.application.access, though the route
    itself is served
  - >-
    services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
    refused where the grants simply never name the access key, as a custom role
    written without it leaves them
  - >-
    services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is
    served it once the access key is allowed beside the same read key
  - >-
    services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses
    every one of them to a staff slice reaching every tenant, 403 at the tenant
    boundary
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0045.spec.ts#CRM-ACC-0045
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0143.spec.ts#CRM-ACC-0143
  - >-
    services-crm:modules/entities/tests/e2e/display-links.e2e.spec.js#answers a
    presigned GET that lasts 15 minutes and downloads as an attachment for a
    document custom field
  - >-
    services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds
    the full name from every part in order, trimmed and singly spaced;
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only
    the fields `?fields=` named, with the id
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the descendant, never saved again, its ancestry under the new parent;
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
    a contact whose affiliation in effect to an organization of the walk is not
    its primary one (M2-D3)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
    a contact whose affiliation to the organization is not its primary one;
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
    two affiliations newly marked primary at once
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only
    the contacts a picker may offer: not the inactive, not the deceased;
    services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
    archived=only, and only it
  - >-
    services-crm:aws/crm.service.ts:ApplicationPermissions;
    services-crm:modules/entities/aws/api/entities.routes.ts
coverage:
  rule: covered
  transition: not-applicable
  validation: 'gap: FND-CRM-ENT-073'
  permission: covered
  edge: covered
  failure: 'gap: FND-CRM-ENT-073'
  boundary: covered
  concurrency: 'gap: FND-CRM-ENT-073'
  invariant: 'gap: FND-CRM-ENT-073'
  interaction: covered
previous_approval:
  revision: 1
  approved_by: Andrew
  approved_at: '2026-10-22T00:00:00.000Z'
coverage_notes:
  transition:
    reason: >-
      Discovery reads do not change a lifecycle state; archive and relationship
      transitions are proved in their owning specifications.
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
  validation:
    reason: >-
      No detailed validation scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-073
  failure:
    reason: >-
      No detailed failure scenario currently specifies this aspect; the coverage
      finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-073
  concurrency:
    reason: >-
      No detailed concurrency scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-073
  invariant:
    reason: >-
      No detailed invariant scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-073
coverage_findings:
  - FND-CRM-ENT-073
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.contacts.read
    where_checked: api
    scenario: ATS-CRM-ENT-003-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
  - key: crm.organizations.read
    where_checked: api
    scenario: ATS-CRM-ENT-003-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
  - key: crm.contacts.query
    where_checked: service
    scenario: ATS-CRM-ENT-003-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
  - key: crm.organizations.query
    where_checked: service
    scenario: ATS-CRM-ENT-003-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
  - key: crm.application.access
    where_checked: service
    scenario: ATS-CRM-ENT-003-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md
related_coverage:
  - capability: CAP-CRM-ENT-001
    scenarios:
      - ATS-CRM-ENT-003-13
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md:related
  - capability: CAP-CRM-ENT-002
    scenarios:
      - ATS-CRM-ENT-003-14
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md:related
  - capability: CAP-CRM-ENT-009
    scenarios:
      - ATS-CRM-ENT-003-15
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.md:related
  - capability: CAP-CRM-ENT-013
    scenarios:
      - ATS-CRM-ENT-003-16
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-003-find-and-read-contacts-and-organizations.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
---

# Locate people and organizations
## 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-003-01` | `ATS-CRM-ENT-003-01` |
| `BR-CAP-CRM-ENT-003-02` | `ATS-CRM-ENT-003-02` |
| `BR-CAP-CRM-ENT-003-03` | `ATS-CRM-ENT-003-03`, `ATS-CRM-ENT-003-15` |
| `BR-CAP-CRM-ENT-003-04` | `ATS-CRM-ENT-003-04`, `ATS-CRM-ENT-003-14` |
| `BR-CAP-CRM-ENT-003-05` | `ATS-CRM-ENT-003-05`, `ATS-CRM-ENT-003-16` |
| `BR-CAP-CRM-ENT-003-07` | `ATS-CRM-ENT-003-06`, `ATS-CRM-ENT-003-17` |
| `BR-CAP-CRM-ENT-003-08` | `ATS-CRM-ENT-003-07` |
| `BR-CAP-CRM-ENT-003-09` | `ATS-CRM-ENT-003-08`, `ATS-CRM-ENT-003-17` |
| `BR-CAP-CRM-ENT-003-10` | `ATS-CRM-ENT-003-09`, `ATS-CRM-ENT-003-17` |

## Interactions With Related Capabilities

The interaction scenarios exercise establish a person’s CRM record; establish an organization and its hierarchy; maintain a person’s organizational relationships; set records aside and restore them. 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.
