---
id: WF-CRM-ENT-004
title: Recalculate role compliance and submit the notice
trigger: >-
  A contact relationship or role change, contact deletion, date settlement or
  reprocess requests organization compliance evaluation.
product_area: crm
module: entities
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Andrew, 2026-10-09 (the notice is submitted in the recalculation's
  transaction), cited in
  services-crm:modules/entities/services/organizations/compliance/compliance-email.ts
  and services-platform docs/communications/architecture.md §16.8 and §18
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-CRM-ENT-012
  - CAP-CRM-ENT-011
moves:
  - STATE-CRM-ENT-003
crosses:
  - SVC-CRM-ENT-entities
idempotency: >-
  Evaluation rereads committed state and does not write unchanged violations.
  Each notification batch is keyed by organization, resulting version and batch
  index. Repetition deduplicates accepted batch submissions; neither one
  submission per transition nor guaranteed delivery follows.
concurrency: >-
  The transaction advisory lock and organization row lock serialize
  recalculations. A later contact commit raises its own reaction. Serialization
  prevents overlapping recalculation writes but does not make all inputs a
  workspace-wide policy snapshot.
hops:
  - 'n': 1
    actor_or_service: CRM change-events dispatcher
    action: >-
      Claim pending contact events and send a separate message for each matching
      handler, removing events after all required queue submissions succeed.
    anchor: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
    seam: event
    on_failure: Retain or reclaim events that were not fully accepted.
    left_inconsistent: Compliance can remain behind the committed contact.
    observer: Dispatcher logs report publication attempts.
    provenance: documented
    source: >-
      services-crm:docs/change-events.md#4. Dispatcher (one per macroservice,
      every stream)
  - 'n': 2
    actor_or_service: Compliance recalculation handler
    action: >-
      Find affected organizations from before-and-after relationships and role
      changes, respecting the legacy save escape and import suppression, then
      retain governed organizations or those needing old compliance cleared.
    anchor: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
    seam: queue
    on_failure: >-
      Throw on failure so SQS retries and eventually dead-letters the handler
      message.
    left_inconsistent: >-
      Affected organizations retain previous compliance until another successful
      trigger.
    observer: Consumer logs and handler dead-letter messages expose the failure.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/organizations/compliance/compliance-change-events.integration.spec.js#a
      contact saved with the notes DISABLE_CONTACT_ROLE_COMPLIANCE_CHECK:
      nothing (its delete still records)
  - 'n': 3
    actor_or_service: Entities compliance workflow (one transaction per organization)
    action: >-
      Lock the organization, count distinct effective role holders, compare
      policy bounds and update changed violations, status and enabled attention
      in one transaction.
    anchor: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
    seam: sync
    on_failure: Roll back the evaluation transaction and fail its handler delivery.
    left_inconsistent: No partial business write remains.
    observer: Organization revisions show committed recalculation.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#writes
      nothing for a duplicate delivery, or a second run on an unchanged
      organization
  - 'n': 4
    actor_or_service: Entities compliance e-mail
    action: >-
      For an allowed transition notice, compose sorted recipient batches, write
      request objects to Platform storage and insert deduplicated submissions of
      at most 500 recipients each within the CRM transaction.
    anchor: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
    seam: sync
    on_failure: >-
      Fail and roll back recalculation if composition, storage or submission
      insertion fails.
    left_inconsistent: >-
      A stored request object can survive rollback until the bucket’s 30-day
      expiry.
    observer: Platform submission and communication records show accepted batches.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/organizations/compliance/organization-compliance.integration.spec.js#is
      not submitted again while the organization stays out of compliance, nor
      for a type that does not notify
  - 'n': 5
    actor_or_service: Entities compliance workflow
    action: >-
      Nudge communication dispatch after committing an owned transaction; nested
      batch work relies on scheduled dispatch.
    anchor: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
    seam: event
    on_failure: Ignore a failed nudge and allow the schedule to attempt publication later.
    left_inconsistent: Submissions can wait beyond one dispatch interval.
    observer: Communication dispatcher logs show publication attempts.
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - 'n': 6
    actor_or_service: Platform communications
    action: >-
      Platform queues accepted submissions, resolves the current published owned
      template, attempts delivery and records outcomes.
    anchor: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
    seam: queue
    on_failure: >-
      Missing template configuration blocks delivery; other failures use
      Platform retry policy.
    left_inconsistent: >-
      The organization may remain noncompliant without any delivered notice, and
      CRM has no delivery feedback loop.
    observer: Platform communication outcomes identify blocked or failed sends.
    provenance: documented
    source: >-
      services-crm:docs/entities-api.md#`compliance_notification_template`: the
      e-mail the type sends
