---
id: CAP-MBR-STR-002
title: Restructure units while preserving dated placement
feature: FEAT-MBR-STR-01
product_area: membership
module: structure
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-MBR-STR-005
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources: []
purpose: Staff reorganize the association without losing the explanation of where a unit belonged.
outcomes:
  - Restructuring preserves valid parentage for the complete branch.
  - 'Placement history identifies the effective date, actor and reason.'
concepts:
  - CON-MBR-STR-001
  - CON-MBR-STR-002
actors:
  - ACT-MBR-001
  - ACT-MBR-005
surfaces:
  - api
preconditions: []
postconditions: []
permissions:
  - key: membership.structure.manage
    where_checked: api
    scope: tenant
  - key: membership.application.access
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-MBR-STR-002-01
    proved_by: [ATS-MBR-STR-002-01, ATS-MBR-STR-002-13, TST-MBR-STR-006, TST-MBR-STR-069, TST-MBR-STR-100]
    statement: >-
      A unit can have a parent only of an allowed kind, and can have no parent only when its kind permits
      roots, with service and database checks on placement.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/structure-api.e2e.spec.ts#the structure API, end to end in process ›
      refuses a parent of a type not allowed, 409 parent_type_not_allowed
    origin: none
  - id: BR-CAP-MBR-STR-002-02
    proved_by: [ATS-MBR-STR-002-02, TST-MBR-STR-006, TST-MBR-STR-007, TST-MBR-STR-075, TST-MBR-STR-100]
    statement: >-
      A move beneath the unit itself or its descendants is rejected; serialized workspace tree edits make
      opposing concurrent moves produce one success and one refusal.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
      units in the database › the refusals › refuses a move under the unit’s own descendant as a cycle
    origin: none
  - id: BR-CAP-MBR-STR-002-03
    proved_by: [ATS-MBR-STR-001-11, ATS-MBR-STR-002-03, TST-MBR-STR-006, TST-MBR-STR-010, TST-MBR-STR-073]
    statement: Inactive and merged units cannot receive newly placed or moved child units.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
      units in the database › the refusals › refuses a new child under an inactive unit
    origin: none
  - id: BR-CAP-MBR-STR-002-04
    proved_by: [ATS-MBR-STR-002-04, TST-MBR-STR-073]
    statement: A unit must be active to move and cannot be moved to its existing parent.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-membership:modules/structure/services/tree/tree-edits.ts:moveUnit'
    origin: none
  - id: BR-CAP-MBR-STR-002-05
    proved_by: [ATS-MBR-STR-002-05, TST-MBR-STR-010, TST-MBR-STR-074]
    statement: A proposed branch placement or final rebuild reaching depth 32 is rejected as a whole.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits › move ›
      refuses a move that would put the subtree at the depth cap, before any write
    origin: none
  - id: BR-CAP-MBR-STR-002-06
    proved_by: [ATS-MBR-STR-002-06, TST-MBR-STR-006, TST-MBR-STR-010, TST-MBR-STR-090]
    statement: >-
      A move request supplies a reason and uses the workspace’s current date by default; a supplied date
      cannot be in the future or precede the current parent period.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
      units in the database › the parent history and the as-of tree › refuses a move dated before the current
      parent began
    origin: none
  - id: BR-CAP-MBR-STR-002-07
    proved_by: [ATS-MBR-STR-002-07, ATS-MBR-STR-002-14, TST-MBR-STR-006, TST-MBR-STR-072, TST-MBR-STR-098]
    statement: >-
      Parent periods cover dates from the first placement onward without gaps between successive periods,
      retaining the actor and reason for each change.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange'
    origin: none
  - id: BR-CAP-MBR-STR-002-08
    proved_by: [ATS-MBR-STR-002-08, TST-MBR-STR-072]
    statement: >-
      A second placement change effective on the current period’s first day updates that history row, with the
      earlier value retained in its versions.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange'
    origin: none
  - id: BR-CAP-MBR-STR-002-09
    proved_by: [ATS-MBR-STR-002-09, ATS-MBR-STR-002-15, TST-MBR-STR-007, TST-MBR-STR-010, TST-MBR-STR-011, TST-MBR-STR-091]
    statement: Tree edits recompute the workspace’s ancestor relationships and unit depths within the same transaction.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits › move ›
      locks, validates against the current closure, changes the parent, records history, rebuilds, updates
      depths and checks the cap, in that order
    origin: none
  - id: BR-CAP-MBR-STR-002-10
    proved_by: []
    statement: >-
      Move concurrency checks compare the supplied version with the stored version only when a version is
      supplied.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-membership:modules/structure/services/tree/tree-edits.ts:moveUnit'
    origin: none
  - id: BR-CAP-MBR-STR-002-11
    proved_by: [ATS-MBR-STR-002-10, TST-MBR-STR-010]
    statement: 'A successful move publishes the old parent, new parent and effective date within its transaction.'
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits › move ›
      states membership.unit.reparented with the old and the new parent and the business date
    origin: none
  - id: BR-CAP-MBR-STR-002-12
    proved_by: [ATS-MBR-STR-002-12]
    statement: >-
      Retrying the same completed move fails because the proposed parent is already current; moves have no
      retry key.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: documented
    source: 'services-membership:docs/migrations/structure/README.md#What could not be done as specified, and why'
    origin: none
  - id: BR-CAP-MBR-STR-002-13
    proved_by: []
    statement: >-
      Future-effective restructuring is unavailable; moves apply immediately with today’s date or an allowed
      earlier date.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-membership:docs/migrations/structure/README.md#The move sequence as implemented'
    origin: none
  - id: BR-CAP-MBR-STR-002-14
    proved_by: [ATS-MBR-STR-002-11, TST-MBR-STR-071, TST-MBR-STR-098]
    statement: >-
      Restructuring changes current regional membership visibility without rewriting affiliation records,
      while historical placement reads retain earlier parentage.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#scenario 9: organizational restructuring ›
      rewrites no affiliation: Jo’s row is the same row, version and unit
    origin: none
