---
id: DOM-MBR-STR-003
title: Membership Program
realises: CON-MBR-STR-003
product_area: membership
module: structure
revision: 1
status: draft
design_intent: proposed
implementation_evidence: partially-observed
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-MBR-STR-configuration
readable_by:
  - SVC-MBR-STR-terms
  - SVC-PFM-TNL-membership-tenant-lifecycle
lifecycle: STATE-MBR-STR-006
custom_fields: none
identity: >-
  Programs and membership types use permanent readable identifiers of at most 63 characters within a
  workspace. Staff supply them or derive them from names. Requirements are keyed by program and unit kind;
  flow-down selections use the type together with the referenced CRM role or organization type.
attributes:
  - name: name and description
    kind: text
    required: true
    computed: false
    notes: 'Names contain 1–200 characters and optional descriptions up to 4,000.'
  - name: eligible owners
    kind: enum
    required: true
    computed: false
    notes: 'A program admits contacts, organizations or either owner category.'
  - name: unit binding
    kind: enum
    required: true
    computed: false
    notes: >-
      The binding is none, affiliation or identity; an identifying kind is required exactly for identity
      binding.
  - name: identifying unit kind and owning unit
    kind: reference
    required: false
    computed: false
    notes: >-
      Both optional references must resolve within the workspace; the owning unit supplies the program's
      administrative context.
  - name: affiliation requirements
    kind: collection
    required: false
    computed: false
    notes: >-
      Requirements hold per-kind counts and primary choices. Portal and suggestion settings are stored but
      unused, and replacing requirements submits the whole list at the program version.
  - name: exclusivity group
    kind: text
    required: false
    computed: false
    notes: >-
      Membership creation and purchase preparation check overlapping holdings across programs sharing an
      exclusivity group.
  - name: numbering scope
    kind: enum
    required: true
    computed: false
    notes: 'Membership numbering uses a workspace sequence, a program sequence or the owner''s CRM number.'
  - name: grace and continuity allowances
    kind: number
    required: true
    computed: false
    notes: >-
      Grace and continuity are nonnegative whole-day allowances read from current configuration during
      computation.
  - name: sales context
    kind: reference
    required: false
    computed: false
    notes: >-
      Sales settings include an optional ordering-profile pointer, same-order pricing qualification and
      cancellation choices; Commerce owns settlement and amounts.
  - name: undelivered preferences
    kind: enum
    required: false
    computed: false
    notes: Grace-benefit and competing-membership preferences have storage but no operational reader.
  - name: membership types
    kind: collection
    required: true
    computed: false
    notes: >-
      Types classify a program's memberships, select benefits and optional grace overrides, and retain unused
      portal labels and descriptions.
  - name: organization flow-down
    kind: collection
    required: false
    computed: false
    notes: >-
      Flow-down can target another type in the same program and select up to 100 roles and 100 organization
      types. Selections are deduplicated without consulting CRM.
  - name: 'display order, availability, version and stamps'
    kind: computed
    required: true
    computed: false
    notes: >-
      Display order is stored, but lists use identifiers; ordinary saves change availability and advance the
      version.
relationships:
  - to: DOM-MBR-STR-001
    cardinality: Each program has zero or many required unit kinds and zero or one identifying kind
    ownership: >-
      Configuration owns program requirements and kind references; structure owns the referenced kind
      catalogue.
    cross_service: true
  - to: DOM-MBR-STR-002
    cardinality: many programs to zero or one owning unit
    ownership: The program stores and owns its optional administering-unit reference.
    cross_service: true
  - to: DOM-MBR-STR-004
    cardinality: Each program has many packages; each package uses one type of its own program
    ownership: >-
      Packages own their program and classification pointers; database references prevent moving a used type
      into another program.
    cross_service: false
  - to: DOM-MBR-STR-005
    cardinality: A program and type supplies grace and continuity inputs for each membership
    ownership: The engine consumes current allowances through domain hooks without taking ownership of program records.
    cross_service: true
