---
id: DOM-MBR-MEM-001
title: Membership
realises: CON-MBR-MEM-001
product_area: membership
module: memberships
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-MBR-MEM-memberships
readable_by:
  - SVC-MBR-MEM-applications
  - SVC-MBR-MEM-billing
  - SVC-MBR-MEM-commerce-participant
  - SVC-MBR-MEM-snapshots
  - SVC-MBR-MEM-events
  - SVC-MBR-MEM-history
lifecycle: STATE-MBR-MEM-001
custom_fields: declared
identity: >-
  Creation assigns a stable record identifier. Principal holdings are
  distinguished by program, owner and identity unit; organizational children by
  parent and person. The program selects the numbering source, and a new
  purchase remains unnumbered until fulfilment.
attributes:
  - name: owner
    kind: reference
    required: true
    computed: false
    notes: >-
      An owner is either a CRM person or organization. The current staff API
      provides no owner transfer.
  - name: program
    kind: reference
    required: true
    computed: false
    notes: >-
      The referenced program supplies the rules for numbering, grace,
      continuity, exclusivity and unit identity.
  - name: identity unit
    kind: reference
    required: false
    computed: false
    notes: >-
      A program with unit-based identity requires a unit of its configured
      identity type.
  - name: origin
    kind: enum
    required: true
    computed: false
    notes: >-
      The origin distinguishes principal holdings from flow-down coverage and
      assigned seats. Organization owners hold principal memberships.
  - name: parent membership
    kind: reference
    required: false
    computed: false
    notes: >-
      Derived holdings reference their organizational parent; principal holdings
      have no parent.
  - name: membership number
    kind: number
    required: false
    computed: true
    notes: >-
      The number is positive and comes from the configured workspace sequence,
      program sequence or CRM owner number. An initial reservation receives it
      at fulfilment.
  - name: original join date
    kind: date
    required: false
    computed: true
    notes: >-
      The date records the earliest join, together with whether it came from the
      first term, staff or an import.
  - name: continuous since
    kind: date
    required: false
    computed: true
    notes: >-
      This cache identifies the beginning of the current continuity episode
      under the program’s permitted gap.
  - name: current term and stored status
    kind: computed
    required: true
    computed: true
    notes: >-
      The current-term reference and standing are cached for a stated business
      date. Term writes and scheduled sweeps refresh them; rights use dated
      computation.
  - name: suspension
    kind: computed
    required: false
    computed: false
    notes: >-
      Suspension records an instant and an optional reason. A reason cannot
      remain when suspension is absent.
  - name: member settings
    kind: collection
    required: true
    computed: false
    notes: >-
      The record stores renewal and drop preferences, directory exclusion,
      automatic renewal and its payment-method reference. Payment credentials
      remain with Accounting.
  - name: Commerce fence
    kind: enum
    required: false
    computed: false
    notes: >-
      Cancellation and amendment fences record the pending Commerce document.
      The declared renewal fence has no writer in the current implementation.
  - name: migration quality
    kind: enum
    required: false
    computed: false
    notes: >-
      Imports can classify the completeness of membership history as exact,
      partial or gaps.
  - name: lifecycle events
    kind: collection
    required: true
    computed: false
    notes: >-
      Each lifecycle entry separates the effective date from recording time and
      includes attribution, relevant term and order references, and bounded
      details.
  - name: custom field values
    kind: collection
    required: false
    computed: false
    notes: >-
      Membership custom values have storage, versions and export support, but
      staff and import operations do not populate them.
  - name: version
    kind: number
    required: true
    computed: true
    notes: >-
      Saves advance the version; suspension and restoration must submit the
      version the caller read.
relationships:
  - to: DOM-MBR-MEM-002
    cardinality: one-to-many
    ownership: Terms are dependent records and cascade when their membership is removed.
    cross_service: false
  - to: DOM-MBR-MEM-001
    cardinality: one-to-many
    ownership: >-
      An organizational principal is the parent of its flow-down and assigned
      holdings; those children mirror its periods.
    cross_service: false
  - to: DOM-MBR-MEM-003
    cardinality: one-to-many
    ownership: >-
      Term-owned affiliations also identify the membership for visibility and
      roster queries.
    cross_service: false
  - to: DOM-MBR-MEM-004
    cardinality: one-to-many
    ownership: >-
      An application maintains a reference to the holding it concerns, without
      owning that holding.
    cross_service: false
  - to: DOM-MBR-MEM-006
    cardinality: one-to-many
    ownership: >-
      Revisions and reporting snapshots retain evidence about the holding under
      the history concept.
    cross_service: false
