---
id: FEAT-CRM-ENT-01
title: Contact Records
product_area: crm
module: entities
revision: 1
status: approved
product_intent: proposed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
purpose: >-
  Staff keep a useful record of each person and find the people they need. Customer-specific
  information stays with the records it describes.
outcomes:
  - Staff keep a useful record of each person and find the people they need.
concepts:
  - CON-CRM-ENT-001
actors:
  - ACT-CRM-001
  - ACT-CRM-002
  - ACT-CRM-003
  - ACT-CRM-004
  - ACT-CRM-005
acceptance: FAS-CRM-ENT-01
capabilities:
  - CAP-CRM-ENT-001
  - CAP-CRM-ENT-003
  - CAP-CRM-ENT-008
journeys:
  - JNY-CRM-ENT-001
  - JNY-CRM-ENT-005
approved_by: Andrew
approved_at: 2026-10-22T00:00:00.000Z
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Exists
    provenance: inferred
    source: CAP-CRM-ENT-001; CAP-CRM-ENT-003; CAP-CRM-ENT-008
    note: >-
      Operational consequences are the reviewer’s reading of the child contracts, not attributed
      historical motivation. The affiliation milestone supplies the current-membership distinction
      where named.
  - section: Decisions and Tradeoffs
    provenance: inferred
    source: "CAP-CRM-ENT-001; CAP-CRM-ENT-003; CAP-CRM-ENT-008"
    note: The tradeoffs interpret the child contracts; no historical motive is attributed.
  - section: How It Works
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a contact
      with neither a first nor a last name, naming the rule;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#builds the full name from
      every part in order, trimmed and singly spaced;
      services-crm:modules/entities/tests/e2e/contacts-names.e2e.spec.js#answers 400 for a full name
      sent instead of name parts, and never splits it into parts;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      an honorific salutation to the prefix, start-cased and dotted (RB-2367);
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#names
      every overlong name part of one request at once;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#keeps the id the body named,
      which is no UUIDv7;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#keeps
      the number consumed when the transaction rolls back: a gap, never given back;
      services-crm:modules/entities/tests/integration/services/contacts/contact-create.integration.spec.js#answers
      the retry with the contact the first attempt made;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 for a type the tenant
      does not have, and saves no contact;
      services-crm:modules/entities/tests/integration/db/contacts.table.integration.spec.js#refuses
      a status outside the closed list by contacts_status_check;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.spouse.integration.spec.js#links
      the other spouse back when a contact is created naming it;
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0042.spec.ts#CRM-ACC-0042;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for a photo whose
      upload is not there, naming the field that failed;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 for a cursor it did
      not answer; services-crm:modules/entities/services/persistence/contacts.ts:listContactFields;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#answers
      a contact whose affiliation to the organization is not its primary one;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.queries.integration.spec.js#includes
      a contact whose affiliation in effect to an organization of the walk is not its primary one
      (M2-D3); services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only the
      contacts a picker may offer: not the inactive, not the deceased;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers only the fields
      `?fields=` named, with the id;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the other tenant its own
      contact for that id, to a context that covers it;
      services-crm:modules/entities/aws/api/contacts.routes.ts:contactsRoutes;
      services-crm:modules/entities/tests/e2e/application-access-enforced.e2e.spec.js#is refused
      where the assignment DENIES crm.application.access, though the route itself is served;
      services-crm:modules/entities/tests/e2e/platform-scope.e2e.spec.js#refuses every one of them
      to a staff slice reaching every tenant, 403 at the tenant boundary;
      services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#CRM-ACC-0064;
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0064.spec.ts#CRM-ACC-0064;
      services-crm:modules/entities/tests/e2e/display-links.e2e.spec.js#answers a presigned GET that
      lasts 15 minutes and downloads as an attachment for a document custom field;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-01;
      services-crm:modules/entities/services/quick-search/quick-search-indexer.ts:quickSearchIndexerOf;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#sets the key it
      sends, clears the key sent null and the selection sent empty, and leaves the others;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses a write to
      an archived field;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#refuses yes for a
      yes/no field: no truthiness;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#saves a replace of
      a contact created before the field was required, leaving the field out;
      services-crm:modules/entities/tests/e2e/contacts-custom-fields.e2e.spec.js#puts no custom
      field at the top level of the record;
      services-crm:modules/entities/tests/e2e/contact-roles.e2e.spec.js#refuses a create that
      carries custom_fields, as an unknown property;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-04
related_concepts:
  - CON-CRM-ENT-007
acceptance_suite: FAS-CRM-ENT-01
---

# Contact Records

## Overview

Contact Records gives association staff a continuing identity for each person they know. Names, contact points, organizational connections, and customer-defined information belong to that identity, so staff can recognize the same person across different kinds of work.

A contact is useful beyond the moment of creation. Staff can find it again, inspect its saved information, and use it as the person named by other applications. The feature supports both a coordinator entering a member and an integration creating records through the service.

## Why It Exists

