---
id: ATS-CRM-ENT-004
scope: CAP-CRM-ENT-004
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-004-01
    title: >-
      Writes need the version read; stale writes are conflicts that name the
      change
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-01
      - INV-DOM-CRM-ENT-033-01
    preconditions: Prepare a contact at version n.
    steps: >-
      PUT without sys_version; PATCH without a test; two patches at n; a PUT at
      n after a colleague saved.
    expected: >-
      400 naming sys_version twice; the second patch 409 with the first value
      preserved; 409 record_changed naming the colleague.
    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
      naming sys_version to a patch that tests no version
    category: concurrency
    action_evidence:
      provenance: test
      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: test
      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:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        for a replacement that names no version
      - >-
        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/e2e/contacts.e2e.spec.js#answers 409
        to a second patch at the version the first one moved on from
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the
        first writer’s value
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        with the code record_changed and the latest change when a colleague
        saved first (M1-D6)
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a version the organization has moved past
    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-004-02
    title: an unmodified save changes nothing
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-02
      - INV-DOM-CRM-ENT-033-02
    preconditions: Prepare a stored contact.
    steps: Replace it with exactly what was read; then with a stale version.
    expected: 'Same version, stamp and no revision; the stale one rejected.'
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
      no revision
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
        no revision
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
        no revision
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#answers
        the version it had
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#keeps
        the stored version and last-modified stamp
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
        no revision
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#is
        refused as stale, though the save changes nothing
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0060.spec.ts#CRM-ACC-0060
    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-004-03
    title: a patch changes what it names and null clears
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-03
    preconditions: Prepare a contact with a job title and two phones.
    steps: >-
      Patch replace one field, edit one element, add one, remove one, then
      remove a field.
    expected: 'Each change applied, others untouched, version advanced.'
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#sets the
      field the patch replaced and bumps the version
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#sets the
        field the patch replaced and bumps the version
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#sets the
        field the patch replaced and bumps the version
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#sets the
        field the patch replaced and bumps the version
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#edits the
        one element the patch pointed at
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#appends the
        element the patch added
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#removes the
        element the patch removed
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#clears a
        field a later patch removes
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#clears
        the field the patch removed
    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-004-04
    title: 'a replacement clears what it omits, except affiliations and custom values'
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-04
    preconditions: 'Prepare a contact with addresses, affiliations and custom values.'
    steps: >-
      Replace it without a field, without addresses, without affiliations,
      without custom_fields.
    expected: >-
      Field cleared, addresses emptied, affiliations and custom values
      preserved.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-786
      - TST-CRM-ENT-776
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
      the collection
    category: edge
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
        the collection
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
        the collection
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#clears
        the field, as every replacement does
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
        the collection
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
        every custom field as it was when the map is omitted
      - >-
        react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
    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-004-05
    title: >-
      Elements keep their ids; foreign or repeated ids are rejected; types can
      swap
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-05
    preconditions: Prepare a contact with home and work addresses.
    steps: >-
      Replace with home edited and work omitted; with an element id of another
      contact; with one id twice; swap two types.
    expected: >-
      Home preserved under its id at version 2, work removed; 400 naming the
      element; 400; swap accepted.
    negative: true
    data_needs: Use two contacts.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      the second address into the type the first left, and the preferred flag
      with it
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the
        edited element under its id, with its own version bumped
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#removes the
        element the body left out and adds the one it named
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        the update as ObjectInvalid, naming the element
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps
        the edited element under its id, with its own version bumped
    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-004-06
    title: 'Read-only fields: unmodified ignored, changed rejected'
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-06
    preconditions: Prepare a contact and an organization as read.
    steps: 'Send each back complete; then with a changed full name, number or origin.'
    expected: >-
      Unchanged read-only values are accepted, changed values are rejected, and
      derived ancestry and compliance values are ignored.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-787
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
      a replacement whose contact number differs from the stored one, naming
      number
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        a replacement whose contact number differs from the stored one, naming
        number
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
        a replacement whose contact number differs from the stored one, naming
        number
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 200
        for a replacement that sends back everything a read answered, the
        computed names included (M1-D5)
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming the full name for a replacement that sends back another than the
        stored one (M1-D5)
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
        naming origin_service for a replacement that alters it (M1-D5)
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#writes
        nothing when it refuses a changed computed name
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a replacement whose number differs from the stored one, naming number
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0072.spec.ts#CRM-ACC-0072
    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-004-07
    title: a rejected save leaves every row as it was
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-07
      - INV-DOM-CRM-ENT-033-03
    preconditions: Prepare a contact with elements.
    steps: Replace it with an element of another contact.
    expected: Rejected; the contact row rolled back and every element as it was.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
      back the contact row the transaction had already updated
    category: failure
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
        back the contact row the transaction had already updated
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
        back the contact row the transaction had already updated
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
        back the contact row the transaction had already updated
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#leaves
        every row as it was
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a replacement with an invalid custom value as a whole, its standard
        change included
    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-004-08
    title: Every problem at once
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-08
    preconditions: No additional setup is required.
    steps: >-
      Create an organization that is its own parent with two preferred
      addresses.
    expected: One refusal listing both.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-787
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
      the create as ObjectInvalid, listing both problems
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the create as ObjectInvalid, listing both problems
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the create as ObjectInvalid, listing both problems
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the create as ObjectInvalid, listing both problems
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#names
        every overlong name part of one request at once
    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-004-09
    title: Strings are trimmed and blanks are nothing
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-09
    preconditions: No additional setup is required.
    steps: 'Send a blank e-mail element, a padded name, an empty-string name.'
    expected: Element dropped; name trimmed; required name rejected.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-788
      - TST-CRM-ENT-786
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops
      a blank e-mail element rather than refusing the null the string standard
      made of it
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops
        a blank e-mail element rather than refusing the null the string standard
        made of it
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops
        a blank e-mail element rather than refusing the null the string standard
        made of it
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops
        a blank e-mail element rather than refusing the null the string standard
        made of it
      - >-
        services-crm:modules/entities/tests/unit/api/organization-api-mapper.spec.js#accepts
        a name with spaces around it, trimmed
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 200
        for a body with a blank e-mail element, dropping the element
        (DEF-CRM-0013)
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0008.spec.ts#CRM-ACC-0008
    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-004-10
    title: a system change is a conflict naming the system
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-10
    preconditions: Prepare a contact read by an editor; its spouse then deleted.
    steps: The editor saves at the version read.
    expected: 409 naming the system.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
      naming the system, not a colleague, when the system cleared the spouse
      link while the contact was edited (M1-D6)
    category: interaction
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        naming the system, not a colleague, when the system cleared the spouse
        link while the contact was edited (M1-D6)
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        naming the system, not a colleague, when the system cleared the spouse
        link while the contact was edited (M1-D6)
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        naming the system, not a colleague, when the system cleared the spouse
        link while the contact was edited (M1-D6)
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0068.spec.ts#CRM-ACC-0068
    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-004-11
    title: the editor patches only what changed and handles conflicts safely
    level: browser
    proves:
      - BR-CAP-CRM-ENT-004-11
    preconditions: Prepare two staff members on one contact.
    steps: >-
      Edit several fields through Review Changes; then save after a colleague;
      press Keep Editing.
    expected: >-
      One patch of the changed keys, one version bump, one revision; the
      conflict notice; no silent version advance.
    negative: true
    data_needs: Use two personas.
    bound_to:
      - TST-CRM-ENT-774
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009
    category: concurrency
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009
    test_anchors:
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0006.spec.ts#CRM-ACC-0006
      - >-
        react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009
    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-004-13
    title: A whole-record save must carry every replaceable property
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-13
    preconditions: >-
      A contact exists with a job title and two addresses; the caller holds
      crm.contacts.update.
    steps: >-
      Read the contact; send it back unchanged with PUT; then send a PUT that
      omits job_title; then send a PUT with job_title null.
    expected: >-
      The unchanged save succeeds without changing the version; the omitting
      save is refused with 400 naming job_title and writes nothing; the null
      save clears the job title and keeps both addresses.
    negative: true
    data_needs: >-
      One contact with a job title and two typed addresses in a disposable
      workspace.
    bound_to: []
    provenance: proposed
    source: >-
      OQ-CRM-ENT-001 decided 2026-10-22: A;
      services-crm:docs/entities-api.md#Not yet under the complete-payload rule
    category: rule
    action_evidence:
      provenance: proposed
      source: >-
        OQ-CRM-ENT-001 decided 2026-10-22: A;
        services-crm:docs/entities-api.md#Not yet under the complete-payload
        rule
    assertion_evidence:
      provenance: proposed
      source: >-
        OQ-CRM-ENT-001 decided 2026-10-22: A;
        services-crm:docs/entities-api.md#Not yet under the complete-payload
        rule
    test_anchors: []
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-14
    title: Reject corrections without the matching update authority
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-01
      - BR-CAP-CRM-ENT-003-09
    preconditions: A caller can read both record kinds but has neither update permission.
    steps: >-
      Attempt a version-tested correction on a contact and an organization;
      repeat with each matching update grant.
    expected: >-
      Unauthorized corrections are refused and leave the saved values intact;
      the matching grant allows the corresponding eligible correction.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/aws/api/contacts.routes.ts;
        services-crm:modules/entities/aws/api/organizations.routes.ts
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/aws/api/contacts.routes.ts;
        services-crm:modules/entities/aws/api/organizations.routes.ts
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-15
    title: Correct a newly established person
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-001-02
      - BR-CAP-CRM-ENT-004-01
    preconditions: A person has just been created.
    steps: Read its version and correct its name parts through a version-tested save.
    expected: >-
      The corrected record retains its identity and shows the newly derived
      names.
    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 400
      naming sys_version to a patch that tests no version
    exercises:
      - CAP-CRM-ENT-001
      - CAP-CRM-ENT-004
    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 400
        naming sys_version to a patch that tests no version
    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 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-004-16
    title: Correct a contact point without replacing its identity
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-05
      - BR-CAP-CRM-ENT-005-10
    preconditions: A person’s address has a known element identity and version.
    steps: Patch its city and read the collection and record version.
    expected: >-
      The city changes under the original element identity and the aggregate
      advances once.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      the second address into the type the first left, and the preferred flag
      with it;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
    workflows:
      - WF-CRM-ENT-002
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it;
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
        the second address into the type the first left, and the preferred flag
        with it;
        services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-17
    title: Keep all values after a mixed correction fails
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-07
      - BR-CAP-CRM-ENT-008-03
    preconditions: A person has saved notes and a valid custom number.
    steps: >-
      Change the notes while submitting an invalid custom number in the same
      save.
    expected: Neither proposed change survives the refusal.
    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#rolls
      back the contact row the transaction had already updated;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
      yes for a yes/no field: no truthiness
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-008
    workflows:
      - WF-CRM-ENT-002
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
        back the contact row the transaction had already updated;
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        yes for a yes/no field: no truthiness
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
        back the contact row the transaction had already updated;
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        yes for a yes/no field: no truthiness
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-18
    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-004-19
    title: Archive invalidates an open correction
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-01
      - BR-CAP-CRM-ENT-013-05
    preconditions: An editor reads a live person while an administrator can archive it.
    steps: Archive the person; submit the editor’s pending correction.
    expected: >-
      The correction is refused and the archived record retains its pre-edit
      values.
    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;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      replacement, 409 RecordArchived
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-013
    workflows: []
    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;
        services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
        replacement, 409 RecordArchived
    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;
        services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
        replacement, 409 RecordArchived
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-20
    title: A full lock blocks an ordinary correction
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-004-01
      - BR-CAP-CRM-ENT-015-05
    preconditions: An editor has read a record and an administrator can change its lock.
    steps: >-
      Apply a full lock; attempt the pending ordinary correction; unlock and
      reread before saving again.
    expected: >-
      The locked attempt preserves the record; a newly based correction succeeds
      after release.
    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;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
      IllegalOperation, not 403, for a replacement of a contact locked against
      updates, the lock sent back unchanged
    exercises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-015
    workflows: []
    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;
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        IllegalOperation, not 403, for a replacement of a contact locked against
        updates, the lock sent back unchanged
    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;
        services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
        IllegalOperation, not 403, for a replacement of a contact locked against
        updates, the lock sent back unchanged
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-21
    title: Change person standing and preserve an unavailable email reservation
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-ENT-001-10
      - BR-CAP-CRM-ENT-005-02
      - BR-CAP-CRM-ENT-004-01
    preconditions: >-
      A person is active with a primary email; another run-owned person can
      claim that email after its reservation is released.
    steps: >-
      Save the first person inactive, then deceased; let the other person claim
      its email and attempt reactivation. Release the competing reservation and
      reactivate, then mark duplicate.
    expected: >-
      The stored standing follows each accepted save. The conflicting
      reactivation preserves deceased status; reactivation succeeds after
      release and duplicate status remains supported.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
      to make an inactive contact active while another contact holds its primary
      address
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
        to make an inactive contact active while another contact holds its
        primary address
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-22
    title: Change organization status through versioned corrections
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-08
      - BR-CAP-CRM-ENT-004-01
    preconditions: An active organization has a known current version.
    steps: >-
      Change it to inactive, then active, then duplicate, reading each result
      before the next save.
    expected: >-
      Each successful status change preserves organization identity and is based
      on the latest saved version.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
      a status outside the closed list by organizations_status_check
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        a status outside the closed list by organizations_status_check
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        a status outside the closed list by organizations_status_check
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-004-23
    title: Exercise every declared authority independently
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-07
      - BR-CAP-CRM-ENT-003-09
    preconditions: >-
      An eligible record and configuration fixture exist for each operation; the
      caller can reach only the run’s workspace.
    steps: >-
      For each declared permission, execute its matching operation with that
      permission, then without it while retaining the other required grants.
      Read affected records after every refused mutation.
    expected: >-
      The authorized case reaches the operation; the missing-grant case is
      refused and preserves saved data. Record access alone does not authorize
      the other declared actions.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:aws/crm.service.ts:ApplicationPermissions;
      services-crm:modules/entities/aws/api/entities.routes.ts
    permission_keys:
      - crm.contacts.update
      - crm.organizations.update
      - crm.contacts.unlock
      - crm.organizations.unlock
    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.'
  - id: ATS-CRM-ENT-004-24
    title: A stale hierarchy correction preserves the colleague’s parent move
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-07
      - BR-CAP-CRM-ENT-004-01
    preconditions: >-
      An organization editor has read a chapter; a colleague has a current
      version and can move it between two roots.
    steps: >-
      Save the colleague’s parent move; submit the first editor’s hierarchy
      correction at its old version and read the chapter’s ancestry.
    expected: >-
      The stale correction is refused and ancestry still follows the colleague’s
      committed parent move.
    negative: true
    data_needs: Run-owned chapter and two roots in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 for a version the organization has moved past;
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-004
    workflows:
      - WF-CRM-ENT-003
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a version the organization has moved past;
        rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a version the organization has moved past;
        rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
