---
id: ATS-MBR-MEM-005
scope: CAP-MBR-MEM-005
product_area: membership
module: memberships
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-MBR-MEM-005-01
    title: Read standing across expiry and return
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-01
    preconditions: Sam has coverage separated by a lapse and a returning term.
    steps:
      - do: >-
          Read Sam’s standing on dates during grace, during lapse and after his
          return.
        expect: >-
          Grace follows expiry, lapse follows the 60-day allowance, and return
          coverage is active.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
            Sam grace, lapsed, then active across the lapse and rejoin (scenario
            4)
          - >-
            services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
            Sam lapsed, not a member, on 2024-01-31 and active on 2024-12-31 in
            memberships_as_of
          - >-
            services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#answers
            grace the day after the term ended and lapsed once the 60 days are
            over
    expected: >-
      Grace follows expiry, lapse follows the 60-day allowance, and return
      coverage is active.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-020
      - TST-MBR-MEM-247
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
      Sam grace, lapsed, then active across the lapse and rejoin (scenario 4)
    category: boundary
    binding_evidence:
      - suite: TST-MBR-MEM-020
        source: >-
          services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
          Sam grace, lapsed, then active across the lapse and rejoin (scenario
          4)
      - suite: TST-MBR-MEM-020
        source: >-
          services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
          Sam lapsed, not a member, on 2024-01-31 and active on 2024-12-31 in
          memberships_as_of
      - suite: TST-MBR-MEM-247
        source: >-
          services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#answers
          grace the day after the term ended and lapsed once the 60 days are
          over
  - id: ATS-MBR-MEM-005-02
    title: Distinguish the full range of membership standing
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-02
    preconditions: >-
      Fixtures include empty, pending, future, current, non-benefit, grace,
      lapsed and suspended memberships.
    steps:
      - do: >-
          Inspect each fixture’s derived standing and compare the non-benefit
          type’s result.
        expect: >-
          The fixtures return their expected standing, including active without
          benefits for the non-benefit type.
        provenance: test
        source:
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0179.acceptance.ts#MBR-ACC-0179:
            Every status the engine can answer, from the terms and the type's or
            program's grace: none, pending, future, active, active without
            benefits, grace, lapsed and suspended, with the same answer from the
            SQL twins
    expected: >-
      The fixtures return their expected standing, including active without
      benefits for the non-benefit type.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-198
    provenance: test
    source: >-
      services-membership:tests/acceptance/memberships/MBR-ACC-0179.acceptance.ts#MBR-ACC-0179:
      Every status the engine can answer, from the terms and the type's or
      program's grace: none, pending, future, active, active without benefits,
      grace, lapsed and suspended, with the same answer from the SQL twins
    category: rule
    binding_evidence:
      - suite: TST-MBR-MEM-198
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0179.acceptance.ts#MBR-ACC-0179:
          Every status the engine can answer, from the terms and the type's or
          program's grace: none, pending, future, active, active without
          benefits, grace, lapsed and suspended, with the same answer from the
          SQL twins
  - id: ATS-MBR-MEM-005-03
    title: Read current truth despite a stale cache
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-03
    preconditions: >-
      The clock has crossed expiry while saved status still reflects an earlier
      date.
    steps:
      - do: >-
          Read the record before refreshing, then compare cached-status and
          current-status lists.
        expect: >-
          The record reflects expiry before a sweep; cached filtering can lag
          behind current filtering.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#answers
            the right status on each side of the expiry before any sweep runs
            (the resolver needs none)
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0178.acceptance.ts#MBR-ACC-0178:
            When the nightly sweep is missed the cached status lies but the
            record's status does not, lists filtered by the cache disagree with
            lists filtered by today's status, and only a catch-up repairs it
    expected: >-
      The record reflects expiry before a sweep; cached filtering can lag behind
      current filtering.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-029
      - TST-MBR-MEM-197
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#answers
      the right status on each side of the expiry before any sweep runs (the
      resolver needs none)
    category: invariant
    binding_evidence:
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#answers
          the right status on each side of the expiry before any sweep runs (the
          resolver needs none)
      - suite: TST-MBR-MEM-197
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0178.acceptance.ts#MBR-ACC-0178:
          When the nightly sweep is missed the cached status lies but the
          record's status does not, lists filtered by the cache disagree with
          lists filtered by today's status, and only a catch-up repairs it
  - id: ATS-MBR-MEM-005-04
    title: Refresh the memberships at a normal nightly boundary
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-04
    preconditions: >-
      The previous sweep ran yesterday and one membership reaches an expiry
      boundary tonight.
    steps:
      - do: >-
          Run the boundary sweep and inspect the examined count and refreshed
          status.
        expect: >-
          The boundary member is examined and refreshed to grace while
          mid-period memberships are omitted.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#examines
            only the memberships with a term at a boundary
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#moves
            the cache to grace at the expiry boundary and records
            expired_detected
    expected: >-
      The boundary member is examined and refreshed to grace while mid-period
      memberships are omitted.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-029
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#examines
      only the memberships with a term at a boundary
    category: transition
    binding_evidence:
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#examines
          only the memberships with a term at a boundary
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#moves
          the cache to grace at the expiry boundary and records expired_detected
  - id: ATS-MBR-MEM-005-05
    title: Record actual expiry and lapse dates once
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-05
    preconditions: >-
      A dated period crosses expiry and grace-end boundaries, including a
      delayed sweep.
    steps:
      - do: >-
          Sweep through expiry and repeat the day, then discover a lapse after
          its boundary.
        expect: >-
          Expiry retains the day after the term end; late lapse retains the day
          after grace, without duplicate detections.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
            lapsed_detected effective the day after grace ended, even when found
            late
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#moves
            the cache to grace at the expiry boundary and records
            expired_detected
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
            nothing twice when the same night is swept again
    expected: >-
      Expiry retains the day after the term end; late lapse retains the day
      after grace, without duplicate detections.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-029
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
      lapsed_detected effective the day after grace ended, even when found late
    category: transition
    binding_evidence:
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
          lapsed_detected effective the day after grace ended, even when found
          late
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#moves
          the cache to grace at the expiry boundary and records expired_detected
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
          nothing twice when the same night is swept again
  - id: ATS-MBR-MEM-005-06
    title: Catch up over the workspace after a missed run
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-06
    preconditions: The previous recorded run predates yesterday.
    steps:
      - do: Run the next sweep and inspect its mode and every membership examined.
        expect: Catch-up examines all workspace memberships.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#catches
            up after a missed night, recomputing every membership
    expected: Catch-up examines all workspace memberships.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-029
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#catches
      up after a missed night, recomputing every membership
    category: failure
    binding_evidence:
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#catches
          up after a missed night, recomputing every membership
  - id: ATS-MBR-MEM-005-07
    title: Repeat a sweep without duplicate detections
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-07
    preconditions: An administrator has run today’s sweep once.
    steps:
      - do: Repeat today’s on-demand sweep and inspect detection counts.
        expect: The repeat adds no new detections.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
            nothing twice when the same night is swept again
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0177.acceptance.ts#MBR-ACC-0177:
            An administrator runs the status sweep by hand: the first run
            catches up over every membership, later runs look only at today's
            boundaries, nothing is detected twice, and a cache-only change
            leaves no visible history
    expected: The repeat adds no new detections.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-029
      - TST-MBR-MEM-196
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
      nothing twice when the same night is swept again
    category: invariant
    binding_evidence:
      - suite: TST-MBR-MEM-029
        source: >-
          services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
          nothing twice when the same night is swept again
      - suite: TST-MBR-MEM-196
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0177.acceptance.ts#MBR-ACC-0177:
          An administrator runs the status sweep by hand: the first run catches
          up over every membership, later runs look only at today's boundaries,
          nothing is detected twice, and a cache-only change leaves no visible
          history
  - id: ATS-MBR-MEM-005-08
    title: Inspect joining history and continuity episodes
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-09
    preconditions: Sam’s returning term follows a gap longer than the continuity allowance.
    steps:
      - do: >-
          Read the original join date and episode periods after the returning
          term.
        expect: >-
          The original join date is retained and two continuity episodes are
          returned.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
            Sam two continuity episodes (scenario 4)
          - >-
            services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#keeps
            the original join date and restarts continuity at the rejoin (a gap
            of 437 days is more than 60)
    expected: >-
      The original join date is retained and two continuity episodes are
      returned.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-020
      - TST-MBR-MEM-247
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
      Sam two continuity episodes (scenario 4)
    category: invariant
    binding_evidence:
      - suite: TST-MBR-MEM-020
        source: >-
          services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
          Sam two continuity episodes (scenario 4)
      - suite: TST-MBR-MEM-247
        source: >-
          services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#keeps
          the original join date and restarts continuity at the rejoin (a gap of
          437 days is more than 60)
  - id: ATS-MBR-MEM-005-09
    title: Filter and page membership lists without duplicate unit matches
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-10
    preconditions: >-
      A member belongs to multiple chapters under the same ancestor, with status
      and pagination fixtures.
    steps:
      - do: >-
          List through ancestor and chapter filters, compare current and saved
          status, and exercise paging and malformed filters.
        expect: >-
          Each matching membership appears once per result set; paging follows
          id order and invalid filters are rejected by name.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/affiliations.integration.spec.ts#lists
            a member of two chapters once under their zone and region, and under
            each chapter
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0185.acceptance.ts#MBR-ACC-0185:
            Lists page by id in creation order with an opaque cursor, cap the
            page at 500, refuse a malformed limit, cursor, status, flag or date
            by name, and never silently ignore a parameter
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0178.acceptance.ts#MBR-ACC-0178:
            When the nightly sweep is missed the cached status lies but the
            record's status does not, lists filtered by the cache disagree with
            lists filtered by today's status, and only a catch-up repairs it
    expected: >-
      Each matching membership appears once per result set; paging follows id
      order and invalid filters are rejected by name.
    negative: false
    data_needs: >-
      Dated memberships, program and type settings, and the controlled clock or
      listing fixtures relevant to this case.
    bound_to:
      - TST-MBR-MEM-024
      - TST-MBR-MEM-204
      - TST-MBR-MEM-197
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/affiliations.integration.spec.ts#lists
      a member of two chapters once under their zone and region, and under each
      chapter
    category: validation
    binding_evidence:
      - suite: TST-MBR-MEM-024
        source: >-
          services-membership:modules/memberships/tests/integration/services/affiliations.integration.spec.ts#lists
          a member of two chapters once under their zone and region, and under
          each chapter
      - suite: TST-MBR-MEM-204
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0185.acceptance.ts#MBR-ACC-0185:
          Lists page by id in creation order with an opaque cursor, cap the page
          at 500, refuse a malformed limit, cursor, status, flag or date by
          name, and never silently ignore a parameter
      - suite: TST-MBR-MEM-197
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0178.acceptance.ts#MBR-ACC-0178:
          When the nightly sweep is missed the cached status lies but the
          record's status does not, lists filtered by the cache disagree with
          lists filtered by today's status, and only a catch-up repairs it
  - id: ATS-MBR-MEM-005-10
    title: Apply grace scope on the resolved membership date
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-01
      - BR-CAP-MBR-MEM-015-05
    interacts_with:
      - CAP-MBR-MEM-015
    workflows: []
    relies_on:
      - ATS-MBR-MEM-005-01
      - ATS-MBR-MEM-015-05
    preconditions: An officer reaches a chapter whose member has just expired.
    steps:
      - do: >-
          Read and list the member with grace scope disabled and enabled, then
          move beyond the grace end.
        expect: >-
          Enabled grace includes the member during grace; the member leaves that
          reach after grace ends.
        provenance: proposed
        source: &ref_0
          - >-
            services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
            Sam grace, lapsed, then active across the lapse and rejoin (scenario
            4)
          - >-
            services-membership:modules/memberships/tests/unit/unit-scope.spec.ts#leaves
            a member in grace out when the roster rule excludes grace
    expected: >-
      Enabled grace includes the member during grace; the member leaves that
      reach after grace ends.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_0
  - id: ATS-MBR-MEM-005-11
    title: Freeze resolved standing at month end
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-01
      - BR-CAP-MBR-MEM-019-02
    interacts_with:
      - CAP-MBR-MEM-019
    workflows: []
    relies_on:
      - ATS-MBR-MEM-005-01
      - ATS-MBR-MEM-019-02
    preconditions: A member expires near month end and the status cache is stale.
    steps:
      - do: >-
          Generate the monthly snapshot and compare its member row with the
          resolver at month end.
        expect: The row has the month-end standing rather than the stale cached value.
        provenance: proposed
        source: &ref_1
          - >-
            services-membership:modules/memberships/tests/integration/db/membership-functions.integration.spec.ts#answers
            Sam grace, lapsed, then active across the lapse and rejoin (scenario
            4)
          - >-
            services-membership:modules/snapshots/tests/integration/snapshot-pipeline.integration.spec.ts#plans
            September’s first version for the tenant, once
    expected: The row has the month-end standing rather than the stale cached value.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_1
  - id: ATS-MBR-MEM-005-12
    title: Publish one lapse event through repeated sweeps
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-005-05
      - BR-CAP-MBR-MEM-020-03
    interacts_with:
      - CAP-MBR-MEM-020
    workflows:
      - WF-MBR-MEM-008
    relies_on:
      - ATS-MBR-MEM-005-05
      - ATS-MBR-MEM-020-03
    preconditions: A term has passed its final grace day without renewed coverage.
    steps:
      - do: >-
          Run the status sweep after the lapse boundary, then repeat the same
          sweep.
        expect: >-
          The saved lapse detection and termination event retain the original
          last covered day and are not duplicated.
        provenance: proposed
        source: &ref_2
          - >-
            services-membership:modules/memberships/tests/integration/services/status-sweep.integration.spec.ts#records
            lapsed_detected effective the day after grace ended, even when found
            late
          - >-
            services-membership:modules/events/tests/integration/business-events.integration.spec.ts#states
            one membership.term.terminated for a staff termination, with the
            person’s contact_id and the last day of rights
    expected: >-
      The saved lapse detection and termination event retain the original last
      covered day and are not duplicated.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_2
