---
id: ATS-CRM-ENT-008
scope: CAP-CRM-ENT-008
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-008-01
    title: Only named keys change
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-01
    preconditions: Prepare a contact with several custom values.
    steps: >-
      Replace with the map omitted, empty, and naming one key set, one null and
      one empty selection; patch removing the map; send custom_fields null.
    expected: Unmodified; unmodified; exactly those keys; rejected; rejected.
    negative: true
    data_needs: Use two custom fields.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#sets
      the key it sends, clears the key sent null and the selection sent empty,
      and leaves the others
    category: edge
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#sets
        the key it sends, clears the key sent null and the selection sent empty,
        and leaves the others
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#sets
        the key it sends, clears the key sent null and the selection sent empty,
        and leaves the others
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
        every custom field as it was when the map is omitted
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
        every custom field as it was for an empty map
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#sets
        the key it sends, clears the key sent null and the selection sent empty,
        and leaves the others
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#merges
        a whole-map replacement key by key, never clearing what it leaves out
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a patch that removes the whole map
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        custom_fields: null on a replacement
      - >-
        services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
        custom_fields: null
    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-008-02
    title: 'Unknown, foreign and archived fields are rejected'
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-02
      - INV-DOM-CRM-ENT-001-12
    preconditions: 'Prepare two tenants with their own fields, one archived.'
    steps: >-
      Write an unknown key, the other workspace’s key, a contact key on an
      organization, the archived field.
    expected: 400 at each path.
    negative: true
    data_needs: Use two tenants.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
      a write to an archived field
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a write to an archived field
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a write to an archived field
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a key the object does not have, at its path
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        another tenant's field as unknown in this tenant
      - >-
        services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
        a contact's custom field as unknown on an organization
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a write to an archived 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-008-03
    title: Values are converted or rejected by type
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-03
    preconditions: Prepare one field of every type.
    steps: >-
      Send digits for a number, "false", "yes", an over-long text, an archived
      option, a date-time without offset, a missing reference, an inexact
      percentage.
    expected: Converted; converted; rejected each other case.
    negative: true
    data_needs: Use fields of every type.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
      yes for a yes/no field: no truthiness
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        yes for a yes/no field: no truthiness
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        yes for a yes/no field: no truthiness
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#stores
        a string of digits sent for a whole-number field as the number
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#stores
        the string false sent for a yes/no field as false
      - >-
        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-custom-fields.e2e.spec.js#refuses
        a text longer than the field allows
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        an archived option
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a date and time without an offset, which is ambiguous
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a reference to a record the tenant does not have
      - >-
        services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
        a number that cannot be written exactly for a percentage
    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-008-04
    title: 'Required fields on create, never emptied, old records saveable'
    level: browser
    proves:
      - BR-CAP-CRM-ENT-008-04
    preconditions: Prepare a field made API-required after a record existed.
    steps: >-
      Create without it; create blank; replace the old record without it;
      replace sending null; patch removing it.
    expected: Rejected; rejected; saved; rejected; rejected.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
      a replace of a contact created before the field was required, leaving the
      field out
    category: transition
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
        a replace of a contact created before the field was required, leaving
        the field out
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
        a replace of a contact created before the field was required, leaving
        the field out
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a create that leaves it out, naming it at its path
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a create that sends it blank
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
        a replace of a contact created before the field was required, leaving
        the field out
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a replace that sends it null
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
        a patch that removes it
      - >-
        services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
        a patch that empties the selection
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0139.spec.ts#CRM-ACC-0139
    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-008-05
    title: No custom fields on roles
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-06
      - INV-DOM-CRM-ENT-003-15
    preconditions: No additional setup is required.
    steps: 'Create, replace and patch a contact role carrying custom_fields.'
    expected: 400 each.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
      a create that carries custom_fields, as an unknown property
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
        a create that carries custom_fields, as an unknown property
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
        a create that carries custom_fields, as an unknown property
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
        a create that carries custom_fields, as an unknown property
      - >-
        services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
        a replacement that carries custom_fields
      - >-
        services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses
        a patch that adds custom_fields
    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-008-06
    title: Refuse an invalid custom value with the standard correction
    category: failure
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-03
      - BR-CAP-CRM-ENT-004-07
    preconditions: A person has a valid numeric custom value and saved notes.
    steps: >-
      Submit an invalid numeric value together with changed notes, then read the
      person.
    expected: >-
      The request is refused; the notes and custom value keep their previous
      contents.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      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
    test_anchors:
      - >-
        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
    action_evidence:
      provenance: test
      source: >-
        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
    assertion_evidence:
      provenance: test
      source: >-
        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-008-07
    title: Read custom values only through the declared map
    category: rule
    level: api
    proves:
      - BR-CAP-CRM-ENT-008-05
    preconditions: A person has a readable populated custom field and a readable empty field.
    steps: Read the person and inspect the map and top-level properties.
    expected: >-
      The populated and empty values use the declared JSON forms in the map;
      neither appears as a top-level custom property.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#puts
      no custom field at the top level of the record
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#puts
        no custom field at the top level of the record
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#puts
        no custom field at the top level of the record
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#puts
        no custom field at the top level of the record
    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-008-08
    title: Create with a required customer value
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-001-01
      - BR-CAP-CRM-ENT-008-04
    preconditions: An active contact definition requires a value during creation.
    steps: >-
      Create without that value, then create with a valid value and read the
      resulting person.
    expected: >-
      The first attempt creates no person; the corrected attempt stores the
      person and its custom value together.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
      400 for a contact with neither a first nor a last name, naming the rule;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
      a replace of a contact created before the field was required, leaving the
      field out
    exercises:
      - CAP-CRM-ENT-001
      - CAP-CRM-ENT-008
    workflows:
      - WF-CRM-ENT-001
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
        400 for a contact with neither a first nor a last name, naming the rule;
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
        a replace of a contact created before the field was required, leaving
        the field out
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
        400 for a contact with neither a first nor a last name, naming the rule;
        services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
        a replace of a contact created before the field was required, leaving
        the field out
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-008-09
    title: Refuse an invalid organization custom value
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-01
      - BR-CAP-CRM-ENT-008-03
    preconditions: An organization definition has a numeric custom field.
    steps: >-
      Create a named organization with invalid numeric text, then correct the
      value and retry with a fresh unused identity.
    expected: >-
      The invalid request creates no organization; the corrected request stores
      the declared numeric value.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
      a name of 201 characters, naming name;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
      yes for a yes/no field: no truthiness
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-008
    workflows:
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        services-crm:modules/entities/tests/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/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        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-008-10
    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-008-11
    title: Exercise every declared authority independently
    category: permission
    level: api
    proves:
      - BR-CAP-CRM-ENT-003-07
      - BR-CAP-CRM-ENT-003-09
    preconditions: >-
      An eligible record and configuration fixture exist for each operation; the
      caller can reach only the run’s workspace.
    steps: >-
      For each declared permission, execute its matching operation with that
      permission, then without it while retaining the other required grants.
      Read affected records after every refused mutation.
    expected: >-
      The authorized case reaches the operation; the missing-grant case is
      refused and preserves saved data. Record access alone does not authorize
      the other declared actions.
    negative: true
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:aws/crm.service.ts:ApplicationPermissions;
      services-crm:modules/entities/aws/api/entities.routes.ts
    permission_keys:
      - crm.contacts.update
      - crm.organizations.update
      - crm.contacts.read
      - crm.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/contacts-custom-fields.e2e.spec.js#sets
    the key it sends, clears the key sent null and the selection sent empty, and
    leaves the others
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
    every custom field as it was when the map is omitted
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#leaves
    every custom field as it was for an empty map
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#merges
    a whole-map replacement key by key, never clearing what it leaves out
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a patch that removes the whole map
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    custom_fields: null on a replacement
  - >-
    services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
    custom_fields: null
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a write to an archived field
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a key the object does not have, at its path
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    another tenant's field as unknown in this tenant
  - >-
    services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
    a contact's custom field as unknown on an organization
  - >-
    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-custom-fields.e2e.spec.js#stores
    a string of digits sent for a whole-number field as the number
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#stores
    the string false sent for a yes/no field as false
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a text longer than the field allows
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    an archived option
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a date and time without an offset, which is ambiguous
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a reference to a record the tenant does not have
  - >-
    services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
    a number that cannot be written exactly for a percentage
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
    a replace of a contact created before the field was required, leaving the
    field out
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a create that leaves it out, naming it at its path
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a create that sends it blank
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a replace that sends it null
  - >-
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    a patch that removes it
  - >-
    services-crm:modules/entities/tests/e2e/organizations-custom-fields.e2e.spec.js#refuses
    a patch that empties the selection
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0139.spec.ts#CRM-ACC-0139
  - >-
    services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses a
    create that carries custom_fields, as an unknown property
  - >-
    services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses a
    replacement that carries custom_fields
  - >-
    services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses a
    patch that adds custom_fields
  - >-
    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/e2e/contacts-custom-fields.e2e.spec.js#puts
    no custom field at the top level of the record
  - >-
    services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers
    400 for a contact with neither a first nor a last name, naming the rule;
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves
    a replace of a contact created before the field was required, leaving the
    field out
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    a name of 201 characters, naming name;
    services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses
    yes for a yes/no field: no truthiness
  - >-
    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:aws/crm.service.ts:ApplicationPermissions;
    services-crm:modules/entities/aws/api/entities.routes.ts
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-CRM-ENT-056'
  invariant: 'gap: FND-CRM-ENT-056'
  interaction: covered
