Skip to content

CAP-CRM-ENT-005Maintain reliable contact points

Markdown

CAP-CRM-ENT-005

Maintain reliable contact points

Markdown
  • DocumentApproved
  • Product intentConfirmed
  • ImplementationObserved

KindCapabilityProduct areaCRMModuleEntitiesRevision1Approved byAndrewApproved at2026-10-22

Read atservices-crm 7ea48a41react-console da630758

The Formal Contract

The narrative above explains; the sections below are authoritative — the rules, scenarios and tests this document binds.

Outcomes
Each contact has at most three e-mails with one primary that no other active contact holds.Each contact and organization has at most one address and one phone number per tenant type, with one preferred.A seasonal address is a recurring season and decides the mailing address while in season.
Surfaces
consoleapi
Intent sources
services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#M1-D1services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#M1-D3services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#M1-D8services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A9services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A12

Controls

17 controls apply, from the corpus control catalogue.

TC-AZ-001 TC-AZ-002 TC-AZ-003 TC-AZ-004 TC-AZ-006 TC-DAT-001 TC-DAT-002 TC-DAT-003 TC-LOG-004 TC-LOG-005 TC-PI-001 TC-PI-003 TC-PRV-001 TC-PRV-002 TC-TEN-001 TC-TEN-002 TC-TEN-003

Repositories Read

RepositoryPaths
undefined:modules/entities/
undefined:docs/milestones/CRM-M1-contacts-and-organizations.plan.md
undefined:modules/entities/tests/e2e/contacts.e2e.spec.js
undefined:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js
undefined:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js
undefined:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js
undefined:modules/entities/tests/unit/services/contacts/seasonal-addresses.spec.js
undefined:app/(console)/console/[tenantId]/crm/contacts/
undefined:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0017.spec.ts

Where It Lives

Permissions

KeyChecked atScope
crm.contacts.updateapitenant
crm.organizations.updateapitenant
crm.contacts.readapitenant
crm.address-types.readapitenant
crm.phone-number-types.readapitenant

Business Rules

10 rules, 9 to be proved before approval. A rule's product intent is derived from its origin: 10 confirmed by a product decision, the rest inheriting this capability's confirmed.

IdRuleKindEnforced atProvenance
01must prove
Confirmed
Contacts store up to three ordered valid e-mail addresses, with the first as primary, canonical lowercase values without whitespace and blank entries removed.
✓ Accepted example: "Ada@Example.com " is stored as "ada@example.com" at position 1.✗ Rejected example: a fourth address is rejected.
validationapi, service, databasetest
02must prove
Confirmed
Primary e-mail addresses are unique among active and duplicate contacts within a tenant; inactive or deceased contacts release them, reactivation conflicts with another holder, secondary addresses are not compared and deletion releases the address immediately.
✓ Accepted example: another contact may take the primary address of a deceased contact.✗ Rejected example: a second active contact with "ADA@example.com" as primary is rejected 409.
invariantservice, databasetest
services-crm:modules/entities/tests/integration/db/contact-email-addresses.table.integration.spec.js#refuses to make an inactive contact active while another contact holds its primary address
03must prove
Confirmed
Each contact or organization phone number uses a tenant phone type, with one number per type and at most one preferred number; if none is selected, the earliest number in type order becomes preferred.
validationapi, service, databasetest
04must prove
Confirmed
Valid US phone numbers are normalized to national format, other numbers retain their input form and blank entries are discarded.
✓ Accepted example: "202-555-0143" is stored as "(202) 555-0143".✗ Rejected example: "+44 20 7946 0958" is stored as typed.
calculationservicetest
services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#formats a valid US number, drops a blank one and marks the default preferred number
05must prove
Confirmed
Each address uses a tenant address type, with one per type and at most one preferred address; the earliest nonseasonal address in type order becomes preferred when needed and replaces a removed preferred address.
validationapi, service, databasetest
services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#marks the first address of the tenant’s display order as preferred, never one with a seasonal period
06must prove
Confirmed
Addresses default to US, normalize recognized country and state names to codes, reject unknown countries and restrict latitude to -90 through 90 and longitude to -180 through 180.
validationservice, databasetest
07must prove
Confirmed
A contact cannot acquire or change to an organization-only address or phone type, but an existing element can retain a type later made organization-only.
validationservicetest
services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#leaves a contact saveable after the type of an address it holds became organizations-only
08must prove
Confirmed
Seasonal addresses use inclusive recurring month-day boundaries that may cross December, reject full calendar dates and overlapping seasons, and treat February 29 as February 28 in non-leap years.
validationapi, servicetest
09must prove
Confirmed
A seasonal address cannot be the regular preferred address and supplies the mailing address only while in season on the workspace calendar and explicitly marked for seasonal use; otherwise the preferred address supplies it.
calculationservicetest
services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the seasonal address in season when the contact marks it, with the workspace date
10
Confirmed
Correcting an address or phone number retains its element identity; replacing the element requires removing it and adding another.
invariantservicedocumented

Scenarios

IdGivenWhenThenProvenance
01Ruth Kim, deceased, retained ruth@example.org as primary.Her son is created with that address as primary.The create succeeds.test
02A contact with a home address and a winter address from 12-01 to 03-31 marked for the season.The mailing address is read in January and in June.The winter address in January, the home address in June.test
services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers the preferred address out of season

Edge Cases

IdGivenWhenThenProvenance
03Two creates racing for the same primary e-mail.Both commit.Exactly one wins and the other is rejected.test

The Markdown files under specs/ in rhythmsoftware/rhythm-v2-docs are the canonical source of truth. This reader is a generated, read-only projection of them.