---
id: ATS-MBR-STR-002
scope: CAP-MBR-STR-002
product_area: membership
module: structure
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-MBR-STR-002-01
    title: Placement checks also guard direct writes
    level: api
    proves:
      - BR-CAP-MBR-STR-002-01
      - AS-JNY-MBR-STR-001-01
      - INV-DOM-MBR-STR-001-01
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: >-
      Try a chapter under a disallowed region and as a disallowed root, then attempt invalid parentage
      through the database.
    expected: Both service cases fail and the database rejects the direct placement.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
        units in the database › the refusals › refuses a root of a type that may not be one
      - >-
        services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
        units in the database › the refusals › refuses, in the database itself, a parent of a type not
        allowed (the constraint trigger)
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0010.acceptance.ts#MBR-ACC-0010 every
        illegal placement of a new unit is refused
      - TST-MBR-STR-100
      - TST-MBR-STR-006
      - TST-MBR-STR-069
    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
    category: validation
  - id: ATS-MBR-STR-002-02
    title: Opposing concurrent moves preserve an acyclic tree
    level: api
    proves:
      - BR-CAP-MBR-STR-002-02
      - INV-DOM-MBR-STR-001-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Try moving a region beneath its chapter, then submit opposing moves concurrently.'
    expected: The cycle fails; one concurrent move succeeds and the final ancestry matches a fresh parent walk.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        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 itself as a cycle
      - >-
        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
      - >-
        services-membership:tests/e2e/structure-api.e2e.spec.ts#the structure API, end to end in process
        › refuses a move under the unit’s own descendant, 409 move_creates_cycle
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0016.acceptance.ts#MBR-ACC-0016 two staff
        reorganise the same chapters at once
      - TST-MBR-STR-006
      - TST-MBR-STR-007
      - TST-MBR-STR-100
      - TST-MBR-STR-075
    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
    category: concurrency
  - id: ATS-MBR-STR-002-03
    title: Closed parents reject incoming children
    level: api
    proves:
      - BR-CAP-MBR-STR-002-03
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Try creation below an inactive unit and relocation below a merged unit.
    expected: Each attempt returns an inactive-parent refusal.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits ›
        move › refuses an inactive parent
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts#MBR-ACC-0014 every move
        the tree refuses
      - TST-MBR-STR-006
      - TST-MBR-STR-010
      - TST-MBR-STR-073
    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
    category: validation
  - id: ATS-MBR-STR-002-04
    title: Only eligible units can change parent
    level: api
    proves:
      - BR-CAP-MBR-STR-002-04
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Attempt relocation of inactive and merged units and relocation to an unchanged parent.
    expected: All attempts fail with the corresponding structural refusal and preserve earlier records.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts#MBR-ACC-0014 every move
        the tree refuses
      - TST-MBR-STR-073
    provenance: test
    source: >-
      services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts#MBR-ACC-0014 every move
      the tree refuses
    category: edge
  - id: ATS-MBR-STR-002-05
    title: Depth checks reject the complete edit
    level: unit
    proves:
      - BR-CAP-MBR-STR-002-05
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Propose a branch reaching depth 32 and separately simulate a rebuild at that depth.
    expected: Both paths reject the excessive depth.
    negative: true
    data_needs: Recording tree-store fixtures and controlled operation inputs.
    bound_to:
      - >-
        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
      - >-
        services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits ›
        move › refuses the whole edit when the rebuilt tree reaches depth 32 (a cycle past validation)
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0015.acceptance.ts#MBR-ACC-0015 thirty-one
        levels, a moved subtree and a second root
      - TST-MBR-STR-010
      - TST-MBR-STR-074
    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
    category: boundary
  - id: ATS-MBR-STR-002-06
    title: Move dates use the workspace calendar
    level: api
    proves:
      - BR-CAP-MBR-STR-002-06
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Compare omitted dates with a date before current placement and a date after today.
    expected: Omission selects the workspace date and the out-of-range dates fail.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        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 after today
      - >-
        services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits ›
        move › records the history from the tenant’s business date
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0033.acceptance.ts#MBR-ACC-0033 today is
        New York's today
      - TST-MBR-STR-006
      - TST-MBR-STR-010
      - TST-MBR-STR-090
    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
    category: boundary
  - id: ATS-MBR-STR-002-07
    title: Successive parent periods meet without overlap
    level: api
    proves:
      - BR-CAP-MBR-STR-002-07
      - INV-DOM-MBR-STR-001-04
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Relocate a unit after its current placement’s first day.
    expected: The earlier period finishes the preceding day and one open attributed period remains.
    negative: false
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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 › closes the open row at the day
        before the new one and keeps one open row per unit
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts#MBR-ACC-0013 a
        backdated move and a same-day correction
      - TST-MBR-STR-006
      - TST-MBR-STR-072
    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 › closes the open row at the day
      before the new one and keeps one open row per unit
    category: invariant
  - id: ATS-MBR-STR-002-08
    title: Same-day corrections retain one current period
    level: api
    proves:
      - BR-CAP-MBR-STR-002-08
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Submit two parent changes effective on the same date.
    expected: Current history names the later parent and retained versions explain the earlier change.
    negative: false
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts#MBR-ACC-0013 a
        backdated move and a same-day correction
      - TST-MBR-STR-072
    provenance: test
    source: >-
      services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts#MBR-ACC-0013 a backdated
      move and a same-day correction
    category: edge
  - id: ATS-MBR-STR-002-09
    title: Large branch moves leave coherent ancestry
    level: api
    proves:
      - BR-CAP-MBR-STR-002-09
      - INV-DOM-MBR-STR-001-03
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Relocate a 68-unit branch within a 500-unit structure and compare the result with a parent walk.
    expected: >-
      The edit preserves its checked operation order and the rebuilt ancestry matches the independent
      walk.
    negative: false
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        services-membership:modules/structure/tests/integration/services/tree/tree-scale.integration.spec.ts#the
        tree at scale › leaves a closure equal to a fresh walk of the parents after the concurrent moves
      - >-
        services-membership:modules/structure/tests/integration/services/tree/tree-scale.integration.spec.ts#the
        tree at scale › answers the moved subtree under its new parent
      - >-
        services-membership:modules/structure/tests/unit/services/tree-statements.spec.ts#the tree
        statements › the closure rebuild › walks every unit down from itself at depth 0, one level per
        step, under the cap of 32, in every statement but the depths
      - >-
        services-membership:modules/structure/tests/unit/services/tree-statements.spec.ts#the tree
        statements › the closure rebuild › removes the tenant’s pairs the walk no longer has, at their
        depth, and only the tenant’s
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0034.acceptance.ts#MBR-ACC-0034 a
        500-unit, six-level structure
      - TST-MBR-STR-010
      - TST-MBR-STR-007
      - TST-MBR-STR-011
      - TST-MBR-STR-091
    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
    category: invariant
  - id: ATS-MBR-STR-002-10
    title: Parent changes publish their context
    level: unit
    proves:
      - BR-CAP-MBR-STR-002-11
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Submit a valid unit relocation and inspect its business event.
    expected: 'The event identifies the unit, previous and new parents, effective date and attribution.'
    negative: false
    data_needs: Recording tree-store fixtures and controlled operation inputs.
    bound_to:
      - >-
        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
      - >-
        services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts#the tree edits ›
        move › builds the reparented payload from the move and the transaction’s attribution
      - TST-MBR-STR-010
    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
    category: rule
  - id: ATS-MBR-STR-002-11
    title: Regional membership views follow placement
    level: api
    proves:
      - BR-CAP-MBR-STR-002-14
      - AS-JNY-MBR-STR-002-01
      - AS-JNY-MBR-STR-002-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Move Atlanta from Southeast to Pacific and inspect Jo’s affiliation and regional views.
    expected: The affiliation is unchanged while current and period-end views use their respective regions.
    negative: false
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        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
      - >-
        services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#scenario 9: organizational
        restructuring › shows Jo under the Western Region and the Pacific Zone today, and no longer under
        the Eastern Region
      - >-
        services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#scenario 9: organizational
        restructuring › still holds Atlanta among the Eastern Region’s descendants as of the period end
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0012.acceptance.ts#MBR-ACC-0012 scenario
        9: Atlanta moves to the Pacific Zone
      - TST-MBR-STR-098
      - TST-MBR-STR-071
    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
    category: interaction
  - id: ATS-MBR-STR-002-12
    title: A repeated completed move has no new effect
    category: edge
    level: api
    proves:
      - BR-CAP-MBR-STR-002-12
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Move a chapter to a different zone, retain the response and immediately repeat the request without
      a version.
    expected: The retry reports the current-parent conflict and adds no parent period.
    negative: true
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:docs/migrations/structure/README.md#What could not be done as specified, and
        why
  - id: ATS-MBR-STR-002-13
    title: Merge children obey the same placement choices as moves
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-002-01
      - BR-CAP-MBR-STR-003-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Prepare a source child whose kind disallows the survivor’s kind and attempt both a direct move and
      a source merge.
    expected: 'Both requests refuse the placement and the merge changes no child, source standing or affiliation.'
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    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
      - >-
        services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
        units in the database › merge › moves the children under the survivor, marks the unit merged and
        lists what it left
    exercises:
      - CAP-MBR-STR-002
      - CAP-MBR-STR-003
  - id: ATS-MBR-STR-002-14
    title: A move changes current reads while retaining dated reads
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-002-07
      - BR-CAP-MBR-STR-005-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: 'Read a branch, move its chapter on a later date and repeat both current and earlier-date reads.'
    expected: Current descendants follow the new branch and the earlier-date answer keeps the old placement.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to:
      - TST-MBR-STR-098
      - >-
        services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#still holds Atlanta among the Eastern
        Region’s descendants as of the period end
    provenance: test
    source:
      - >-
        services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange
      - >-
        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 › answers the descendants as of
        2027-06-30 equal to the tree recorded that day
      - >-
        services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#still holds Atlanta among the Eastern
        Region’s descendants as of the period end
    exercises:
      - CAP-MBR-STR-002
      - CAP-MBR-STR-005
  - id: ATS-MBR-STR-002-15
    title: Detail replacement cannot undo a move
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-002-09
      - BR-CAP-MBR-STR-006-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Move a chapter, then replace its local contact details while submitting its former parent in the
      body.
    expected: The local details change while the chapter retains the accepted placement and depth.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    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
      - >-
        services-membership:modules/structure/services/persistence/organizational-units.ts:updateOrganizationalUnit
    exercises:
      - CAP-MBR-STR-002
      - CAP-MBR-STR-006
  - id: ATS-MBR-STR-002-16
    title: Workspace authority gates this capability
    category: permission
    level: api
    proves:
      - BR-CAP-MBR-STR-019-01
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Exercise the capability’s exposed operations with its read-only and management callers, then repeat
      with application access removed, an unlicensed workspace and an unavailable workspace.
    expected: >-
      Each named operation requires its declared authority; admission failures occur before any record or
      calculated result is exposed.
    negative: true
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/unit/route-authorization.spec.ts#route authorization: every Membership
        operation names its permission › requires membership.structure.read of every structure read and
        membership.structure.manage of every change
      - >-
        services-membership:modules/configuration/aws/api/configuration.routes.ts:configurationModuleRoutes
      - >-
        services-membership:tests/e2e/terms-api.e2e.spec.ts#the terms API, end to end in process ›
        refuses a caller who holds no key in the tenant
    exercises:
      - CAP-MBR-STR-002
      - CAP-MBR-STR-019
