---
id: WF-MBR-STR-006
title: Decide what the nightly status sweep recomputes and records
trigger: >-
  A daily workspace schedule or an on-demand request runs the Memberships status sweep using term-engine
  decisions.
product_area: membership
module: structure
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
inference: []
implements:
  - CAP-MBR-STR-016
moves:
  - STATE-MBR-STR-005
crosses:
  - SVC-MBR-STR-terms
idempotency: >-
  Cache writes apply only differences, and expiry and lapse detections are unique per term. Repeated sweeps
  avoid duplicate detections and publish a lapse termination only on its first recorded detection.
concurrency: >-
  Each holding is recomputed in a separate transaction under its row lock. Concurrent sweeps serialize only
  their per-holding work, not the complete run; unique detections prevent double recording.
hops:
  - 'n': 1
    actor_or_service: Memberships module schedule consumer
    action: >-
      Run scheduled work for the occurrence date converted to the workspace time zone, preserving the intended
      business date on late delivery.
    anchor: 'services-membership:modules/memberships/services/sweep/status-sweep-schedule.ts:statusSweepWork'
    seam: schedule
    on_failure: The scheduled-work call site does not establish framework retry or failure-recording behavior.
    left_inconsistent: Caches and detections can lag until successful work resumes.
    observer: schedule runs table
    provenance: unknown
    source: FND-MBR-STR-051
  - 'n': 2
    actor_or_service: Term engine
    action: >-
      Use the previous sweep date to select full catch-up or supply start, expiry and distinct-grace boundary
      dates.
    anchor: 'services-membership:modules/terms/services/sweep.ts:needsCatchUp'
    seam: sync
    on_failure: Invalid calculation inputs fail the call; the pure helper has no persistence to compensate.
    left_inconsistent: No new holding write has been made at this decision step.
    observer: none
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/sweep.spec.ts#the sweep’s catch-up rule › catches up after
      a missed night
  - 'n': 3
    actor_or_service: Memberships module sweep
    action: Select all memberships for catch-up or memberships whose terms match a boundary.
    anchor: 'services-membership:modules/memberships/services/sweep/status-sweep.ts:sweepTenant'
    seam: sync
    on_failure: Stop before processing and leave this run unrecorded.
    left_inconsistent: Existing cached results can remain stale until a later successful catch-up.
    observer: application logs
    provenance: observed
    source: 'services-membership:modules/memberships/services/sweep/status-sweep.ts:sweepTenant'
  - 'n': 4
    actor_or_service: Memberships module sweep with the term engine
    action: >-
      For each locked membership, recompute cache differences and expiry or lapse detections; uniquely persist
      each detection and publish termination on the first lapse.
    anchor: 'services-membership:modules/terms/services/sweep.ts:detectionsOf'
    seam: sync
    on_failure: >-
      Stop at the failing membership; its transaction rolls back while earlier membership transactions remain
      committed.
    left_inconsistent: The run can have mixed progress until replay or the next catch-up completes remaining work.
    observer: The application's log and the missing completed-sweep record.
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/sweep.spec.ts#the sweep’s detections › detects the lapse
      the day after grace ends, effective that day, with the expiry
  - 'n': 5
    actor_or_service: Memberships module sweep
    action: 'Record sweep date, mode, counts and trigger after all selected memberships finish.'
    anchor: 'services-membership:modules/memberships/services/persistence/status-sweeps.ts:recordSweep'
    seam: sync
    on_failure: A failed ledger write leaves no completed run record.
    left_inconsistent: 'Holding updates remain committed, and the next catch-up can repeat extra work.'
    observer: status sweeps table
    provenance: observed
    source: 'services-membership:modules/memberships/services/sweep/status-sweep.ts:sweepTenant'
  - 'n': 6
    actor_or_service: Membership change-events dispatcher
    action: >-
      Offer termination events through the configured remote handler. Committees registration defaults on at
      synthesis but can be disabled or absent without its function address.
    anchor: 'services-membership:aws/membership.change-events.ts:businessEventHandlersOf'
    seam: event
    on_failure: 'The registry does not certify retry limits, queue redelivery or receiver-side effects.'
    left_inconsistent: A receiver may react later or not be configured; the membership decisions are already committed.
    observer: change-events queue and dead-letter queue
    provenance: unknown
    source: FND-MBR-STR-051
branches:
  - Catch-up selects the whole workspace rather than boundary-only memberships.
  - Expiry during grace produces a detection but no termination business event.
waits: []
evidence:
  - >-
    services-membership at 5994c270: modules/terms/services/sweep.ts, holding-cache.ts, status.ts;
    modules/memberships/services/sweep/status-sweep.ts, status-sweep-schedule.ts, membership-caches.ts;
    aws/membership.change-events.ts.
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, 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-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Decide what the nightly status sweep recomputes and records

## What It Does

Memberships owns the sweep schedule and persistence; the term engine selects boundaries, decides catch-up and computes status and detections. Each holding commits independently, and the completed run is recorded afterward. First lapse publishes termination without changing the expired term's committed state.

## The Flow

```mermaid
sequenceDiagram
  participant Schedule
  participant Memberships
  participant Engine
  participant Database
  Schedule->>Memberships: occurrence business date
  Memberships->>Engine: previous run and configured grace
  Engine-->>Memberships: full selection or boundaries
  loop each selected holding
    Memberships->>Database: begin and lock holding
    Memberships->>Engine: term facts and current settings
    Engine-->>Memberships: cache and detection decisions
    Memberships->>Database: changes, unique detections, event; commit
  end
  Memberships->>Database: completed sweep ledger
```

## When Something Fails

A partial run retains completed holding transactions and records no completed sweep. A later successful catch-up revisits all holdings, although repeated failures can extend staleness beyond one day. Current-input status resolution remains separate from cached reporting; framework retries and remote appointment effects are not certified here.

## Open Design Questions

Subscribers need a clear meaning for termination caused by natural lapse compared with staff termination. Receiver switches and absent handler configuration must be accounted for before relying on downstream appointment changes.
