---
id: ATS-CRM-ENT-002
scope: CAP-CRM-ENT-002
product_area: crm
module: entities
revision: 1
status: proposed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-CRM-ENT-002-01
    title: Name required and limited; notes limited
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-01
      - INV-DOM-CRM-ENT-002-01
    preconditions: No additional setup is required.
    steps: 'Create without a name; with 201 characters; with notes of 10,001.'
    expected: 400 naming name or notes; 200 characters accepted.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-789
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
      a name of 201 characters, naming name
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        400 for a body with no name
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        notes of 10,001 characters, naming notes
    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-002-02
    title: 'Client ids preserved, others minted'
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-02
    preconditions: No additional setup is required.
    steps: 'Create with and without an id, and with an id breaking the grammar.'
    expected: The id; a UUIDv7; 400 naming the characters allowed.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps
      the id the body named, which is no UUIDv7
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps
        the id the body named, which is no UUIDv7
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps
        the id the body named, which is no UUIDv7
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps
        the id the body named, which is no UUIDv7
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#gives
        the organization a UUIDv7 id when the body names none (Andrew,
        2026-10-10)
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        400 for an id that breaks the id grammar, naming the characters an id
        may hold
    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-002-03
    title: Organization numbers have their own counter with recovery
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-03
      - INV-DOM-CRM-ENT-002-02
    preconditions: Prepare a seed handing out a taken number.
    steps: Create organizations.
    expected: >-
      10000 first; the highest plus one after a collision; contacts' seed
      untouched.
    negative: false
    data_needs: Use number_seeds.
    bound_to:
      - TST-CRM-ENT-789
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#saves
      the organization under the highest number plus one, 4001
    category: invariant
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#saves
        the organization under the highest number plus one, 4001
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#saves
        the organization under the highest number plus one, 4001
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the first number, 10000, and leaves 10001 in the seed
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#saves
        the organization under the highest number plus one, 4001
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#renumbers
        after the tenant's highest organization number, never its contacts'
    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-002-04
    title: an unknown type is a conflict
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-04
      - INV-DOM-CRM-ENT-002-12
    preconditions: No additional setup is required.
    steps: Create with a type the workspace lacks.
    expected: '409.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
      an organization of a type the tenant does not have (409), and the delete
      of a type an organization has (409)
    category: validation
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409)
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409)
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409)
    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-002-05
    title: a foreign parent is a conflict
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-05
      - INV-DOM-CRM-ENT-002-03
    preconditions: No additional setup is required.
    steps: Create naming a parent of another workspace.
    expected: '409.'
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
      - TST-CRM-ENT-789
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 for a parent the tenant does not have
    category: boundary
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a parent the tenant does not have
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a parent the tenant does not have
    test_anchors:
      - >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a parent the tenant does not have
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        a parent that is an organization of another tenant, by
        organizations_parent_fk
    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-002-06
    title: 'Loops are rejected at every depth, including racing moves'
    level: browser
    proves:
      - BR-CAP-CRM-ENT-002-06
      - INV-DOM-CRM-ENT-002-04
    preconditions: Prepare a three-level hierarchy.
    steps: >-
      Make it its own parent; move the top under its child and its grandchild;
      race two opposite moves.
    expected: Each rejected; the first racing move preserved.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-789
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
      putting the top under its grandchild by
      organizations_parent_not_descendant
    category: concurrency
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        putting the top under its grandchild by
        organizations_parent_not_descendant
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        putting the top under its grandchild by
        organizations_parent_not_descendant
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#names
        an organization that is its own parent as a field problem, before the
        database sees it
      - >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        putting the top under its grandchild by
        organizations_parent_not_descendant
      - >-
        services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
        the second move by organizations_parent_not_descendant once the first
        commits
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0113.spec.ts#CRM-ACC-0113
    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-002-07
    title: Ancestry is computed on every read
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-07
    preconditions: Prepare a chapter under a region under a national body.
    steps: Read the chapter; move the region; read again; send ancestry back.
    expected: >-
      Ancestors nearest first and the top; new ancestry without saving the
      chapter; sent ancestry ignored.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-789
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
      the descendant, never saved again, its ancestry under the new parent
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the chapter its ancestors nearest first, their number and the top level
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the computed ancestry on create, not what was sent
    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-002-08
    title: Status is a closed list and inactive organizations leave pickers
    level: browser
    proves:
      - BR-CAP-CRM-ENT-002-08
      - INV-DOM-CRM-ENT-002-05
    preconditions: Prepare an active and an inactive organization.
    steps: Write status "archived"; open a picker.
    expected: Rejected; the inactive one not offered and still readable.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-789
      - TST-CRM-ENT-775
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0117.spec.ts#CRM-ACC-0117
    category: validation
    action_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0117.spec.ts#CRM-ACC-0117
    assertion_evidence:
      provenance: test
      source: >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0117.spec.ts#CRM-ACC-0117
    test_anchors:
      - >-
        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:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#defaults
        the status to active
      - >-
        react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0117.spec.ts#CRM-ACC-0117
    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-002-09
    title: Website and domain name derive each other
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-09
    preconditions: No additional setup is required.
    steps: Save a website only; a domain only; a website that is not a URL.
    expected: Domain without protocol or www; http www website; nothing derived.
    negative: false
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-786
    provenance: test
    source: >-
      services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
      the domain name without the protocol or the www prefix
    category: rule
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
        the domain name without the protocol or the www prefix
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
        the domain name without the protocol or the www prefix
    test_anchors:
      - >-
        services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
        the domain name without the protocol or the www prefix
      - >-
        services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
        the website as http and www before the domain name
      - >-
        services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
        no domain name from a website that is not a URL
      - >-
        services-crm:modules/entities/tests/e2e/organizations-domain-name.e2e.spec.js#answers
        200 with the domain name derived from the website
    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-002-10
    title: a logo that cannot be saved rejects the save
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-10
    preconditions: Prepare an organization with a logo.
    steps: Update naming a file that cannot be saved.
    expected: 400 naming the logo field; the old logo preserved.
    negative: true
    data_needs: No additional fixtures.
    bound_to:
      - TST-CRM-ENT-789
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
      the update as ObjectInvalid, naming the logo field
    category: failure
    action_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the update as ObjectInvalid, naming the logo field
    assertion_evidence:
      provenance: test
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the update as ObjectInvalid, naming the logo field
    test_anchors:
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
        the update as ObjectInvalid, naming the logo field
      - >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#leaves
        the organization holding the logo it had
    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-002-11
    title: an organization duplicate block requires a reasoned override
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-11
    preconditions: >-
      Prepare a blocking organization duplicate rule and callers with and
      without the override key.
    steps: >-
      Try the matching create with confirmation alone, with an unauthorized
      reasoned override, and with an authorized reasoned override.
    expected: >-
      Only the authorized reasoned request creates an organization, and its
      override is recorded.
    negative: true
    data_needs: Use one matching organization.
    bound_to: []
    provenance: proposed
    source: FND-CRM-ENT-029
    category: permission
    action_evidence:
      provenance: proposed
      source: FND-CRM-ENT-029
    assertion_evidence:
      provenance: proposed
      source: FND-CRM-ENT-029
    test_anchors: []
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-12
    title: Change organization standing without changing identity
    category: transition
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-08
      - BR-CAP-CRM-ENT-004-01
    preconditions: A newly created organization is active and its current version is known.
    steps: >-
      Save inactive status, then active status, then duplicate status, reading
      each result at its current version.
    expected: >-
      The record keeps its identity and number; each read reflects the requested
      standing.
    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-002-13
    title: Move a branch and rediscover its people
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-07
      - BR-CAP-CRM-ENT-003-04
    preconditions: >-
      Two roots, a child organization and a current affiliate under the child
      exist.
    steps: >-
      Move the child to the other root; read its ancestry and discover people
      below each root.
    expected: >-
      The child’s new ancestry and the two root populations agree with the moved
      structure.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
      the descendant, never saved again, its ancestry under the new parent;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
      a contact whose affiliation in effect to an organization of the walk is
      not its primary one (M2-D3)
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-003
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
        the descendant, never saved again, its ancestry under the new parent;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
        a contact whose affiliation in effect to an organization of the walk is
        not its primary one (M2-D3)
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-14
    title: Save organizational contact points with the body
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-01
      - BR-CAP-CRM-ENT-005-03
    preconditions: A named organization has two usable phone purposes.
    steps: Create it with one phone in each purpose and read its contact points.
    expected: The organization and its typed phones are readable as one saved aggregate.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
      a name of 201 characters, naming name;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      two numbers of the same type
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-005
    workflows:
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
        a name of 201 characters, naming name;
        services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
        two numbers of the same type
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-15
    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-002-16
    title: Establish a body under role policy
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-04
      - BR-CAP-CRM-ENT-012-02
    preconditions: An organization classification requires one holder of a selected role.
    steps: >-
      Create an organization using that classification and read its initial
      compliance.
    expected: >-
      The new organization has the initial zero-holder violation and creation
      submits no notice.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
      an organization of a type the tenant does not have (409), and the delete
      of a type an organization has (409);
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
      a type that holds it to rules: not in compliance, every rule with a
      minimum violated (it has no contact yet)
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-012
    workflows:
      - WF-CRM-ENT-003
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409);
        services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
        a type that holds it to rules: not in compliance, every rule with a
        minimum violated (it has no contact yet)
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409);
        services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
        a type that holds it to rules: not in compliance, every rule with a
        minimum violated (it has no contact yet)
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-17
    title: Keep a parent until its child is removed
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-05
      - BR-CAP-CRM-ENT-014-05
    preconditions: A parent organization has one otherwise deletable child.
    steps: 'Try deleting the parent; remove the child, then retry deleting the parent.'
    expected: >-
      The first delete preserves both records; the later parent delete succeeds
      once the child no longer references it.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 for a parent the tenant does not have;
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
      409 to a delete while the contact is affiliated with it
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-014
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a parent the tenant does not have;
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 to a delete while the contact is affiliated with it
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 for a parent the tenant does not have;
        services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
        409 to a delete while the contact is affiliated with it
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-18
    title: Prevent deletion of a classification held by a body
    category: interaction
    level: api
    proves:
      - BR-CAP-CRM-ENT-002-04
      - BR-CAP-CRM-ENT-018-03
    preconditions: An organization uses a workspace classification.
    steps: >-
      Try to delete the classification, then read the organization and
      classification.
    expected: >-
      The referenced classification remains and the organization keeps its
      classification identity.
    negative: false
    data_needs: Run-owned records and configuration in an isolated workspace.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
      an organization of a type the tenant does not have (409), and the delete
      of a type an organization has (409);
      services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says
      which records keep the type when it refuses the delete
    exercises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-018
    workflows: []
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409);
        services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says
        which records keep the type when it refuses the delete
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
        an organization of a type the tenant does not have (409), and the delete
        of a type an organization has (409);
        services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says
        which records keep the type when it refuses the delete
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
  - id: ATS-CRM-ENT-002-19
    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.organizations.create
      - crm.organizations.create-despite-duplicate-block
      - crm.organization-types.read
    test_anchors: []
    action_evidence:
      provenance: proposed
      source: >-
        services-crm:aws/crm.service.ts:ApplicationPermissions;
        services-crm:modules/entities/aws/api/entities.routes.ts
    assertion_evidence:
      provenance: proposed
      source: >-
        services-crm:aws/crm.service.ts:ApplicationPermissions;
        services-crm:modules/entities/aws/api/entities.routes.ts
    binding_scope: 'unbound: the detailed scenario still needs executable proof.'