invariants:
  - id: INV-DOM-MBR-MEM-001-01
    statement: >-
      For principal holdings, the database allows one identity per program,
      owner and identity unit, treating an absent unit as an equal identity
      value.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/db/memberships.table.ts:memberships_principal_identity_uidx;
      modules/memberships/services/operations/staff-operations.ts:createMembership
  - id: INV-DOM-MBR-MEM-001-02
    statement: Each parent and person pair can identify only one derived holding.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/db/memberships.table.ts:memberships_granted_identity_uidx
  - id: INV-DOM-MBR-MEM-001-03
    statement: >-
      Owner kind and owner reference must identify one person or one
      organization, never both.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/db/memberships.table.ts:memberships_owner_check
  - id: INV-DOM-MBR-MEM-001-04
    statement: >-
      A parent reference is absent for principal holdings and required for every
      derived origin.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/db/memberships.table.ts:memberships_granted_by_check
  - id: INV-DOM-MBR-MEM-001-05
    statement: Organization-owned holdings must use the principal origin.
    enforced_at:
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/db/memberships.table.ts:memberships_organization_principal_check
  - id: INV-DOM-MBR-MEM-001-06
    statement: >-
      Creation requires an identity unit precisely for unit-bound programs and
      checks that unit’s configured type.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/operations/staff-operations.ts:createMembership,
      requireIdentityUnit
  - id: INV-DOM-MBR-MEM-001-07
    statement: >-
      Join date and its source accompany each other; suspension reasons require
      suspension, and a stored payment method requires automatic renewal.
    enforced_at:
      - database
    provenance: observed
    source: 'services-membership:modules/terms/db/holding-columns.ts:holdingChecks'
  - id: INV-DOM-MBR-MEM-001-08
    statement: >-
      Term writes refresh cached standing within their transaction, and the
      database restricts that value to the engine’s standing vocabulary.
    enforced_at:
      - service
      - database
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/operations/term-writes.ts:finishMembershipWrite;
      modules/terms/db/holding-columns.ts:holdingChecks
  - id: INV-DOM-MBR-MEM-001-09
    statement: >-
      An outstanding suspension makes standing suspended for every requested
      date regardless of term coverage.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-membership:tests/acceptance/memberships/MBR-ACC-0174.acceptance.ts#MBR-ACC-0174:
      A membership is suspended over its terms and restored to whatever the
      terms say
  - id: INV-DOM-MBR-MEM-001-10
    statement: >-
      Staff cannot directly add terms to, suspend or restore organizational
      child holdings; those changes come through the parent.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-membership:modules/memberships/services/operations/staff-operations.ts:refuseChild
  - id: INV-DOM-MBR-MEM-001-11
    statement: >-
      Commerce’s owner functions establish the fence, and the completing
      cancellation clears its cancellation fence.
    enforced_at:
      - database
      - service
    provenance: observed
    source: >-
      services-membership:db/migrations/20261018044117_commerce_participant.sql:commerce_set_fence,
      commerce_claim_cancellation;
      modules/commerce-participant/services/cancellation/cancellation.ts:cancel
  - id: INV-DOM-MBR-MEM-001-12
    statement: >-
      Commit-time history requires accepted attribution for membership changes
      and rejects a write that cannot supply it.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-history.integration.spec.ts#refuses
      at commit a write that names no source and no actor
physical:
  - membership.memberships
  - membership.memberships_versions
  - membership.membership_lifecycle_events
  - membership.membership_custom_field_values
  - membership.membership_custom_field_values_versions
  - membership.status_sweeps
  - membership.scope_settings
  - membership.roster_export_jobs
  - membership.roster_export_job_steps
  - membership.roster_export_job_events
  - membership.roster_export_rows
  - membership.idempotency_keys
  - membership.commerce_enforcements
evidence:
  - >-
    services-membership at 5994c270:
    modules/memberships/db/memberships.table.ts, memberships-versions.table.ts,
    membership-lifecycle-events.table.ts,
    membership-custom-field-values*.table.ts, membership-vocabularies.ts;
    modules/terms/db/holding-columns.ts, lifecycle-event-columns.ts;
    services/operations/staff-operations.ts, term-writes.ts;
    services/membership-numbers.ts, membership-caches.ts;
    metadata/memberships.object.ts;
    db/migrations/20261018044117_commerce_participant.sql; AGENTS.md
    (R2-ARCH-001).
  - >-
    rhythm-v2-docs: specs/membership/memberships/planning (CON-MBR-MEM-001,
    CAP-MBR-MEM-001 to 006, 015 to 017)
---

# Membership

## What It Models

The membership record gives a person or organization one enduring holding in a program. Renewals add periods to that record, preserving the membership number and join history. The record combines lasting identity, member preferences and operational caches.

## Identity and Attributes

The program determines whether identity includes a chapter or another unit and which source assigns the membership number. Flow-down and assigned holdings also identify an organizational parent. A first purchase can create an unnumbered record while payment is pending. Suspension withholds rights independently of dates; a Commerce fence prevents competing purchase work without itself changing dated standing.

## Relationships

Terms describe the holding’s coverage. Affiliations attach units to those terms, while applications and history reference the holding for their own work. People, organizations, programs and units remain records owned by neighbouring modules.

## Invariants

Database constraints protect principal and derived identities and the consistency of owner and hold fields. Service checks validate identity binding and restrict direct staff work to principal holdings. Commit-time history validates attribution before the change becomes durable.

## How It Is Stored and Shared

The memberships service owns the holding, its versions, lifecycle entries and custom values. Other service modules in the same macroservice read those records for checkout, decisions, billing and reporting. Metadata enables the shared custom-field capability, although the available write operations do not accept custom values.

## Open Design Questions

Product still needs an owner-transfer and CRM-merge policy and usable write paths for member preferences, join-date changes and custom values. Existing storage does not establish those user operations.