evidence:
  - >-
    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/e2e/contacts.e2e.spec.js#answers 400 for
    a replacement that names no version
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 to
    a second patch at the version the first one moved on from
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the first
    writer’s value
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
    with the code record_changed and the latest change when a colleague saved
    first (M1-D6)
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    409 for a version the organization has moved past
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
    no revision
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#answers
    the version it had
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#keeps
    the stored version and last-modified stamp
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#is
    refused as stale, though the save changes nothing
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0060.spec.ts#CRM-ACC-0060
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#sets the field
    the patch replaced and bumps the version
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#edits the one
    element the patch pointed at
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#appends the
    element the patch added
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#removes the
    element the patch removed
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#clears a field
    a later patch removes
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#clears the
    field the patch removed
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
    the collection
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#clears
    the field, as every replacement does
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
    every custom field as it was when the map is omitted
  - >-
    react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts#CRM-ACC-0177
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
    the second address into the type the first left, and the preferred flag with
    it
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the
    edited element under its id, with its own version bumped
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#removes the
    element the body left out and adds the one it named
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    the update as ObjectInvalid, naming the element
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps the
    edited element under its id, with its own version bumped
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
    a replacement whose contact number differs from the stored one, naming
    number
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 200 for
    a replacement that sends back everything a read answered, the computed names
    included (M1-D5)
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
    naming the full name for a replacement that sends back another than the
    stored one (M1-D5)
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
    naming origin_service for a replacement that alters it (M1-D5)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#writes
    nothing when it refuses a changed computed name
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    a replacement whose number differs from the stored one, naming number
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0072.spec.ts#CRM-ACC-0072
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
    back the contact row the transaction had already updated
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#leaves
    every row as it was
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a replacement with an invalid custom value as a whole, its standard change
    included
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
    the create as ObjectInvalid, listing both problems
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#names
    every overlong name part of one request at once
  - >-
    services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops
    a blank e-mail element rather than refusing the null the string standard
    made of it
  - >-
    services-crm:modules/entities/tests/unit/api/organization-api-mapper.spec.js#accepts
    a name with spaces around it, trimmed
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 200 for
    a body with a blank e-mail element, dropping the element (DEF-CRM-0013)
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0008.spec.ts#CRM-ACC-0008
  - >-
    services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
    naming the system, not a colleague, when the system cleared the spouse link
    while the contact was edited (M1-D6)
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0068.spec.ts#CRM-ACC-0068
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009
  - >-
    react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0006.spec.ts#CRM-ACC-0006
  - >-
    OQ-CRM-ENT-001 decided 2026-10-22: A; services-crm:docs/entities-api.md#Not
    yet under the complete-payload rule
  - >-
    services-crm:modules/entities/aws/api/contacts.routes.ts;
    services-crm:modules/entities/aws/api/organizations.routes.ts
  - >-
    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 400
    naming sys_version to a patch that tests no version
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
    the second address into the type the first left, and the preferred flag with
    it;
    services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
    back the contact row the transaction had already updated;
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    yes for a yes/no field: no truthiness
  - >-
    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/e2e/contacts.e2e.spec.js#answers 400
    naming sys_version to a patch that tests no version;
    services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
    replacement, 409 RecordArchived
  - >-
    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/e2e/contacts.e2e.spec.js#answers 409
    IllegalOperation, not 403, for a replacement of a contact locked against
    updates, the lock sent back unchanged
  - >-
    services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses
    to make an inactive contact active while another contact holds its primary
    address
  - >-
    services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
    a status outside the closed list by organizations_status_check
  - >-
    services-crm:aws/crm.service.ts:ApplicationPermissions;
    services-crm:modules/entities/aws/api/entities.routes.ts
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    409 for a version the organization has moved past;
    rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: 'gap: FND-CRM-ENT-074'
  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-001
    from: active
    to: inactive
    scenarios:
      - ATS-CRM-ENT-004-21
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-001-contact-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-001
    from: active
    to: deceased
    scenarios:
      - ATS-CRM-ENT-004-21
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-001-contact-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-001
    from: inactive
    to: active
    scenarios:
      - ATS-CRM-ENT-004-21
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-001-contact-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-001
    from: active
    to: duplicate
    scenarios:
      - ATS-CRM-ENT-004-21
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-001-contact-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-002
    from: active
    to: inactive
    scenarios:
      - ATS-CRM-ENT-004-22
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-002-organization-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-002
    from: inactive
    to: active
    scenarios:
      - ATS-CRM-ENT-004-22
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-002-organization-record-lifecycle.md:transitions
  - state_model: STATE-CRM-ENT-002
    from: active
    to: duplicate
    scenarios:
      - ATS-CRM-ENT-004-22
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-002-organization-record-lifecycle.md:transitions
coverage_notes:
  boundary:
    reason: >-
      No detailed boundary scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-074
