---
id: CAP-MBR-MEM-018
title: See how a membership changed and what it was on any date
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-001'
  - 'inventory:CAP-MBR-HIS-002'
  - 'inventory:CAP-MBR-HIS-003'
  - 'inventory:CAP-MBR-HIS-004'
  - 'inventory:CAP-MBR-HIS-005'
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'
purpose: >-
  Staff can see who changed a membership or a term, what changed and when, and can ask what a membership was on a
  past date as it was understood at any past moment.
outcomes:
  - Every change is attributable to a named person or process.
  - A past state can be reconstructed exactly as the association knew it at the time.
concepts:
  - CON-MBR-MEM-006
  - CON-MBR-MEM-001
actors:
  - ACT-MBR-007
  - ACT-MBR-008
surfaces:
  - console
  - api
permissions:
  - key: membership.memberships.history
    where_checked: api
    scope: tenant
  - key: membership.members.read
    where_checked: api
    scope: record
rules:
  - id: BR-CAP-MBR-MEM-018-01
    proved_by: [ATS-MBR-MEM-018-01, TST-MBR-MEM-018]
    statement: >-
      Every committed change to a membership, its terms, their affiliations and its custom field values is recorded at
      commit as one revision, naming the person by the name they had then and where the change came from; a change of
      the stored status alone, or a change undone in the same save, records nothing.
    kind: lifecycle
    enforced_at: [database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-history.integration.spec.ts#records nothing for a
      change of the cached status alone, nor for a change undone in the same save
    origin: none
  - id: BR-CAP-MBR-MEM-018-02
    proved_by: [ATS-MBR-MEM-018-02, TST-MBR-MEM-018]
    statement: >-
      A write that names neither its source nor its actor is refused at commit, together with the business write.
    kind: invariant
    enforced_at: [database]
    must_prove: true
    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, and the write with it (a program, which has no versions)
    origin: none
  - id: BR-CAP-MBR-MEM-018-03
    proved_by: [ATS-MBR-MEM-018-03, TST-MBR-MEM-018, TST-MBR-MEM-243]
    statement: >-
      A membership's history is a timeline, newest first, a page at a time, that can be narrowed by operation and by
      person; each revision opens section by section (membership, terms, affiliations, custom fields), and the people
      who changed it can be listed once each.
    kind: policy
    enforced_at: [api, service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-history.integration.spec.ts#filters the timeline
      by operation and by actor
    origin: none
  - id: BR-CAP-MBR-MEM-018-04
    proved_by: [ATS-MBR-MEM-018-04, TST-MBR-MEM-018]
    statement: A term has its own history too, recording the same changes as the membership's terms section.
    kind: policy
    enforced_at: [api, service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-history.integration.spec.ts#records the same
      correction on the term’s own history
    origin: none
  - id: BR-CAP-MBR-MEM-018-05
    proved_by: [ATS-MBR-MEM-018-05, ATS-MBR-MEM-018-11, TST-MBR-MEM-243]
    statement: >-
      Reading history needs the history key as well as the membership-read key, so past values can be withheld from
      someone who may read the membership as it stands.
    kind: permission
    enforced_at: [api]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/history-api.e2e.spec.ts#refuses the history to a reader of memberships who does not
      hold membership.memberships.history
    origin: 'services-crm:docs/record-history.md#Authorization (cited by services-membership:aws/membership.service.ts)'
  - id: BR-CAP-MBR-MEM-018-06
    proved_by: [ATS-MBR-MEM-018-06, TST-MBR-MEM-017]
    statement: >-
      A membership as of a date, as known at an instant, answers its term, type, package and status then, its
      affiliations with each unit's name, parent and ancestors then, its custom fields as recorded at the end of the
      date, its committee seats when Committees keeps their history (otherwise marked unavailable), and its contact as
      it is now, marked as captured.
    kind: calculation
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-as-of.integration.spec.ts#answers Jo’s chapters on
      the date with each unit’s name, parent and ancestors then
    origin: none
  - id: BR-CAP-MBR-MEM-018-07
    proved_by: [ATS-MBR-MEM-018-07, TST-MBR-MEM-017]
    statement: Asked without an instant, the past-date answer equals what the live read gives for that date.
    kind: invariant
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-as-of.integration.spec.ts#answers every FAA
      membership as the live read does, known now and as recorded now
    origin: none
  - id: BR-CAP-MBR-MEM-018-08
    proved_by: [ATS-MBR-MEM-003-12, ATS-MBR-MEM-006-18, ATS-MBR-MEM-018-08, ATS-MBR-MEM-018-09, ATS-MBR-MEM-018-10, ATS-MBR-MEM-018-12, TST-MBR-MEM-017, TST-MBR-MEM-161]
    statement: >-
      A backdated correction is visible only to a reader asking as known after it; one asking as known before it gets
      the earlier answer.
    kind: calculation
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-as-of.integration.spec.ts#answers the term as it
      was known before the correction, with a known_at before it
    origin: none
  - id: BR-CAP-MBR-MEM-018-09
    proved_by: []
    statement: >-
      Changes to programs, types, packages, status reasons and change policies are recorded as revisions of their
      records, and history labels them by what they are called today.
    kind: lifecycle
    enforced_at: [database]
    must_prove: false
    provenance: test
    source: >-
      services-membership:modules/history/tests/integration/membership-history.integration.spec.ts#records a change to
      the configuration as a revision of its record
    origin: none
  - id: BR-CAP-MBR-MEM-018-10
    proved_by: []
    statement: >-
      History shows every business fact a change touched, including a suspension's reason and a purchase's Commerce
      references, and attributes Commerce's changes consistently.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-058
    origin: none
  - id: BR-CAP-MBR-MEM-018-11
    proved_by: []
    statement: An "as known at" instant is honoured at the precision the history records.
    kind: calculation
    enforced_at: [api]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-059
    origin: none
  - id: BR-CAP-MBR-MEM-018-12
    proved_by: []
    statement: >-
      History is read by workspace-wide staff only; officers do not see their members' history, and a member's own
      history is the business log of their own read.
    kind: permission
    enforced_at: [service]
    must_prove: false
    provenance: documented
    source: 'services-membership:docs/migrations/scope/README.md#What differs from 06 §4, or could not be done as specified'
    origin: none
scenarios:
  - id: SC-CAP-MBR-MEM-018-01
    given: A registrar created a membership, corrected its term and suspended it.
    when: Staff open its history.
    then: Three revisions appear newest first, the correction under Terms and the suspension under Membership, each with its person.
    provenance: test
    source: >-
      services-membership:tests/e2e/history-api.e2e.spec.ts#files the correction under the Terms section and the
      suspension under the Membership section
  - id: SC-CAP-MBR-MEM-018-02
    given: Robin's start was corrected on 5 February from 1 February to 1 January.
    when: Staff ask what Robin held on 15 January as known on 1 February, and as known now.
    then: As known on 1 February Robin held nothing; as known now Robin held the corrected term.
    provenance: test
    source: 'services-membership:tests/acceptance/history/MBR-ACC-0539.acceptance.ts#MBR-ACC-0539'
edge_cases: []
constraints: []
depends_on:
  - CAP-MBR-MEM-003
related:
  - CAP-MBR-MEM-019
  - CAP-MBR-MEM-015
journeys:
  - JNY-MBR-MEM-006
open_questions:
  - OQ-MBR-MEM-004
evidence:
  - repository: services-membership
    source_sha: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
    paths:
      - modules/history/aws/api/history.routes.ts
      - modules/history/history/membership-history.config.ts
      - modules/history/history/term-history.config.ts
      - modules/history/services/persistence/record-revisions.ts
      - modules/history/services/reconstruction/membership-as-of.ts
      - modules/history/db/record-revisions.table.ts
      - modules/history/db/change-event-streams.ts
      - modules/memberships/db/membership-custom-field-values-versions.table.ts
      - modules/history/tests
      - tests/e2e/history-api.e2e.spec.ts
      - tests/acceptance/history
      - docs/migrations/history/README.md
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/membership/reports/defects/history.md
controls: [TC-AVL-002, TC-AVL-003, 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-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, 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]
exercised_by: [FAS-MBR-MEM-07-01, FAS-MBR-MEM-07-02, FAS-MBR-MEM-07-03, FAS-MBR-MEM-07-04, FAS-MBR-MEM-07-05]
suites: [TST-MBR-MEM-017, TST-MBR-MEM-018, TST-MBR-MEM-161, TST-MBR-MEM-243]
---

# See how a membership changed and what it was on any date

## Purpose

Associations are asked who changed a member's record and why, and what a member held on a past date. Auditors also ask what the association believed about that date at a given moment. Both must be answerable from Rhythm itself.

## How It Works

Every committed change to a membership, its terms, their chapter ties and its custom field values is recorded as it commits. Each record says what changed, who changed it, by the name they had then, and where the change came from. Nightly status refreshes record nothing, and a write that cannot say who made it is refused. Staff read a membership's timeline newest first, narrow it by person or operation, open one change section by section, and list who has changed the record. Each term has its own timeline too. Reading history needs its own permission beside reading the membership. A membership can also be reconstructed on any date, as it was known at any instant. The answer gives the term, type, package and status then, the chapter ties with their units as named and placed then, the custom fields, any committee seats, and the contact as it is now. A correction made later changes the answer only for a reader asking after the correction was made. Configuration changes are recorded too, but no screen or request reads them yet. The product should show every fact a change touched, such as a suspension's reason, and should honour an instant at full precision.

## Relationships

- Depends on `CAP-MBR-MEM-003` because corrections are what make "as known at" matter.
- Depends on the record history capability shared with CRM, and on Committees (`MOD-CMT-CMT`) for appointment history.
- Related to `CAP-MBR-MEM-019` because monthly snapshots are built from the same versions.
