---
id: WF-MBR-MEM-010
title: Carry affiliations through a unit merge
trigger: An administrator requests a unit merge into a survivor.
product_area: membership
module: memberships
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Andrew's acceptance decision 1 of 2026-10-18 (build the move now), recorded in
  services-membership docs/migrations/unit-merge/README.md
implementation_evidence: observed
inference: []
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-MBR-MEM-008
moves:
  - STATE-MBR-MEM-005
crosses:
  - SVC-MBR-MEM-memberships
  - SVC-MBR-MEM-events
idempotency: >-
  Structure marks the source merged, so repeating the merge is refused unless
  the request ledger returns its original response.
concurrency: >-
  Tree and source-unit locks order the merge; a concurrent affiliation addition
  waits and then checks the source’s merged state.
hops:
  - 'n': 1
    actor_or_service: Structure module's merge
    action: >-
      Under Structure’s merge locks, plan the dated treatment of affiliations
      referring to the source unit.
    anchor: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts:decideMergedTerm
    seam: sync
    on_failure: >-
      Report the affected holdings that prevent movement and leave the merge
      uncommitted.
    left_inconsistent: No partial local change remains.
    observer: Structure merge result
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts:decideMergedTerm
  - 'n': 2
    actor_or_service: Memberships service
    action: >-
      Check identity-bound holdings and reject owner identities that would
      collide in the survivor.
    anchor: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts:decideIdentityBound
    seam: sync
    on_failure: Reject the complete merge without committing its plan.
    left_inconsistent: No partial local change remains.
    observer: Structure merge result
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts:decideIdentityBound
  - 'n': 3
    actor_or_service: Memberships service
    action: >-
      Write transfers, future re-pointings, overlap endings or removals and
      identity changes within the merge transaction.
    anchor: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts:writeDecision
    seam: sync
    on_failure: >-
      An error aborts the merge transaction and every dependent membership
      change.
    left_inconsistent: No partial local change remains.
    observer: Merge result and lifecycle entries
    provenance: test
    source: >-
      services-membership:tests/e2e/s13-unit-merge.e2e.spec.ts#shows Alex Pat
      now, with Pat’s Atlanta affiliation in scope
  - 'n': 4
    actor_or_service: Memberships service
    action: >-
      Publish affiliation events for transferred spans; other merge branches
      currently publish no separate affiliation events.
    anchor: >-
      services-membership:modules/memberships/services/affiliations/affiliation-operations.ts:writeAffiliationTransfer
    seam: sync
    on_failure: An error aborts the transaction and reverses its writes.
    left_inconsistent: >-
      Merge deletion and re-pointing branches produce no separate affiliation
      notification.
    observer: Published business-event log
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/affiliations/unit-merge.ts
      (writeAffiliationTransfer publishes; the other branches record lifecycle
      events only)
branches:
  - Earlier closed spans and connections on cancelled terms remain untouched.
  - >-
    Merge does not transfer dues payment, leaving source-unit products to a
    separate finance policy.
waits: []
evidence:
  - >-
    services-membership at 5994c270:
    modules/memberships/services/affiliations/unit-merge.ts;
    docs/migrations/unit-merge/README.md (Andrew's acceptance decision 1 of
    2026-10-18, "build the move now")
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, 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-004, 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]
---

# Carry affiliations through a unit merge

## What It Does

A Structure unit merge calls Membership to preserve member connections. The plan chooses transfers, future re-pointings, overlap closures and identity-unit moves before committing the merge.

## The Flow

```mermaid
flowchart TD
  H1["Under Structure’s merge locks, plan the dated treatment of affiliations referring to the source unit"]
  H2["Check identity-bound holdings and reject owner identities that would collide in the survivor"]
  H1 --> H2
  H3["Write transfers, future re-pointings, overlap endings or removals and identity changes within the merge transaction"]
  H2 --> H3
  H4["Publish affiliation events for transferred spans; other merge branches currently publish no separate affiliation events"]
  H3 --> H4
```

## When Something Fails

An invalid plan rejects the merge before writes, and later failures roll back all effects. Successful merges can lose primary designation. Only the transfer branch publishes separate affiliation events.

## Open Design Questions

Decide preservation of primary designation and whether every merge branch needs a partner event.
