---
id: WF-CRM-ENT-006
title: Reconcile affiliation dates
trigger: >-
  An enabled scheduled date pass runs after a workspace day boundary, or an
  authorized user requests reconciliation directly.
product_area: crm
module: entities
revision: 1
status: draft
design_intent: confirmed
origin: >-
  D-46 (Andrew, 2026-10-19) with CRM-M2 plan A6 and A7, D-10 and D-12; cited in
  services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-CRM-ENT-010
  - CAP-CRM-ENT-011
  - CAP-CRM-ENT-012
  - CAP-CRM-ENT-006
moves:
  - STATE-CRM-ENT-005
  - STATE-CRM-ENT-003
crosses:
  - SVC-CRM-ENT-entities
idempotency: >-
  The date window begins after the forward-only watermark and covers missed
  calendar boundaries. Pending role and link markers prevent repeated settled
  work, and unchanged compliance does not write. Bounded role or link backlog
  can remain even when the compliance window advances.
concurrency: >-
  Contact work takes contact and affiliation locks; compliance takes
  organization locks. Update-locked work remains pending. The scheduled sweep
  visits workspaces in identity order within a two-minute budget, and caught
  pass failures do not stop later workspaces.
hops:
  - 'n': 1
    actor_or_service: Job reconciler (crm_job_reconciler)
    action: >-
      After other job-reconciler steps, invoke date settlement when its
      deployment switch enables the scheduled path.
    anchor: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
    seam: schedule
    on_failure: Log step failure and leave another scheduled pass to try.
    left_inconsistent: >-
      With the switch disabled, date-only effects require a save, manual pass or
      another appropriate trigger.
    observer: Job reconciler logs expose enabled work and failures.
    provenance: observed
    source: >-
      services-crm:aws/cdk/resources/affiliation-date-reconciliation.ts:AFFILIATION_DATE_RECONCILIATION_ENABLED
  - 'n': 2
    actor_or_service: Date reconciliation sweep
    action: >-
      Visit operational workspaces within the sweep budget and derive each
      calendar window from its watermark and timezone.
    anchor: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
    seam: sync
    on_failure: Log a caught workspace pass failure and continue.
    left_inconsistent: The failed workspace retains its unsettled date window.
    observer: Workspace reconciliation logs show outcomes.
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - 'n': 3
    actor_or_service: Automatic role reconciliation
    action: >-
      Settle due automatic roles in bounded contact batches, using a transaction
      for each contact.
    anchor: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
    seam: sync
    on_failure: Roll back failed contact work and retain its pending marker.
    left_inconsistent: Successful contacts are settled while others can remain due.
    observer: Per-change logs and contact revisions show role assignment.
    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
  - 'n': 4
    actor_or_service: Default link reconciliation
    action: >-
      Apply saved default mappings to eligible empty purposes on affiliations
      now effective.
    anchor: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
    seam: sync
    on_failure: 'Roll back failed contact work, retaining pending mappings.'
    left_inconsistent: >-
      Some default links may remain pending because of failures, locks or the
      batch limit.
    observer: Contact revisions and change logs show applied links.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.follow-ups.integration.spec.js#links
      every default of its type on the start day when the save stated no choice
  - 'n': 5
    actor_or_service: Compliance recalculation
    action: >-
      Recalculate governed organizations touched by relationship or role date
      boundaries in the window, allowing ordinary transition notices.
    anchor: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
    seam: sync
    on_failure: Count failed organizations and prevent compliance-window settlement.
    left_inconsistent: >-
      Completed recalculations remain while failed or unvisited organizations
      are stale.
    observer: Result counts and logs expose incomplete compliance scanning.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.integration.spec.js#takes
      the organization out of compliance the next morning, with no save of the
      contact
  - 'n': 6
    actor_or_service: Date reconciliation
    action: >-
      Advance the watermark after the compliance scan reports no failures or
      budget remainder, creating settings only for an operational workspace.
    anchor: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
    seam: sync
    on_failure: Leave the watermark unchanged if persistence fails.
    left_inconsistent: >-
      Bounded role or default-link backlog can still remain despite an advanced
      watermark.
    observer: The result reports settled or incomplete date-window status.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.integration.spec.js#finds
      the day settled and changes nothing, the organization’s version included
