Skip to content

CAP-CRM-ENT-009Maintain a person’s organizational relationships

Markdown

CAP-CRM-ENT-009

Maintain a person’s organizational relationships

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
A person’s affiliations are history, not overwrites: ending a affiliation preserves it, changing employer never leaves two primaries.The organization's people list and the person’s affiliations agree.A save that leaves out the affiliations never erases them.
Surfaces
consoleapi
Intent sources
services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#D-09services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D5services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D8services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D8 placementservices-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.md#M2-D9services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.plan.md#A15services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.plan.md#A18services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.plan.md#A20services-crm:docs/milestones/CRM-M2-affiliations-roles-compliance.plan.md#A21

Controls

18 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-PI-005 TC-PRV-001 TC-PRV-002 TC-TEN-001 TC-TEN-002 TC-TEN-003

Repositories Read

RepositoryPaths
undefined:modules/entities/
undefined:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js
undefined:modules/entities/tests/e2e/contact-information-sources.e2e.spec.js
undefined:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js
undefined:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js
undefined:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js
undefined:app/(console)/console/[tenantId]/crm/contacts/
undefined:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0172.spec.ts
undefined:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0175.spec.ts
undefined:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0177.spec.ts
undefined:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0201.spec.ts
undefined:acceptance/crm/playwright/journeys/affiliations/CRM-ACC-0202.spec.ts
undefined:components/console/organization/affiliation-dialogs.tsx
undefined:lib/affiliations/section.ts

Where It Lives

Permissions

KeyChecked atScope
crm.contacts.updateapitenant
crm.contacts.readapitenant
crm.affiliation-types.readapitenant
crm.organizations.readapitenant

Business Rules

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

IdRuleKindEnforced atProvenance
01must prove
Confirmed
Affiliations require an organization and affiliation type belonging to the tenant; missing, foreign or unknown references reject the complete save.
validationapi, service, databasetest
services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers 400 for an organization the tenant does not have
02must prove
Confirmed
Affiliations and their role periods are saved transactionally through the contact; organization-side people lists expose reads and write additions through the person’s record.
invariantapi, servicetest
03must prove
Confirmed
Omitting affiliations preserves them even on replacement, while an explicit list replaces the collection and an idless row matching organization, type and start date reuses the existing affiliation.
policyservicetest
04must prove
Confirmed
Selecting a different primary affiliation clears the previous primary in the same save, while selecting two new primaries is rejected.
invariantservice, databasetest
05must prove
Confirmed
A primary affiliation must have started by the workspace’s current day and have no end date.
validationservice, database, consoletest
06must prove
Confirmed
Ending a primary affiliation removes its primary designation without selecting a replacement.
lifecycleservicetest
07must prove
Confirmed
Affiliation periods include their end day, allow a one-day stay and reject an end before the start.
validationservice, database, consoletest
08must prove
Confirmed
One contact’s affiliations of the same type to the same organization cannot overlap, including touching boundary days and undated periods; separate periods and different types are permitted.
invariantservice, databasetest
services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#finds periods that share their last and first day overlapping
09must prove
Confirmed
An affiliation cannot change its organization; the editor supports ending it and adding a new affiliation instead.
invariantservice, consoletest
services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses to move the affiliation to another organization under the same id (docs/affiliations/README.md §0b.2)
10must prove
Confirmed
A contact may have 100 current or upcoming affiliations, with no limit on ended history.
validationservicetest
services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#counts only current and upcoming affiliations against the limit: ended history is unlimited (M2-D5)
11must prove
Confirmed
The Organization command selects an effective open tie or creates one using the default type and contact title, applies a nonfuture effective date defaulting to workspace today, ends the previous primary the preceding day unless retained, removes a zero-day prior stay and clears primary designation when cleared.
calculationservicetest
services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#ends the previous primary the day before the effective date by default, and its open roles with it
12must prove
Confirmed
Adding a primary affiliation over a different current primary asks whether the person left; Yes ends the old affiliation before the new start but not before its own start, and Make Primary on an existing affiliation does not ask.
policyconsoleobserved
13must prove
Confirmed
The contact and primary affiliation share the same job title in either direction, a titleless primary inherits the contact title and historical affiliations retain their ending titles.
calculationservicetest
services-crm:modules/entities/tests/unit/services/persistence/contact-affiliations.spec.js#gives the contact the primary affiliation's job title when the save changed only the affiliation's
14must prove
Confirmed
Client affiliation ids must be unused lowercase UUIDv7 values appearing once in the save, and existing role ids must belong to the affiliation under which they are submitted.
validationservicetest
services-crm:modules/entities/tests/e2e/contact-information-sources.e2e.spec.js#refuses 400 a second contact naming the same affiliation id, saying another affiliation has it
15must prove
Confirmed
Organization people lists default to current affiliations, support past, upcoming, all and as-of filters plus type and role, return pages and total head count with separate rows for separate ties, return an empty page for an unknown organization and require contact read permission.
permissionapi, servicetest
services-crm:modules/entities/tests/e2e/contact-affiliations.e2e.spec.js#answers 403 permission_missing to a caller who may read organizations but not contacts
16
Confirmed
Add Person on an organization preselects the workspace default affiliation type and initially selects Primary when the person has no current primary affiliation.
policyconsoleobserved
17
Confirmed
Blank affiliation titles and notes are saved as absent values.
calculationapi, servicetest

Scenarios

IdGivenWhenThenProvenance
01Lena is primary at Harbor Society.The staff member adds Bayview Guild as a new primary and returns "Yes, she left".Bayview is her Organization, Harbor lists her under Past and her roles there ended.test
02A member primary at Acme and current at Globex.Make Primary on Globex.Globex is the Organization and Acme stays current, not primary, in one revision.test

Edge Cases

IdGivenWhenThenProvenance
03Two staff members change the same person’s primary at once.Both save.One primary remains, the winner's, and the other is told the record changed.test

Decision History

Product decisions recorded in this module's findings that bear on this specification, as recorded.

  1. OQ-CRM-ENT-003DecisionAndrew2026-10-22

    B: a rule's product intent is derived from its origin: an origin that names a product decision (a milestone decision id or an answered open question) makes the rule confirmed; otherwise the rule inherits its capability's intent. No rule-level field.

    Applied to: Structured decision update record

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.