---
id: STATE-CRM-ENT-005
title: Affiliation and role period lifecycle
object: DOM-CRM-ENT-003
product_area: crm
module: entities
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: new
    meaning: The relationship has not been persisted.
    entry_conditions: The affiliation is a draft nested in a contact request.
    allowed_actions:
      - 'Select organization, type, dates and roles'
      - save after validation.
    terminal: false
    reversible: false
    provenance: observed
    source: >-
      services-crm at 7ea48a41:
      modules/entities/services/persistence/contact-affiliations.ts;
      services/contacts/role-assignment/contact-role-assignment.ts,
      automatic-role-reconciliation.ts;
      services/affiliations/affiliation-date-reconciliation.ts,
      affiliation-default-link-reconciliation.ts; docs/affiliations/README.md.
    experience: The proposed relationship has not yet contributed committed coverage.
  - name: upcoming
    meaning: >-
      The start date is later than the workspace date; pending role and link
      choices await eligibility.
    entry_conditions: The saved start date is after the workspace day.
    allowed_actions:
      - Read
      - edit under contact guards
      - settle pending work once eligible.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path
    experience: The editor identifies a future tie and defers live sharing.
  - name: current
    meaning: >-
      The relationship is effective through its inclusive end date and is not
      designated primary.
    entry_conditions: >-
      The day falls inside the inclusive saved period without a primary
      designation.
    allowed_actions:
      - Read
      - edit
      - select primary only if open-ended
      - end or remove under contact guards.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list
    experience: >-
      The tie can contribute to current-person discovery even when it is
      secondary.
  - name: current primary
    meaning: >-
      An effective, open-ended relationship is designated primary and shares its
      title with the contact.
    entry_conditions: >-
      The tie is effective, open-ended and selected as the contact’s sole
      primary.
    allowed_actions:
      - Read
      - change coordinated title
      - clear primary
      - end through save.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      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
    experience: Its organization and title supply the prominent person context.
  - name: past
    meaning: >-
      The workspace date has passed the inclusive end date; the saved
      relationship remains available as history.
    entry_conditions: The workspace day is later than the inclusive end date.
    allowed_actions:
      - Read dated history
      - correct permissible dates
      - remove through an explicit list.
    terminal: false
    reversible: true
    provenance: observed
    source: >-
      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
    experience: The tie remains visible as former organizational context.
  - name: removed
    meaning: >-
      The live affiliation is removed, leaving its earlier values in contact
      revisions.
    entry_conditions: A valid explicit contact collection removes the affiliation.
    allowed_actions:
      - Inspect earlier values through contact revisions.
    terminal: true
    reversible: false
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it
    experience: The live relationship and owned role periods are absent.
transitions:
  - from: new
    to: upcoming
    trigger: Save a new affiliation with a future start date.
    guard: >-
      Workspace-local organization and type, no prohibited overlap and no more
      than 100 current or future affiliations.
    side_effects:
      - mark automatic roles pending
      - retain selected default mappings
      - defer new shared links
    who: An authorized contact editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: new
    to: current
    trigger: Save a relationship already effective today.
    guard: 'New-affiliation checks apply, and the end cannot precede the start.'
    side_effects:
      - fill eligible empty contact purposes from chosen defaults
      - raise a post-commit compliance reaction
    who: An authorized contact editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: current
    to: current primary
    trigger: Select a primary affiliation directly or through the Organization command.
    guard: >-
      It must have started and be open-ended; the save cannot newly select two
      primaries.
    side_effects:
      - clear previous primary
      - synchronize contact and affiliation title
      - assign eligible missing automatic roles
      - serialize first-contact role assignment
    who: An authorized editor or integration
    provenance: test
    source: >-
      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
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: current primary
    to: current
    trigger: Clear primary designation or choose another primary.
    guard: Pass the owning contact save checks.
    side_effects:
      - leave no automatically selected replacement when merely cleared
    who: An authorized contact editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: upcoming
    to: current
    trigger: The workspace date reaches the start.
    guard: >-
      Date classification changes without a write; pending effects need
      reconciliation or a later save.
    side_effects:
      - >-
        attempt roles on eligible five-minute passes subject to budgets and
        locks
      - >-
        apply pending defaults and compliance when date reconciliation is
        enabled
      - allow save-time settlement
    who: The workspace calendar and reconciliation workers
    provenance: test
    source: >-
      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
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: current
    to: past
    trigger: End by a save or by passing the saved end date.
    guard: An edited end must not precede the start.
    side_effects:
      - clip affected role periods when editing the end
      - leave source links unresolved when no longer effective
      - refresh compliance through an event or date pass
    who: An authorized editor or calendar progression
    provenance: test
    source: >-
      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
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: current primary
    to: past
    trigger: >-
      End the primary relationship through the save, departure flow or
      Organization command.
    guard: The normal ending checks apply.
    side_effects:
      - clear primary without replacement
      - close role periods
      - remove a zero-day prior stay in the Organization command
    who: An authorized editor or integration
    provenance: test
    source: >-
      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
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: current
    to: removed
    trigger: Omit an affiliation from an explicit replacement list.
    guard: No submitted contact detail may remain linked through it.
    side_effects:
      - delete owned role periods
      - raise compliance reaction after commit
    who: An authorized contact editor
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
  - from: past
    to: removed
    trigger: Remove a relationship entered in error through the explicit list.
    guard: Resolve any detail still linked through the relationship.
    side_effects:
      - remove affiliation and owned roles
      - recalculate affected compliance after commit
    who: An authorized contact editor
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
    audit_event: >-
      A changed contact save captures affiliation and role differences in its
      aggregate revision. Merely crossing a calendar date writes no relationship
      revision; settled roles or links create their own contact changes.
