---
id: DOM-CRM-REG-002
title: Assignment Policy
realises: CON-CRM-REG-002
product_area: crm
module: regions
revision: 2
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-CRM-REG-regions
readable_by:
  - SVC-PFM-QRY-data-exports
lifecycle: STATE-CRM-REG-002
custom_fields: none
identity: >-
  A workspace has one assignment policy, keyed by the workspace rather than a separate policy identifier.
  Before its first save, reads return defaults with assignment disabled and no rules. Persisted rules have
  their own workspace-specific identifiers and evaluation positions.
attributes:
  - name: automatic assignment switch
    kind: enum
    required: true
    computed: false
    notes: 'Automatic placement starts disabled, including when no policy row exists.'
  - name: participating record kinds
    kind: enum
    required: true
    computed: false
    notes: 'The policy selects contacts, organizations or both; both is the default choice.'
  - name: contact address source
    kind: enum
    required: true
    computed: false
    notes: Contacts use their preferred address by default; choosing a named type requires its reference.
  - name: organization address source
    kind: enum
    required: true
    computed: false
    notes: >-
      Organizations can use a preferred or typed address. The default keeps the baseline Main type to preserve
      older policy meaning.
  - name: deciding address types
    kind: reference
    required: false
    computed: false
    notes: >-
      A typed source must identify a workspace address type. Applicability labels do not restrict backend
      acceptance, and the reference blocks type deletion.
  - name: rules
    kind: collection
    required: false
    computed: false
    notes: >-
      The policy contains at most 1,000 ordered rules. Each has an identity, kind, target, enabled flag,
      optional description and its kind’s geographic criteria.
  - name: postal code ranges
    kind: collection
    required: false
    computed: false
    notes: >-
      Postal rules require 1–1,000 nonblank inclusive endpoint pairs compared as text. An optional two-letter
      country limits their coverage.
  - name: states and countries
    kind: collection
    required: false
    computed: false
    notes: >-
      State and country rules require 1–1,000 values. States compare exactly; country values are uppercased
      before storage and compared without case.
  - name: policy generation
    kind: number
    required: true
    computed: true
    notes: A private change counter tracks policy updates and is compared with completed recalculation generations.
  - name: rules changed at
    kind: date
    required: false
    computed: true
    notes: >-
      Settings reads and export include the latest policy change time; the built policy view does not render
      it.
  - name: recalculation needed
    kind: computed
    required: true
    computed: true
    notes: The read computes this flag from policy and completed-run generations; it is not a stored flag.
  - name: version
    kind: number
    required: false
    computed: true
    notes: >-
      Persisted settings have a version that advances on settings and individual-rule writes. Unsaved defaults
      have none. Individual rules also carry separate versions.
relationships:
  - to: DOM-CRM-REG-001
    cardinality: 'Every rule selects one region, and many rules can select the same region.'
    ownership: The policy owns the reference and retains the target until its referencing rules are removed.
    cross_service: false
  - to: DOM-CRM-ENT-001
    cardinality: A policy selects at most one contact address type; it separately supports an organization address type.
    ownership: Entities owns the address catalogue; policy references prevent deleting a selected type.
    cross_service: false
  - to: DOM-CRM-REG-003
    cardinality: One workspace policy governs all eligible placements in that workspace.
    ownership: Placement processing reads the policy; policy persistence performs no record placement update.
    cross_service: false
  - to: DOM-CRM-REG-004
    cardinality: A workspace policy can be applied by multiple historical recalculation runs.
    ownership: The run stores its starting generation and rereads policy during processing.
    cross_service: false