evidence:
  - >-
    services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts
  - 'services-membership:modules/structure/tests/integration/services/tree/tree-scale.integration.spec.ts'
  - 'services-membership:modules/structure/tests/unit/services/tree-edits.spec.ts'
  - 'services-membership:modules/structure/tests/unit/services/tree-statements.spec.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0010.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0012.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0015.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0016.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0033.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0034.acceptance.ts'
  - 'services-membership:tests/e2e/s09-restructuring.e2e.spec.ts'
  - 'services-membership:tests/e2e/structure-api.e2e.spec.ts'
  - >-
    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
  - >-
    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
  - >-
    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
  - 'services-membership:modules/structure/services/tree/tree-edits.ts:moveUnit'
  - >-
    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
  - >-
    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
  - 'services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange'
  - >-
    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
  - >-
    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
  - 'services-membership:docs/migrations/structure/README.md#What could not be done as specified, and why'
  - 'services-membership:docs/migrations/structure/README.md#The move sequence as implemented'
  - >-
    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
  - >-
    services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts#MBR-ACC-0014 every move the
    tree refuses
  - >-
    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 › closes the open row at the day before
    the new one and keeps one open row per unit
  - >-
    services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts#MBR-ACC-0013 a backdated
    move and a same-day correction
  - >-
    services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
    units in the database › merge › moves the children under the survivor, marks the unit merged and
    lists what it left
  - >-
    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 › answers the descendants as of
    2027-06-30 equal to the tree recorded that day
  - >-
    services-membership:modules/structure/services/persistence/organizational-units.ts:updateOrganizationalUnit
  - membership.structure.manage
  - membership.application.access
  - >-
    services-membership:tests/unit/route-authorization.spec.ts#route authorization: every Membership
    operation names its permission › requires membership.structure.read of every structure read and
    membership.structure.manage of every change
  - 'services-membership:modules/configuration/aws/api/configuration.routes.ts:configurationModuleRoutes'
  - >-
    services-membership:tests/e2e/terms-api.e2e.spec.ts#the terms API, end to end in process › refuses a
    caller who holds no key in the tenant