invalid:
  - from: upcoming
    to: current primary
    why: Future affiliations are ineligible for primary designation.
    response: >-
      A new or redirected future tie cannot become primary. The save returns
      relationship validation problems, leaving the prior aggregate intact.
  - from: past
    to: current primary
    why: A primary affiliation cannot have an end date.
    response: >-
      A primary tie cannot retain an end date. Staff must choose a valid
      open-ended current tie or clear the designation.
  - from: current
    to: current
    why: >-
      Saving cannot redirect an existing affiliation to another organization;
      end it and add a distinct relationship.
    response: >-
      An existing affiliation identity cannot be redirected to another
      organization. The save refuses it; a separate tie preserves the distinct
      episode.
  - from: upcoming
    to: removed
    why: >-
      Omitting the entire affiliations property preserves relationships. Removal
      requires submitting an explicit list without the entry.
    response: >-
      Omitting the affiliations property preserves existing relationships. It
      produces no removal; an explicit collection excluding the entry is
      required.
concurrency: >-
  The contact version and row lock serialize relationship saves with role
  settlement. First-contact assignment additionally locks the destination
  organization. Locks provide serialization, not a universal deadlock guarantee.
  Fully locked contacts or affiliations can remain pending beyond their start
  date.
invariants:
  - INV-DOM-CRM-ENT-003-02
  - INV-DOM-CRM-ENT-003-03
  - INV-DOM-CRM-ENT-003-05
  - INV-DOM-CRM-ENT-003-07
  - INV-DOM-CRM-ENT-003-10
  - INV-DOM-CRM-ENT-003-11
  - INV-DOM-CRM-ENT-003-12
evidence:
  - >-
    services-crm at 7ea48a41:
    modules/entities/services/persistence/contact-affiliations.ts;
    services/contacts/role-assignment/contact-role-assignment.ts,
    automatic-role-reconciliation.ts;
    services/affiliations/affiliation-date-reconciliation.ts,
    affiliation-default-link-reconciliation.ts; docs/affiliations/README.md.
  - >-
    react-console at da630758: lib/affiliations/section.ts (departure question);
    components/affiliations/affiliation-editor.tsx.
automatic:
  - from: upcoming
    to: current
    when: >-
      The workspace day reaches the saved start date; effect settlement can
      follow later.
    provenance: observed
    source: >-
      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
  - from: current
    to: past
    when: The day passes the inclusive saved end date.
    provenance: observed
    source: >-
      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