evidence:
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    a name of 201 characters, naming name
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    400 for a body with no name
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    notes of 10,001 characters, naming notes
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#keeps the
    id the body named, which is no UUIDv7
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#gives the
    organization a UUIDv7 id when the body names none (Andrew, 2026-10-10)
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    400 for an id that breaks the id grammar, naming the characters an id may
    hold
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#saves
    the organization under the highest number plus one, 4001
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the first number, 10000, and leaves 10001 in the seed
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#renumbers
    after the tenant's highest organization number, never its contacts'
  - >-
    services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
    an organization of a type the tenant does not have (409), and the delete of
    a type an organization has (409)
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    409 for a parent the tenant does not have
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
    a parent that is an organization of another tenant, by
    organizations_parent_fk
  - >-
    services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
    putting the top under its grandchild by organizations_parent_not_descendant
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#names
    an organization that is its own parent as a field problem, before the
    database sees it
  - >-
    services-crm:modules/entities/tests/integration/db/organizations.table.integration.spec.js#refuses
    the second move by organizations_parent_not_descendant once the first
    commits
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0113.spec.ts#CRM-ACC-0113
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the descendant, never saved again, its ancestry under the new parent
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the chapter its ancestors nearest first, their number and the top level
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the computed ancestry on create, not what was sent
  - >-
    react-console:acceptance/crm/playwright/journeys/organizations/CRM-ACC-0117.spec.ts#CRM-ACC-0117
  - >-
    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:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#defaults
    the status to active
  - >-
    services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
    the domain name without the protocol or the www prefix
  - >-
    services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
    the website as http and www before the domain name
  - >-
    services-crm:modules/entities/tests/unit/db/organization-domain-name.spec.js#derives
    no domain name from a website that is not a URL
  - >-
    services-crm:modules/entities/tests/e2e/organizations-domain-name.e2e.spec.js#answers
    200 with the domain name derived from the website
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
    the update as ObjectInvalid, naming the logo field
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#leaves
    the organization holding the logo it had
  - FND-CRM-ENT-029
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#answers
    the descendant, never saved again, its ancestry under the new parent;
    services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
    a contact whose affiliation in effect to an organization of the walk is not
    its primary one (M2-D3)
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organizations.field-rules.integration.spec.js#refuses
    a name of 201 characters, naming name;
    services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
    two numbers of the same type
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/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/e2e/organization-types.e2e.spec.js#refuses
    an organization of a type the tenant does not have (409), and the delete of
    a type an organization has (409);
    services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
    a type that holds it to rules: not in compliance, every rule with a minimum
    violated (it has no contact yet)
  - >-
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    409 for a parent the tenant does not have;
    services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers
    409 to a delete while the contact is affiliated with it
  - >-
    services-crm:modules/entities/tests/e2e/organization-types.e2e.spec.js#refuses
    an organization of a type the tenant does not have (409), and the delete of
    a type an organization has (409);
    services-crm:modules/entities/tests/e2e/address-types.e2e.spec.js#says which
    records keep the type when it refuses the delete
  - >-
    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: 'gap: FND-CRM-ENT-072'
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
previous_approval:
  revision: 1
  approved_by: Andrew
  approved_at: '2026-10-22T00:00:00.000Z'