coverage:
  rule: covered
  transition: 'gap: FND-MBR-STR-055'
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-MBR-STR-055'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  transition: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
  failure: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
unproved_rules: []
coverage_obligations:
  permissions:
    - key: membership.structure.manage
      scenario_ids:
        - ATS-MBR-STR-019-01
      local_scenario_ids:
        - ATS-MBR-STR-002-16
      coverage: partial
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
    - key: membership.application.access
      scenario_ids:
        - ATS-MBR-STR-019-04
      local_scenario_ids:
        - ATS-MBR-STR-002-16
      coverage: partial
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
  state_models: []
  related:
    - capability: CAP-MBR-STR-003
      scenario: ATS-MBR-STR-002-13
      provenance: proposed
    - capability: CAP-MBR-STR-005
      scenario: ATS-MBR-STR-002-14
      provenance: proposed
    - capability: CAP-MBR-STR-006
      scenario: ATS-MBR-STR-002-15
      provenance: proposed
  invariants:
    - id: INV-DOM-MBR-STR-001-01
      scenario_ids:
        - ATS-MBR-STR-002-01
      coverage: specified
      gap: null
      provenance: documented
      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
    - id: INV-DOM-MBR-STR-001-02
      scenario_ids:
        - ATS-MBR-STR-002-02
      coverage: specified
      gap: null
      provenance: documented
      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
    - id: INV-DOM-MBR-STR-001-03
      scenario_ids:
        - ATS-MBR-STR-002-09
      coverage: specified
      gap: null
      provenance: documented
      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
    - id: INV-DOM-MBR-STR-001-04
      scenario_ids:
        - ATS-MBR-STR-002-07
      coverage: specified
      gap: null
      provenance: documented
      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 › closes the open row at the day
          before the new one and keeps one open row per unit
    - id: INV-DOM-MBR-STR-001-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-001-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-001-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-01
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-02
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-03
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-04
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-08
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
    - id: INV-DOM-MBR-STR-002-09
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
  workflows:
    - id: WF-MBR-STR-001
      scenarios:
        - ATS-MBR-STR-002-02
        - ATS-MBR-STR-002-11
        - ATS-MBR-STR-002-13
        - ATS-MBR-STR-002-14
        - ATS-MBR-STR-002-15
      unproved_failure_and_concurrency: FND-MBR-STR-055
      provenance: unknown
      source: FND-MBR-STR-055
  workflow_interactions:
    - workflow: WF-MBR-STR-001
      capability: CAP-MBR-STR-006
      scenario_ids:
        - ATS-MBR-STR-002-15
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-002-15
      note: Cross-capability examples are cited from their owning specifications.