ai_may_recommend: false
ai_may_execute: false
permissions_by_state:
  - state: new
    action: create
    permission: crm.contacts.update
  - state: upcoming
    action: read
    permission: crm.contacts.read
  - state: upcoming
    action: update
    permission: crm.contacts.update
  - state: current
    action: read
    permission: crm.contacts.read
  - state: current
    action: update
    permission: crm.contacts.update
  - state: current primary
    action: read
    permission: crm.contacts.read
  - state: current primary
    action: update
    permission: crm.contacts.update
  - state: past
    action: read
    permission: crm.contacts.read
  - state: past
    action: update
    permission: crm.contacts.update
  - state: removed
    action: history
    permission: crm.contacts.history
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Purpose
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: Ownership and Authority
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: The Lifecycle
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: States
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: Transitions Explained
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: What Cannot Happen
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: Concurrency and Timing
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a new link through an upcoming affiliation, naming the path;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#links
      nothing when the choice is an empty list;
      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/persistence/contact-affiliations.integration.spec.js#makes
      nothing primary in its place;
      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/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/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;
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#refuses
      a save that removes the affiliation and keeps the rows linked through it;
      services-crm:modules/entities/services/persistence/contact-affiliations.ts:writeAffiliations
  - section: Permissions by State
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/AUTHORIZATION.md#Role Entitlement Matrix'
  - section: Purpose
    provenance: inferred
    source: >-
      services-crm at 7ea48a41:
      modules/entities/services/persistence/contact-affiliations.ts;
      services/contacts/role-assignment/contact-role-assignment.ts,
      automatic-role-reconciliation.ts;
      services/affiliations/affiliation-date-reconciliation.ts,
      affiliation-default-link-reconciliation.ts; docs/affiliations/README.md.
    note: >-
      The explicitly labeled rationale interprets the separation of state
      dimensions and the existing trigger and guard rows.
  - section: Invariants
    provenance: inferred
    source: >-
      INV-DOM-CRM-ENT-003-02; INV-DOM-CRM-ENT-003-03; INV-DOM-CRM-ENT-003-05;
      INV-DOM-CRM-ENT-003-07; INV-DOM-CRM-ENT-003-10; INV-DOM-CRM-ENT-003-11;
      INV-DOM-CRM-ENT-003-12
    note: >-
      Protection explanations interpret the unchanged domain-object invariants;
      no lifecycle policy is added.
  - section: Invariants
    provenance: inferred
    source: "services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts; services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts; services-crm:modules/entities/services/persistence/record-locks.ts"
    note: "The rationale interprets this artifact’s sourced invariants and linked capability rules; it does not claim a recorded product decision."
---

# Affiliation and role period lifecycle

## Purpose

The affiliation lifecycle explains when a dated tie and its responsibilities count as current. Ruth’s future employment, current chapter service and former employer need different interpretations even though all three rows remain in her contact. The primary designation adds prominent organization context rather than replacing the full set of current connections.

Our reading is that calendar classification must remain separate from the side effects that follow a start day. A tie can become current immediately in a read while its automatic-role or sharing work is still pending. The lifecycle therefore explains eligibility, designation, retention and removal without promising synchronous midnight processing.

## Ownership and Authority

Contact editors and authorized integrations submit dated relationship changes through the owning contact’s save. Organization-page actions use that same contact authority. The workspace calendar changes effective interpretation, and reconciliation workers settle pending roles or defaults under their system context. No inference operation chooses dates, primaries or responsibilities.

The lifecycle preserves the observed aggregate-write route for ending and removal; it does not resolve the open disagreement with Platform’s explicit-operation requirement. The contact version and element restrictions remain part of authority for a relationship change, even when the organization page is the entry point.

## The Lifecycle


```mermaid
stateDiagram-v2
  state "Current primary (designation)" as primary
  new --> upcoming: future start
  new --> current: effective start
  upcoming --> current: calendar reaches start
  current --> primary: select eligible primary
  primary --> current: clear primary
  current --> past: inclusive end has passed
  primary --> past: save ends primary
  current --> removed: explicit list removes entry
  past --> removed: explicit list removes entry
```

## States

### new

The relationship has not been persisted. Entry requires this condition: The affiliation is a draft nested in a contact request. Available behavior is Select organization, type, dates and roles; save after validation.. The proposed relationship has not yet contributed committed coverage.

### upcoming

The start date is later than the workspace date; pending role and link choices await eligibility. Entry requires this condition: The saved start date is after the workspace day. Available behavior is Read; edit under contact guards; settle pending work once eligible.. The editor identifies a future tie and defers live sharing.

### current

The relationship is effective through its inclusive end date and is not designated primary. Entry requires this condition: The day falls inside the inclusive saved period without a primary designation. Available behavior is Read; edit; select primary only if open-ended; end or remove under contact guards.. The tie can contribute to current-person discovery even when it is secondary.

### current primary

An effective, open-ended relationship is designated primary and shares its title with the contact. Entry requires this condition: The tie is effective, open-ended and selected as the contact’s sole primary. Available behavior is Read; change coordinated title; clear primary; end through save.. Its organization and title supply the prominent person context.

### past

The workspace date has passed the inclusive end date; the saved relationship remains available as history. Entry requires this condition: The workspace day is later than the inclusive end date. Available behavior is Read dated history; correct permissible dates; remove through an explicit list.. The tie remains visible as former organizational context.

### removed

The live affiliation is removed, leaving its earlier values in contact revisions. Entry requires this condition: A valid explicit contact collection removes the affiliation. Available behavior is Inspect earlier values through contact revisions.. The live relationship and owned role periods are absent. This row’s lifecycle ends here; a new request or new record is separate work.