Our reading is that staff need one continuing person identity because a member's circumstances change more often than their identity does. Ruth can change employer, replace her work phone and acquire a different classification while association work still concerns the same person. If staff create another contact for each change, they must guess which record carries the usable information and which organizational ties explain the member's past.

Contact creation supplies that shared starting point; discovery brings staff back to it, and customer-defined values let the association retain information that the common identity model does not name. These are related needs, but they remain separate operations. A saved identity does not by itself establish membership entitlement, grant application access or prove that a search projection has caught up with the latest change.

## Who Uses It

The CRM Editor establishes and maintains a person's record, while the CRM Reader finds people and inspects information within granted access. The Data Importer brings external information through authorized record operations. The CRM Administrator handles classifications and exceptional creation authority; the CRM System supplies derived information and related updates.

## How It Works

A staff member chooses a contact type where appropriate, enters name components and other information, and creates the record. Rhythm derives the names used for display and sorting, assigns a workspace contact number, validates the supplied references, and stores the person and owned information together. The contact page and a service read expose the resulting record.

Later discovery can use paged lists, name search, or current affiliations to an organization and its descendants. Quick Search is a separate projection updated from committed changes, so its timing differs from a direct record read. Customer-defined values remain attached to the contact but use definitions supplied by Platform metadata.

Creation begins with identifying the person, rather than with typing a single display label. The coordinator enters Ruth's first and last names and, where useful, a prefix, nickname or credentials. Review Changes makes supported normalization visible before the console submits the request. A contact can be untyped; selecting Member gives the record a classification, rather than granting membership benefits. A supplied classification must exist in this workspace. A reference failure stops creation instead of leaving a person whose classification cannot be explained.

Duplicate handling is another decision before success. A blocked creation cannot proceed merely because the coordinator confirms a warning. The exceptional path requires the dedicated override authority and a reason. That Data Quality boundary is separate from the primary-e-mail constraint, which the contact-point capability enforces. After validation, the saved contact has a durable identity and a separately allocated number. Integrations can recover a repeated creation through the supported identical-request retry path; the number is not a retry token or a count of successfully created people.

Opening the saved record is the next meaningful step. A direct read exposes its owned information and derived names; a limited service projection returns selected information with the identity. List discovery searches names rather than every visible field. Filtering for a chapter considers current secondary connections as well as the primary one. Quick Search follows a committed-change projection, so an immediate direct read and a later search result need separate inspection. Finding the contact does not authorize every related operation: application access, workspace reach and operation permissions still apply.

## Examples

A professional association records Ruth Kim as a member, with work and home contact points and a customer-defined specialization. After saving, a coordinator opens Ruth's contact page to inspect the information and searches for her again by name. A chapter coordinator can also find Ruth through a currently effective secondary chapter affiliation even when her primary organization is her employer.

Ruth's association records a specialization through an existing contact custom-field definition. If the coordinator sends an undeclared specialization key or an invalid option, the save refuses the field instead of inventing a new definition. She corrects the input, saves, and reads back the declared value. When an integration later submits another named custom key, Ruth's specialization remains because custom values have their own merge semantics.

A week later Ruth reports a corrected work phone. The coordinator edits the existing contact rather than creates another Ruth, reviews the correction, and saves against the version she read. She opens the record again to check the phone and the unchanged specialization. She then searches by Ruth's name; searching the ordinary name-list operation for the phone or contact number is not equivalent. If Quick Search still shows the older label immediately after saving, the direct read establishes what was persisted while search freshness remains a separate question.

## Decisions and Tradeoffs

Our reading is that a continuing person identity separates changes in circumstances from changes in who the person is. A simpler collection of names in each application would leave staff reconciling conflicting phone numbers and employers. Derived names make display and sorting consistent, but require callers to supply structured name parts rather than an arbitrary computed label.

Custom values give an association flexibility within declared definitions. That flexibility has a boundary: it does not make every related record extensible or permit undeclared fields. Likewise, discovery offers several questions with different answers. A primary-organization search subtitle helps recognition; it does not replace current-affiliate membership. Event-fed search keeps a save separate from the search reaction, at the cost of requiring readers and tests to distinguish durable persistence from projection freshness.

## Scope and Boundaries

This feature owns person creation, record discovery, and persistence of customer-defined values. Contact Details and Communication Preferences explains how contact points behave; Affiliations and Roles explains organizational connections. Platform owns custom field definitions, layouts, query execution, and search infrastructure. Data Quality owns duplicate decisions and merging, although creation must respect its refusal boundary.

## Outcomes

Staff have a shared person record that they can retrieve and use for later association work. The Contact Records acceptance suite examines creation, reading, discovery, and custom information together. Its specifications and executable bindings describe intended proof; their existence alone does not establish that all acceptance tests pass at the pinned milestone.

## Its Capabilities

Establish a person’s CRM record turns entered information into a recognizable person with derived names and a durable identity. Locate people and organizations provides authorized discovery and reads for staff and integrations. Keep customer-defined information with records preserves customer values alongside their owning contact or organization. Create a member record and inspect the saved information and Find current affiliates across an organization hierarchy show these capabilities in staff workflows.