branches:
  - >-
    Contact deletion recalculates regardless of the legacy notes escape used for
    save reactions.
  - Initial imports may persist compliance while suppressing notification.
  - >-
    An absent recipient-role selection includes all currently affiliated
    contacts with primary email, including contacts with no effective role; an
    explicit empty selection includes none.
waits: []
evidence:
  - >-
    services-crm at 7ea48a41:
    modules/entities/services/organizations/compliance/compliance-change-events.ts,
    organization-compliance.ts, compliance-email.ts, contact-role-compliance.ts;
    services/persistence/communication-request-submissions.ts,
    organization-attention-items.ts; docs/change-events.md;
    docs/entities-api.md.
  - >-
    services-platform at ad3f6a65: docs/queued-work.md; docs/jobs.md §4 (the
    bounded in-transaction object write).
controls:
  - 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-PRV-004
  - TC-TEN-001
  - TC-TEN-002
  - TC-TEN-003
  - TC-VEN-001
  - TC-VEN-003
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What It Does
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What It Does
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: What Starts It
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What Starts It
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: The Flow
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: The Flow
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: Hop by Hop
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: Hop by Hop
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: When Something Fails
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: When Something Fails
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: Repeats and Races
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: Repeats and Races
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: What Is Observable
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance
  - section: What Is Observable
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
  - section: What It Does
    provenance: inferred
    source: >-
      services-crm:aws/crm.change-events.ts:crmChangeEventStreams;
      services-crm:modules/entities/services/organizations/compliance/compliance-change-events.ts:organizationComplianceRecalculationHandler;
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance;
      services-crm:modules/entities/services/organizations/compliance/compliance-email.ts:complianceIdempotencyKey;
      services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts:recalculateOrganizationCompliance;
      services-platform:docs/communications/architecture.md#18. Rework
      2026-10-10: queue pointers, S3 for request data and bodies, SQS owns
      retries
    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'
---

# Recalculate role compliance and submit the notice

## What It Does

Recalculation compares effective role holders with an organization type’s expectations and persists the explanation of any failure. A chapter that loses its billing representative can move out of compliance after relationship work or a date boundary. If its type permits transition notices, CRM prepares submissions describing that same assessment.

Our reading is that the commit boundary matters because staff must not see a new assessment whose notice request describes an unrelated state. The local assessment and submission pointers commit together, but request-object storage and Platform delivery remain separate. A noncompliant badge is therefore not proof that anybody received mail.

## What Starts It

A committed person or organization change reaches the compliance reaction, or authorized reprocessing reaches the organization. Date-sensitive work can also request evaluation when relevant boundaries pass. The evaluator reads current policy and eligible people under its organization boundary. Opening the organization screen merely reads the stored assessment and does not initiate a fresh calculation.

## 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 H as Compliance trigger
  participant C as Recalculation
  participant D as CRM database
  participant S as Platform request storage
  participant P as Platform delivery
  H->>C: affected organization
  C->>D: lock and compare violations
  opt eligible transition notice
    C->>S: write bounded recipient-batch objects
    C->>D: insert deduplicated submission pointers
  end
  D-->>C: commit status and accepted pointers
  D->>P: scheduled publication of accepted pointers
  P->>P: render and attempt delivery
