---
id: CAP-CRM-REG-011
title: Apply the current policy across existing records
feature: FEAT-CRM-REG-04
product_area: crm
module: regions
revision: 2
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - CAP-CRM-CFG-017
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-08 — Final acceptance (decision 2026-10-08,
    Andrew: saving rules changes no record; staff press Recalculate)
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), decision
    2026-10-11: rule or setting changes move no record; manual Recalculate with a recalculation-needed banner
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), decision
    2026-10-11: the region follows an address change after the save, through the change-event queue
purpose: >-
  Administrators apply saved policy changes to existing people and organizations through an explicit
  recalculation.
outcomes:
  - Eligible records are reconciled against the current policy.
  - 'Overrides, update locks and records with no past or present deciding address are preserved.'
  - Processing can resume and coordinate with concurrent saves.
concepts:
  - CON-CRM-REG-004
  - CON-CRM-REG-003
actors:
  - ACT-CRM-003
  - ACT-CRM-005
surfaces:
  - console
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: crm.regions.recalculate
    where_checked: api
    scope: tenant
  - key: crm.regions.recalculate
    where_checked: console
    scope: tenant
rules:
  - id: BR-CAP-CRM-REG-011-01
    proved_by: [ATS-CRM-REG-011-01, ATS-CRM-REG-011-10, ATS-CRM-REG-013-07, TST-CRM-REG-067]
    statement: Starting recalculation requires the administrator-tier recalculation permission.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-crm:tests/unit/regions-authorization.spec.ts#requires crm.regions.recalculate to start a
      recalculation
    origin: none
  - id: BR-CAP-CRM-REG-011-02
    proved_by: [ATS-CRM-REG-011-02, TST-CRM-REG-001, TST-CRM-REG-052, TST-CRM-REG-058]
    statement: >-
      A workspace permits one running recalculation; another start returns that run and can advance its
      remaining batches within the request budget.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#a
      recalculation resumed in batches › answers the running run to a second request instead of starting
      another
    origin: none
  - id: BR-CAP-CRM-REG-011-03
    proved_by: [ATS-CRM-REG-011-03, TST-CRM-REG-047, TST-CRM-REG-058]
    statement: >-
      A small recalculation can finish during its start request, while remaining work continues in the
      background.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#rule
      changes and the recalculation › finishes a small tenant within the request
    origin: none
  - id: BR-CAP-CRM-REG-011-04
    proved_by: [ATS-CRM-REG-011-04, ATS-CRM-REG-011-11, ATS-CRM-REG-012-07, TST-CRM-REG-058]
    statement: >-
      Recalculation traverses contacts before organizations in batches and resumes from its committed position
      after an interruption.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#a
      recalculation resumed in batches › continues from its cursor, never repeating a record
    origin: none
  - id: BR-CAP-CRM-REG-011-05
    proved_by: [ATS-CRM-REG-006-15, ATS-CRM-REG-010-08, ATS-CRM-REG-011-05, ATS-CRM-REG-011-10, TST-CRM-REG-009, TST-CRM-REG-047, TST-CRM-REG-058, TST-CRM-REG-061, TST-CRM-REG-066]
    statement: >-
      Recalculation reevaluates eligible records under current rules through the common assignment decision,
      regardless of their remembered deciding address.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#rule
      changes and the recalculation › moves a contact to its region under the new rules
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#2026-10-08 — Final acceptance (decision 2026-10-08,
      Andrew: saving rules changes no record; staff press Recalculate)
  - id: BR-CAP-CRM-REG-011-06
    proved_by: [ATS-CRM-REG-008-15, ATS-CRM-REG-009-05, ATS-CRM-REG-011-06, ATS-CRM-REG-011-10, TST-CRM-REG-058]
    statement: >-
      Recalculation skips overrides, records without a deciding address that never had one and records locked
      against updates.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#rule
      changes and the recalculation › leaves a contact that overrides automatic assignment;
      services-crm:modules/regions/services/region-assignment/region-assignment.ts:reconcileRegionAssignments
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#Behaviour changes, Not changed (kept as Rhythm 1.0
      did; accepted by Andrew 2026-10-08)
  - id: BR-CAP-CRM-REG-011-07
    proved_by: [ATS-CRM-REG-011-07, TST-CRM-REG-058]
    statement: Repeating recalculation under unchanged rules does not change placements.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#rule
      changes and the recalculation › changes nothing on a second run under the same rules
    origin: none
  - id: BR-CAP-CRM-REG-011-08
    proved_by: [ATS-CRM-REG-011-08, TST-CRM-REG-058]
    statement: >-
      A record save concurrent with recalculation ultimately places the record according to its newest
      address.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#a
      save racing a recalculation › ends with the region of the newest address
    origin: none
  - id: BR-CAP-CRM-REG-011-09
    proved_by: []
    statement: Policy changes during a run are visible to subsequent batches.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-crm:modules/regions/services/region-assignment/region-recalculation.ts:recalculateBatch'
    origin: none
  - id: BR-CAP-CRM-REG-011-10
    proved_by: [ATS-CRM-REG-011-09, ATS-CRM-REG-011-11, TST-CRM-REG-053]
    statement: >-
      The examined count includes every traversed record, including skipped records, while changed counts only
      placements set or cleared.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-recalculations.queries.integration.spec.js#the
      record a batch of the run answers › answers the whole record of the run the following batches finished,
      counting every record it walked
    origin: none
  - id: BR-CAP-CRM-REG-011-11
    proved_by: []
    statement: Starting recalculation while assignment is disabled traverses records but changes no placements.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: inferred
    source: >-
      services-crm:modules/regions/services/region-assignment/region-assignment.ts:reconcileRegionAssignments;
      services-crm:modules/regions/services/region-assignment/region-recalculation.ts:recalculateBatch
    origin: none
    note: >-
      The batch advances by the selected record count; reconciliation returns without assignment when the
      workspace switch is off.
  - id: BR-CAP-CRM-REG-011-12
    proved_by: []
    statement: >-
      The console places Recalculate in the needed banner, the toolbar during processing or an undismissed
      failure, and the action menu otherwise; disabled assignment hides it and missing authority disables it
      with an explanation.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: >-
      react-console:components/regions/__tests__/recalculation.test.tsx#where Recalculate belongs › is in the
      banner that says it is needed
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#2026-10-15 — Final acceptance (console follow-ups)
      (Andrew approved 2026-10-15: two tabs, Test an Address dialog, drawers, a read-only Assignment Rules tab
      with its own editor screen and the banner)
