---
id: WF-MBR-STR-001
title: Place a new unit or move a branch in the tree
trigger: An administrator adds a unit or moves an existing branch to another permitted parent.
product_area: membership
module: structure
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
inference: []
implements:
  - CAP-MBR-STR-002
  - CAP-MBR-STR-006
moves:
  - STATE-MBR-STR-001
crosses:
  - SVC-MBR-STR-structure
idempotency: >-
  There is no request repeat key. Duplicate creation fails on identity or code, and repeating a move fails
  when the requested parent is already current; neither returns a saved copy of the earlier response.
concurrency: >-
  Creation, movement, merging, closure and deletion share a transaction tree lock for the workspace. Competing
  moves validate against the preceding committed result. Separate workspaces use separate logical lock keys;
  move versions are checked only if supplied.
hops:
  - 'n': 1
    actor_or_service: Structure API
    action: >-
      Authorize Membership access and structure management, then validate the request; API moves require an
      explanation and valid calendar dates.
    anchor: 'services-membership:modules/structure/aws/api/structure.routes.ts:structureRoutes'
    seam: sync
    on_failure: Reject the request before mutation and report validation or authorization failure.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: the caller; API logs
    provenance: test
    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
  - 'n': 2
    actor_or_service: Structure service
    action: Begin the edit transaction and acquire the workspace tree lock.
    anchor: 'services-membership:modules/structure/services/persistence/organizational-units.ts:lockUnitTree'
    seam: sync
    on_failure: Wait behind an existing edit; a lock or database error aborts the request.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: database lock waits
    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
  - 'n': 3
    actor_or_service: Structure service
    action: >-
      For a move, lock the unit and check supplied version, active standing and a different parent. Validate
      kind activity, root permission or active allowed parent, absence of a cycle, branch depth and permitted
      effective date.
    anchor: 'services-membership:modules/structure/services/tree/tree-edits.ts:validatePlacement'
    seam: sync
    on_failure: Return the named placement refusal and roll back the transaction.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: the caller
    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
  - 'n': 4
    actor_or_service: Structure service
    action: >-
      Insert the new unit and custom values or update the moved parent; declared database checks and version
      capture participate in the write.
    anchor: 'services-membership:modules/structure/services/persistence/organizational-units.ts:setUnitParent'
    seam: sync
    on_failure: A duplicate identity or failed database constraint aborts the complete edit.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: the caller
    provenance: observed
    source: >-
      services-membership:modules/structure/services/persistence/organizational-units.ts:insertOrganizationalUnit
  - 'n': 5
    actor_or_service: Structure service
    action: >-
      Record effective parentage: close the previous period before the new first day, or rewrite the open
      period for a same-day correction, retaining the previous value in versions.
    anchor: 'services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange'
    seam: sync
    on_failure: Invalid earlier dates or persistence failure roll back the edit.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: parent history and its versions
    provenance: observed
    source: 'services-membership:modules/structure/services/persistence/organizational-units.ts:recordParentChange'
  - 'n': 6
    actor_or_service: Structure service
    action: >-
      Recompute whole-workspace ancestry using difference writes, update changed depths and reject any final
      depth of 32 or more.
    anchor: 'services-membership:modules/structure/services/persistence/organizational-units.ts:rebuildUnitClosure'
    seam: sync
    on_failure: A failed rebuild or depth refusal rolls back parent and history changes too.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: the caller
    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
  - 'n': 7
    actor_or_service: Structure service
    action: 'For movement, persist the reparent business event inside the transaction and then commit.'
    anchor: 'services-membership:modules/events/services/publishing/publish-business-event.ts:publishBusinessEvent'
    seam: sync
    on_failure: Payload or persistence failure prevents the edit from committing.
    left_inconsistent: No partial Membership write survives the failed transaction.
    observer: the published-events table
    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
  - 'n': 8
    actor_or_service: Structure service
    action: 'After commit, call creation or movement telemetry and return the unit and service rebuild summary.'
    anchor: 'services-membership:modules/structure/services/tree/tree-edits.ts:moveUnit'
    seam: sync
    on_failure: >-
      The call has no local catch; telemetry exception behavior is unknown and a failure cannot reverse the
      committed edit.
    left_inconsistent: The edit remains committed even if the caller does not receive a successful response.
    observer: execution telemetry
    provenance: unknown
    source: FND-MBR-STR-051
  - 'n': 9
    actor_or_service: Membership change-events dispatcher
    action: >-
      After commit, dispatch the published event through the registry. No handler acts on reparenting; a
      deployment with no handlers marks the stream history-only.
    anchor: 'services-membership:aws/membership.change-events.ts:publishedEventStream'
    seam: event
    on_failure: Retry bounds and dead-letter handling are not established by the permitted registry source.
    left_inconsistent: >-
      A future subscriber may learn of an already committed change late; current reparent events have no
      reacting subscriber.
    observer: change-events queue and dead-letter queue
    provenance: unknown
    source: FND-MBR-STR-051
branches:
  - Creation omits business-event publication and opens the initial parent period.
  - A move to a parentless position requires a root-enabled kind.
waits:
  - The transaction waits for another tree edit holding the workspace lock.
evidence:
  - >-
    services-membership at 5994c270: modules/structure/services/tree/tree-edits.ts (createUnit, moveUnit,
    validatePlacement, rebuildTree); services/persistence/organizational-units.ts; modules/events;
    aws/membership.change-events.ts; docs/migrations/structure/README.md (the move sequence; measured reparent
    time).
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-002, TC-DAT-003, 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-LOG-005, 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-PRV-001, TC-PRV-002, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004]
---

# Place a new unit or move a branch in the tree

## What It Does

Adding and moving units use one validated placement transaction. The tree lock protects validation against competing structural edits, and parentage, ancestry, depths and move publication commit together. Creation returns the unit through the HTTP route; the service itself also computes a rebuild summary.

## The Flow

```mermaid
sequenceDiagram
  actor Staff
  participant Structure
  participant Database
  Staff->>Structure: add unit or move branch
  Structure->>Database: begin and lock workspace tree
  Structure->>Database: validate current placement and dates
  Structure->>Database: write unit, parent periods and rebuilt ancestry
  Structure->>Database: record reparent event for a move
  Structure->>Database: commit
  Structure-->>Staff: record and operation result
  Structure->>Structure: after-commit telemetry
```

## When Something Fails

A pre-commit refusal leaves no partial placement. After-commit telemetry cannot undo the edit, and its library failure behavior is not established here. Reparent events have no reacting subscriber at these sources; the registry alone does not certify delivery retries.

## Open Design Questions

Future scheduling and repeat keys require product decisions. Same-day replacement retains the previous parent only in versions, which can weaken deletion checks that consult current parent-history rows.