previous_approval:
  revision: 1
  approved_by: Andrew
  approved_at: '2026-10-22T00:00:00.000Z'
coverage_notes:
  concurrency:
    reason: >-
      No detailed concurrency scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-056
  invariant:
    reason: >-
      No detailed invariant scenario currently specifies this aspect; the
      coverage finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-056
coverage_findings:
  - FND-CRM-ENT-056
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.contacts.update
    where_checked: api
    scenario: ATS-CRM-ENT-008-11
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md
  - key: crm.organizations.update
    where_checked: api
    scenario: ATS-CRM-ENT-008-11
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md
  - key: crm.contacts.read
    where_checked: api
    scenario: ATS-CRM-ENT-008-11
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md
  - key: crm.organizations.read
    where_checked: api
    scenario: ATS-CRM-ENT-008-11
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md
related_coverage:
  - capability: CAP-CRM-ENT-001
    scenarios:
      - ATS-CRM-ENT-008-08
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md:related
  - capability: CAP-CRM-ENT-002
    scenarios:
      - ATS-CRM-ENT-008-09
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md:related
  - capability: CAP-CRM-ENT-004
    scenarios:
      - ATS-CRM-ENT-008-10
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-008-hold-the-tenant-s-custom-field-values-on-contacts-and.md:related
examination_evidence:
  provenance: documented
  source: 'rhythm-v2-docs:specs/crm/entities/reports/EXAMINATION-2026-10-23.md'
  heading_conflict: FND-CRM-ENT-075
---

# Keep workspace’s-defined information with records
## 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-008-01` | `ATS-CRM-ENT-008-01` |
| `BR-CAP-CRM-ENT-008-02` | `ATS-CRM-ENT-008-02` |
| `BR-CAP-CRM-ENT-008-03` | `ATS-CRM-ENT-008-03`, `ATS-CRM-ENT-008-06`, `ATS-CRM-ENT-008-09`, `ATS-CRM-ENT-008-10` |
| `BR-CAP-CRM-ENT-008-04` | `ATS-CRM-ENT-008-04`, `ATS-CRM-ENT-008-08` |
| `BR-CAP-CRM-ENT-008-06` | `ATS-CRM-ENT-008-05` |

## Interactions With Related Capabilities

The interaction scenarios exercise establish a person’s CRM record; establish an organization and its hierarchy; save corrections without overwriting another change. 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.
