---
id: CAP-CRM-REG-004
title: Build the geographic assignment policy
feature: FEAT-CRM-REG-02
product_area: crm
module: regions
revision: 2
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - CAP-CRM-CFG-016
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), decision
    2026-10-12: a country sent on a rule that is not a postal code rule is dropped silently
  - >-
    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)
  - 'services-crm:docs/milestones/CRM-M3-crm-configuration.md#D-35'
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-11 second pass (Andrew, 2026-10-11: rules are
    managed one at a time; an organization has a preferred address)
purpose: Administrators describe geographic coverage in an ordered policy that maps addresses to named regions.
outcomes:
  - Rules connect geographic criteria to workspace regions.
  - The complete policy can be saved in a single ordered replacement.
  - Staff see validation issues before submitting changes.
concepts:
  - CON-CRM-REG-002
actors:
  - ACT-CRM-003
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: crm.regions.update
    where_checked: api
    scope: tenant
  - key: crm.regions.read
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-REG-004-01
    proved_by: [ATS-CRM-REG-004-01, ATS-CRM-REG-006-11, TST-CRM-REG-048, TST-CRM-REG-065]
    statement: 'An assignment rule uses postal code ranges, states, countries or a rest-of-the-world fallback.'
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/db/region-assignment-settings.table.integration.spec.js#rule
      constraints › refuses a rule type outside the legacy list
    origin: none
  - id: BR-CAP-CRM-REG-004-02
    proved_by: [ATS-CRM-REG-004-02, TST-CRM-REG-047, TST-CRM-REG-048, TST-CRM-REG-050]
    statement: >-
      Postal rules require one to one thousand nonblank endpoint pairs, state and country rules require one to
      one thousand entries, and fallback rules carry no criteria list.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for a postal code rule with no
      ranges;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:regionAssignmentRuleProblems
    origin: none
  - id: BR-CAP-CRM-REG-004-03
    proved_by: [ATS-CRM-REG-004-03, TST-CRM-REG-047, TST-CRM-REG-048]
    statement: >-
      Saving retains only the criteria associated with the rule kind and silently discards a country
      restriction on other than postal rules.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#drops a country given on a rule that is not a
      postal code rule, as it drops the other types’ lists
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), decision
      2026-10-12: a country sent on a rule that is not a postal code rule is dropped silently
  - id: BR-CAP-CRM-REG-004-04
    proved_by: [ATS-CRM-REG-004-04, TST-CRM-REG-047, TST-CRM-REG-048]
    statement: >-
      A postal rule’s optional country restriction accepts a two-letter ISO country code, stores it in
      capitals and rejects invalid codes.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for a country that is not an ISO
      3166-1 alpha-2 code
    origin: none
  - id: BR-CAP-CRM-REG-004-05
    proved_by: []
    statement: Country-rule entries are trimmed and saved in capitals.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#a
      countries rule given its codes in lower case › stores the codes of a settings save trimmed and
      upper-cased
    origin: none
  - id: BR-CAP-CRM-REG-004-06
    proved_by: [ATS-CRM-REG-004-05, TST-CRM-REG-048, TST-CRM-REG-050, TST-CRM-REG-058]
    statement: >-
      Every rule targets a region in its own workspace, and a nonexistent target causes a refusal identifying
      the region.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#the
      capability › refuses a rule naming a region the tenant does not have, naming it
    origin: none
  - id: BR-CAP-CRM-REG-004-07
    proved_by: []
    statement: >-
      Rules can be disabled without deletion and carry an explanatory note of at most one thousand characters
      that does not affect matching.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: false
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#patches the rule to disabled;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:regionAssignmentRuleProblems
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#2026-10-11 second pass (Andrew, 2026-10-11: rules are
      managed one at a time; an organization has a preferred address)
  - id: BR-CAP-CRM-REG-004-08
    proved_by: [ATS-CRM-REG-004-06]
    statement: >-
      The policy accepts at most one thousand rules and rejects creation or replacement that exceeds that
      limit.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/aws/api/regions.schemas.ts:regionAssignmentSettingsSchema;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:createRegionAssignmentRule
    origin: none
  - id: BR-CAP-CRM-REG-004-09
    proved_by: [ATS-CRM-REG-003-08, ATS-CRM-REG-004-07, ATS-CRM-REG-004-09, ATS-CRM-REG-007-06, TST-CRM-REG-047, TST-CRM-REG-050]
    statement: >-
      Settings replacement with a rules list atomically replaces the complete ordered policy; omitting that
      list preserves the current rules.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#the
      capability › replaces the rules whole on the next save
    origin: none
  - id: BR-CAP-CRM-REG-004-10
    proved_by: [ATS-CRM-REG-004-08, TST-CRM-REG-048, TST-CRM-REG-050]
    statement: >-
      Saved rules have distinct identifiers and consecutive positions beginning at one, with no shared
      position.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#the
      capability › gives every rule it saved an id of its own, because a rule is a record a console can now
      manage one at a time
    origin: none
  - id: BR-CAP-CRM-REG-004-11
    proved_by: []
    statement: >-
      The console saves the staged settings and full ordered policy against the editor’s initial version,
      omitting identifiers for rules the service has not yet created.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: >-
      react-console:components/regions/__tests__/assignment-draft.test.ts#what Save sends › is the settings at
      the version the editor opened with and every rule in its staged order
    origin: none
  - id: BR-CAP-CRM-REG-004-12
    proved_by: [ATS-CRM-REG-004-11]
    statement: >-
      The editor flags absent or missing target regions, incomplete criteria and missing address choices
      before saving, but suppresses these checks while assignment is disabled.
    kind: validation
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: >-
      react-console:components/regions/__tests__/assignment-draft.test.ts#the issues a save would be refused
      for › names a rule without a region, and points at the rule
    origin: none
  - id: BR-CAP-CRM-REG-004-13
    proved_by: []
    statement: >-
      The console warns about covered rules, inactive targets and missing or repeated fallback rules without
      treating those warnings as save refusals.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: >-
      react-console:lib/configuration/regions-analysis.test.ts#a rule an earlier rule already covers › flags a
      postal range inside an earlier range for the same country
    origin: none
  - id: BR-CAP-CRM-REG-004-14
    proved_by: [ATS-CRM-REG-004-10]
    statement: >-
      Whole-list replacement recreates retained rule identifiers at version one, allowing an older single-rule
      edit also based on version one to pass its version check.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:saveRegionAssignmentSettings,
      insertRule, updateRegionAssignmentRule
    origin: none
