---
id: CAP-MBR-MEM-019
title: Report on membership now and month by month
feature: FEAT-MBR-MEM-07
product_area: membership
module: memberships
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - 'inventory:CAP-MBR-HIS-006'
  - 'inventory:CAP-MBR-HIS-007'
  - 'inventory:CAP-MBR-HIS-008'
  - 'inventory:CAP-MBR-HIS-009'
  - 'inventory:CAP-MBR-HIS-010'
  - 'inventory:CAP-MBR-MEM-019'
  - 'inventory:CAP-MBR-DAT-005'
  - 'inventory:CAP-MBR-DAT-006'
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-membership:README.md#2026-10-18 — Final acceptance'
  - 'services-membership:docs/migrations/history/README.md'
  - 'services-membership:docs/migrations/query-registration/README.md'
purpose: >-
  Analysts can query memberships, terms, lifecycle events and affiliations as they stand, and a frozen monthly
  snapshot of membership that reproduces the same numbers whenever it is read.
outcomes:
  - Today's membership can be counted and sliced in the query layer.
  - Each month's membership report is reproducible and auditable.
  - The association chooses which member characteristics each month freezes.
concepts:
  - CON-MBR-MEM-006
  - CON-MBR-MEM-001
actors:
  - ACT-MBR-008
  - ACT-MBR-006
  - ACT-MBR-012
surfaces:
  - console
  - api
  - job