invariants:
  - id: INV-DOM-CRM-REG-002-01
    statement: 'Only one settings row may exist per workspace, and absence returns defaults with assignment disabled.'
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/db/region-assignment-settings.table.integration.spec.js#region
      assignment settings tables (integration) › settings constraints › holds one settings row per tenant;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:DEFAULT_REGION_ASSIGNMENT_SETTINGS
  - id: INV-DOM-CRM-REG-002-02
    statement: >-
      A typed source requires a valid local address-type reference, while a preferred source stores no type
      reference.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/db/region-assignment-settings.table.integration.spec.js#region
      assignment settings tables (integration) › settings constraints › refuses deciding by a type with no
      address type named;
      services-crm:modules/regions/db/region-assignment-settings.table.ts:regionAssignmentSettings
  - id: INV-DOM-CRM-REG-002-03
    statement: >-
      The database enforces positive unique positions; service writers produce a consecutive order beginning
      at one.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/regions/db/region-assignment-settings.table.ts:regionAssignmentRules
      (region_assignment_rules_position_uq);
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:setRulePositions
  - id: INV-DOM-CRM-REG-002-04
    statement: >-
      The service retains and validates kind-specific criteria; database checks constrain state and country
      lists but do not require or type-check postal child ranges.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/regions/db/region-assignment-settings.table.ts:regionAssignmentRules (states,
      countries and country checks);
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:normalizeRegionAssignmentRule,
      regionAssignmentRuleProblems
  - id: INV-DOM-CRM-REG-002-05
    statement: Every rule’s regional target must exist in the rule’s workspace.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment.integration.spec.js#region
      assignment (integration) › refusals › refuses settings whose rule names a region the tenant does not
      have
  - id: INV-DOM-CRM-REG-002-06
    statement: 'Policy requests and individual creation permit no more than 1,000 rules.'
    enforced_at:
      - api
      - service
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:createRegionAssignmentRule;
      services-crm:modules/regions/aws/api/regions.schemas.ts:regionAssignmentSettingsSchema
  - id: INV-DOM-CRM-REG-002-07
    statement: >-
      Writers lock an existing settings row before changing policy; competing first insertions can still fail
      on its unique key.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:saveRegionAssignmentSettings,
      lockSettingsRowForRuleWrite
  - id: INV-DOM-CRM-REG-002-08
    statement: >-
      Settings saves, individual-rule writes and retirement of enabled targets increment the generation within
      their transaction.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#region
      assignment rules and slots (integration) › whether a recalculation is needed › says one is needed again
      as soon as a rule is added;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:saveRegionAssignmentSettings,
      stampRulesChanged, noteRuleRegionDeactivated
  - id: INV-DOM-CRM-REG-002-09
    statement: Policy persistence leaves contact and organization placements untouched.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#region
      assignment rules and slots (integration) › whether a recalculation is needed › says a recalculation is
      needed once the rules have been saved and nothing has recalculated
  - id: INV-DOM-CRM-REG-002-10
    statement: Regional processing uses only the execution workspace’s policy.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js#region
      assignment rules and slots (integration) › tenant isolation, with rules on both sides › assigns a
      contact of the neighbour tenant from the neighbour’s own rules
physical:
  - crm.region_assignment_settings
  - crm.region_assignment_rules
  - crm.region_assignment_rule_postal_code_ranges
evidence:
  - >-
    services-crm at a8d1364f: modules/regions/db/region-assignment-settings.table.ts;
    services/persistence/region-assignment-settings.ts; services/region-assignment/region-matching.ts;
    aws/api/regions.routes.ts (settings and rule routes); aws/api/regions.schemas.ts;
    export/region-assignment-settings.dataset.ts, region-assignment-settings.reader.ts;
    tests/integration/db/region-assignment-settings.table.integration.spec.js;
    tests/integration/services/region-assignment/region-assignment-rules.integration.spec.js.
  - >-
    react-console at baf6dae3: lib/configuration/regions.ts (assignmentsBody, addressTypesFor);
    lib/configuration/regions-analysis.ts.
---

# Assignment Policy

## What It Models

The assignment policy defines the customer’s geographic coverage and which address supplies each placement decision. It combines automatic assignment, participating record kinds, address sources and ordered rules. Saving it changes coverage for future decisions; staff use recalculation to apply it across existing records.

## Identity and Attributes

The workspace identifies its single policy. Rules are owned children with stable identifiers and ordered positions. Postal, state and country rules carry their corresponding criteria; fallback rules carry no geographic list. Disabled rules remain stored but do not match. The generation counts policy writes, and the read computes whether a completed run covers that generation.

## Relationships

Rules refer to catalogue regions. Typed address choices refer to Entities’ address types, preventing their deletion while selected. Placements read the policy, and recalculation runs remember the generation under which they began.

## Invariants

Persistence coordinates writers through an existing policy row. The database checks reference integrity, unique positive positions and selected criterion shapes. Service normalization and validation supply additional rules, including dense final ordering and required postal coverage. An absent settings row does not provide a lock for competing first saves.

## How It Is Stored and Shared

Regions persists settings, rules and postal child ranges. Export packages a saved policy with its ordered rules and omits the private generation. Regional imports load definitions only. Workspace cleanup removes policy before its referenced address types.

## Open Design Questions

Whole-policy replacement recreates retained rules at version one, which weakens concurrent individual editing. Policy reads do not request the available read lock, so settings and criteria may come from different saves. Optional settings versions, unchanged saves and reversed postal intervals also need resolution.