coverage_findings:
  - FND-CRM-ENT-074
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.contacts.update
    where_checked: api
    scenario: ATS-CRM-ENT-004-23
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md
  - key: crm.organizations.update
    where_checked: api
    scenario: ATS-CRM-ENT-004-23
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md
  - key: crm.contacts.unlock
    where_checked: service
    scenario: ATS-CRM-ENT-004-23
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md
  - key: crm.organizations.unlock
    where_checked: service
    scenario: ATS-CRM-ENT-004-23
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md
related_coverage:
  - capability: CAP-CRM-ENT-001
    scenarios:
      - ATS-CRM-ENT-004-15
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md:related
  - capability: CAP-CRM-ENT-002
    provenance: documented
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 for a version the organization has moved past;
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
    scenarios:
      - ATS-CRM-ENT-004-24
  - capability: CAP-CRM-ENT-005
    scenarios:
      - ATS-CRM-ENT-004-16
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md:related
  - capability: CAP-CRM-ENT-009
    scenarios:
      - ATS-CRM-ENT-004-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md:related
  - capability: CAP-CRM-ENT-013
    scenarios:
      - ATS-CRM-ENT-004-19
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.md:related
  - capability: CAP-CRM-ENT-015
    scenarios:
      - ATS-CRM-ENT-004-20
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-004-change-a-record-without-losing-anyone-s-edit.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
---

