---
id: CAP-CRM-REG-001
title: Maintain the region catalogue
feature: FEAT-CRM-REG-01
product_area: crm
module: regions
revision: 2
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - CAP-CRM-CFG-015
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-crm:docs/debt/region-custom-fields.md#Custom-field values of a region (Andrew, 2026-10-19,
    R2-ARCH-001: only contacts and organizations carry custom fields)
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-05'
  - 'services-crm:docs/milestones/CRM-M8-activities-and-tasks.md#D-43'
purpose: >-
  Staff maintain the named regional groups used to organise people and organizations for outreach, reporting
  and staff responsibility.
outcomes:
  - 'Regions have stable identifiers, readable names, optional descriptions and an active state.'
  - Validation failures identify the affected field and commit no region.
  - Versioned replacement protects intervening edits.
concepts:
  - CON-CRM-REG-001
actors:
  - ACT-CRM-003
  - ACT-CRM-002
  - ACT-CRM-004
surfaces:
  - console
  - api
preconditions:
  - The caller has regional read or write authority for the requested workspace.
postconditions:
  - Reading a completed save returns the persisted region.
permissions:
  - key: crm.regions.read
    where_checked: api
    scope: tenant
  - key: crm.regions.create
    where_checked: api
    scope: tenant
  - key: crm.regions.update
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-REG-001-01
    statement: >-
      Region identifiers accept letters, digits, underscores, hyphens and vertical bars; other characters
      cause a refusal that describes the permitted characters.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for an id with a space, naming
      the characters an id may hold
    origin: none
    examples:
      positive: 'A mixed identifier containing a hyphen, underscore and vertical bar is valid.'
      negative: An identifier containing a space is rejected.
  - id: BR-CAP-CRM-REG-001-02
    statement: >-
      A region requires a nonblank name, which is trimmed before acceptance; a name containing only spaces is
      rejected against the name field.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 naming the field for a name of
      nothing but spaces
    origin: none
  - id: BR-CAP-CRM-REG-001-03
    statement: >-
      Creation generates an identifier when none is supplied and rejects an identifier already used by a
      region in the workspace.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/regions.integration.spec.js#refuses
      an id the tenant already uses; services-crm:modules/regions/services/persistence/regions.ts:createRegion
    origin: none
  - id: BR-CAP-CRM-REG-001-04
    statement: A region starts active when creation does not specify its active state.
    kind: invariant
    enforced_at:
      - database
    must_prove: false
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/regions.integration.spec.js#creates
      a region active by default
    origin: none
  - id: BR-CAP-CRM-REG-001-05
    statement: >-
      Replacement requires the version previously read, and patching tests that version; a missing replacement
      version or a stale version is rejected.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the regions › answers 409 for a replace from
      a stale read
    origin: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-05'
  - id: BR-CAP-CRM-REG-001-06
    statement: 'Replacement supplies the complete region, so an omitted description or external identifier is removed.'
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/regions.queries.integration.spec.js#the
      record a replace left › answers the whole record as the replace left it: the fields it did not state
      cleared, the version bumped
    origin: none
  - id: BR-CAP-CRM-REG-001-07
    statement: >-
      Region lists use character-by-character identifier order and return pages with a continuation when
      another page exists.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/regions.integration.spec.js#lists
      the tenant's regions in id order by bytes, a page at a time
    origin: none
  - id: BR-CAP-CRM-REG-001-08
    statement: >-
      Region reads and writes stay within the requested workspace, even for callers with access to several
      workspaces, and a conflicting workspace in the request body is rejected.
    kind: invariant
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the regions › answers 404 for this tenant's
      region read in another tenant, to a context that covers both;
      services-crm:modules/regions/aws/api/regions.routes.ts:toWriteInput
    origin: none
  - id: BR-CAP-CRM-REG-001-09
    statement: Regions have no custom fields; CRM reserves custom fields for contacts and organizations.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/debt/region-custom-fields.md#Custom-field values of a region'
    origin: >-
      services-crm:docs/debt/region-custom-fields.md#Custom-field values of a region (Andrew, 2026-10-19,
      R2-ARCH-001: only contacts and organizations carry custom fields)
  - id: BR-CAP-CRM-REG-001-10
    statement: >-
      The console chooses a new region’s identifier before sending creation, allowing a retry after an
      ambiguous response to find the original region.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: >-
      react-console:lib/configuration/regions.test.ts#the regions api writes (D-05, D-35, D-43) › creates a
      region under the id the drawer chose and finds it when the answer was lost
    origin: 'services-crm:docs/milestones/CRM-M8-activities-and-tasks.md#D-43'
  - id: BR-CAP-CRM-REG-001-11
    statement: >-
      Console replacement retains the fields returned by the read, including the external identifier and lock,
      while applying the visible edits.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: observed
    source: 'react-console:lib/configuration/regions.ts:carriedThrough'
    origin: none
  - id: BR-CAP-CRM-REG-001-12
    statement: >-
      Every successful replacement or patch increments the region version, including a write that leaves its
      fields unchanged.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-crm:modules/regions/services/persistence/regions.ts:updateRegionRow'
    origin: none
  - id: BR-CAP-CRM-REG-001-13
    statement: >-
      The console uses a drawer on the right to edit a region, labels it by record kind and requests
      confirmation before discarding unsaved edits.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: >-
      react-console:components/regions/__tests__/editor-drawers.test.tsx#the region editor › renders in a
      right-side drawer, not in a dialog
    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-001-01
    given: The region catalogue is being established.
    when: An administrator creates Northeast with a description and leaves it active.
    then: The new active region appears in the catalogue and is available as a rule target.
    provenance: test
    source: 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0357.spec.ts#CRM-ACC-0357'
  - id: SC-CAP-CRM-REG-001-02
    given: A new region uses an identifier containing a space.
    when: The coordinator submits creation.
    then: Validation identifies the identifier grammar and creates nothing.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for an id with a space, naming
      the characters an id may hold
  - id: SC-CAP-CRM-REG-001-03
    given: Two editors read one region at the same version.
    when: One saves and the other submits the older replacement.
    then: The later request conflicts and preserves the earlier edit.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the regions › answers 409 for a replace from
      a stale read
