---
id: WF-CRM-REG-001
title: Place a record after its deciding address changes
trigger: >-
  Committed record changes affecting addresses, or updates affecting the automatic-assignment override, reach
  regional handlers.
product_area: crm
module: regions
revision: 2
status: draft
design_intent: confirmed
origin: >-
  Andrew, 2026-10-11 (the region follows an address change after the save, through the change-event queue) and
  2026-10-12 (the assignment bumps a record's version only when its region changes), cited in
  services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass); DEF-CRM-0120
  for the override handler
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
implements:
  - CAP-CRM-REG-008
  - CAP-CRM-REG-009
  - CAP-CRM-REG-006
moves:
  - STATE-CRM-REG-003
crosses:
  - SVC-CRM-ENT-entities
  - SVC-CRM-REG-regions
idempotency: >-
  Ordinary reconciliation compares current deciding input with its stored fingerprint. Equal input suppresses
  a repeat write; changes to unrelated input do not force placement. Policy interleaving and later input
  changes qualify this behavior.
concurrency: >-
  Processing locks eligible record rows before reading their deciding addresses. Update-locked records are
  excluded. Policy reads happen before the record lock and do not request a policy lock; linked organization
  data is not frozen by a contact lock.
hops:
  - 'n': 1
    actor_or_service: Entities record save
    action: >-
      Entities commits the record and its address updates, capturing a net record change in the transaction.
      Ordinary record saving does not calculate placement or write the fingerprint.
    anchor: 'services-crm:modules/entities/db/region-fingerprint.ts:withoutRegionFingerprint'
    seam: sync
    on_failure: A rejected save rolls back and reports the failure to its caller.
    left_inconsistent: No committed address update or regional event remains from a rolled-back save.
    observer: The caller receives the save result; committed record history captures the change.
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › initial assignment › answers no region from the create itself, because the
      assignment is a reaction to the committed save;
      services-crm:modules/entities/db/region-fingerprint.ts:withoutRegionFingerprint;
      services-crm:docs/change-events.md#2. Capture
  - 'n': 2
    actor_or_service: CRM change-events dispatcher
    action: The shared dispatcher publishes committed captured changes after a nudge or its minute schedule.
    anchor: 'services-crm:docs/change-events.md#4. Dispatcher (one per macroservice, every stream)'
    seam: event
    on_failure: Unaccepted changes remain available after their dispatch claim expires.
    left_inconsistent: The saved address can coexist with the preceding regional value while publication waits.
    observer: Dispatch logs and remaining captured rows identify unpublished work.
    provenance: documented
    source: 'services-crm:docs/change-events.md#4. Dispatcher (one per macroservice, every stream)'
  - 'n': 3
    actor_or_service: Change-events consumer
    action: The consumer invokes address handlers for address changes and override handlers for override updates.
    anchor: 'services-crm:aws/crm.change-events.ts (contact and organization streams)'
    seam: queue
    on_failure: 'A thrown handler fails its message, allowing the queue to deliver it again.'
    left_inconsistent: The regional value remains unreconciled until successful handling.
    observer: Consumer outcomes identify the handler and delivery result.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-delivery.integration.spec.js#region
      assignment deliveries (integration) › one delivery of one address change › routes the save to the region
      assignment, once
  - 'n': 4
    actor_or_service: Region assignment reconcile
    action: >-
      Reconciliation reads policy, excludes disabled kinds, locks eligible records, resolves current deciding
      input and skips overrides or absent unremembered input. Changed fingerprints lead to geographic
      matching.
    anchor: 'services-crm:modules/regions/services/region-assignment/region-assignment.ts:reconcileRegionAssignments'
    seam: sync
    on_failure: A failed transaction rolls back and propagates its error for queue retry.
    left_inconsistent: The last committed regional value and fingerprint remain.
    observer: The handler reports examined and changed counts without address values.
    provenance: observed
    source: 'services-crm:modules/regions/services/region-assignment/region-assignment.ts:reconcileRegionAssignments'
  - 'n': 5
    actor_or_service: Entities persistence (called by Regions)
    action: >-
      Entities persistence stores region and fingerprint together. It updates version and modification stamps
      only when the region changes.
    anchor: >-
      services-crm:modules/entities/services/persistence/contacts.ts:setContactRegionAssignments;
      services-crm:modules/entities/services/persistence/organizations.ts:setOrganizationRegionAssignments
    seam: sync
    on_failure: Write failure rolls back reconciliation and fails the delivery.
    left_inconsistent: No partial regional write commits; the previous regional facet remains.
    observer: >-
      Subsequent record reads and history show the change; captured regional fields do not trigger address
      handlers.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-delivery.integration.spec.js#region
      assignment deliveries (integration) › one delivery of one address change › records its own write as a
      change of the region and the fingerprint, and of no address
  - 'n': 6
    actor_or_service: Change-events consumer
    action: >-
      Successful handling acknowledges its message; repeated failure through the final receive exhausts
      delivery into dead-letter storage.
    anchor: 'services-crm:docs/change-events.md#7. Failures and recovery'
    seam: queue
    on_failure: A dead-lettered message needs operator redrive or another applicable reconciliation trigger.
    left_inconsistent: 'Without recovery, placement can remain stale until a relevant change or recalculation.'
    observer: 'Dead-letter storage exposes the message, with an oldest-message alarm documented for production.'
    provenance: documented
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-delivery.integration.spec.js#region
      assignment deliveries (integration) › a delivery that fails and is made again › assigns the region on
      the next delivery of the same message; services-crm:docs/change-events.md#7. Failures and recovery