scenarios:
  - id: SC-CAP-MBR-STR-002-01
    given: Atlanta belongs to the Southeast Zone.
    when: Staff relocate Atlanta to the Pacific Zone and explain the change.
    then: The branch appears beneath Pacific and its placement history retains the transition.
    provenance: test
    source: >-
      services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#scenario 9: organizational restructuring ›
      moves Atlanta under the Pacific Zone
edge_cases:
  - id: SC-CAP-MBR-STR-002-02
    given: Two staff requests propose opposing parent relationships.
    when: Both requests attempt their moves concurrently.
    then: The first succeeds and the second fails after detecting the resulting cycle.
    provenance: test
    source: >-
      services-membership:modules/structure/tests/integration/services/tree/tree-scale.integration.spec.ts#the
      tree at scale › serialises two concurrent moves of one tenant by the tree lock, the second validating
      against the first’s closure
constraints: []
depends_on:
  - CAP-MBR-STR-001
related:
  - CAP-MBR-STR-003
  - CAP-MBR-STR-005
  - CAP-MBR-STR-006
journeys:
  - JNY-MBR-STR-001
  - JNY-MBR-STR-002
open_questions: []
evidence:
  - >-
    services-membership: Examination of tree edits, unit persistence, parent history and the rebuild of the
    current tree.
  - 'services-membership: Examination of unit, integration, scale and end-to-end tests and scenario 9.'
  - 'services-membership: Examination of the structure module record.'
  - 'services-membership:docs/migrations/structure/README.md'
  - 'services-membership:modules/structure/services/persistence/organizational-units.ts'
  - 'services-membership:modules/structure/services/tree/tree-edits.ts'
  - >-
    services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts
  - 'services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts'
  - 'services-membership:tests/e2e/s09-restructuring.e2e.spec.ts'
  - 'services-membership:tests/e2e/structure-api.e2e.spec.ts'
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-DAT-001, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004]
exercised_by: [FAS-MBR-STR-01-01, FAS-MBR-STR-01-02, FAS-MBR-STR-01-03, FAS-MBR-STR-01-04, FAS-MBR-STR-01-05]
suites: [TST-MBR-STR-006, TST-MBR-STR-007, TST-MBR-STR-010, TST-MBR-STR-011, TST-MBR-STR-069, TST-MBR-STR-071, TST-MBR-STR-072, TST-MBR-STR-073, TST-MBR-STR-074, TST-MBR-STR-075, TST-MBR-STR-090, TST-MBR-STR-091, TST-MBR-STR-098, TST-MBR-STR-100]
---

# Restructure units while preserving dated placement

## Purpose

Staff reorganize the association without losing the explanation of where a unit belonged.

## How It Works

Staff choose the unit to relocate and a new parent, or an independent root placement where its kind allows one. They provide a reason and can use an earlier effective date within the current placement period. The service evaluates the branch against the tree that currently exists. It refuses invalid parents, loops and excessive depth. Workspace tree edits wait for one another, so concurrent requests evaluate a committed predecessor’s result. The unit’s earlier parent period closes and its new period becomes current. Same-day corrections retain earlier values in versions rather than creating an empty period. The current tree is rebuilt before the save completes. Connected membership views then follow the new placement. Staff can inspect the parent history to explain the reorganization. The product should make repeat requests return the first result through an explicit retry key.

## Relationships

- Unit-kind configuration defines which parent choices are valid.
- Merge operations apply placement checks when relocating child branches.
- Historical structure reads use the periods recorded by this capability.