rule_coverage:
  - rule: BR-CAP-MBR-STR-002-01
    scenarios:
      - ATS-MBR-STR-002-01
      - ATS-MBR-STR-002-13
    gap: null
    provenance: documented
    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
      - >-
        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
      - >-
        services-membership:modules/structure/tests/integration/services/persistence/organizational-units.integration.spec.ts#organizational
        units in the database › merge › moves the children under the survivor, marks the unit merged and
        lists what it left
  - rule: BR-CAP-MBR-STR-002-02
    scenarios:
      - ATS-MBR-STR-002-02
    gap: null
    provenance: documented
    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
  - rule: BR-CAP-MBR-STR-002-03
    scenarios:
      - ATS-MBR-STR-002-03
    gap: null
    provenance: documented
    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
  - rule: BR-CAP-MBR-STR-002-04
    scenarios:
      - ATS-MBR-STR-002-04
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0014.acceptance.ts#MBR-ACC-0014 every move
        the tree refuses
  - rule: BR-CAP-MBR-STR-002-05
    scenarios:
      - ATS-MBR-STR-002-05
    gap: null
    provenance: documented
    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
  - rule: BR-CAP-MBR-STR-002-06
    scenarios:
      - ATS-MBR-STR-002-06
    gap: null
    provenance: documented
    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
  - rule: BR-CAP-MBR-STR-002-07
    scenarios:
      - ATS-MBR-STR-002-07
      - ATS-MBR-STR-002-14
    gap: null
    provenance: documented
    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 › closes the open row at the day
        before the new one and keeps one open row per unit
      - >-
        services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange
      - >-
        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 › answers the descendants as of
        2027-06-30 equal to the tree recorded that day
      - >-
        services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#still holds Atlanta among the Eastern
        Region’s descendants as of the period end
  - rule: BR-CAP-MBR-STR-002-08
    scenarios:
      - ATS-MBR-STR-002-08
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0013.acceptance.ts#MBR-ACC-0013 a
        backdated move and a same-day correction
  - rule: BR-CAP-MBR-STR-002-09
    scenarios:
      - ATS-MBR-STR-002-09
      - ATS-MBR-STR-002-15
    gap: null
    provenance: documented
    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
      - >-
        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
      - >-
        services-membership:modules/structure/services/persistence/organizational-units.ts:updateOrganizationalUnit
  - rule: BR-CAP-MBR-STR-002-11
    scenarios:
      - ATS-MBR-STR-002-10
    gap: null
    provenance: documented
    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
  - rule: BR-CAP-MBR-STR-002-12
    scenarios:
      - ATS-MBR-STR-002-12
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:docs/migrations/structure/README.md#What could not be done as specified, and
        why
  - rule: BR-CAP-MBR-STR-002-14
    scenarios:
      - ATS-MBR-STR-002-11
    gap: null
    provenance: documented
    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