transition_coverage:
  - state_model: STATE-CRM-ENT-002
    from: new
    to: active
    scenarios:
      - ATS-CRM-ENT-002-08
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/design/state-models/STATE-CRM-ENT-002-organization-record-lifecycle.md:transitions
coverage_notes:
  edge:
    reason: >-
      No detailed edge scenario currently specifies this aspect; the coverage
      finding records the required follow-through.
    provenance: unknown
    source: FND-CRM-ENT-072
coverage_findings:
  - FND-CRM-ENT-072
  - FND-CRM-ENT-075
permission_coverage:
  - key: crm.organizations.create
    where_checked: api
    scenario: ATS-CRM-ENT-002-19
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
  - key: crm.organizations.create-despite-duplicate-block
    where_checked: api
    scenario: ATS-CRM-ENT-002-19
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
  - key: crm.organization-types.read
    where_checked: api
    scenario: ATS-CRM-ENT-002-19
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md
related_coverage:
  - capability: CAP-CRM-ENT-003
    scenarios:
      - ATS-CRM-ENT-002-13
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md:related
  - capability: CAP-CRM-ENT-005
    scenarios:
      - ATS-CRM-ENT-002-14
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md:related
  - capability: CAP-CRM-ENT-012
    scenarios:
      - ATS-CRM-ENT-002-16
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md:related
  - capability: CAP-CRM-ENT-014
    scenarios:
      - ATS-CRM-ENT-002-17
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.md:related
  - capability: CAP-CRM-ENT-018
    scenarios:
      - ATS-CRM-ENT-002-18
    provenance: documented
    source: >-
      rhythm-v2-docs:specs/crm/entities/planning/capabilities/CAP-CRM-ENT-002-record-an-organization-and-its-place-in-a-hierarchy.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
