---
id: TST-CRM-ENT-791
title: Classification types and CRM settings integration
kind: integration
product_area: crm
module: entities
revision: 1
status: draft
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
repository: services-crm
files:
  - modules/entities/tests/integration/db/affiliation-type-contact-info-defaults.table.integration.spec.js
  - modules/entities/tests/integration/db/affiliation-types.table.integration.spec.js
  - modules/entities/tests/integration/db/collection-types.table.integration.spec.js
  - modules/entities/tests/integration/db/contact-types.table.integration.spec.js
  - modules/entities/tests/integration/db/organization-types.table.integration.spec.js
  - modules/entities/tests/integration/services/persistence/address-types.integration.spec.js
  - modules/entities/tests/integration/services/persistence/address-types.queries.integration.spec.js
  - modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js
  - modules/entities/tests/integration/services/persistence/affiliation-types.queries.integration.spec.js
  - >-
    modules/entities/tests/integration/services/persistence/configuration-type-usages.queries.integration.spec.js
  - modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js
  - modules/entities/tests/integration/services/persistence/contact-types.queries.integration.spec.js
  - modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js
  - modules/entities/tests/integration/services/persistence/crm-settings.queries.integration.spec.js
  - modules/entities/tests/integration/services/persistence/organization-types.integration.spec.js
  - modules/entities/tests/integration/services/persistence/organization-types.queries.integration.spec.js
  - modules/entities/tests/integration/services/persistence/phone-number-types.integration.spec.js
  - modules/entities/tests/integration/services/persistence/phone-number-types.queries.integration.spec.js
implements:
  - VT-IMC-CRM-ENT-160-01
  - VT-IMC-CRM-ENT-158-01
  - VT-IMC-CRM-ENT-005-01
  - ATS-CRM-ENT-017-07
  - ATS-CRM-ENT-017-08
  - ATS-CRM-ENT-018-04
  - ATS-CRM-ENT-019-04
  - VT-IMC-CRM-ENT-134-01
  - VT-IMC-CRM-ENT-136-01
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: null
estimate_basis: No measured duration is available for this cohesive group; per-file allowances are not runtime estimates.
isolation: >-
  Each spec owns the catalogue records it creates and removes dependent mappings before their owners. Shared
  seed definitions are read as premises. Tests that alter configuration dependencies restore them after the
  stage; this register selects a serial invocation to avoid interleaving those changes.
binding_scope: >-
  Bindings preserve the existing assertion contributions and exact source file. Composite scenarios still
  require every bound contribution and any unbound checks; consolidation does not certify conformance.
bindings:
  - scenario: ATS-CRM-ENT-017-07
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js#keeps
      the mappings, as an omitted collection means unchanged
  - scenario: ATS-CRM-ENT-017-08
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js#numbers
      a placeless type locked against updates that the first reorder leaves where its name stands
  - scenario: ATS-CRM-ENT-018-04
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js#numbers
      a placeless type locked against updates that the first reorder leaves where its name stands
  - scenario: ATS-CRM-ENT-019-04
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      an id no definition of the tenant has, naming the section and the field
  - scenario: ATS-CRM-ENT-019-04
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      another tenant’s definition
  - scenario: ATS-CRM-ENT-019-04
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      a definition of the other purpose in a section: an organizations layout under contacts
  - scenario: ATS-CRM-ENT-019-04
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js#refuses
      a definition of the wrong kind: a console form in the console page layout slot
evidence:
  - >-
    services-crm:modules/entities/tests/integration/db/affiliation-type-contact-info-defaults.table.integration.spec.js
  - 'services-crm:modules/entities/tests/integration/db/affiliation-types.table.integration.spec.js'
  - 'services-crm:modules/entities/tests/integration/db/collection-types.table.integration.spec.js'
  - 'services-crm:modules/entities/tests/integration/db/contact-types.table.integration.spec.js'
  - 'services-crm:modules/entities/tests/integration/db/organization-types.table.integration.spec.js'
  - 'services-crm:modules/entities/tests/integration/services/persistence/address-types.integration.spec.js'
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/address-types.queries.integration.spec.js
  - 'services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js'
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.queries.integration.spec.js
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/configuration-type-usages.queries.integration.spec.js
  - 'services-crm:modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js'
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/contact-types.queries.integration.spec.js
  - 'services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js'
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/crm-settings.queries.integration.spec.js
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organization-types.integration.spec.js
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/organization-types.queries.integration.spec.js
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/phone-number-types.integration.spec.js
  - >-
    services-crm:modules/entities/tests/integration/services/persistence/phone-number-types.queries.integration.spec.js
