---
id: CON-CRM-ENT-005
name: Contact Role
feature: FEAT-CRM-ENT-06
product_area: crm
module: entities
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
definition: A contact role names a responsibility a person can hold at an organization.
owned_by: MOD-CRM-ENT
facets: []
evidence:
  - >-
    `services-crm` at `7ea48a414c42eade10d60753b8bcc323b1dd726f`:
    `modules/entities/db/contact-roles.table.ts` records this concept.
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Exists
    provenance: inferred
    source: >-
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#answers 400 naming the
      field for a create whose name is nothing but spaces;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#answers 400 for an id
      that is not a configuration id, saying what an id may hold;
      services-crm:modules/entities/tests/integration/services/persistence/contact-roles.integration.spec.js#refuses
      it with the legacy words, naming the organization types whose rules depend on it;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0196.spec.ts#CRM-ACC-0196;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses the delete (409)
      while an affiliation of a contact is of that type;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses a caller who may
      only read affiliation types the change of the default (403);
      services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js#keeps
      the mappings, as an omitted collection means unchanged;
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#answers 400 for a reorder
      that leaves a role of the tenant out, naming it;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#D-11;
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#answers a portal user of the
      tenant;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0199.spec.ts#CRM-ACC-0199;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:affiliationRefusal;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      a role whose dates lie outside its affiliation's;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      the same role overlapping itself on one affiliation;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
      the open roles of an affiliation the save ends, on the same day;
      services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#refuses
      a role outside the list added to an existing affiliation;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#gives
      the organization's first contact the automatic and first-contact roles, besides the roles it
      states;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/automatic-role-reconciliation.integration.spec.js#gives
      the automatic and first-contact roles on the start day in the tenant calendar;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/automatic-role-reconciliation.integration.spec.js#does
      not give back a role a person removed after it was given;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#counts
      an inactive contact as already belonging to the organization, so the next contact is not its
      first;
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.follow-ups.integration.spec.js#skips
      the forbidden role on the start day;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D12;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#gives
      the first-contact roles to exactly one of several contacts joining an empty organization at
      once; react-console:components/affiliations/affiliation-editor.tsx:Add Period;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D13;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
      a type that holds it to no rule, or with no type: no status and no violations;
      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/integration/services/organizations/compliance/organization-compliance.integration.spec.js#brings
      an organization into compliance for a role held on a current affiliation that is not primary;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/compliance-change-events.integration.spec.js#a
      contact saved with the notes DISABLE_CONTACT_ROLE_COMPLIANCE_CHECK: nothing (its delete still
      records);
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#writes
      nothing for a duplicate delivery, or a second run on an unchanged organization;
      services-crm:modules/entities/tests/e2e/organization-compliance.e2e.spec.js#keep them against
      a PUT that sends others back;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#is
      not submitted again while the organization stays out of compliance, nor for a type that does
      not notify;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/compliance-reads.integration.spec.js#leaves
      out the contact whose addresses sit past the primary slot, no secondary address standing in;
      services-crm:docs/entities-api.md#`compliance_notification_template`: the e-mail the type
      sends;
      services-crm:modules/entities/tests/integration/services/persistence/compliance-reprocesses.integration.spec.js#refuses
      a second running run for the tenant;
      services-crm:modules/entities/tests/e2e/email-previews.e2e.spec.js#answers 403 to a caller who
      may administer types but may not read the records the answer contains;
      services-crm:modules/entities/tests/e2e/organization-compliance.e2e.spec.js#answers 400 for a
      template both named and typed in, or neither;
      services-crm:modules/entities/services/persistence/organization-types.ts:organizationTypeProblems;
      services-crm:modules/entities/tests/integration/services/persistence/organization-attention-items.queries.integration.spec.js#answers
      the organizations out of compliance by id, and no other;
      services-crm:modules/entities/services/templates/compliance-notification-template.ts:effectiveTemplateChange;
      services-crm:modules/entities/services/persistence/organization-types.ts:updateOrganizationType;
      services-crm:modules/entities/tests/integration/services/templates/compliance-notification-template.integration.spec.js#keeps
      the delete and logs the type id when Platform cannot clear it: the type is gone either way
    note: Business need inferred from the sourced behavior; no historical motivation is attributed.
  - section: What It Controls
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#answers 400 naming the
      field for a create whose name is nothing but spaces;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#answers 400 for an id
      that is not a configuration id, saying what an id may hold;
      services-crm:modules/entities/tests/integration/services/persistence/contact-roles.integration.spec.js#refuses
      it with the legacy words, naming the organization types whose rules depend on it;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0196.spec.ts#CRM-ACC-0196;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses the delete (409)
      while an affiliation of a contact is of that type;
      services-crm:modules/entities/tests/e2e/affiliation-types.e2e.spec.js#refuses a caller who may
      only read affiliation types the change of the default (403);
      services-crm:modules/entities/tests/integration/services/persistence/affiliation-types.integration.spec.js#keeps
      the mappings, as an omitted collection means unchanged;
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#answers 400 for a reorder
      that leaves a role of the tenant out, naming it;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#D-11;
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#answers a portal user of the
      tenant;
      react-console:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0199.spec.ts#CRM-ACC-0199;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:affiliationRefusal;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      a role whose dates lie outside its affiliation's;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#refuses
      the same role overlapping itself on one affiliation;
      services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends
      the open roles of an affiliation the save ends, on the same day;
      services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#refuses
      a role outside the list added to an existing affiliation;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#gives
      the organization's first contact the automatic and first-contact roles, besides the roles it
      states;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/automatic-role-reconciliation.integration.spec.js#gives
      the automatic and first-contact roles on the start day in the tenant calendar;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/automatic-role-reconciliation.integration.spec.js#does
      not give back a role a person removed after it was given;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#counts
      an inactive contact as already belonging to the organization, so the next contact is not its
      first;
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.follow-ups.integration.spec.js#skips
      the forbidden role on the start day;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D12;
      services-crm:modules/entities/tests/integration/services/contacts/role-assignment/contact-role-assignment.integration.spec.js#gives
      the first-contact roles to exactly one of several contacts joining an empty organization at
      once; react-console:components/affiliations/affiliation-editor.tsx:Add Period;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D13;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#with
      a type that holds it to no rule, or with no type: no status and no violations;
      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/integration/services/organizations/compliance/organization-compliance.integration.spec.js#brings
      an organization into compliance for a role held on a current affiliation that is not primary;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/compliance-change-events.integration.spec.js#a
      contact saved with the notes DISABLE_CONTACT_ROLE_COMPLIANCE_CHECK: nothing (its delete still
      records);
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#writes
      nothing for a duplicate delivery, or a second run on an unchanged organization;
      services-crm:modules/entities/tests/e2e/organization-compliance.e2e.spec.js#keep them against
      a PUT that sends others back;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#is
      not submitted again while the organization stays out of compliance, nor for a type that does
      not notify;
      services-crm:modules/entities/tests/integration/services/organizations/compliance/compliance-reads.integration.spec.js#leaves
      out the contact whose addresses sit past the primary slot, no secondary address standing in;
      services-crm:docs/entities-api.md#`compliance_notification_template`: the e-mail the type
      sends;
      services-crm:modules/entities/tests/integration/services/persistence/compliance-reprocesses.integration.spec.js#refuses
      a second running run for the tenant;
      services-crm:modules/entities/tests/e2e/email-previews.e2e.spec.js#answers 403 to a caller who
      may administer types but may not read the records the answer contains;
      services-crm:modules/entities/tests/e2e/organization-compliance.e2e.spec.js#answers 400 for a
      template both named and typed in, or neither;
      services-crm:modules/entities/services/persistence/organization-types.ts:organizationTypeProblems;
      services-crm:modules/entities/tests/integration/services/persistence/organization-attention-items.queries.integration.spec.js#answers
      the organizations out of compliance by id, and no other;
      services-crm:modules/entities/services/templates/compliance-notification-template.ts:effectiveTemplateChange;
      services-crm:modules/entities/services/persistence/organization-types.ts:updateOrganizationType;
      services-crm:modules/entities/tests/integration/services/templates/compliance-notification-template.integration.spec.js#keeps
      the delete and logs the type id when Platform cannot clear it: the type is gone either way