permissions:
  - key: membership.memberships.query
    where_checked: service
    scope: tenant
  - key: membership.membership-terms.query
    where_checked: service
    scope: tenant
  - key: membership.lifecycle-events.query
    where_checked: service
    scope: tenant
  - key: membership.term-affiliations.query
    where_checked: service
    scope: tenant
  - key: membership.membership-snapshot-members.query
    where_checked: service
    scope: tenant
  - key: membership.membership-snapshot-affiliations.query
    where_checked: service
    scope: tenant
  - key: membership.membership-snapshot-dimensions.query
    where_checked: service
    scope: tenant
  - key: membership.membership-snapshot-counts.query
    where_checked: service
    scope: tenant
  - key: membership.configuration.read
    where_checked: api
    scope: tenant
  - key: membership.configuration.manage
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-MBR-MEM-019-01
    statement: >-
      The query layer offers memberships (with today's status, current type, episode count, primary unit of each type
      and every unit with its ancestors), terms, lifecycle events and term affiliations, each behind its own key.
    kind: permission
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers the
      query layer’s picks from memberships_for_queries: status today, current type, episode count
    origin: none
  - id: BR-CAP-MBR-MEM-019-02
    statement: >-
      Each workspace has a monthly snapshot of every membership as of the month's end, as known at the month's end in the
      workspace's time zone, so nothing recorded later appears in it.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/snapshot-pipeline.integration.spec.ts#captures nothing
      recorded after the month’s end: the membership entered in October is not in September
    origin: none
  - id: BR-CAP-MBR-MEM-019-03
    statement: >-
      Each member row is described by the term covering the month's end, else the latest before it, else the first after
      it, and each of its affiliations is filed with its unit and every ancestor the unit had at the month's end, nearest
      first.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#describes each
      member row by its term: the covering one, else the latest before, else the first after
    origin: none
  - id: BR-CAP-MBR-MEM-019-04
    statement: >-
      Each month's counts group the member rows by type, package, status, grace and origin, with how many joined,
      renewed, lapsed and were reinstated in the month.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#counts the member
      rows by type, package, status, grace and origin
    origin: none
  - id: BR-CAP-MBR-MEM-019-05
    statement: >-
      A month is published only after its checks pass (the counts equal the grouped rows, every type exists, the rows
      equal an independent count, every affiliation has its member); a failing run publishes nothing and the current
      version stays current.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#fails the run on
      the counts check, publishes nothing, and leaves the current version current
    origin: none
  - id: BR-CAP-MBR-MEM-019-06
    statement: >-
      Rerunning a month as known at the original instant reproduces its rows exactly; rerunning as known later differs
      only by the memberships corrected meanwhile; each rerun is published as the next version and earlier versions stay
      readable.
    kind: invariant
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#writes identical
      member, affiliation and count rows with the original known_at
    origin: none
  - id: BR-CAP-MBR-MEM-019-07
    statement: >-
      The workspace chooses, in order, the characteristics each month freezes beside its members: a contact field or a
      contact custom field (captured) or a membership custom field (reconstructed); a characteristic left out is
      deactivated, and a change applies from the next run.
    kind: policy
    enforced_at: [api, service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/s10-historical-dimension.e2e.spec.ts#configures rank__c as a captured dimension of
      the tenant’s snapshots
    origin: none
  - id: BR-CAP-MBR-MEM-019-08
    statement: >-
      A captured characteristic is read from the live contact for the first capture of the latest completed month,
      carried forward unchanged on every rerun, and not captured for a back-filled older month that has no version.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/unit/dimension-plan.spec.ts#captures nothing for an older month with no
      published version: today’s value is not the period end’s
    origin: none
  - id: BR-CAP-MBR-MEM-019-09
    statement: >-
      The snapshot datasets read only each month's current published version, each behind its own key, and ask for the
      month when the query runs.
    kind: permission
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#reads the current
      published version alone through the published view, in the tenant
    origin: none
  - id: BR-CAP-MBR-MEM-019-10
    statement: >-
      A month restated after a restructuring still files units under the tree as it was at the month's end.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#still files Atlanta under the Eastern Region in a
      September restated after the move: the period end’s tree
    origin: none
  - id: BR-CAP-MBR-MEM-019-11
    statement: An empty or blank value in a captured contact field does not stop the month from publishing.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-061
    origin: none
  - id: BR-CAP-MBR-MEM-019-12
    statement: >-
      The first months after a migration hold the migrated members, and a month's movements reconcile its opening and
      closing counts.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-062
    origin: none
  - id: BR-CAP-MBR-MEM-019-13
    statement: >-
      A workspace's snapshot rows are removed only while the workspace is being deleted, and in batches.
    kind: invariant
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#refuses to purge the
      tenant while Platform holds it active, before any row is removed (DEF-MBR-0010)
    origin: 'DEF-MBR-0010 repair (services-membership:docs/migrations/acceptance-repairs/README.md)'
scenarios:
  - id: SC-CAP-MBR-MEM-019-01
    given: September is published; Lee's October start is later corrected to 1 September and Jo's rank changes in CRM.
    when: September is rerun as known originally, and as known now.
    then: The first rerun is identical; the second differs by Lee alone; both keep Jo's rank as captured.
    provenance: test
    source: >-
      services-membership:modules/snapshots/tests/integration/membership-snapshot.integration.spec.ts#keeps the captured
      contact dimension of the version it supersedes, though the contact changed since
  - id: SC-CAP-MBR-MEM-019-02
    given: Atlanta was under the Eastern Region at the end of September and moved to the Pacific Zone in October.
    when: September is read or restated.
    then: Atlanta's members are counted under the Eastern Region for September.
    provenance: test
    source: 'services-membership:tests/e2e/s09-restructuring.e2e.spec.ts#files Atlanta under the Eastern Region in September’s snapshot, taken before the move'
edge_cases: []
constraints:
  - A workspace configures at most 25 snapshot characteristics.
  - One run per month per workspace runs at a time.
depends_on:
  - CAP-MBR-MEM-018
related:
  - CAP-MBR-MEM-005
journeys:
  - JNY-MBR-MEM-006
open_questions: []
evidence:
  - repository: services-membership
    source_sha: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
    paths:
      - modules/snapshots
      - modules/memberships/queries
      - modules/memberships/metadata
      - aws/membership.metadata.ts
      - modules/snapshots/db/membership-snapshot-members.table.ts
      - modules/snapshots/db/membership-snapshot-affiliations.table.ts
      - modules/snapshots/db/membership-snapshot-dimensions.table.ts
      - modules/snapshots/db/membership-snapshot-counts.table.ts
      - modules/snapshots/db/snapshot-dimension-settings.table.ts
      - modules/snapshots/db/snapshot-run-jobs.table.ts
      - modules/snapshots/db/snapshot-run-job-steps.table.ts
      - modules/snapshots/db/snapshot-run-job-events.table.ts
      - tests/e2e/s09-restructuring.e2e.spec.ts
      - tests/e2e/s10-historical-dimension.e2e.spec.ts
      - tests/acceptance/history
      - docs/migrations/history/README.md
      - docs/migrations/query-registration/README.md
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/membership/reports/defects/history.md
      - acceptance/membership/reports/defects/data-operations.md
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-PRV-001, TC-PRV-002, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Report on membership now and month by month

## Purpose

Associations count their members constantly: for boards, for dues, and for trend reports month after month. Today's numbers come from the live data. A month's numbers must never shift because someone corrected a record later, unless the report is deliberately restated.

## How It Works

Analysts query four live datasets: memberships, terms, lifecycle events and term affiliations, each behind its own permission. The memberships dataset carries today's status, the current type, the number of continuity episodes and the units each member belongs to. Once each month ends, Rhythm takes a snapshot of every membership as it stood at the month's end, as known at that moment in the workspace's own time zone. Each member is described by the term covering the month's end, with its chapter ties placed in the tree as it was then. The month's counts are grouped by type, package, status, grace and origin, with the month's joins, renewals, lapses and reinstatements. A snapshot is checked before it is published, and a month that fails publishes nothing. Rerunning a month reproduces it exactly unless the rerun asks for a later "as known" moment. The workspace chooses which characteristics, such as a member's rank, each month freezes beside its members. Reports read only each month's current published version. The product should keep blank contact values from failing a month, and should make the first months after a migration hold the migrated members. Today neither holds.

## Relationships

- Depends on `CAP-MBR-MEM-018` because snapshots read the same recorded versions as the past-date reads.
- Depends on Platform's queries and snapshots (`MOD-PFM-QRY`) for the query layer, the snapshot ledger and run scheduling.