branches:
  - >-
    The direct operation requires contact bulk permission, uses a 20-second
    budget and nudges change dispatch afterward.
  - >-
    Dry run executes the pass inside a rolled-back transaction and may simulate
    a later date. It still acquires real locks and can create orphan notice
    objects outside the database.
waits:
  - >-
    Scheduled work first becomes eligible after the workspace date changes; a
    settled same-day window can return after a watermark read.
evidence:
  - >-
    services-crm at 7ea48a41:
    modules/entities/services/affiliations/affiliation-date-reconciliation.ts,
    affiliation-date-window.ts, affiliation-default-link-reconciliation.ts;
    services/contacts/role-assignment/automatic-role-reconciliation.ts;
    services/persistence/date-reconciliations.ts; aws/lambda/JOB_RECONCILER.ts;
    aws/crm.affiliation-date-reconciliation.names.ts;
    aws/cdk/resources/affiliation-date-reconciliation.ts (dev on, prod off);
    docs/affiliations/README.md §5.6b.
controls:
  - TC-AVL-004
  - 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-IAM-001
  - TC-IAM-005
  - TC-IAM-006
  - TC-LOG-004
  - TC-LOG-005
  - TC-MON-001
  - TC-MON-002
  - TC-PI-001
  - TC-PI-002
  - TC-PI-003
  - TC-PI-004
  - TC-PI-005
  - TC-PRV-001
  - TC-PRV-002
  - TC-TEN-001
  - TC-TEN-002
  - TC-TEN-003
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What It Does
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: What Starts It
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: The Flow
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: Hop by Hop
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: When Something Fails
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: Repeats and Races
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: What Is Observable
    provenance: observed
    source: 'services-crm:aws/lambda/JOB_RECONCILER.ts:handler'
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
  - section: What It Does
    provenance: inferred
    source: >-
      services-crm:aws/lambda/JOB_RECONCILER.ts:handler;
      services-crm:modules/entities/services/affiliations/affiliation-date-reconciliation.ts:sweepAffiliationDates;
      services-crm:modules/entities/services/contacts/role-assignment/automatic-role-reconciliation.ts:reconcileTenantAutomaticRoles;
      services-crm:modules/entities/services/affiliations/affiliation-default-link-reconciliation.ts:reconcileTenantDefaultLinks;
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:listOrganizationDateBoundaries;
      services-crm:modules/entities/services/persistence/date-reconciliations.ts:recordDatesReconciledThrough
    note: >-
      The explicitly labeled rationale interprets the existing transaction,
      transport and recovery boundaries; it is not a product decision.
  - section: Open Design Questions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/entities/FINDINGS.md#Findings'
---

# Reconcile affiliation dates

## What It Does

Date reconciliation settles effects of affiliation and role boundaries that pass without a person opening the record. When Ruth’s future employment begins, current interpretation changes on the workspace day; roles, chosen defaults and organizational assessments may then need persisted work. The flow also covers missed days from its recorded calendar cursor.

Our reading is that classification and settlement must stay distinct. A read can identify a current tie before every pending effect has been processed. Bounded passes, locks and deployment configuration therefore matter to the visible outcome, and a settled compliance window does not prove that all shared-link backlog is empty.

## What Starts It

An enabled scheduled date pass runs after a workspace day boundary, or an authorized user requests reconciliation directly.

The system selects eligible work using the workspace calendar and pending or cursor state. Operational lifecycle and deployment conditions described in the hop contract determine whether a scheduled pass reaches it. A scheduled invocation begins an attempt; the observable result comes from the writes it actually commits.

## The Flow

The diagram shows the sequence and service seams. Each synchronous stage waits for its result, while captured-event, queue and scheduled stages can run after the originating request has returned. The hop descriptions below identify the write and failure boundary, so the last arrow must not be mistaken for one atomic transaction across every participant.

```mermaid
sequenceDiagram
  participant T as Schedule or authorized request
  participant C as Date reconciliation
  participant D as CRM persistence
  T->>C: attempt workspace date window
  C->>D: bounded pending role and link passes
  C->>D: scan and recalculate affected organizations
  alt compliance window settled
    C->>D: advance watermark
  else failure or budget remainder
    C-->>T: report incomplete window
  end
  Note over C,D: A settled watermark can coexist with pending link backlog.
```

## Hop by Hop

### 1. Job reconciler (crm_job_reconciler)