scenarios:
  - id: SC-CAP-CRM-REG-011-01
    given: 'New York coverage now points elsewhere, with one contact held in the old region.'
    when: An administrator runs recalculation.
    then: Eligible contacts move while the held contact remains.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0360.spec.ts#CRM-ACC-0360'
  - id: SC-CAP-CRM-REG-011-02
    given: A recalculation is already running.
    when: Another user requests recalculation.
    then: The request returns the same run rather than creating another.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/adversarial/CRM-ACC-0749.spec.ts#CRM-ACC-0749'
edge_cases:
  - id: SC-CAP-CRM-REG-011-03
    given: Processing stops after a committed contact batch.
    when: A worker resumes the run.
    then: It continues beyond the saved position and completes the traversal.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#a
      recalculation resumed in batches › ends completed, with every record examined once
constraints: []
depends_on:
  - CAP-CRM-REG-006
  - CAP-CRM-REG-008
related:
  - CAP-CRM-REG-008
  - CAP-CRM-REG-009
  - CAP-CRM-REG-010
  - CAP-CRM-REG-012
journeys:
  - JNY-CRM-REG-002
open_questions: []
evidence:
  - >-
    services-crm: Examined the pinned regional implementation, its declared constraints and the supporting
    records.
  - 'react-console: Examined the pinned regional configuration surfaces and their available test evidence.'
  - 'services-crm:docs/migrations/crm-regions/README.md'
  - 'services-crm:tests/unit/regions-authorization.spec.ts'
  - >-
    services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js
  - 'services-crm:modules/regions/services/region-assignment/region-assignment.ts'
  - 'services-crm:modules/regions/services/region-assignment/region-recalculation.ts'
  - >-
    services-crm:modules/regions/tests/integration/services/persistence/region-recalculations.queries.integration.spec.js
  - 'react-console:components/regions/__tests__/recalculation.test.tsx'
  - 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0360.spec.ts'
  - 'react-console:acceptance/crm/playwright/journeys/adversarial/CRM-ACC-0749.spec.ts'
controls: [TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, 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]
exercised_by: [FAS-CRM-REG-04-01, FAS-CRM-REG-04-02, FAS-CRM-REG-04-03, FAS-CRM-REG-04-04, FAS-CRM-REG-04-05]
suites: [TST-CRM-REG-001, TST-CRM-REG-009, TST-CRM-REG-047, TST-CRM-REG-052, TST-CRM-REG-053, TST-CRM-REG-058, TST-CRM-REG-061, TST-CRM-REG-066, TST-CRM-REG-067]
---

# Apply the current policy across existing records

## Purpose

Administrators apply saved policy changes to existing people and organizations through an explicit recalculation.

## How It Works

An authorized administrator starts recalculation from regional configuration. The request creates a run or returns the one already running. It can process batches during the request, including when returning an existing run. Remaining work continues through change-event delivery. Contacts are traversed before organizations, with each batch committing progress and regional writes together. Workers coordinate through the run and record locks. Each batch uses the settings it reads at that time rather than a frozen policy snapshot. Overrides, update locks and the absence of a past or present deciding address preserve the record. The examined count includes traversed records that assignment skips. A completed run can be followed by another without changing placements under unchanged inputs.

## Relationships

- Determine an address’s geographic region supplies the common geographic decision.
- Keep placements aligned with address changes coordinates placement after committed address changes.
- Preserve a deliberate regional exception preserves deliberate record exceptions during regional processing.
- Identify placements that need recalculation identifies policy generations still needing application.
- Monitor recalculation and recover from failure exposes processing progress and failure outcomes.