```

## Hop by Hop

### 1. CRM change-events dispatcher

Event dispatch claims committed contact changes and sends one message per applicable handler. It removes captured events only after the required queue accepts all messages. This stage transports the trigger; it does not calculate coverage while the original contact save is still open.

### 2. Compliance recalculation handler

The compliance handler compares before-and-after relationship and role context to identify organizations needing evaluation. It also includes those whose old assessment needs clearing. The legacy save escape and import suppression affect this selection, while contact deletion still requests its own recalculation.

### 3. Entities compliance workflow (one transaction per organization)

For each organization, the evaluator locks the subject and counts distinct eligible holders on effective affiliations. It compares the type’s bounds and writes changed violations, status and enabled attention in one transaction. A result equal to storage avoids a redundant assessment write.

### 4. Entities compliance e-mail

An eligible transition notice is composed from the transaction’s organization and recipients. Sorted recipients are split into batches of at most five hundred. Request objects go to Platform storage, while deduplicated submission pointers are inserted locally; a failure in this stage aborts the assessment transaction.

### 5. Entities compliance workflow

Owned evaluation transactions ask for early communications dispatch after committing. Evaluation nested within batch work relies on the schedule instead. This nudge changes when publication may begin, not whether the already committed status exists or whether Platform will deliver a message.

### 6. Platform communications

Platform accepts the submissions and attempts communication using the current published owned template. It records delivery outcomes under its separate contract. Missing configuration or send failure can leave CRM’s valid noncompliant assessment without a delivered notice, and CRM does not close that gap by fabricating delivery feedback.

## When Something Fails

### Hop 1 — CRM change-events dispatcher

The failure boundary is: Retain or reclaim events that were not fully accepted. The remaining work or data is: Compliance can remain behind the committed contact. Dispatcher logs report publication attempts. A later attempt can be required; the scheduling or publication boundary does not establish an immediate user-visible completion.

### Hop 2 — Compliance recalculation handler

The failure boundary is: Throw on failure so SQS retries and eventually dead-letters the handler message. The remaining work or data is: Affected organizations retain previous compliance until another successful trigger. Consumer logs and handler dead-letter messages expose the failure. This failure is downstream of committed input; operators investigate its own delivery rather than treating the caller’s original save as failed.

### Hop 3 — Entities compliance workflow (one transaction per organization)

The failure boundary is: Roll back the evaluation transaction and fail its handler delivery. The remaining work or data is: No partial business write remains. Organization revisions show committed recalculation.

### Hop 4 — Entities compliance e-mail

The failure boundary is: Fail and roll back recalculation if composition, storage or submission insertion fails. The remaining work or data is: A stored request object can survive rollback until the bucket’s 30-day expiry. Platform submission and communication records show accepted batches.

### Hop 5 — Entities compliance workflow

The failure boundary is: Ignore a failed nudge and allow the schedule to attempt publication later. The remaining work or data is: Submissions can wait beyond one dispatch interval. Communication dispatcher logs show publication attempts. A later attempt can be required; the scheduling or publication boundary does not establish an immediate user-visible completion.

### Hop 6 — Platform communications

The failure boundary is: Missing template configuration blocks delivery; other failures use Platform retry policy. The remaining work or data is: The organization may remain noncompliant without any delivered notice, and CRM has no delivery feedback loop. Platform communication outcomes identify blocked or failed sends. This failure is downstream of committed input; operators investigate its own delivery rather than treating the caller’s original save as failed.

## Repeats and Races

Evaluation reads committed state again on a repeated trigger and avoids writing unchanged compared violations. Notice batch keys include the organization, resulting version and batch index, so repeated accepted submissions deduplicate at that boundary. Multiple batches can still exist for one transition, and no key establishes guaranteed delivery.

The organization advisory lock and row lock serialize competing recalculations. They do not pin all people and policy for a workspace-wide instant. A contact committed later produces another reaction. Continuing noncompliance and a new transition have different notice behavior, and recipient selection distinguishes absence from an explicit empty list.

## What Is Observable

Dispatcher logs report publication attempts. Consumer logs and handler dead-letter messages expose the failure. Organization revisions show committed recalculation. Platform submission and communication records show accepted batches. Communication dispatcher logs show publication attempts. Platform communication outcomes identify blocked or failed sends.

The person or initiating caller sees the synchronous result at its own boundary. Captured history establishes committed local changes; logs, durable job state and queue outcomes explain remaining work. A successful database change, accepted downstream request and completed delivery are different observations and must be investigated through the component that owns each one.

## Open Design Questions

The existing findings retain missing delivery feedback, policy-change timing and recipient or replay proof limits. Object storage can retain an orphaned request after database rollback until storage expiry. Operators must use communication outcomes to investigate actual delivery rather than treating the organization revision as a mail receipt.