evidence:
  - >-
    services-membership at 5994c270e8a37a30f848e99ba5b5ee6b87c08893: reviewed
    cache recomputation, record and list reads, SQL standing functions, schedule
    configuration, and the cited sweep, standing and list assertions.
  - >-
    rhythm-v2-docs:specs/membership/memberships/design/: domain objects, state
    models and workflows reviewed for coverage.
coverage:
  rule: 'gap: FND-MBR-MEM-087'
  transition: 'gap: FND-MBR-MEM-087'
  validation: covered
  permission: 'gap: FND-MBR-MEM-087'
  edge: 'gap: FND-MBR-MEM-087'
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-MBR-MEM-087'
  invariant: 'gap: FND-MBR-MEM-087'
  interaction: covered
coverage_notes:
  transition: >-
    The scenarios do not enumerate every applicable object transition; the
    finding preserves the remaining transition matrix.
  permission: >-
    The finding records named permissions without a complete positive and
    negative proof.
  invariant: >-
    Existing invariant examples do not cover every applicable domain-object
    constraint.
  interaction: >-
    Every related capability and each cross-capability workflow has an explicit
    composite scenario. Unbound composites remain proposed.
---

# Determine membership standing and refresh list status: Capability Test Specification

## Coverage

| Rule | Scenario or Finding |
|---|---|
| `BR-CAP-MBR-MEM-005-01` | `ATS-MBR-MEM-005-01`, `ATS-MBR-MEM-005-10`, `ATS-MBR-MEM-005-11` |
| `BR-CAP-MBR-MEM-005-02` | `ATS-MBR-MEM-005-02` |
| `BR-CAP-MBR-MEM-005-03` | `ATS-MBR-MEM-005-03` |
| `BR-CAP-MBR-MEM-005-04` | `ATS-MBR-MEM-005-04` |
| `BR-CAP-MBR-MEM-005-05` | `ATS-MBR-MEM-005-05`, `ATS-MBR-MEM-005-12` |
| `BR-CAP-MBR-MEM-005-06` | `ATS-MBR-MEM-005-06` |
| `BR-CAP-MBR-MEM-005-07` | `ATS-MBR-MEM-005-07` |
| `BR-CAP-MBR-MEM-005-09` | `ATS-MBR-MEM-005-08` |
| `BR-CAP-MBR-MEM-005-10` | `ATS-MBR-MEM-005-09` |
| `BR-CAP-MBR-MEM-005-11` | no scenario: `FND-MBR-MEM-013` |

## Interactions With Related Capabilities

The interaction cases test how a shared holding, period, chapter connection or recorded change affects related work. Each case names its related capabilities and workflows in frontmatter. A composite case remains proposed until executable assertions establish every part of its expected outcome.