---

# Establish an organization and its hierarchy
## 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-002-01` | `ATS-CRM-ENT-002-01`, `ATS-CRM-ENT-002-14`, `ATS-CRM-ENT-002-15` |
| `BR-CAP-CRM-ENT-002-02` | `ATS-CRM-ENT-002-02` |
| `BR-CAP-CRM-ENT-002-03` | `ATS-CRM-ENT-002-03` |
| `BR-CAP-CRM-ENT-002-04` | `ATS-CRM-ENT-002-04`, `ATS-CRM-ENT-002-16`, `ATS-CRM-ENT-002-18` |
| `BR-CAP-CRM-ENT-002-05` | `ATS-CRM-ENT-002-05`, `ATS-CRM-ENT-002-17` |
| `BR-CAP-CRM-ENT-002-06` | `ATS-CRM-ENT-002-06` |
| `BR-CAP-CRM-ENT-002-07` | `ATS-CRM-ENT-002-07`, `ATS-CRM-ENT-002-13` |
| `BR-CAP-CRM-ENT-002-08` | `ATS-CRM-ENT-002-08`, `ATS-CRM-ENT-002-12` |
| `BR-CAP-CRM-ENT-002-09` | `ATS-CRM-ENT-002-09` |
| `BR-CAP-CRM-ENT-002-10` | `ATS-CRM-ENT-002-10` |
| `BR-CAP-CRM-ENT-002-11` | `ATS-CRM-ENT-002-11` |

## Interactions With Related Capabilities

The interaction scenarios exercise locate people and organizations; maintain reliable contact points; review organization role compliance and prepare notices; remove records and their owned information; organize the workspace’s record classifications. They change one part of a prepared record and inspect the dependent outcome. Unbound interactions remain proposed rather than implying an existing end-to-end implementation.

## Source Evidence

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