branches:
  - >-
    Enabling the override invokes reconciliation, which sees the protected record and performs no placement
    update.
  - Releasing the override evaluates current input against the fingerprint retained during the hold.
  - A record removed before processing leaves no eligible target and completes without a write.
  - Disabled assignment or an excluded record kind stops after reading policy.
waits:
  - >-
    Nudged publication normally starts delivery promptly. A lost nudge relies on the minute schedule, with
    further delay possible from failed dispatch or queue retries.
evidence:
  - >-
    services-crm at a8d1364f: modules/regions/services/region-assignment/region-assignment.ts,
    region-matching.ts; modules/entities/services/persistence/contacts.ts, organizations.ts (region
    functions); aws/crm.change-events.ts; docs/change-events.md;
    tests/integration/services/region-assignment/region-assignment-delivery.integration.spec.js,
    region-assignment.integration.spec.js.
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-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Place a record after its deciding address changes

## What It Does

After a person’s or organization’s deciding address changes, regional processing updates its placement from committed input. The record save completes independently, and captured changes reach the common reconciliation routine. Override updates invoke the same routine, so releasing an exception can apply address changes made during the hold.

## The Flow

```mermaid
sequenceDiagram
  participant C as Record caller
  participant E as Entities
  participant D as Dispatcher
  participant Q as Queue
  participant R as Regions
  C->>E: Save record
  E-->>C: Committed record
  E->>D: Captured change and nudge
  D->>Q: Publish handler message
  Q->>R: Address or override reaction
  R->>R: Read policy
  R->>E: Lock eligible record and read input
  R->>R: Compare fingerprint and match coverage
  R->>E: Persist changed regional facet
  R-->>Q: Successful delivery
```

## When Something Fails

The regional transaction rolls back on failure, and queue retries use the committed record again. Staff can temporarily see a changed address with the former region. Final delivery failure leaves the message in dead-letter storage; operators may redrive it, or a subsequent relevant change or recalculation may repair placement. The documented alarm is a production facility, not proof of an alarm in every environment.

## Open Design Questions

Policy reads are not locked into one consistent snapshot, and a contact lock does not freeze linked organization input. Affiliation expiry also supplies no independent reconciliation event. These limits need resolution before stronger convergence or timing guarantees are adopted.
