---
id: ATS-CRM-ENT-009
scope: CAP-CRM-ENT-009
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-009-01
    title: an affiliation needs the workspace’s organization and type
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-01
      - INV-DOM-CRM-ENT-003-01
    preconditions: No additional setup is required.
    steps: >-
      Create a contact with an affiliation to an organization the workspace
      lacks.
    expected: 400; no contact saved.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
      400 for an organization the tenant does not have
    category: failure
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for an organization the tenant does not have
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for an organization the tenant does not have
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for an organization the tenant does not have
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#saves
        no contact for a write it refused
    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-009-02
    title: Affiliations are written only through the contact
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-02
    preconditions: Prepare an organization with people.
    steps: >-
      Add Person, Edit and End from the organization's Contacts tab; look for an
      organization-side write route.
    expected: Each writes the person’s record; none exists.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    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-009-03
    title: Omitted affiliations are unmodified; an explicit list is the collection
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-03
    preconditions: Prepare a contact with two affiliations.
    steps: >-
      Replace without affiliations; replace with an empty list; replace with
      affiliations without ids matching the stored ones.
    expected: 'Unmodified; removed; matched, not duplicated.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-776
      - TST-CRM-ENT-788
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
    category: edge
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#matches
        each stored affiliation once: a second statement of it stays new
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0205.spec.ts#CRM-ACC-0205
    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-009-04
    title: One primary; Make Primary unmarks the old
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-04
      - INV-DOM-CRM-ENT-003-02
    preconditions: 'Prepare acme primary, Globex current.'
    steps: Mark Globex primary; mark two non-primaries primary.
    expected: 'Globex primary, Acme current; rejected.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      two affiliations newly marked primary at once
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two affiliations newly marked primary at once
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two affiliations newly marked primary at once
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#unmarks
        the previous primary when another affiliation is marked primary
      - >-
        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/contact-affiliations.e2e.spec.js#unmarks
        the old primary, which stays open and current
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for two affiliations newly marked primary
    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-009-05
    title: a primary is open-ended and has started
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-05
    preconditions: No additional setup is required.
    steps: Save a primary with an end date; one starting tomorrow.
    expected: 'Rejected each, naming the field.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-786
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      a primary affiliation that starts after today
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        a primary affiliation that starts after today
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        a primary affiliation that starts after today
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        a new primary affiliation with an end date
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        a primary affiliation that starts after today
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for a primary affiliation with an end date, naming the field
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0176.spec.ts#CRM-ACC-0176
    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-009-06
    title: Ending the primary promotes nothing
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-06
    preconditions: 'Prepare acme primary, Globex open.'
    steps: >-
      Give the primary affiliation an end date equal to the workspace’s current
      day; read it that day and on the following day.
    expected: >-
      Primary designation clears immediately and no replacement is selected. The
      affiliation remains current on its inclusive end day and reads as past the
      next day.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-787
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place
    category: transition
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
        nothing primary in its place
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
        nothing primary in its place
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#clears
        the flag of a stored primary the save ends, and promotes nothing
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#clears
        its primary flag
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
        nothing primary in its place
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0174.spec.ts#CRM-ACC-0174
    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-009-07
    title: Dates are ordered and inclusive
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-07
      - INV-DOM-CRM-ENT-003-04
    preconditions: No additional setup is required.
    steps: Save an affiliation ending before it starts; a one-day affiliation.
    expected: Rejected; accepted and current that day.
    negative: true
    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.integration.spec.js#refuses
      an affiliation that ends before it starts
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        an affiliation that ends before it starts
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        an affiliation that ends before it starts
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        an affiliation that ends before it starts
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0173.spec.ts#CRM-ACC-0173
    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-009-08
    title: No overlapping affiliations of one type at one organization
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-08
      - INV-DOM-CRM-ENT-003-03
    preconditions: Prepare employer at Acme 2019-2021.
    steps: >-
      Add Employer at Acme from 2021-12-31; from 2024; Board Member at Acme
      overlapping.
    expected: Rejected; accepted; accepted.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-786
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      two overlapping affiliations to the same organization of the same type
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two overlapping affiliations to the same organization of the same type
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two overlapping affiliations to the same organization of the same type
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        two overlapping affiliations to the same organization of the same type
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#finds
        periods that share their last and first day overlapping
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#finds
        an undated period overlapping every other
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#keeps
        two current affiliations to the same organization of different types
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#accepts
        two affiliations to the same organization of the same type in separate
        periods
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0179.spec.ts#CRM-ACC-0179
    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-009-09
    title: an affiliation's organization never changes
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-09
      - INV-DOM-CRM-ENT-003-05
    preconditions: Prepare an existing affiliation.
    steps: Send it with another organization; use the editor.
    expected: Rejected; the editor offers End and Add Another.
    negative: true
    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.integration.spec.js#refuses
      to move the affiliation to another organization under the same id
      (docs/affiliations/README.md §0b.2)
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        to move the affiliation to another organization under the same id
        (docs/affiliations/README.md §0b.2)
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        to move the affiliation to another organization under the same id
        (docs/affiliations/README.md §0b.2)
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        to move the affiliation to another organization under the same id
        (docs/affiliations/README.md §0b.2)
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0187.spec.ts#CRM-ACC-0187
    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-009-10
    title: At most 100 current and upcoming affiliations
    level: unit
    proves:
      - BR-CAP-CRM-ENT-009-10
      - INV-DOM-CRM-ENT-003-06
    preconditions: Prepare a contact with 100 ended affiliations.
    steps: Add one current affiliation; then 101 current affiliations.
    expected: Accepted; rejected.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts
      only current and upcoming affiliations against the limit: ended history is
      unlimited (M2-D5)
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts
        only current and upcoming affiliations against the limit: ended history
        is unlimited (M2-D5)
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts
        only current and upcoming affiliations against the limit: ended history
        is unlimited (M2-D5)
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
        more than 100 affiliations, and takes 100
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts
        only current and upcoming affiliations against the limit: ended history
        is unlimited (M2-D5)
    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-009-11
    title: the Organization field command
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-11
    preconditions: Prepare contacts with and without a primary.
    steps: >-
      Pick Acme; change to Globex with end and keep; backdate; clear; change on
      the day the primary started; effective date tomorrow.
    expected: >-
      Primary of the default type from today; old ended yesterday or preserved;
      old ends the day before; none; removed; 400.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#ends
      the previous primary affiliation yesterday by default ('end'), no longer
      primary and now past
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#ends
        the previous primary affiliation yesterday by default ('end'), no longer
        primary and now past
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#ends
        the previous primary affiliation yesterday by default ('end'), no longer
        primary and now past
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#makes
        one primary affiliation of the default type other, starting today,
        open-ended, with the contact’s job title
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#ends
        the previous primary affiliation yesterday by default ('end'), no longer
        primary and now past
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#keeps
        the previous affiliation open and current, no longer primary, with
        'keep'
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#starts
        the new affiliation on an earlier effective date and ends the previous
        one the day before it
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#leaves
        no primary organization when the organization is cleared, and ends the
        previous one yesterday
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#removes
        a previous primary that began on the day it is ended before, as a stay
        of no days
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        400 for an effective date after today, naming the field
    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-009-12
    title: Did they leave?
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-12
    preconditions: Prepare a person primary at Harbor.
    steps: >-
      Add Bayview as primary and answer Yes; repeat answering No; Make Primary
      on an existing affiliation.
    expected: >-
      The old affiliation ends the day before the new start, or on its own start
      date if later; choosing to stay preserves it current, and Make Primary
      asks no question.
    negative: true
    data_needs: Use two organizations.
    bound_to: []
    provenance: proposed
    source: 'react-console:lib/affiliations/section.ts:affiliationBeingLeft'
    category: transition
    action_evidence:
      provenance: proposed
      source: 'react-console:lib/affiliations/section.ts:affiliationBeingLeft'
    assertion_evidence:
      provenance: proposed
      source: 'react-console:lib/affiliations/section.ts:affiliationBeingLeft'
    test_anchors: []
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-13
    title: Job title follows the primary both ways
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-13
      - INV-DOM-CRM-ENT-003-09
    preconditions: Prepare a contact with a primary affiliation.
    steps: >-
      Change the contact's title; change the primary's title; add a primary with
      no title.
    expected: Each side takes the other's; the new primary takes the contact's.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
      the contact the primary affiliation's job title when the save changed only
      the affiliation's
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
        the contact the primary affiliation's job title when the save changed
        only the affiliation's
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
        the contact the primary affiliation's job title when the save changed
        only the affiliation's
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
        the primary affiliation the contact's job title when the save changed
        the contact's
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
        the contact the primary affiliation's job title when the save changed
        only the affiliation's
      - >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
        a primary affiliation with no job title the contact's
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0056.spec.ts#CRM-ACC-0056
    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-009-14
    title: Client affiliation ids are lowercase UUIDv7 and unique
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-14
    preconditions: No additional setup is required.
    steps: >-
      Send a lowercase UUIDv7, an uppercase one, a UUIDv4, a used id and one id
      twice.
    expected: Accepted; rejected for each other case.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-776
      - TST-CRM-ENT-786
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0208.spec.ts#CRM-ACC-0208
    category: validation
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0208.spec.ts#CRM-ACC-0208
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0208.spec.ts#CRM-ACC-0208
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0208.spec.ts#CRM-ACC-0208
      - >-
        services-crm:modules/entities/tests/e2e/contact-information-sources.e2e.spec.js#refuses
        400 a second contact naming the same affiliation id, saying another
        affiliation has it
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
        a UUIDv7 written in uppercase, which is not the canonical form
    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-009-15
    title: the organization's people list
    level: browser
    proves:
      - BR-CAP-CRM-ENT-009-15
    preconditions: 'Prepare an organization with current, ended and upcoming affiliations.'
    steps: >-
      List by default, past, future, all, as_of, by type, by role, paged; as an
      organizations-only reader.
    expected: Each filter as named with totals; 403 permission_missing.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-788
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
      403 permission_missing to a caller who may read organizations but not
      contacts
    category: permission
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        403 permission_missing to a caller who may read organizations but not
        contacts
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        403 permission_missing to a caller who may read organizations but not
        contacts
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        the affiliations effective today by default, in contact order
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        the ended affiliations for effective=past
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        the affiliations effective on the as_of date
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        only the affiliations holding the contact_role_id given
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        200 with no affiliations for an organization the tenant does not have
      - >-
        services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
        403 permission_missing to a caller who may read organizations but not
        contacts
      - >-
        services-crm:modules/entities/tests/unit/api/organization-affiliation-page-mapper.spec.js#answers
        the total beside the page count
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0207.spec.ts#CRM-ACC-0207
    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-009-16
    title: 'the journey: record an employer and move to a new primary'
    level: browser
    proves:
      - AS-JNY-CRM-ENT-002-01
      - AS-JNY-CRM-ENT-002-02
      - AS-JNY-CRM-ENT-002-03
    preconditions: Prepare two organizations with an Employer type and default links.
    steps: Run JNY-CRM-ENT-002.
    expected: >-
      One primary at the new employer; the old affiliation history with ended
      roles, listed under Past; only ticked links made.
    negative: false
    data_needs: 'Use organizations, roles and default links.'
    bound_to:
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts#CRM-ACC-0201
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts#CRM-ACC-0201
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts#CRM-ACC-0201
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0170.spec.ts#CRM-ACC-0170
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts#CRM-ACC-0201
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0185.spec.ts#CRM-ACC-0185
    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-009-17
    title: Preserve the first relationship correction when edits compete
    category: concurrency
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-02
      - BR-CAP-CRM-ENT-004-01
    preconditions: Two editors read one person with a current affiliation.
    steps: >-
      Save a title correction from the first editor; submit a different
      relationship correction from the second at the old person version.
    expected: >-
      The second save conflicts and the first relationship correction remains
      stored.
    negative: true
    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 400
      naming sys_version to a patch that tests no version
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming sys_version to a patch that tests no version
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming sys_version to a patch that tests no version
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-18
    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-009-19
    title: Relationship edits invalidate an open contact edit
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-01
      - BR-CAP-CRM-ENT-009-02
    preconditions: >-
      Two editors read the same person; one uses the organization people
      workflow.
    steps: >-
      Save an affiliation correction from the organization, then submit the
      original contact edit at its old version.
    expected: >-
      The second editor receives a conflict and the relationship correction
      remains.
    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 400
      naming sys_version to a patch that tests no version;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-009
    workflows:
      - WF-CRM-ENT-002
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming sys_version to a patch that tests no version;
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming sys_version to a patch that tests no version;
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-20
    title: Remove a relationship and resolve its dependent details together
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-006-10
      - BR-CAP-CRM-ENT-009-02
    preconditions: A person has one affiliation supplying a linked address and phone.
    steps: >-
      Try removing only the affiliation; then remove it while copying the
      address and removing the phone.
    expected: >-
      The incomplete removal changes nothing; the complete save removes the tie
      and handles both dependent details.
    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/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    exercises:
      - CAP-CRM-ENT-006
      - CAP-CRM-ENT-009
    workflows:
      - WF-CRM-ENT-001
      - WF-CRM-ENT-002
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
        a save that removes the affiliation and keeps the rows linked through
        it;
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
        a save that removes the affiliation and keeps the rows linked through
        it;
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-21
    title: Keep a one-day affiliation current on its last day
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-07
      - BR-CAP-CRM-ENT-010-02
    preconditions: >-
      A person has an affiliation whose start and end are the same workspace
      date.
    steps: Read the people list on that date and again on the next workspace date.
    expected: The tie is current on its single day and past on the following day.
    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#refuses
      an affiliation that ends before it starts;
      services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.business-dates.integration.spec.js#reads
      a tie ending on the tenant's today as current
    exercises:
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-010
    workflows:
      - WF-CRM-ENT-002
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        an affiliation that ends before it starts;
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.business-dates.integration.spec.js#reads
        a tie ending on the tenant's today as current
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        an affiliation that ends before it starts;
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.business-dates.integration.spec.js#reads
        a tie ending on the tenant's today as current
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-22
    title: End a primary tie and its open responsibility period
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-06
      - BR-CAP-CRM-ENT-011-04
    preconditions: A person’s primary affiliation has an open role period.
    steps: >-
      Save an end date for that affiliation; read the relationship and role
      periods.
    expected: >-
      Primary designation clears and the open role ends with the affiliation
      without promoting another tie.
    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/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
      the open roles of an affiliation the save ends, on the same day
    exercises:
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-011
    workflows:
      - WF-CRM-ENT-001
      - WF-CRM-ENT-002
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
        nothing primary in its place;
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
        the open roles of an affiliation the save ends, on the same day
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
        nothing primary in its place;
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
        the open roles of an affiliation the save ends, on the same day
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-23
    title: The configured default supplies the Organization command
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-11
      - BR-CAP-CRM-ENT-017-06
    preconditions: An administrator selects a nondefault affiliation type for the workspace.
    steps: >-
      Set a person’s primary organization using the Organization command and
      inspect the created tie.
    expected: The saved relationship uses the selected workspace default type.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
      the previous primary the day before the effective date by default, and its
      open roles with it;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses
      a caller who may only read affiliation types the change of the default
      (403)
    exercises:
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-017
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
        the previous primary the day before the effective date by default, and
        its open roles with it;
        services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses
        a caller who may only read affiliation types the change of the default
        (403)
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
        the previous primary the day before the effective date by default, and
        its open roles with it;
        services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses
        a caller who may only read affiliation types the change of the default
        (403)
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-24
    title: Establish current and future ties and remove an erroneous past tie
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-03
      - BR-CAP-CRM-ENT-009-07
    preconditions: >-
      A person has one ended affiliation and two unused organization and type
      combinations.
    steps: >-
      Add one future affiliation and one current affiliation; then submit an
      explicit list without the erroneous past affiliation.
    expected: >-
      The new ties read as upcoming and current; the explicitly omitted past tie
      and its owned role periods are removed.
    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/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
        a new link through an upcoming affiliation, naming the path
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
        a new link through an upcoming affiliation, naming the path
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-009-25
    title: Exercise every declared authority independently
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-009-15
    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.contacts.read
      - crm.affiliation-types.read
      - crm.organizations.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/e2e/contact-affiliations.e2e.spec.js#answers
    400 for an organization the tenant does not have
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#saves
    no contact for a write it refused
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#matches
    each stored affiliation once: a second statement of it stays new
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0205.spec.ts#CRM-ACC-0205
  - >-
    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/unit/services/persistence/contact-affiliations.spec.js#unmarks
    the previous primary when another affiliation is marked primary
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#unmarks
    the old primary, which stays open and current
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    400 for two affiliations newly marked primary
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
    a primary affiliation that starts after today
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
    a new primary affiliation with an end date
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    400 for a primary affiliation with an end date, naming the field
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0176.spec.ts#CRM-ACC-0176
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
    nothing primary in its place
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#clears
    the flag of a stored primary the save ends, and promotes nothing
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#clears
    its primary flag
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0174.spec.ts#CRM-ACC-0174
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    an affiliation that ends before it starts
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0173.spec.ts#CRM-ACC-0173
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
    two overlapping affiliations to the same organization of the same type
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#finds
    periods that share their last and first day overlapping
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#finds
    an undated period overlapping every other
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#keeps
    two current affiliations to the same organization of different types
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#accepts
    two affiliations to the same organization of the same type in separate
    periods
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0179.spec.ts#CRM-ACC-0179
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    to move the affiliation to another organization under the same id
    (docs/affiliations/README.md §0b.2)
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0187.spec.ts#CRM-ACC-0187
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts
    only current and upcoming affiliations against the limit: ended history is
    unlimited (M2-D5)
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
    more than 100 affiliations, and takes 100
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#ends
    the previous primary affiliation yesterday by default ('end'), no longer
    primary and now past
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#makes
    one primary affiliation of the default type other, starting today,
    open-ended, with the contact’s job title
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#keeps
    the previous affiliation open and current, no longer primary, with 'keep'
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#starts
    the new affiliation on an earlier effective date and ends the previous one
    the day before it
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#leaves
    no primary organization when the organization is cleared, and ends the
    previous one yesterday
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#removes
    a previous primary that began on the day it is ended before, as a stay of no
    days
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    400 for an effective date after today, naming the field
  - 'react-console:lib/affiliations/section.ts:affiliationBeingLeft'
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
    the contact the primary affiliation's job title when the save changed only
    the affiliation's
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
    the primary affiliation the contact's job title when the save changed the
    contact's
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives
    a primary affiliation with no job title the contact's
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0056.spec.ts#CRM-ACC-0056
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0208.spec.ts#CRM-ACC-0208
  - >-
    services-crm:modules/entities/tests/e2e/contact-information-sources.e2e.spec.js#refuses
    400 a second contact naming the same affiliation id, saying another
    affiliation has it
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
    a UUIDv7 written in uppercase, which is not the canonical form
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    403 permission_missing to a caller who may read organizations but not
    contacts
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    the affiliations effective today by default, in contact order
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    the ended affiliations for effective=past
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    the affiliations effective on the as_of date
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    only the affiliations holding the contact_role_id given
  - >-
    services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers
    200 with no affiliations for an organization the tenant does not have
  - >-
    services-crm:modules/entities/tests/unit/api/organization-affiliation-page-mapper.spec.js#answers
    the total beside the page count
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0207.spec.ts#CRM-ACC-0207
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts#CRM-ACC-0201
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0170.spec.ts#CRM-ACC-0170
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0185.spec.ts#CRM-ACC-0185
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
    naming sys_version to a patch that tests no version
  - >-
    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 400
    naming sys_version to a patch that tests no version;
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
    a save that removes the affiliation and keeps the rows linked through it;
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts#CRM-ACC-0202
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    an affiliation that ends before it starts;
    services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.business-dates.integration.spec.js#reads
    a tie ending on the tenant's today as current
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
    nothing primary in its place;
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
    the open roles of an affiliation the save ends, on the same day
  - >-
    services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
    the previous primary the day before the effective date by default, and its
    open roles with it;
    services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses
    a caller who may only read affiliation types the change of the default (403)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
    a new link through an upcoming affiliation, naming the path
  - >-
    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: covered
  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-005
    from: new
    to: upcoming
    scenarios:
      - ATS-CRM-ENT-009-24
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: new
    to: current
    scenarios:
      - ATS-CRM-ENT-009-24
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: current
    to: current primary
    scenarios:
      - ATS-CRM-ENT-009-04
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: current primary
    to: current
    scenarios:
      - ATS-CRM-ENT-009-11
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: current
    to: past
    scenarios:
      - ATS-CRM-ENT-009-06
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: current primary
    to: past
    scenarios:
      - ATS-CRM-ENT-009-06
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: current
    to: removed
    scenarios:
      - ATS-CRM-ENT-009-03
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-005
    from: past
    to: removed
    scenarios:
      - ATS-CRM-ENT-009-24
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-005-affiliation-and-role-period-lifecycle.md:transitions
coverage_notes: {}
coverage_findings:
  - FND-CRM-ENT-057
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.contacts.update
    where_checked: api
    scenario: ATS-CRM-ENT-009-25
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md
  - key: crm.contacts.read
    where_checked: api
    scenario: ATS-CRM-ENT-009-25
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md
  - key: crm.affiliation-types.read
    where_checked: api
    scenario: ATS-CRM-ENT-009-25
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md
  - key: crm.organizations.read
    where_checked: api
    scenario: ATS-CRM-ENT-009-25
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md
related_coverage:
  - capability: CAP-CRM-ENT-003
    scenarios:
      - ATS-CRM-ENT-009-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md:related
  - capability: CAP-CRM-ENT-006
    scenarios:
      - ATS-CRM-ENT-009-20
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md:related
  - capability: CAP-CRM-ENT-010
    scenarios:
      - ATS-CRM-ENT-009-21
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md:related
  - capability: CAP-CRM-ENT-011
    scenarios:
      - ATS-CRM-ENT-009-22
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.md:related
  - capability: CAP-CRM-ENT-017
    scenarios:
      - ATS-CRM-ENT-009-23
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-009-record-who-works-where-and-the-person-s-primary-organization.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 a person’s organizational relationships
## 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-009-01` | `ATS-CRM-ENT-009-01` |
| `BR-CAP-CRM-ENT-009-02` | `ATS-CRM-ENT-009-02`, `ATS-CRM-ENT-009-17`, `ATS-CRM-ENT-009-19`, `ATS-CRM-ENT-009-20` |
| `BR-CAP-CRM-ENT-009-03` | `ATS-CRM-ENT-009-03`, `ATS-CRM-ENT-009-24` |
| `BR-CAP-CRM-ENT-009-04` | `ATS-CRM-ENT-009-04`, `ATS-CRM-ENT-009-18` |
| `BR-CAP-CRM-ENT-009-05` | `ATS-CRM-ENT-009-05` |
| `BR-CAP-CRM-ENT-009-06` | `ATS-CRM-ENT-009-06`, `ATS-CRM-ENT-009-22` |
| `BR-CAP-CRM-ENT-009-07` | `ATS-CRM-ENT-009-07`, `ATS-CRM-ENT-009-21`, `ATS-CRM-ENT-009-24` |
| `BR-CAP-CRM-ENT-009-08` | `ATS-CRM-ENT-009-08` |
| `BR-CAP-CRM-ENT-009-09` | `ATS-CRM-ENT-009-09` |
| `BR-CAP-CRM-ENT-009-10` | `ATS-CRM-ENT-009-10` |
| `BR-CAP-CRM-ENT-009-11` | `ATS-CRM-ENT-009-11`, `ATS-CRM-ENT-009-23` |
| `BR-CAP-CRM-ENT-009-12` | `ATS-CRM-ENT-009-12` |
| `BR-CAP-CRM-ENT-009-13` | `ATS-CRM-ENT-009-13` |
| `BR-CAP-CRM-ENT-009-14` | `ATS-CRM-ENT-009-14` |
| `BR-CAP-CRM-ENT-009-15` | `ATS-CRM-ENT-009-15`, `ATS-CRM-ENT-009-25` |

## Interactions With Related Capabilities

The interaction scenarios exercise locate people and organizations; share organization contact points with affiliated people; keep dated relationships current; maintain responsibilities on organizational relationships; configure affiliation types and contact roles. 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.