The job reconciler invokes date settlement only when its deployment switch enables this path. A scheduled interval is an opportunity to attempt work rather than an unconditional guarantee that every workspace is settled. Other reconciler steps and their failures have their own boundaries.

### 2. Date reconciliation sweep

The sweep visits operational workspaces and constructs each date window from the timezone and forward-only cursor. A failed workspace pass is logged while later workspaces can still proceed. Identity ordering and the overall time budget mean unvisited workspaces wait for another attempt.

### 3. Automatic role reconciliation

Automatic-role settlement processes due contacts in bounded batches, with each contact transaction deciding and recording its own assignments. A failed or fully locked contact can remain pending while another contact succeeds. The date pass reports the roles it gives and any responsibilities excluded by type policy.

### 4. Default link reconciliation

Default-link settlement uses the saved choices from future affiliations now eligible. It fills supported empty destinations rather than replacing the person’s existing independent details. Locks, failures and bounded work can leave mappings pending even though effective interpretation already includes the relationship.

### 5. Compliance recalculation

The compliance stage scans governed organizations affected by date boundaries and reevaluates them under ordinary notice rules. Successful subjects commit independently. Failed subjects or remaining budget work prevent the compliance window from being declared settled, so the next pass can revisit the incomplete interval.

### 6. Date reconciliation

The final stage persists the forward date cursor only after the compliance scan reports no failure or remainder. It creates settings only for an operational workspace. This cursor measures that scan’s completion, not every pending role or default-link queue; its apparent progress must not conceal other backlog.

## When Something Fails

### Hop 1 — Job reconciler (crm_job_reconciler)

The failure boundary is: Log step failure and leave another scheduled pass to try. The remaining work or data is: With the switch disabled, date-only effects require a save, manual pass or another appropriate trigger. Job reconciler logs expose enabled work and failures. A later attempt can be required; the scheduling or publication boundary does not establish an immediate user-visible completion.

### Hop 2 — Date reconciliation sweep

The failure boundary is: Log a caught workspace pass failure and continue. The remaining work or data is: The failed workspace retains its unsettled date window. Workspace reconciliation logs show outcomes.

### Hop 3 — Automatic role reconciliation

The failure boundary is: Roll back failed contact work and retain its pending marker. The remaining work or data is: Successful contacts are settled while others can remain due. Per-change logs and contact revisions show role assignment.

### Hop 4 — Default link reconciliation

The failure boundary is: Roll back failed contact work, retaining pending mappings. The remaining work or data is: Some default links may remain pending because of failures, locks or the batch limit. Contact revisions and change logs show applied links.

### Hop 5 — Compliance recalculation

The failure boundary is: Count failed organizations and prevent compliance-window settlement. The remaining work or data is: Completed recalculations remain while failed or unvisited organizations are stale. Result counts and logs expose incomplete compliance scanning.

### Hop 6 — Date reconciliation

The failure boundary is: Leave the watermark unchanged if persistence fails. The remaining work or data is: Bounded role or default-link backlog can still remain despite an advanced watermark. The result reports settled or incomplete date-window status.

## Repeats and Races

The next window starts after the stored cursor and includes missed calendar boundaries. Role and default-link markers prevent successful settled work from being restored on every pass; unchanged compliance avoids another write. Failed compliance leaves the window open, while bounded contact work can remain pending even after that window advances.

Contact and affiliation locks order settlement against editing, and organization locks order assessments. A manual operation requires contact bulk authority and normally uses twenty seconds. Dry run rolls back database work but still takes real locks and can leave external request objects, so it is not a purely read-only simulation.

## What Is Observable

Job reconciler logs expose enabled work and failures. Workspace reconciliation logs show outcomes. Per-change logs and contact revisions show role assignment. Contact revisions and change logs show applied links. Result counts and logs expose incomplete compliance scanning. The result reports settled or incomplete date-window status.

Contact revisions identify successful system changes. Pass results and logs report actual work and failures; a pending marker or an open date window explains why later work remains. Reading effective dates does not establish that a worker reached the record or that every resulting notice was delivered.

## Open Design Questions

The watermark’s relationship to bounded pending-link work remains an existing finding. Deployment-switch state and future backlog recovery deadlines are not established by the source alone. Dry-run rollback also does not undo external object storage. The specification preserves those limits instead of describing an exactly-at-midnight guarantee.