scenarios:
  - id: SC-CAP-CRM-REG-004-01
    given: 'Coverage distinguishes Boston, nearby states, Canada and all remaining addresses.'
    when: 'An administrator saves postal, state, country and fallback rules in that sequence.'
    then: Reads return four identified rules with consecutive positions and their appropriate criteria.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0357.spec.ts#CRM-ACC-0357'
  - id: SC-CAP-CRM-REG-004-02
    given: A state rule has no state entries.
    when: The administrator submits the policy.
    then: Validation identifies the missing entries and retains the previous policy.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#the
      capability › refuses a state rule with no states
edge_cases:
  - id: SC-CAP-CRM-REG-004-03
    given: A country entry is written in lower case.
    when: The client saves that rule.
    then: The stored entry is returned in capitals.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#a
      countries rule given its codes in lower case › stores the codes of a rule created on its own upper-cased
constraints: []
depends_on:
  - CAP-CRM-REG-001
related:
  - CAP-CRM-REG-003
  - CAP-CRM-REG-005
  - CAP-CRM-REG-006
  - CAP-CRM-REG-007
journeys:
  - JNY-CRM-REG-001
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:docs/milestones/CRM-M3-crm-configuration.md'
  - 'services-crm:modules/regions/tests/integration/db/region-assignment-settings.table.integration.spec.js'
  - 'services-crm:modules/regions/tests/e2e/regions.e2e.spec.js'
  - 'services-crm:modules/regions/services/persistence/region-assignment-settings.ts'
  - >-
    services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js
  - 'services-crm:modules/regions/aws/api/regions.schemas.ts'
  - 'react-console:components/regions/__tests__/assignment-draft.test.ts'
  - 'react-console:lib/configuration/regions-analysis.test.ts'
  - 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0357.spec.ts'
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-003, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-CRM-REG-02-01, FAS-CRM-REG-02-02, FAS-CRM-REG-02-03, FAS-CRM-REG-02-04, FAS-CRM-REG-02-05]
suites: [TST-CRM-REG-047, TST-CRM-REG-048, TST-CRM-REG-050, TST-CRM-REG-058, TST-CRM-REG-065]
---

# Build the geographic assignment policy

## Purpose

Administrators describe geographic coverage in an ordered policy that maps addresses to named regions.

## How It Works

The policy editor presents geographic coverage as an ordered list. Staff add or edit a rule in a drawer, choose its geographic kind and select its region. Reordering lets narrow geographic coverage precede broader coverage. Disabled rules remain in the policy without participating in matching. Notes explain why a rule exists without altering its result. The editor identifies incomplete inputs and separately warns about questionable coverage. Saving submits the settings and complete staged order together. The service removes the former list and recreates the submitted rules atomically, retaining supplied identifiers but resetting individual versions. That version reset weakens coordination with clients editing individual rules. Existing contact and organization placements wait for explicit recalculation.

## Relationships

- Maintain the region catalogue supplies the regional catalogue used by the surrounding work.
- Configure automatic placement and its address sources establishes participation and address-source choices for placement.
- Maintain individual rules alongside the shared policy provides individual-rule API maintenance alongside whole-policy editing.
- Determine an address’s geographic region supplies the common geographic decision.
- Check an address against the saved policy lets staff inspect the saved policy’s result before applying it widely.
