---
id: STATE-CRM-REG-003
title: Regional placement
object: DOM-CRM-REG-003
product_area: crm
module: regions
revision: 2
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
states:
  - name: unplaced
    meaning: The owning record currently has no regional reference.
  - name: placed by the rules
    meaning: >-
      A descriptive matching outcome; storage has no origin flag distinguishing it from an identical staff
      choice.
  - name: set by hand
    meaning: A descriptive manual-selection outcome without an override; the record stores no distinct manual state.
  - name: held
    meaning: >-
      The record’s override protects its regional value, including an empty value, against automated
      decisions.
transitions:
  - from: unplaced
    to: set by hand
    trigger: Staff save a regional selection on an unplaced record.
    guard: Entities update authority and a valid workspace region are required.
    side_effects:
      - The record save stores the regional choice without modifying its fingerprint.
    who: A coordinator or administrator through Entities' editors
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › initial assignment › keeps a region the client sends with a contact that has
      no address (nothing decides it)
  - from: unplaced
    to: placed by the rules
    trigger: A relevant committed address change produces a matching geographic decision.
    guard: >-
      The kind participates, assignment is enabled, no override or update lock applies, and the current
      fingerprint differs.
    side_effects:
      - Regional reference and fingerprint are persisted together.
      - A changed region increments record version and modification stamps.
      - The captured regional write changes no address fields.
    who: The region-assignment handler
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › initial assignment › gives a contact created with a matching address that
      address's region
  - from: placed by the rules
    to: placed by the rules
    trigger: The deciding input changes or a forced recalculation revisits the record.
    guard: Ordinary processing uses the changed-input guards; forced processing bypasses fingerprint equality.
    side_effects:
      - >-
        Processing changes the region when needed or refreshes only the fingerprint without advancing the
        version.
    who: 'The region-assignment handler, the linked-contact handler or a recalculation run'
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › moving › moves a contact from region A to region B when its address moves
  - from: placed by the rules
    to: unplaced
    trigger: Previously deciding input disappears or present input matches no enabled coverage.
    guard: Automatic matching guards must pass for the record.
    side_effects:
      - Processing clears the region and remembers the new or absent deciding input.
    who: The region-assignment handler or a recalculation run
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › moving › clears the region when the new address matches no rule
  - from: placed by the rules
    to: set by hand
    trigger: Staff replace the regional selection while leaving the override disabled.
    guard: The record update must pass Entities authority and version checks.
    side_effects:
      - 'The fingerprint remains, preserving the manual value until new input or forced reconciliation.'
    who: A coordinator or administrator
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › manual regions and the override › keeps a manual region while the deciding
      address is unchanged
  - from: set by hand
    to: placed by the rules
    trigger: Changed deciding input or forced recalculation replaces a manual selection.
    guard: >-
      Matching must be enabled for the kind and no override or full lock may apply; a current or remembered
      address supplies input.
    side_effects:
      - The matched value replaces the manual choice; unmatched input can instead leave the record unplaced.
    who: The region-assignment handler or a recalculation run
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › manual regions and the override › replaces a manual region when the deciding
      address changes
  - from: placed by the rules
    to: held
    trigger: Staff enable the override on a calculated placement.
    guard: Entities must authorize the record update.
    side_effects:
      - The asynchronous override reaction reads the enabled override and leaves placement untouched.
    who: A coordinator or administrator
    provenance: observed
    source: >-
      services-crm:modules/regions/services/region-assignment/region-assignment.ts:regionAssignmentOverrideHandler
  - from: set by hand
    to: held
    trigger: Staff enable the override while retaining or changing their selection.
    guard: Entities must authorize changing the record-level exception.
    side_effects:
      - Enabling the override causes no automated regional change.
    who: A coordinator or administrator
    provenance: observed
    source: >-
      services-crm:modules/regions/services/region-assignment/region-assignment.ts:regionAssignmentOverrideHandler
  - from: held
    to: placed by the rules
    trigger: Staff disable the override after deciding input changed.
    guard: >-
      Assignment must cover the kind, the record must be editable and input must have changed; a matching rule
      is needed for a placed destination.
    side_effects:
      - The override reaction evaluates the current input rather than the address at the start of the hold.
    who: The region-assignment-override handler
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › manual regions and the override › a contact moved while overridden, then
      switched back to automatic › assigns the region of the address it moved to once the override is off
  - from: held
    to: set by hand
    trigger: Staff disable the override while remembered input still matches.
    guard: The current fingerprint must equal the remembered deciding input.
    side_effects:
      - >-
        The former exception remains eligible for later replacement, while its regional value stays unchanged
        now.
    who: The region-assignment-override handler
    provenance: inferred
    source: 'services-crm:modules/regions/services/region-assignment/region-assignment.ts:reconcileRegionAssignments'
invalid:
  - from: held
    to: unplaced
    why: Automated matching cannot clear a held value; staff can still clear it through a record save.
  - from: unplaced
    to: placed by the rules
    why: 'This automatic outcome requires enabled eligible assignment, an editable record and deciding input.'
  - from: set by hand
    to: unplaced
    why: >-
      A manual value is preserved only when both current and remembered deciding addresses are absent;
      unmatched current input can clear it.
concurrency: >-
  Automatic processing locks eligible records and reads their current deciding address before writing. Equal
  fingerprints suppress ordinary repeat work, and fingerprint-only changes leave record versions intact. The
  contact lock does not lock policy reads or freeze a linked organization address. Version changes still occur
  when the region changes.
invariants:
  - INV-DOM-CRM-REG-003-01
  - INV-DOM-CRM-REG-003-02
  - INV-DOM-CRM-REG-003-03
  - INV-DOM-CRM-REG-003-04
  - INV-DOM-CRM-REG-003-05
  - INV-DOM-CRM-REG-003-06
evidence:
  - >-
    services-crm at a8d1364f: modules/regions/services/region-assignment/region-assignment.ts,
    region-matching.ts (regionAddressFingerprint, fingerprintNamesAnAddress);
    tests/integration/services/region-assignment/region-assignment.integration.spec.js,
    region-assignment-delivery.integration.spec.js.
---

# Regional placement

## The Lifecycle

```mermaid
stateDiagram-v2
  state "placed by the rules" as calculated
  state "set by hand" as manual
  [*] --> unplaced
  unplaced --> manual: choose region
  unplaced --> calculated: matched deciding input
  unplaced --> held: enable override
  calculated --> calculated: reconcile
  calculated --> unplaced: absent or unmatched input
  calculated --> manual: staff selection
  manual --> calculated: matched input or forced recalculation
  manual --> unplaced: unmatched input
  calculated --> held: enable override
  manual --> held: enable override
  held --> calculated: release with changed matching input
  held --> unplaced: release with unmatched input
  held --> manual: release with unchanged input
```

## Transitions Explained

The diagram explains regional outcomes, not a stored state machine or a complete record-editor transition catalogue. Storage contains a regional reference, override and fingerprint rather than a manual-versus-calculated status. Staff can select or clear regions directly. Automation evaluates changed deciding input, and forced recalculation can replace a manual value even when the address stayed the same.

## What Cannot Happen

Automated processing preserves an enabled override and skips update-locked records. It also preserves a manual region when there is no current or remembered deciding address. A released hold can produce a matched region, no region or an unchanged value; release is processed asynchronously.

## Concurrency and Timing

The save commits before the regional reaction runs. Record locking and fingerprint comparison coordinate record updates and repeat deliveries. They do not make policy reads or linked-address reads one immutable snapshot.