# Save corrections without overwriting another change
## 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-004-01` | `ATS-CRM-ENT-004-01`, `ATS-CRM-ENT-004-14`, `ATS-CRM-ENT-004-15`, `ATS-CRM-ENT-004-18`, `ATS-CRM-ENT-004-19`, `ATS-CRM-ENT-004-20`, `ATS-CRM-ENT-004-21`, `ATS-CRM-ENT-004-22` |
| `BR-CAP-CRM-ENT-004-02` | `ATS-CRM-ENT-004-02` |
| `BR-CAP-CRM-ENT-004-03` | `ATS-CRM-ENT-004-03` |
| `BR-CAP-CRM-ENT-004-04` | `ATS-CRM-ENT-004-04` |
| `BR-CAP-CRM-ENT-004-05` | `ATS-CRM-ENT-004-05`, `ATS-CRM-ENT-004-16` |
| `BR-CAP-CRM-ENT-004-06` | `ATS-CRM-ENT-004-06` |
| `BR-CAP-CRM-ENT-004-07` | `ATS-CRM-ENT-004-07`, `ATS-CRM-ENT-004-17` |
| `BR-CAP-CRM-ENT-004-08` | `ATS-CRM-ENT-004-08` |
| `BR-CAP-CRM-ENT-004-09` | `ATS-CRM-ENT-004-09` |
| `BR-CAP-CRM-ENT-004-10` | `ATS-CRM-ENT-004-10` |
| `BR-CAP-CRM-ENT-004-11` | `ATS-CRM-ENT-004-11` |
| `BR-CAP-CRM-ENT-004-13` | `ATS-CRM-ENT-004-13` |

## Interactions With Related Capabilities

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