narrative_sources:
  - section: Coverage
    provenance: documented
    source:
      - CAP-MBR-STR-002
      - FND-MBR-STR-055
  - section: Interactions With Related Capabilities
    provenance: proposed
    source:
      - ATS-MBR-STR-002-11
      - ATS-MBR-STR-002-13
      - ATS-MBR-STR-002-14
      - ATS-MBR-STR-002-15
---

# Restructure units while preserving dated placement: Capability Test Specification

## Coverage

The coverage map distinguishes specified examples from missing proof. A covered category means an expected case is specified; executable bindings and their run results remain separate evidence. Each required rule has a proof link or an explicit coverage finding in the structured rule ledger.

| Required Rule | Scenario or Finding |
|---|---|
| `BR-CAP-MBR-STR-002-01` | `ATS-MBR-STR-002-01`, `ATS-MBR-STR-002-13` |
| `BR-CAP-MBR-STR-002-02` | `ATS-MBR-STR-002-02` |
| `BR-CAP-MBR-STR-002-03` | `ATS-MBR-STR-002-03` |
| `BR-CAP-MBR-STR-002-04` | `ATS-MBR-STR-002-04` |
| `BR-CAP-MBR-STR-002-05` | `ATS-MBR-STR-002-05` |
| `BR-CAP-MBR-STR-002-06` | `ATS-MBR-STR-002-06` |
| `BR-CAP-MBR-STR-002-07` | `ATS-MBR-STR-002-07`, `ATS-MBR-STR-002-14` |
| `BR-CAP-MBR-STR-002-08` | `ATS-MBR-STR-002-08` |
| `BR-CAP-MBR-STR-002-09` | `ATS-MBR-STR-002-09`, `ATS-MBR-STR-002-15` |
| `BR-CAP-MBR-STR-002-11` | `ATS-MBR-STR-002-10` |
| `BR-CAP-MBR-STR-002-12` | `ATS-MBR-STR-002-12` |
| `BR-CAP-MBR-STR-002-14` | `ATS-MBR-STR-002-11` |

## Interactions With Related Capabilities

Merge children obey the same placement choices as moves. Prepare a source child whose kind disallows the survivor’s kind and attempt both a direct move and a source merge.

A move changes current reads while retaining dated reads. Read a branch, move its chapter on a later date and repeat both current and earlier-date reads.

Detail replacement cannot undo a move. Move a chapter, then replace its local contact details while submitting its former parent in the body.