edge_cases:
  - id: SC-CAP-CRM-REG-001-04
    given: An identifier combines upper and lower case with a vertical bar.
    when: A client creates that region.
    then: The legacy identifier grammar accepts it.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/db/regions.table.integration.spec.js#accepts the
      characters of the legacy grammar (word characters, | and -)
constraints: []
depends_on: []
related:
  - CAP-CRM-REG-002
  - CAP-CRM-REG-013
  - CAP-CRM-REG-014
  - CAP-CRM-REG-015
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/debt/region-custom-fields.md'
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md'
  - 'services-crm:docs/milestones/CRM-M8-activities-and-tasks.md'
  - 'services-crm:modules/regions/tests/e2e/regions.e2e.spec.js'
  - 'services-crm:modules/regions/tests/integration/services/persistence/regions.integration.spec.js'
  - 'services-crm:modules/regions/services/persistence/regions.ts'
  - 'services-crm:modules/regions/tests/integration/services/persistence/regions.queries.integration.spec.js'
  - 'services-crm:modules/regions/aws/api/regions.routes.ts'
  - 'react-console:lib/configuration/regions.test.ts'
  - 'react-console:lib/configuration/regions.ts'
  - 'react-console:components/regions/__tests__/editor-drawers.test.tsx'
  - 'react-console:acceptance/crm/playwright/journeys/configuration/CRM-ACC-0357.spec.ts'
  - 'services-crm:modules/regions/tests/integration/db/regions.table.integration.spec.js'
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Maintain the region catalogue

## Purpose

Staff maintain the named regional groups used to organise people and organizations for outreach, reporting and staff responsibility.

## How It Works

An administrator opens the Regions tab in CRM configuration to maintain the catalogue. The list provides names, descriptions and active markers, while the drawer handles creation and editing. Identifiers remain stable after creation and preserve the legacy character grammar. A blank name is rejected instead of producing an unusable catalogue entry. The console retains less visible fields when replacing a region. It submits the version originally read, so another edit can produce a conflict that requires a fresh read. Even an unchanged accepted replacement advances the version. Integrations can use the same catalogue through regional API operations.

## Relationships

- Retire regions while preserving their references handles retirement and reference-sensitive removal of regional definitions.
- Restrict regional work to authorised users defines authority for the regional workflow.
- Import a catalogue of regions loads regional definitions through shared data-job processing.
- Share regions through record references and exports shares regional definitions with record references and export.