---

# Contact Role

## What It Is

A Contact Role names a responsibility a person can hold at an organization, such as Billing Contact or Chapter Chair. The role is a reusable workspace catalogue entry with a name and stable identity. An actual holder is a contact with a dated role period on an affiliation; the definition itself does not identify who currently performs the work.

A role can have automatic-assignment settings, including assignment for a qualifying first contact. Catalogue activity and ordering help govern eligible choices and automatic use. Existing holders and organization policy references retain the role's meaning even when its future use changes.

## Why It Exists

Our reading is that association responsibilities need a shared meaning to support both staff interpretation and organizational policy. If each contact carries a free-form title as the only explanation, staff cannot consistently count billing representatives or select notice recipients by responsibility. A reusable role connects dated individual work to the workspace's expectations without treating every person with a similar job title as equivalent.

## What It Is Not

A Contact Role is not a Platform security role and grants no application permission by itself. It is also not the person's job title or their affiliation type. Billing Contact describes responsibility within an organizational tie; Employer describes the kind of tie. A role period is a facet of the affiliation, carrying when this particular holder performs the responsibility.

## What It Controls

Role definitions determine the responsibility choices and the identity used by dated role validation. Automatic flags can supply eligible assignments on new qualifying relationships or their start-day processing, subject to contact status, locks, and organization-type restrictions. Repeated processing does not restore a deliberately removed automatic responsibility.

Organization Type can name roles in its allowed list, coverage rules, and notification selection. Organization Compliance counts effective holders by role and selects applicable recipients. Current and historical holders or policy references can block deleting the definition. Catalogue order affects staff choice presentation; a customer lock protects content or deletion separately from its position in that order.

## How It Is Used

The CRM Administrator maintains the role catalogue, while the CRM Editor gives people dated responsibility periods through Maintain responsibilities on organizational relationships. The CRM Reader inspects held roles in person and organization relationship views. Good use separates an allowed responsibility from a required coverage count and distinguishes the catalogue entry from each individual period of service, especially when someone leaves and later returns.

## Example

A member firm type requires a Billing Contact and selects that role for compliance notices. Ruth holds it on her current firm affiliation, so the assessment can count her as a billing representative. Ending that period changes effective coverage without deleting the role definition. If she returns later, a new period preserves the earlier service gap rather than claiming continuous responsibility.

## How It Relates

Contact Role belongs to the classification catalogue and is held within Affiliation. Organization Type can restrict or require it; Organization Compliance evaluates its current holders and notification recipients. Contact supplies the holder identity and primary e-mail. Record Protection and History explains supported role locks and audited changes. Platform security roles remain a separate access model whose similar name must not obscure the distinction.