source_observations:
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/affiliation-type-contact-info-defaults.table.integration.spec.js
    declared_test_titles:
      - affiliation type contact information defaults table (integration)
      - the table
      - takes an address mapping naming two address types of the tenant
      - >-
        takes a phone mapping to the same destination id as an address mapping of the type, since the kinds
        differ
      - refuses a kind that is neither address nor phone (affiliation_type_contact_info_defaults_kind_check)
      - refuses a lock value that is not one the system uses
      - the pair of columns of the kind (affiliation_type_contact_info_defaults_kind_columns_check)
      - refuses an address mapping with no destination
      - refuses an address mapping that also fills the phone pair
      - refuses a phone mapping whose ids are in the address pair
      - 'turns a blank id into null before the check, so a blank destination is refused as a missing one'
      - the type keys
      - >-
        refuses an address mapping naming a type that is only a phone number type
        (affiliation_type_contact_info_defaults_destination_address_fk)
      - >-
        refuses a phone mapping naming a type that is only an address type
        (affiliation_type_contact_info_defaults_source_phone_fk)
      - >-
        refuses a source address type that is another tenant's
        (affiliation_type_contact_info_defaults_source_address_fk)
      - >-
        refuses a destination phone number type that is another tenant's
        (affiliation_type_contact_info_defaults_destination_phone_fk)
      - >-
        refuses an affiliation type the tenant does not have
        (affiliation_type_contact_info_defaults_affiliation_type_fk)
      - one mapping per destination of a kind (the destination uniques)
      - holds the two mappings it was given (premise)
      - 'refuses a second address mapping to the same destination, from another source'
      - 'refuses a second phone mapping to the same destination, from another source'
      - takes the same destination for another affiliation type
      - the owner and the types it names
      - 'refuses the delete of an address type a mapping names, keeping the type'
      - removes the mappings with the affiliation type they belong to
      - the forced tenant policy
      - 'reads none of another tenant''s mappings as the application role, with no predicate in the statement'
      - refuses a mapping written into another tenant by the application role (the policy's WITH CHECK)
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/db/affiliation-types.table.integration.spec.js'
    declared_test_titles:
      - affiliation types table (integration)
      - the table
      - takes the same id in two tenants
      - 'refuses the same id twice in one tenant, by the primary key'
      - requires a name
      - makes a type active when the insert says nothing about it
      - refuses a lock value that is not one the system uses
      - the name a type must have (affiliation_types_name_not_blank_check)
      - refuses a name that is the empty string
      - refuses a name that is nothing but spaces
      - 'refuses a name of nothing but a tab and a newline, which the trigger trims as JavaScript does'
      - writes no row at all for a name it refused
      - takes a name with spaces around it and stores it trimmed
      - refuses an update that blanks the name of a type that has one
      - the id a type may be written under (affiliation_types_id_configuration_id_check)
      - 'takes an id of lowercase letters, digits and single underscores'
      - refuses an id with a capital letter
      - refuses an id with a hyphen
      - refuses an id with a space
      - refuses an id that starts with a digit
      - refuses an id that ends with an underscore
      - refuses an id with two underscores in a row
      - refuses an empty id
      - refuses an id longer than a PostgreSQL identifier
      - takes an id exactly as long as a PostgreSQL identifier
      - 'refuses an id that would read as something else in an API: one of the reserved words'
      - refuses an id under the prefix Rhythm keeps for the types it ships itself
      - takes an id that merely begins with the letters of that prefix
      - >-
        the key holding the default affiliation type to one of the tenant's own
        (crm_settings_default_affiliation_type_fk)
      - takes settings whose default is the tenant's own type
      - takes settings that name no default at all
      - refuses a default no tenant has
      - refuses a default that is a type of another tenant
      - the delete of the type that is the default
      - leaves the type in the table
      - 'leaves the settings where they are: the key cascades to nothing'
      - deletes a type no settings name
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/db/collection-types.table.integration.spec.js'
    declared_test_titles:
      - collection types tables (integration)
      - crm.address_types
      - takes the same id in two tenants
      - 'refuses the same id twice in one tenant, by the primary key'
      - refuses a name another type of the tenant already holds
      - 'takes the same name in two tenants: a name is unique to its tenant and no further'
      - refuses a blank name
      - refuses a name longer than a hundred characters
      - refuses an applicability outside the closed list
      - refuses a place before the start of the list
      - refuses a lock value that is not one the system uses
      - 'the id a type may be written under (address_types_id_check, the configuration-id grammar)'
      - 'takes an id of lowercase letters, digits and single underscores'
      - refuses an id with a capital letter
      - refuses an id with a hyphen
      - refuses an id with a space
      - refuses an id that starts with a digit
      - refuses an id that ends with an underscore
      - refuses an id with two underscores in a row
      - refuses an id longer than a PostgreSQL identifier
      - takes an id exactly as long as a PostgreSQL identifier
      - 'refuses an id that would read as something else in an API: one of the reserved words'
      - refuses an id under the prefix Rhythm keeps for what it ships itself
      - takes an id that merely begins with the letters of that prefix
      - requires a name
      - 'applies a type to contacts, at the start of the list, when the insert says neither'
      - holds the applicability of a type of contacts as it was written
      - holds the applicability of a type of organizations as it was written
      - holds the applicability of a type of both as it was written
      - takes a change of applicability
      - crm.phone_number_types
      - takes the same id in two tenants
      - 'refuses the same id twice in one tenant, by the primary key'
      - refuses a name another type of the tenant already holds
      - 'takes the same name in two tenants: a name is unique to its tenant and no further'
      - refuses a blank name
      - refuses a name longer than a hundred characters
      - refuses an applicability outside the closed list
      - refuses a place before the start of the list
      - refuses a lock value that is not one the system uses
      - 'the id a type may be written under (phone_number_types_id_check, the configuration-id grammar)'
      - 'takes an id of lowercase letters, digits and single underscores'
      - refuses an id with a capital letter
      - refuses an id with a hyphen
      - refuses an id with a space
      - refuses an id that starts with a digit
      - refuses an id that ends with an underscore
      - refuses an id with two underscores in a row
      - refuses an id longer than a PostgreSQL identifier
      - takes an id exactly as long as a PostgreSQL identifier
      - 'refuses an id that would read as something else in an API: one of the reserved words'
      - refuses an id under the prefix Rhythm keeps for what it ships itself
      - takes an id that merely begins with the letters of that prefix
      - requires a name
      - 'applies a type to contacts, at the start of the list, when the insert says neither'
      - holds the applicability of a type of contacts as it was written
      - holds the applicability of a type of organizations as it was written
      - holds the applicability of a type of both as it was written
      - takes a change of applicability
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/db/contact-types.table.integration.spec.js'
    declared_test_titles:
      - contact types table (integration)
      - the table
      - takes the same id in two tenants
      - 'refuses the same id twice in one tenant, by the primary key'
      - requires a name
      - makes a type active when the insert says nothing about it
      - refuses a lock value that is not one the system uses
      - 'the name a type must have (contact_types_name_not_blank_check, migration 0036)'
      - refuses a name that is the empty string
      - refuses a name that is nothing but spaces
      - 'refuses a name of nothing but a tab and a newline, which the trigger trims as JavaScript does'
      - writes no row at all for a name it refused
      - takes a name with spaces around it and stores it trimmed
      - refuses an update that blanks the name of a type that has one
      - 'the id a type may be written under (contact_types_id_configuration_id_check, migration 0033)'
      - 'takes an id of lowercase letters, digits and single underscores'
      - refuses an id with a capital letter
      - refuses an id with a hyphen
      - refuses an id with a space
      - refuses an id that starts with a digit
      - refuses an id that ends with an underscore
      - refuses an id with two underscores in a row
      - refuses an empty id
      - refuses an id longer than a PostgreSQL identifier
      - takes an id exactly as long as a PostgreSQL identifier
      - 'refuses an id that would read as something else in an API: one of the reserved words'
      - refuses an id under the prefix Rhythm keeps for the types it ships itself
      - takes an id that merely begins with the letters of that prefix
      - the key joining a contact to its type (contacts_type_fk)
      - takes a contact with no type at all
      - takes a contact whose type is its own tenant's
      - refuses a contact whose type no tenant has
      - refuses a contact whose type belongs to another tenant
      - the delete of a type a contact has
      - leaves the type in the table
      - 'leaves the contact where it is: the key cascades to nothing'
      - 'deletes a type that only another tenant''s contact names, by the same id'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/db/organization-types.table.integration.spec.js'
    declared_test_titles:
      - crm.organization_types and its compliance rules (integration)
      - what the database refuses
      - >-
        nothing for a type that notifies with no template: the e-mail is the template bound in Platform
        (migration 0025)
      - a name that is the empty string
      - a name that is nothing but spaces
      - 'a name of nothing but a tab and a newline, which the trigger trims as JavaScript does'
      - an update that blanks the name of a type that has one
      - 'a restricted type without applicable roles, or an empty applicable list'
      - >-
        nothing about a web form: the five legacy form columns and their check are gone, so a type names
        Platform's definitions only (migration 0032)
      - 'an id that is not a configuration id, whatever writes the row (migration 0038)'
      - an organization of a type the tenant does not have
      - the delete of a type an organization has
      - 'a rule of a role the tenant does not have, when the transaction commits'
      - >-
        the delete of a role a rule names, when the transaction commits (the legacy relationship registry
        refused it)
      - what the database allows
      - 'a name with spaces around it, which it stores trimmed'
      - 'a role deleted with the rules naming it, in one transaction'
      - 'any number as a bound, as the legacy Joi.number() did (fractions, zero, negatives)'
      - a type's rules deleted with the type
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/services/persistence/address-types.integration.spec.js'
    declared_test_titles:
      - address types persistence (integration)
      - create_address_type
      - 'answers the whole record it wrote, every field as sent and the system properties stamped'
      - 'stores the applicability it was given, which is the guide a list filters on'
      - applies a type to contacts when the create says nothing about it
      - puts a type the create gives no display order at the end of the tenant’s list
      - derives the id from the name when the create names none
      - 'keeps an id the create does state, exactly as sent, rather than deriving one from the name'
      - 'refuses an id the create states that is not a configuration id, saying what an id may hold'
      - refuses an id with two underscores in a row
      - refuses an id the create states that is reserved
      - refuses an id the create states that begins with the prefix Rhythm keeps for itself
      - refuses an id longer than a PostgreSQL identifier
      - writes no type at all for an id it refused
      - 'refuses a create whose name yields no id at all, asking for one rather than inventing it'
      - refuses a create at an id the tenant already uses
      - refuses a create at a name another type of the tenant holds
      - 'refuses a create with a blank name, by the column that requires one'
      - creates a type whose name is the 100 characters the table allows
      - 'refuses a create whose name is 101 characters, as ObjectInvalid naming name'
      - 'refuses a create whose applies_to is not one of the three, by the check that lists them'
      - 'refuses a negative display order, by the check that requires a place in the list'
      - update_address_type
      - 'answers the whole record as replaced, the version bumped and the caller stamped'
      - keeps the place in the list a replacement that states no display order is silent about
      - stores the applicability the replacement gave it
      - >-
        refuses a replacement whose name is 101 characters, as ObjectInvalid naming name, and leaves the type
        as it was
      - refuses a replacement of a type the tenant does not have
      - 'leaves the id as it was created, whatever the replacement states: a configuration id never changes'
      - a replacement at a version the row has moved past
      - is refused as a stale object
      - leaves the row as the replacement that did succeed left it
      - a replacement of a type locked against updates
      - is refused as an illegal operation
      - leaves the row at the name and version it had
      - 'applies_to: a type used on the side it is not marked for'
      - >-
        gives the organization the contacts-only type and reads it back, since applies_to guides an
        organization only
      - 'refuses the contact the organizations-only type, naming its type field (CRM-M1 decision M1-D8)'
      - a link whose source type is marked for the other side
      - 'accepts the link, because applies_to says nothing about the type an organization’s address uses'
      - resolves the organization’s address of the source type under the destination type
      - changing the applies_to of a type that is in use
      - 'takes the change, whatever already uses the type'
      - 'leaves the row at the applicability it was given, its version bumped'
      - still reads the contact’s address of that type back
      - 'still resolves the organization’s changed address into it, with no contact write'
      - delete_address_type
      - 'removes the type, so a read answers undefined'
      - refuses the delete of a type the tenant does not have
      - 'refuses the delete of a locked type, leaving it in the table'
      - the delete of a type a contact address uses
      - 'is refused, naming the type and what uses it'
      - leaves the type in the table
      - leaves the contact its address of that type
      - succeeds once the contact no longer has an address of it
      - refuses the delete of a type an organization address uses
      - 'refuses the delete of the type a linked contact address is of, as any contact address of it'
      - refuses the delete of the type the tenant’s region settings decide an organization’s region from
      - reorder_address_types
      - a reorder of every type of the tenant
      - 'answers the tenant’s whole list in the new order, every record as the list maps it'
      - 'stores the dense order 1..n, bumping and stamping only the types whose place changed'
      - is what the list answers afterwards
      - 'numbers a list with ties and gaps 1..n in the order given, bumping every type it moved'
      - changes nothing for a reorder that keeps every place
      - answers an empty list to an empty reorder of a tenant with no types
      - lets a type locked against updates keep its place while the others move
      - places the first type a tenant creates without a display order at 1
      - places a type created without a display order one past the greatest place the tenant uses
      - 'keeps the display order a create does state, even one inside the list'
      - >-
        places a type created without a display order after the greatest place of its own tenant, not
        another’s
      - 'the refusals, each of which writes nothing'
      - refuses a list that names an id twice
      - refuses a list that names a type the tenant does not have
      - refuses a list that leaves a type of the tenant out
      - 'moves a type locked against updates as well: a lock does not hold a place in the list'
      - another tenant’s types
      - 'reads them as unknown, refusing the reorder'
      - 'reorders only the tenant’s own types, even one that shares an id with another tenant’s'
      - leaves the other tenant’s types as they were
      - two reorders of one tenant at once
      - lets the first finish with its own order
      - 'lets the second finish after it, reading what the first wrote'
      - 'leaves the last one’s order, dense, each version bumped once for every reorder that moved the type'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/address-types.queries.integration.spec.js
    declared_test_titles:
      - address types read (integration)
      - get_address_type
      - 'answers the whole record of the tenant’s type, every property as the API reads it'
      - answers undefined for an id the tenant has no type at
      - answers each tenant its own type where both have one of that id
      - list_address_types
      - answers the tenant’s whole list in display order
      - ends a list that has no further page with no cursor
      - 'answers the whole record of each type it lists, not a projection of it'
      - keeps a page to its limit and names where it ended
      - begins the next page after the place the last one ended at
      - 'answers the types that apply to contacts, `both` included, in the tenant’s order'
      - 'answers the types that apply to organizations, `both` included, in the tenant’s order'
      - 'pages a filtered list by the same places, so the filter does not renumber the list'
      - answers each tenant only its own types
      - 'answers an empty list, and no cursor, for a tenant with no types at all'
      - 'breaks a shared display order by id, bytewise'
      - read_address_type_order
      - 'answers the display order of every type of the tenant, by id'
      - answers an empty map for a tenant with no types
      - 'getAddressTypesByIds, the read of one address type made over a set (integration)'
      - >-
        answers the tenant’s address types with those ids in id order, leaving out an id nobody has and
        another tenant’s
      - answers each address type exactly as the read of that one address type does
      - answers no address types for no ids
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js
    declared_test_titles:
      - affiliation types persistence (integration)
      - createAffiliationType
      - 'answers the whole record it wrote, every field as sent and the system properties stamped'
      - defaults is_active to true and leaves out a description the create did not give
      - derives the id from the name when the create names none
      - 'keeps an id the create does state, exactly as sent, rather than deriving one from the name'
      - 'refuses an id the create states that is not a configuration id, saying what an id may hold'
      - refuses an id the create states that is reserved
      - refuses an id the create states that begins with the prefix Rhythm keeps for itself
      - writes no type at all for an id it refused
      - 'refuses a create whose name yields no id at all, asking for one rather than inventing it'
      - refuses a create at an id the tenant already uses
      - 'refuses a create with no name, by the column that requires one'
      - stores a name with spaces around it trimmed
      - the name a type must have
      - refuses a create whose name is the empty string
      - refuses a create whose name is nothing but spaces
      - >-
        refuses a create whose name is nothing but a tab and a newline, which the database’s own btrim would
        let through
      - writes no type at all for a create it refused
      - 'refuses the blank name before it asks the name for an id, so the person is told which field is wrong'
      - a replacement whose name is blank
      - is refused as an invalid object
      - 'leaves the type exactly as it was, name, description and version alike'
      - refuses a replacement whose name is the empty string
      - stores a replacement’s name with the spaces around it removed
      - updateAffiliationType
      - 'answers the whole record as replaced, with the fields the input states'
      - clears the description the replacement omitted
      - refuses a replacement of a type the tenant does not have
      - a replacement at a version the row has moved past
      - is refused as a stale object
      - leaves the row as the replacement that did succeed left it
      - a replacement of a type locked against updates
      - is refused as an illegal operation
      - leaves the row at the name and version it had
      - deleteAffiliationType
      - 'removes the type, so a read answers undefined'
      - refuses the delete of a type the tenant does not have
      - the delete of a locked type
      - is refused as an illegal operation
      - leaves the type in the table
      - the delete of the type that is the tenant’s default affiliation type
      - 'is refused, naming the affiliation type that is the default'
      - leaves the type in the table
      - leaves the settings naming the type as the default
      - and then the delete of the same type once the default is reset
      - 'succeeds, so a read of the type answers undefined'
      - leaves the settings reading the default other
      - 'insert_affiliation_type_if_absent, which a tenant’s provisioning calls'
      - answers inserted the first time
      - answers adopted the second time
      - 'keeps the type the first insert wrote, whole, rather than overwriting it'
      - 'contact_information_defaults, the default mappings a type owns'
      - a create with two mappings
      - 'answers the mappings it wrote, addresses before phones'
      - 'answers the same mappings on a later read: they were persisted, not just returned'
      - 'stores each mapping in the pair of columns of its kind, the other pair null, stamped as the type is'
      - 'answers an empty list for a type created without mappings, and stores none'
      - trims the ids it is given
      - a replacement with another list
      - answers the new list in place of the old one
      - keeps only the new mapping in the table
      - bumps the version of the type
      - answers the replacement on a later read
      - a replacement that omits the list (A17)
      - 'keeps the mappings, as an omitted collection means unchanged'
      - 'leaves the mapping rows as they were, ids and all'
      - 'still replaces the type itself, bumping its version'
      - a replacement that sends an empty list
      - 'clears the mappings: a stated list replaces them'
      - a replacement with the same list in another order
      - answers the same mappings
      - 'leaves the rows as they were, ids and all, rather than rewriting them'
      - the refusals of a write
      - 'refuses a kind that is neither address nor phone, naming it'
      - 'refuses a source address type the tenant does not have, naming it'
      - 'refuses a phone mapping whose destination is an address type only, naming it'
      - 'refuses a missing destination, naming it'
      - 'refuses two mappings of one kind to one destination, naming the second'
      - 'refuses more than 20 mappings, saying how many it holds at most'
      - writes no type at all for a create it refused
      - a replacement it refused
      - names the field it refused
      - leaves the type at its name and version
      - leaves the mapping it had
      - the types a mapping names
      - 'refuses the delete of a phone number type a mapping names, saying what uses it'
      - removes the mappings with the type that owns them
      - 'insert_affiliation_type_contact_information_default_if_absent, which a tenant’s provisioning calls'
      - answers inserted the first time
      - 'answers adopted for a second mapping to the same destination, and keeps the first'
      - 'answers adopted, writing nothing, for a mapping naming a type the tenant does not have'
      - 'answers adopted, writing nothing, for an affiliation type the tenant does not have'
      - 'bumps the version of the type it gave a mapping, once'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.queries.integration.spec.js
    declared_test_titles:
      - affiliation types read (integration)
      - getAffiliationType
      - 'answers the whole record, every field of the row mapped'
      - answers undefined for an id the tenant does not have
      - answers undefined in another tenant for the first tenant's type
      - getAffiliationTypesByIds
      - 'answers each type the tenant has, whole and in id order, exactly as the single get does'
      - 'answers the whole record of each, every field of the row mapped'
      - leaves out the id another tenant holds
      - 'answers nothing for no ids, without a statement'
      - listAffiliationTypes
      - 'answers a page of at most the limit, in id order'
      - answers the whole record of each element of the page
      - says where the page ended while another may follow
      - answers the next page beginning where the first one ended
      - answers no records and no next position for a page that begins past every id
      - leaves out the type another tenant holds at the very next id
      - a last page the limit fills exactly
      - 'answers the first two of the four types, and says where that page ended'
      - answers the two types that remain when the limit is exactly their number
      - 'answers no next position for that page, full though it is'
      - a tenant with no affiliation types at all
      - answers an empty page with no next position
      - read_affiliation_type_contact_information_defaults
      - 'answers every mapping of a type, addresses before phones, each by its destination id'
      - answers an empty list for a type with no mappings and for an id the tenant does not have
      - 'answers every id asked for, and nothing else'
      - 'answers nothing of another tenant''s type, and reaches no other tenant''s mappings from that one'
      - answers an empty map when asked for no types
      - the mappings on the reads of the record
      - answers the whole record with its mappings on a read by id
      - 'answers each element of a page with its own mappings, and none of another''s'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/configuration-type-usages.queries.integration.spec.js
    declared_test_titles:
      - configuration type usages read (integration)
      - contact types
      - counts the contacts that hold the type (premise)
      - leaves out a type nothing uses
      - answers nothing of another tenant’s rows
      - answers an empty map for no ids
      - contact roles
      - counts the affiliation roles that hold the role
      - counts the compliance rules of organization types that name the role
      - 'counts both kinds of use of one role, each under its own object type'
      - leaves out a role nothing uses
      - organization types
      - counts the organizations that hold the type
      - leaves out a type nothing uses
      - address types
      - >-
        counts the contact addresses and the organization addresses of the type, each under its own object
        type
      - leaves out a type nothing uses
      - phone number types
      - >-
        counts the contact phone numbers and the organization phone numbers of the type, each under its own
        object type
      - leaves out a type nothing uses
      - protected records
      - answers the protected role and not the other
      - answers nothing of another tenant’s protection
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js'
    declared_test_titles:
      - contact types persistence (integration)
      - createContactType
      - 'answers the whole record it wrote, every field as sent and the system properties stamped'
      - defaults is_active to true and leaves out a description the create did not give
      - derives the id from the name when the create names none
      - 'keeps an id the create does state, exactly as sent, rather than deriving one from the name'
      - 'refuses an id the create states that is not a configuration id, saying what an id may hold'
      - refuses an id the create states that is reserved
      - refuses an id the create states that begins with the prefix Rhythm keeps for itself
      - writes no type at all for an id it refused
      - 'refuses a create whose name yields no id at all, asking for one rather than inventing it'
      - refuses a create at an id the tenant already uses
      - 'refuses a create with no name, by the column that requires one'
      - stores a name with spaces around it trimmed
      - the name a type must have
      - refuses a create whose name is the empty string
      - refuses a create whose name is nothing but spaces
      - >-
        refuses a create whose name is nothing but a tab and a newline, which the database’s own btrim would
        let through
      - writes no type at all for a create it refused
      - 'refuses the blank name before it asks the name for an id, so the person is told which field is wrong'
      - a replacement whose name is blank
      - is refused as an invalid object
      - 'leaves the type exactly as it was, name, description and version alike'
      - refuses a replacement whose name is the empty string
      - stores a replacement’s name with the spaces around it removed
      - updateContactType
      - 'answers the whole record as replaced, with the fields the input states'
      - clears the description the replacement omitted
      - refuses a replacement of a type the tenant does not have
      - a replacement at a version the row has moved past
      - is refused as a stale object
      - leaves the row as the replacement that did succeed left it
      - a replacement of a type locked against updates
      - is refused as an illegal operation
      - leaves the row at the name and version it had
      - the five page layout and form assignments
      - a create that assigns all five
      - answers the record carrying the five ids it was given
      - registers one assignment per slot with Platform
      - registers nothing in the deprecated console_form slot Platform is still carrying
      - 'lets the two console forms be the same definition, which is what a tenant that has not split them has'
      - leaves the five properties off a type that was created with none of them
      - the five ids a write may not state
      - refuses an id no definition of the tenant has
      - refuses another tenant’s definition
      - refuses a definition of the organizations purpose on a contact type
      - 'refuses a definition of the wrong kind: a console form in the console page layout slot'
      - refuses a portal form in the console create form slot
      - refuses a portal form in the console edit form slot
      - refuses an organizations console form in the console edit form slot of a contact type
      - 'names the field that was wrong, so a person can see which one to fix'
      - writes no type at all for a refused create
      - a replacement whose assignment is refused
      - 'leaves the type exactly as it was, name, version and assignment alike'
      - leaves the assignment it had registered with Platform
      - a replacement that is silent about the assignments
      - >-
        KEEPS every assignment the row held: the one exception to "a replacement clears what it does not
        state"
      - still clears the ordinary fields it did not state
      - leaves the registry as it was
      - a replacement that states null
      - clears the one it stated null and keeps the one it was silent about
      - withdraws the cleared slot’s registration and leaves the other
      - withdraws every registration when the type is deleted
      - deleteContactType
      - 'removes the type, so a read answers undefined'
      - refuses the delete of a type the tenant does not have
      - the delete of a locked type
      - is refused as an illegal operation
      - leaves the type in the table
      - the delete of a type a contact has
      - 'is refused, naming the contact type that is in use'
      - leaves the type in the table
      - leaves the contact with the type it had
      - and then the delete of the same type once the contact no longer has it
      - 'succeeds, so a read of the type answers undefined'
      - leaves the contact that had the type in the table
      - leaves that contact with no type
      - the place a type holds in the tenant’s list
      - leaves a type created without a display order holding no place at all
      - keeps the place a create does state
      - lets a replacement give a place to a type that held none
      - 'clears the place a replacement is silent about, as it clears any other column'
      - clears the place a replacement states as null
      - 'refuses a place below 1, by the column’s own check'
      - reorderContactTypes
      - a reorder of every type of the tenant
      - 'answers the tenant’s whole list in the new order, every record as the list maps it'
      - 'stores the dense order 1..n, bumping and stamping only the types whose place changed'
      - is what the list answers afterwards
      - numbers a list no type of which held a place 1..n in the order given
      - gives a place to the types that held none while leaving a type already at its place alone
      - 'numbers a list with ties and gaps 1..n in the order given, bumping every type it moved'
      - changes nothing for a reorder that keeps every place
      - 'arranges the inactive types along with the active ones: the list is the whole list'
      - answers an empty list to an empty reorder of a tenant with no types
      - lets a type locked against updates keep its place while the others move
      - numbers a placeless type locked against updates that the first reorder leaves where its name stands
      - 'the refusals, each of which writes nothing'
      - refuses a list that names an id twice
      - refuses a list that names a type the tenant does not have
      - refuses a list that leaves a type of the tenant out
      - 'moves a type locked against updates as well: a lock does not hold a place in the list'
      - moves another type across a placeless locked one and numbers the locked one where it lands
      - another tenant’s types
      - 'reads them as unknown, refusing the reorder'
      - 'reorders only the tenant’s own types, even one that shares an id with another tenant’s'
      - leaves the other tenant’s types as they were
      - two reorders of one tenant at once
      - lets the first finish with its own order
      - 'lets the second finish after it, reading what the first wrote'
      - 'leaves the last one’s order, dense, each version bumped once for every reorder that moved the type'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-types.queries.integration.spec.js
    declared_test_titles:
      - contact types read (integration)
      - getContactType
      - 'answers the whole record, every field of the row mapped'
      - answers undefined for an id the tenant does not have
      - a type that was given all five page layouts and forms
      - 'answers the whole record, the five assignment ids among its fields'
      - 'leaves a property off for a slot the type never had: a null id is absent, not null'
      - answers undefined in another tenant for the first tenant's type
      - listContactTypes
      - 'answers a page of at most the limit, in name order'
      - answers the whole record of each element of the page
      - 'says where the page ended while another may follow: the place, the name lower-cased, and the id'
      - answers the next page beginning where the first one ended
      - answers no records and no next position for a page that begins past every name
      - leaves out the type another tenant holds at the very next id
      - a last page the limit fills exactly
      - 'answers the first two of the four types, and says where that page ended'
      - answers the two types that remain when the limit is exactly their number
      - 'answers no next position for that page, full though it is'
      - a tenant with no contact types at all
      - answers an empty page with no next position
      - 'the order, when some types hold a place in the list and some hold none'
      - 'answers every numbered type by its place, before every type that holds none'
      - 'answers the unnumbered types by name with case disregarded, so a lower-case name may come first'
      - >-
        answers each numbered type its place as display_order, and leaves the property off a type that holds
        none
      - 'says where a page that ended among the numbered types ended, by the place it reached'
      - 'resumes after a numbered position with the next numbered type, the unnumbered ones behind it'
      - 'says where a page that ended among the unnumbered types ended, by a null place and the name'
      - 'resumes after an unnumbered position with the next name alone: no numbered type is listed twice'
      - 'getContactTypesByIds, the read of one contact type made over a set (integration)'
      - >-
        answers the tenant’s contact types with those ids in id order, leaving out an id nobody has and
        another tenant’s
      - answers each contact type exactly as the read of that one contact type does
      - answers no contact types for no ids
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: 'services-crm:modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js'
    declared_test_titles:
      - crm settings persistence (integration)
      - the first save a tenant makes
      - had no row to begin with
      - 'answers the defaults as saved, the other nine slots still empty'
      - 'commits version 1, so the version 0 a tenant with no row reads is the version its first save states'
      - registers the default with Platform under the contacts section
      - a save that states only part of the record
      - leaves the section it did not name exactly as it was
      - leaves the fields of the named section it did not state
      - bumps the version once for the save
      - clearing a default with null
      - answers the cleared slot as null and the other as it was
      - withdraws the cleared slot’s registration with Platform and leaves the other
      - the five ids a save may not state
      - 'refuses an id no definition of the tenant has, naming the section and the field'
      - refuses another tenant’s definition
      - 'refuses a definition of the other purpose in a section: an organizations layout under contacts'
      - 'refuses a definition of the wrong kind: a console form in the console page layout slot'
      - 'refuses a portal form in the console create form slot, naming the section and the field'
      - 'refuses a portal form in the console edit form slot, naming the section and the field'
      - refuses a contacts console form in the organizations section
      - makes no row at all for a refused first save
      - leaves a saved default as it was when a later save is refused
      - the version a save may state
      - accepts version 0 as the first save of a tenant that has no row
      - refuses a save at a version the row has moved past
      - leaves the row at the version and the value the save that did succeed left
      - two first saves of one tenant at the same moment
      - 'lets both succeed: the row is made with ON CONFLICT DO NOTHING, not claimed by one of them'
      - leaves one row for the tenant
      - 'keeps what each of them saved, because the second waited on the first’s lock'
      - counts both saves in the version
      - another tenant
      - saves its own defaults without touching the first tenant’s
      - the default affiliation type for the Organization field
      - a save naming one of the tenant’s types
      - 'answers the type as the default, the layout sections untouched'
      - is what read_default_affiliation_type_id answers afterwards
      - resets the default to other on a save of null
      - 'resets the default to other on a save of nothing but spaces, which the table’s trigger stores as null'
      - leaves the default as it was on a save that does not state it
      - 'refuses a type the tenant does not have, naming default_affiliation_type_id'
      - refuses another tenant’s type
      - leaves a saved default and the version as they were when a later save is refused
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/crm-settings.queries.integration.spec.js
    declared_test_titles:
      - crm settings read (integration)
      - a tenant that has never saved a default
      - 'had no row to begin with, so the case below is about a tenant with none'
      - 'answers both sections empty, at version 0'
      - creates no row by being read
      - a tenant that has saved some of its defaults
      - 'answers the whole record: every saved id in its own section and slot, every unsaved one null'
      - 'answers the tenant of the execution, never a tenant of the request'
      - >-
        answers another tenant its own empty defaults, not these: the row is invisible across the tenant
        boundary
      - read_default_affiliation_type_id
      - answers other for a tenant that has no row at all
      - answers other for a tenant whose row names no default
      - a tenant that chose its default
      - answers the type the tenant chose
      - answers the same type as the settings record’s default_affiliation_type_id
      - 'answers another tenant other, not this choice: the row is invisible across the tenant boundary'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organization-types.integration.spec.js
    declared_test_titles:
      - organization types persistence (integration)
      - clean_organization_type
      - 'drops a compliance rule with neither a minimum nor a maximum, as the legacy cleaning did'
      - keeps a compliance rule whose only bound is a maximum of zero
      - organization_type_problems
      - 'reports every rule the record breaks at once, each naming its field'
      - reports nothing for a record whose switches all have their list
      - createOrganizationType
      - 'answers the record it wrote, in the tenant of the execution, at version 1'
      - 'runs the legacy cleaning before the write: a rule with neither bound is gone'
      - 'writes the owned compliance rules as rows of their own, in the record’s order'
      - 'answers the same record a read answers, children and all'
      - answers no key-role property (key roles removed 2026-10-11)
      - >-
        ignores a key_roles list a caller still passes, rather than writing it (the API refuses one at the
        boundary)
      - 'derives the id from the name when the input names none, in the configuration-id grammar'
      - 'keeps an id the input does state exactly as sent, rather than repairing it'
      - 'refuses an id that is not a configuration id (InvalidRequest), naming the rule, and writes nothing'
      - refuses a create whose name yields no id and which names none itself (InvalidRequest)
      - 'refuses a create whose name is the empty string (ObjectInvalid naming name), and writes nothing'
      - refuses a create whose name is nothing but spaces
      - >-
        refuses a create whose name is nothing but a tab and a newline, which the database’s own btrim would
        let through
      - 'refuses the blank name before it asks the name for an id, so the person is told which field is wrong'
      - 'stores a name with spaces around it trimmed, so no two types differ only by their padding'
      - 'refuses a record whose switch is on with no list (ObjectInvalid), and writes nothing'
      - 'refuses a second type at an id the tenant already has (InvalidRequest), and leaves the first as it was'
      - >-
        refuses a compliance rule of a role the tenant does not have, and leaves neither the type nor a child
        behind
      - updateOrganizationType
      - 'replaces the record at the version read, bumping it, and answers it as read back'
      - replaces the owned children rather than adding to them
      - 'creates a type whose only compliance rule is a maximum of zero, and writes that rule'
      - 'leaves the id as it was created, whatever the replacement states: a configuration id never changes'
      - 'refuses a replacement whose name is nothing but spaces, leaving the type exactly as it was'
      - refuses a replacement whose name is the empty string
      - 'answers the database’s own check in the same words, for a writer that reaches it another way'
      - 'refuses a replacement at a version the type has moved past (StaleObject), and changes nothing'
      - refuses a replacement of a type the tenant does not have (RecordNotFound)
      - >-
        refuses a replacement of a type locked against updates (IllegalOperation), and leaves the row as it
        was
      - >-
        refuses a replacement whose rule names a role the tenant does not have, leaving the type and its
        children as they were
      - the five page layout and form assignments
      - a create that assigns all five
      - answers the record carrying the five ids it was given
      - 'registers one assignment per slot with Platform, under the organizations purpose'
      - registers nothing in the deprecated console_form slot Platform is still carrying
      - 'refuses an id no definition of the tenant has, naming the field'
      - refuses another tenant’s definition
      - refuses a definition of the contacts purpose on an organization type
      - 'refuses a definition of the wrong kind: a portal form in the console create form slot'
      - 'refuses a definition of the wrong kind: a portal form in the console edit form slot'
      - refuses a contacts console form in the console edit form slot of an organization type
      - a replacement whose assignment is refused
      - 'leaves the row at the name, version and assignment it had'
      - leaves the assignment it had registered with Platform
      - 'KEEPS an assignment a replacement is silent about, and clears one it states null'
      - withdraws every registration when the type is deleted
      - deleteOrganizationType
      - 'deletes the type and its owned children, and answers the id'
      - refuses the delete of a type the tenant does not have (RecordNotFound)
      - 'refuses the delete of a locked type (IllegalOperation), and leaves the row'
      - 'refuses the delete of a type an organization has, and leaves the type'
      - the place a type holds in the tenant’s list
      - leaves a type created without a display order holding no place at all
      - keeps the place a create does state
      - lets a replacement give a place to a type that held none
      - 'clears the place a replacement is silent about, as it clears any other column'
      - 'refuses a place below 1, by the column’s own check'
      - reorderOrganizationTypes
      - 'writes the tenant’s whole list dense 1..n in the order given, bumping only the types that moved'
      - 'answers every type in the new order, each as the list maps it, its compliance rules included'
      - is what the list answers afterwards
      - 'refuses a list that leaves a type of the tenant out, naming the concept and writing nothing'
      - 'refuses a list that names a type only another tenant has, leaving both tenants’ places alone'
      - another tenant
      - >-
        does not reach a type of the first tenant at the same id: its own write and delete touch only its own
        row
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies. Platform communication operations use the local test adapter, so this suite does not
      prove deployed template validation or delivery.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/organization-types.queries.integration.spec.js
    declared_test_titles:
      - 'organization types persistence, its reads (integration)'
      - get_organization_type_fields
      - >-
        answers the legacy record: the compliance options with the rules in position order, and no e-mail
        template (Platform holds it)
      - answers a type without rules or notification without compliance options
      - stamps the record with the tenant and the system properties the row carries
      - answers undefined for an id the tenant does not have
      - >-
        answers the WHOLE record of a type that was given every field, system properties and all four
        assignments included
      - 'leaves a property off for a slot the type never had, as every nullable column of the row is left off'
      - >-
        answers undefined in another tenant for a type the first tenant has (row-level security, no tenant
        predicate in the read)
      - list_organization_type_fields
      - 'answers the tenant’s types in name order, each mapped as the single read maps it'
      - 'answers the whole of a listed type exactly as the single read does, the four assignments included'
      - 'answers at most the limit and the cursor of the next page, and resumes after it'
      - answers no next position for a last page the limit fills exactly
      - 'answers no record of another tenant, and nothing at all for a tenant with no type'
      - 'getOrganizationTypesByIds, the read of one organization type made over a set (integration)'
      - >-
        answers the tenant’s organization types with those ids in id order, leaving out an id nobody has and
        another tenant’s
      - answers each organization type exactly as the read of that one organization type does
      - answers no organization types for no ids
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/phone-number-types.integration.spec.js
    declared_test_titles:
      - phone number types persistence (integration)
      - create_phone_number_type
      - 'answers the whole record it wrote, every field as sent and the system properties stamped'
      - 'stores the applicability it was given in its own table, which is the guide a list filters on'
      - derives the id from the name when the create names none
      - 'refuses an id the create states that is not a configuration id, saying what an id may hold'
      - refuses an id the create states that is reserved
      - refuses a create at an id the tenant already uses
      - 'applies_to: a type used on the side it is not marked for'
      - >-
        refuses a contact a phone number of an organizations-only type, naming its type field (CRM-M1 decision
        M1-D8)
      - gives an organization a phone number of a contacts-only type and reads it back
      - update_phone_number_type
      - 'answers the record as replaced, the version bumped'
      - refuses a replacement of a type the tenant does not have
      - refuses a replacement of a type locked against updates
      - 'leaves the id as it was created, whatever the replacement states: a configuration id never changes'
      - delete_phone_number_type
      - 'removes the type, so a read answers undefined'
      - the delete of a type a contact phone number uses
      - 'is refused, naming the type and what uses it'
      - leaves the type in the table
      - succeeds once the contact no longer has a phone number of it
      - refuses the delete of a type an organization phone number uses
      - the two ends of a linked contact phone number
      - links the contact’s number of the destination type to the organization’s (premise)
      - refuses the delete of the type of the organization’s number the contact is linked to
      - refuses the delete of the type of the contact’s linked number
      - reorder_phone_number_types
      - a reorder of every type of the tenant
      - 'answers the tenant’s whole list in the new order, every record as the list maps it'
      - 'stores the dense order 1..n, bumping and stamping only the types whose place changed'
      - is what the list answers afterwards
      - 'numbers a list with ties and gaps 1..n in the order given, bumping every type it moved'
      - changes nothing for a reorder that keeps every place
      - answers an empty list to an empty reorder of a tenant with no types
      - lets a type locked against updates keep its place while the others move
      - places the first type a tenant creates without a display order at 1
      - places a type created without a display order one past the greatest place the tenant uses
      - 'keeps the display order a create does state, even one inside the list'
      - >-
        places a type created without a display order after the greatest place of its own tenant, not
        another’s
      - 'the refusals, each of which writes nothing'
      - refuses a list that names an id twice
      - refuses a list that names a type the tenant does not have
      - refuses a list that leaves a type of the tenant out
      - 'moves a type locked against updates as well: a lock does not hold a place in the list'
      - another tenant’s types
      - 'reads them as unknown, refusing the reorder'
      - 'reorders only the tenant’s own types, even one that shares an id with another tenant’s'
      - leaves the other tenant’s types as they were
      - two reorders of one tenant at once
      - lets the first finish with its own order
      - 'lets the second finish after it, reading what the first wrote'
      - 'leaves the last one’s order, dense, each version bumped once for every reorder that moved the type'
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
  - provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/phone-number-types.queries.integration.spec.js
    declared_test_titles:
      - phone number types read (integration)
      - 'answers the whole record of the tenant’s type, every property as the API reads it'
      - answers undefined for an id the tenant has no type at
      - answers the tenant’s whole list in display order
      - 'answers the types that apply to contacts, `both` included'
      - 'answers the types that apply to organizations, `both` included'
      - 'answers the display order of every type of the tenant, by id'
      - answers each tenant only its own types
      - 'getPhoneNumberTypesByIds, the read of one phone number type made over a set (integration)'
      - >-
        answers the tenant’s phone number types with those ids in id order, leaving out an id nobody has and
        another tenant’s
      - answers each phone number type exactly as the read of that one phone number type does
      - answers no phone number types for no ids
    skipped_declarations: []
    dependency_summary: >-
      The prepared PostgreSQL database and its declared constraints, policies and transaction behavior are
      real dependencies.
command: >-
  npx mocha --timeout 60000
  'modules/entities/tests/integration/db/affiliation-type-contact-info-defaults.table.integration.spec.js'
  'modules/entities/tests/integration/db/affiliation-types.table.integration.spec.js'
  'modules/entities/tests/integration/db/collection-types.table.integration.spec.js'
  'modules/entities/tests/integration/db/contact-types.table.integration.spec.js'
  'modules/entities/tests/integration/db/organization-types.table.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/address-types.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/address-types.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/affiliation-types.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/configuration-type-usages.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/contact-types.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/contact-types.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/crm-settings.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/crm-settings.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/organization-types.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/organization-types.queries.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/phone-number-types.integration.spec.js'
  'modules/entities/tests/integration/services/persistence/phone-number-types.queries.integration.spec.js'
retries: none
proves_controls: []
product_intent: proposed
implementation_evidence: observed
narrative:
  status: enriched
---

# Classification types and CRM settings integration

## Verification Boundary

Classification catalogue persistence, catalogue usage reads and CRM default settings cross the real PostgreSQL boundary.

## What This Suite Proves

Contact, organization, address, phone and affiliation types are persisted and read with their stored order and values. Contact-role catalogue tests exercise automatic-role selection and retained role definitions. Usage queries count references that prevent deletion. CRM settings tests distinguish a workspace with no saved default from one whose saved choice must survive another read.

## What It Does Not Prove

The group does not prove configuration screens or public write authority. It does not prove delivery of an organization compliance template, which belongs to compliance integration. Effective form resolution consumes these records but has its own separate integration boundary.

## Level and Rationale

Catalogue uniqueness, destination mapping references and live usage counts depend on existing rows. The absence of a settings row is itself a storage condition, so a fabricated settings answer cannot establish fallback behavior or the first write. Real constraints are required to distinguish legal configuration from broken references.

## Real Dependencies and Test Doubles

Catalogue tables, query functions, execution contexts and PostgreSQL are real. Seeded definitions supply valid references for constraint premises. No console manager is part of this group. Any Platform-owned template interaction in an organization-type write retains the exact test seam the source declares.

## Must Not Be Doubled

Keep catalogue uniqueness and workspace references real, including both sides of each mapping reference. Do not double the usage query when claiming a referenced type cannot be removed. Default-setting reads must consult the actual saved row rather than a test-supplied cached selection.

## Fixtures and Isolation

Each spec owns the catalogue records it creates and removes dependent mappings before their owners. Shared seed definitions are read as premises. Tests that alter configuration dependencies restore them after the stage; this register selects a serial invocation to avoid interleaving those changes.

## Determinism and Retries

Ordered type fixtures state their exact expected positions and version values. Configuration identifiers come from the fixture helper or literal scope-owned records. A failed setup fails dependent cases; the register introduces no retry that could conceal a uniqueness or concurrent-ordering problem.

## How It Runs

Run the command in the frontmatter from the service repository after preparing the test database. It selects every file in this aggregate register under the repository Mocha bootstrap and runs serially. This is the aggregate selection of the integration gate; the complete repository integration command includes other modules and is wider than this register.

## Coverage Gaps

Direct catalogue tests are narrower than a complete console administration journey. The existing catalogue-transition and competing-write findings continue to identify missing proof. Archive is not a classification transition here: inactive status is a change, while record archive belongs to contacts and organizations.