## Transitions Explained

### new → upcoming

An authorized contact editor starts this change when the following occurs: Save a new affiliation with a future start date. The transition checks this boundary: Workspace-local organization and type, no prohibited overlap and no more than 100 current or future affiliations. The accompanying work is mark automatic roles pending; retain selected default mappings; defer new shared links. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### new → current

An authorized contact editor starts this change when the following occurs: Save a relationship already effective today. The transition checks this boundary: New-affiliation checks apply, and the end cannot precede the start. The accompanying work is fill eligible empty contact purposes from chosen defaults; raise a post-commit compliance reaction. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### current → current primary

An authorized editor or integration starts this change when the following occurs: Select a primary affiliation directly or through the Organization command. The transition checks this boundary: It must have started and be open-ended; the save cannot newly select two primaries. The accompanying work is clear previous primary; synchronize contact and affiliation title; assign eligible missing automatic roles; serialize first-contact role assignment. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### current primary → current

An authorized contact editor starts this change when the following occurs: Clear primary designation or choose another primary. The transition checks this boundary: Pass the owning contact save checks. The accompanying work is leave no automatically selected replacement when merely cleared. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### upcoming → current

The workspace calendar and reconciliation workers starts this change when the following occurs: The workspace date reaches the start. The transition checks this boundary: Date classification changes without a write; pending effects need reconciliation or a later save. The accompanying work is attempt roles on eligible five-minute passes subject to budgets and locks; apply pending defaults and compliance when date reconciliation is enabled; allow save-time settlement. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### current → past

An authorized editor or calendar progression starts this change when the following occurs: End by a save or by passing the saved end date. The transition checks this boundary: An edited end must not precede the start. The accompanying work is clip affected role periods when editing the end; leave source links unresolved when no longer effective; refresh compliance through an event or date pass. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### current primary → past

An authorized editor or integration starts this change when the following occurs: End the primary relationship through the save, departure flow or Organization command. The transition checks this boundary: The normal ending checks apply. The accompanying work is clear primary without replacement; close role periods; remove a zero-day prior stay in the Organization command. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### current → removed

An authorized contact editor starts this change when the following occurs: Omit an affiliation from an explicit replacement list. The transition checks this boundary: No submitted contact detail may remain linked through it. The accompanying work is delete owned role periods; raise compliance reaction after commit. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

### past → removed

An authorized contact editor starts this change when the following occurs: Remove a relationship entered in error through the explicit list. The transition checks this boundary: Resolve any detail still linked through the relationship. The accompanying work is remove affiliation and owned roles; recalculate affected compliance after commit. A changed contact save captures affiliation and role differences in its aggregate revision. Merely crossing a calendar date writes no relationship revision; settled roles or links create their own contact changes.

## What Cannot Happen

A new or redirected future tie cannot become primary. The save returns relationship validation problems, leaving the prior aggregate intact.

A primary tie cannot retain an end date. Staff must choose a valid open-ended current tie or clear the designation.

An existing affiliation identity cannot be redirected to another organization. The save refuses it; a separate tie preserves the distinct episode.

Omitting the affiliations property preserves existing relationships. It produces no removal; an explicit collection excluding the entry is required.

## Permissions by State

The state rows reference grants from the CRM authorization model. Read access is distinct from mutation, and history requires the corresponding history and read checks. Administrator and Power User hold administrative record actions; ordinary Standard User grants include applicable read and update operations, while Read Only grants do not authorize edits.

Contact update controls these nested corrections even when initiated from an organization. Archive and locks can still refuse an otherwise authorized change. Calendar interpretation needs no user grant.

## Concurrency and Timing

Contact row locking and read-version comparison serialize relationship corrections and pending-role settlement. First-contact assignment also locks the organization so simultaneous joins cannot each independently win the same first-contact decision. This lock arrangement does not prove that all multi-record deadlocks are impossible.

Future, current and past come from dates, while pending work has separate bounded processing. Full record or element locks can defer that work after eligibility begins. Ending a tie clips applicable responsibilities, leaves former context readable and can leave linked details unresolved until an authorized editor chooses a recovery.

## Invariants

Our reading is that effective dates must remain independent of primary prominence. A current secondary affiliation contributes relationship context even though another organization appears beside the person’s name. Inclusive ends keep the last day interpretable; a future beginning can change read interpretation before all pending side effects settle. The saved primary and coordinated title must remain consistent. Role-period identities remain attached to their owning affiliation, so a collection save cannot disguise moving another relationship’s responsibility into this one.