invariants:
  - id: INV-DOM-MBR-STR-003-01
    statement: >-
      Identity binding requires an identifying kind, and any kind or owning unit referenced by a program
      belongs to its workspace.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses an identity-bound program without its identity unit type 400 naming identity_unit_type_id
  - id: INV-DOM-MBR-STR-003-02
    statement: >-
      Each membership type references one program, and a package reference prevents changing that type to
      another program.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses moving a type a package names to another program 400 naming program_id, and leaves it where it
      was
  - id: INV-DOM-MBR-STR-003-03
    statement: Enabled flow-down requires a target type that exists in the same program.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses flow-down enabled without a target type 400 naming flowdown_type_id, and writes no type
  - id: INV-DOM-MBR-STR-003-04
    statement: >-
      Only affiliation-bound programs accept nonempty requirement lists; each kind exists, accepts
      affiliations, appears once and has a maximum at least its minimum.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:replaceProgramUnitTypesOf;
      services-membership:modules/configuration/services/configuration/configuration-records.ts:unitTypeProblems
  - id: INV-DOM-MBR-STR-003-05
    statement: >-
      Requirement replacement cannot drop a kind used by a non-cancelled program term's affiliation whose end
      is absent or today or later.
    enforced_at:
      - service
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:replaceProgramUnitTypesOf
  - id: INV-DOM-MBR-STR-003-06
    statement: Stored grace and continuity values are bounded nonnegative whole numbers.
    enforced_at:
      - api
      - service
      - database
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses a grace period of 2^31 days 400 naming grace_days, beyond what the column stores
  - id: INV-DOM-MBR-STR-003-07
    statement: 'Programs, requirements and membership types have no declared custom-field capability.'
    enforced_at:
      - api
    provenance: documented
    source: 'services-membership:AGENTS.md#Custom Fields Are Opt-In Per Entity: R2-ARCH-001 (Andrew, 2026-10-19)'
physical:
  - programs
  - program_unit_types
  - membership_types
  - membership_type_flowdown_contact_roles
  - membership_type_flowdown_organization_types
evidence:
  - >-
    services-membership at 5994c270: modules/configuration/db/programs.table.ts, program-unit-types.table.ts,
    membership-types.table.ts, the two flow-down tables, configuration-vocabularies.ts;
    services/configuration/configuration-records.ts (programContent, typeContent, programResource,
    replaceProgramUnitTypesOf); services/persistence/membership-types.ts;
    tests/e2e/database-refusals.e2e.spec.ts.
  - >-
    rhythm-v2-docs: specs/membership/structure/planning (CAP-MBR-STR-009, 010, 011; FND-MBR-STR-013, 014, 015,
    031).
---

# Membership Program

## What It Models

A program defines a membership context, such as national or student membership. It sets eligible owners, structural binding, classification, numbering, grace, continuity and sales choices. Membership types are classifications within that context.

## Identity and Attributes

Readable identities let packages and imports reference programs and types consistently. The binding controls the structural context a membership carries. Types select benefits and can override grace; their flow-down choices describe how organization membership extends to people.

## Relationships

Programs refer to unit kinds and, optionally, an administering unit. Packages sell a classification from their own program. The memberships module consumes eligibility, exclusivity and numbering choices, while the engine receives live grace and continuity settings through hooks.

## Invariants

The structured rows separate service validation of requirement lists from database constraints on program and classification references. Changing a program's binding does not automatically remove its existing requirements or affiliations.

## How It Is Stored and Shared

Configuration owns programs, requirements, types and flow-down selections. Their submitted lists participate in the same save transaction, and history streams capture content revisions. Lifecycle exports expose program and type configuration; purchase preparation passes sales pointers to Commerce.

## Open Design Questions

Stored preferences need a defined product effect or a decision to keep them unavailable. Product should also settle binding changes with retained affiliations, numbering uniqueness across programs and the different effects of retirement on staff and Commerce consumers.
