---
id: CAP-CRM-REG-005
title: Maintain individual rules alongside the shared 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-11 second pass (Andrew, 2026-10-11: rules are
    managed one at a time; an organization has a preferred address)
  - >-
    services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), Since:
    2026-10-14, Andrew: assignment may be on with no rules
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-05'
purpose: >-
  Clients maintain selected rules without replacing unrelated rules or losing changes made through the shared
  policy.
outcomes:
  - Successful writes retain consecutive rule positions.
  - Version checks detect stale edits except where whole-list replacement resets individual versions.
concepts:
  - CON-CRM-REG-002
actors:
  - ACT-CRM-003
  - ACT-CRM-004
surfaces:
  - api
preconditions: []
postconditions: []
permissions:
  - key: crm.regions.read
    where_checked: api
    scope: tenant
  - key: crm.regions.update
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-REG-005-01
    proved_by: [ATS-CRM-REG-005-01, TST-CRM-REG-047]
    statement: >-
      Creating a rule appends it unless an earlier insertion position is requested, in which case following
      rules move down one position.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#appends a created rule at the end of the
      order;
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:createRegionAssignmentRule
    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-005-02
    proved_by: [ATS-CRM-REG-005-02]
    statement: Rule replacement and patching require the version read and reject missing or stale versions.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the rules, one at a time › 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-005-03
    proved_by: [ATS-CRM-REG-004-09]
    statement: >-
      Replacement preserves the rule identifier and current position; changing order uses the separate reorder
      operation.
    kind: invariant
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:updateRegionAssignmentRule
    origin: none
  - id: BR-CAP-CRM-REG-005-04
    proved_by: [ATS-CRM-REG-005-03, ATS-CRM-REG-005-09, TST-CRM-REG-047]
    statement: Deleting a rule preserves the remaining sequence and closes the gap with positions starting at one.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#closes the gap the delete left, so the rule
      that remains is at the first position
    origin: none
  - id: BR-CAP-CRM-REG-005-05
    proved_by: [ATS-CRM-REG-005-04, ATS-CRM-REG-005-08, TST-CRM-REG-047]
    statement: >-
      Reordering requires every workspace rule exactly once and rejects omissions, duplicates and unknown
      identifiers with an explanation.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for an order that does not name
      every rule of the tenant
    origin: none
  - id: BR-CAP-CRM-REG-005-06
    proved_by: [ATS-CRM-REG-005-05, ATS-CRM-REG-005-08, TST-CRM-REG-050]
    statement: >-
      Each individual-rule write increments the settings version, causing a whole-list save based on the
      earlier version to conflict rather than discard that write.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#rules
      written one at a time, beside a settings save › a whole-list save read before a rule was created › is
      refused as stale, so the new rule is not dropped
    origin: none
  - id: BR-CAP-CRM-REG-005-07
    proved_by: []
    statement: >-
      Individual-rule writes and settings replacements serialize on the workspace settings row so their rule
      updates cannot interleave.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: false
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:lockSettingsRowForRuleWrite
    origin: none
  - id: BR-CAP-CRM-REG-005-08
    proved_by: [ATS-CRM-REG-005-09]
    statement: Creating a workspace’s first rule also creates default settings with automatic assignment disabled.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: >-
      services-crm:modules/regions/services/persistence/region-assignment-settings.ts:lockSettingsRowForRuleWrite
    origin: none
  - id: BR-CAP-CRM-REG-005-09
    proved_by: [ATS-CRM-REG-005-06, TST-CRM-REG-047]
    statement: >-
      The final enabled rule can be disabled or removed while automatic assignment remains on, leaving no
      matching rule.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the last enabled rule while assignment is on
      › disables it, leaving the tenant assignment on with nothing to match
    origin: >-
      services-crm:docs/migrations/crm-regions/README.md#2026-10-12 — Final acceptance (second pass), Since:
      2026-10-14, Andrew: assignment may be on with no rules
  - id: BR-CAP-CRM-REG-005-10
    proved_by: []
    statement: >-
      Authorized region readers receive rules in evaluation order, with the default list returning the bounded
      policy of up to one thousand rules.
    kind: policy
    enforced_at:
      - api
    must_prove: false
    provenance: test
    source: 'services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#lists both rules in the order they are tried'
    origin: none
  - id: BR-CAP-CRM-REG-005-11
    proved_by: [ATS-CRM-REG-005-07, TST-CRM-REG-047]
    statement: 'Rule reads and writes are confined to their workspace, and an unknown rule returns not found.'
    kind: invariant
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#the rules, one at a time › answers none of
      this tenant's rules to another tenant's list
    origin: none
scenarios:
  - id: SC-CAP-CRM-REG-005-01
    given: The policy already contains three rules.
    when: An integration adds a rule without an insertion position.
    then: The new rule follows the existing three at position four.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#appends a created rule at the end of the
      order
  - id: SC-CAP-CRM-REG-005-02
    given: A policy contains two rules.
    when: A client submits an order containing only one.
    then: The missing rule is identified and the order remains intact.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/e2e/regions.e2e.spec.js#answers 400 for an order that does not name
      every rule of the tenant
edge_cases:
  - id: SC-CAP-CRM-REG-005-03
    given: An editor holds an earlier whole-list draft.
    when: An integration adds a rule before that draft is saved.
    then: The settings-version conflict preserves the integration’s addition.
    provenance: test
    source: >-
      services-crm:modules/regions/tests/integration/services/persistence/region-assignment-settings.integration.spec.js#rules
      written one at a time, beside a settings save › a whole-list save read before a rule was created ›
      leaves the rule created beside it in the list
constraints: []
depends_on:
  - CAP-CRM-REG-001
  - CAP-CRM-REG-004
related:
  - CAP-CRM-REG-004
  - CAP-CRM-REG-006
  - CAP-CRM-REG-010
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:docs/milestones/CRM-M1-contacts-and-organizations.md'
  - '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
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-02, FAS-CRM-REG-02-05]
suites: [TST-CRM-REG-047, TST-CRM-REG-050]
---

# Maintain individual rules alongside the shared policy

## Purpose

Clients maintain selected rules without replacing unrelated rules or losing changes made through the shared policy.

## How It Works

API clients can maintain individual rules alongside whole-policy editing. A new rule goes at the end unless the client requests an earlier insertion. Replacement and patching use the individual rule version. A reorder supplies the complete desired sequence. Deletion closes the resulting gap. Each write also advances the settings version, protecting an older whole-list draft from discarding it. The first rule creates disabled default settings when none exist. A policy may remain enabled after its final enabled rule is removed. The current console uses whole-list replacement, so individual operations primarily support other clients. A whole-list save can reset individual versions and therefore remains a coordination risk.

## Relationships

- Maintain the region catalogue supplies the regional catalogue used by the surrounding work.
- Build the geographic assignment policy maintains the geographic policy that other regional workflows evaluate.
- Determine an address’s geographic region supplies the common geographic decision.
- Identify placements that need recalculation identifies policy generations still needing application.
