---
id: CAP-MBR-MEM-015
title: Give officers their own units' members and nothing else
feature: FEAT-MBR-MEM-06
product_area: membership
module: memberships
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - 'inventory:CAP-MBR-SEC-001'
  - 'inventory:CAP-MBR-SEC-002'
  - 'inventory:CAP-MBR-SEC-003'
  - 'inventory:CAP-MBR-SEC-004'
  - 'inventory:CAP-MBR-SEC-006'
  - 'inventory:CAP-MBR-SEC-010'
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-membership:README.md#2026-10-18 — Final acceptance'
  - 'services-membership:docs/migrations/scope/README.md'
purpose: >-
  A chapter, zone or regional officer sees and changes the memberships of their own units and those below them, and
  no others, while staff keep the whole workspace.
outcomes:
  - Officers work from complete, correct rosters of their own units.
  - No officer can read, infer or change a member outside their units.
  - Authority starts and ends with the officer's appointment.
concepts:
  - CON-MBR-MEM-001
  - CON-MBR-MEM-003
actors:
  - ACT-MBR-009
  - ACT-MBR-006
  - ACT-MBR-007
surfaces:
  - portal
  - console
  - api
permissions:
  - key: membership.members.read
    where_checked: api
    scope: record
  - key: membership.affiliations.manage
    where_checked: api
    scope: record
  - key: membership.configuration.read
    where_checked: api
    scope: tenant
  - key: membership.configuration.manage
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-MBR-MEM-015-01
    statement: >-
      Every key an officer may hold is scoped to organizational units, and a grant on a unit reaches that unit and
      every unit below it but no sibling or parent; staff hold the same keys for the whole workspace.
    kind: permission
    enforced_at: [api, service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/unit/unit-scope.spec.ts#does not reach a sibling or a parent of the
      granted unit
    origin: none
  - id: BR-CAP-MBR-MEM-015-02
    statement: >-
      A membership's units on a date are the units, with their ancestors, of the affiliations in force on that date of
      a term covering it, and the identity unit with its ancestors while a term covers the date; pending and cancelled
      terms give none.
    kind: calculation
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/unit-scope.integration.spec.ts#agrees with the
      in-memory rule for every membership, on three dates, with grace on and off
    origin: none
  - id: BR-CAP-MBR-MEM-015-03
    statement: >-
      An officer's list holds only memberships whose units reach their grants, and the restriction is applied before
      paging, so every page is full and the counts agree.
    kind: permission
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/unit-scope.integration.spec.ts#fills every
      page from the memberships in scope (the predicate is in the WHERE, not after the LIMIT)
    origin: none
  - id: BR-CAP-MBR-MEM-015-04
    statement: >-
      A membership outside the officer's units reads as not found; a visible membership marks each of its
      affiliations as in or out of the officer's scope.
    kind: permission
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/unit-scope.integration.spec.ts#reads Jo for
      the Atlanta officer, the Savannah affiliation marked out of scope
    origin: none
  - id: BR-CAP-MBR-MEM-015-05
    statement: >-
      When the workspace keeps grace members in scope, a member in grace stays in their officers' scope, in the units
      of the term's last day, until the grace runs out; otherwise they leave it the day after their term ends.
    kind: policy
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/unit/unit-scope.spec.ts#keeps a member in grace, in the units of the
      term’s last day, when the roster rule includes grace
    origin: none
  - id: BR-CAP-MBR-MEM-015-06
    statement: >-
      The grace rule applies equally to an officer's roster filtered by one of their units and to an export with the
      same filter.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-051
    origin: none
  - id: BR-CAP-MBR-MEM-015-07
    statement: >-
      Each affiliation change checks its own object: an addition the new unit, an end the row's unit, making primary the
      row's unit and every unit losing the flag, and a transfer both units; a unit outside the scope is refused naming
      it, and a membership the officer cannot see is not found, before any version check.
    kind: permission
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/unit/unit-scope.spec.ts#answers not_found, not allowed, for a
      membership whose chain the set does not reach although the set holds the named unit
    origin: 'DEF-MBR-0454 repair (services-membership:docs/migrations/acceptance-repairs/README.md)'
  - id: BR-CAP-MBR-MEM-015-08
    statement: >-
      No unit-scoped grant reaches a workspace-level operation (creating memberships, adding, correcting or terminating
      terms, suspending, restoring, the sweep, configuration, structure, billing); such a request is refused before it
      is handled.
    kind: permission
    enforced_at: [api]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/s07-regional-officer.e2e.spec.ts#does not reach a staff operation: terminating Jo’s
      term is refused before the handler
    origin: none
  - id: BR-CAP-MBR-MEM-015-09
    statement: >-
      The workspace's scope settings (whether grace members stay in scope, and each role's contact field list) are
      replaced whole at the version read and bounded in size; a workspace without settings keeps grace members out and
      has no lists.
    kind: validation
    enforced_at: [api, service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#refuses 100 roles of 50 hundred-character fields 400
      naming the 32768-byte bound, and saves nothing
    origin: none
  - id: BR-CAP-MBR-MEM-015-10
    statement: >-
      Authority is decided on every request from the officer's current grants: an appointment that has ended is refused
      at the next request, and a successor is admitted from the appointment's start.
    kind: permission
    enforced_at: [api]
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/s12-expiring-appointment.e2e.spec.ts#refuses Lee’s roster once the exclusive
      valid_until has passed
    origin: none
  - id: BR-CAP-MBR-MEM-015-11
    statement: >-
      A refusal for a membership outside the officer's scope reads exactly as one for a membership that does not exist,
      revealing nothing about it.
    kind: permission
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-052
    origin: none
  - id: BR-CAP-MBR-MEM-015-12
    statement: >-
      A grant on a unit always includes the units below it; a grant limited to the unit alone is not offered.
    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-015-01
    given: Dana is the Eastern Regional Secretary; Jo belongs to two Eastern chapters and Sam to San Francisco.
    when: Dana lists and reads members.
    then: Dana sees Jo with both chapters in scope and does not find Sam.
    provenance: test
    source: 'services-membership:tests/e2e/s07-regional-officer.e2e.spec.ts#does not find Sam, in San Francisco'
  - id: SC-CAP-MBR-MEM-015-02
    given: Lee is Atlanta's chapter president; Jo belongs to Atlanta and Savannah.
    when: Lee tries to end Jo's Savannah row and to make the Atlanta row primary.
    then: The Savannah change is refused as out of scope naming Savannah; the Atlanta change succeeds.
    provenance: test
    source: 'services-membership:tests/e2e/s08-cross-chapter-restrictions.e2e.spec.ts#refuses ending the Savannah row 403 out_of_scope'
edge_cases: []
constraints: []
depends_on:
  - CAP-MBR-MEM-007
related:
  - CAP-MBR-MEM-016
  - CAP-MBR-MEM-017
  - CAP-MBR-MEM-005
journeys:
  - JNY-MBR-MEM-005
open_questions: []
evidence:
  - repository: services-membership
    source_sha: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
    paths:
      - aws/membership.service.ts
      - modules/memberships/services/access/unit-scope.ts
      - modules/memberships/services/access/scope-checks.ts
      - modules/memberships/services/access/scope-settings.ts
      - modules/memberships/services/persistence/visibility-chains.ts
      - modules/memberships/db/scope-settings.table.ts
      - modules/memberships/tests/unit/unit-scope.spec.ts
      - modules/memberships/tests/integration/services/unit-scope.integration.spec.ts
      - tests/e2e/s07-regional-officer.e2e.spec.ts
      - tests/e2e/s08-cross-chapter-restrictions.e2e.spec.ts
      - tests/e2e/s12-expiring-appointment.e2e.spec.ts
      - docs/migrations/scope/README.md
      - docs/migrations/acceptance-repairs/README.md
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/membership/reports/defects/security.md
controls: [TC-AVL-002, TC-AVL-003, 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-002, TC-DAT-003, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PRV-001, TC-PRV-002, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004]
---

# Give officers their own units' members and nothing else

## Purpose

Chapters and regions are run by volunteer officers who need the members of their units, and only those. Their authority usually comes from a committee appointment and must start and end with it.

## How It Works

Every Membership key an officer can hold is scoped to organizational units. A grant on a chapter covers the chapter and its sections; a grant on a region covers every chapter below it. Staff hold the same keys for the whole workspace and use the same screens and requests. A member belongs to an officer's scope on a date through the units of the chapter ties in force on that date, or through an identity unit, together with every unit above them. Lists show only members in scope, and every page is full. A member outside the scope cannot be found. A visible member's ties to other units are shown, marked as out of scope. Each change to a tie is checked against the units it touches, so an Atlanta officer cannot end a member's Savannah tie. The workspace decides whether members in grace stay on officers' rosters until their grace runs out. Officers never reach the workspace-level operations. Authority is checked on every request, so an ended appointment loses access at once. The product should apply the grace rule to unit-filtered rosters too, and should word refusals so they reveal nothing; today neither holds.

## Relationships

- Depends on `CAP-MBR-MEM-007` because affiliations decide which units a member is in.
- Depends on Platform security (`MOD-PFM-SEC`) for grants, their evaluation and the application gate, and on Committees (`MOD-CMT-CMT`) for the appointments that derive officers' grants.
- Related to `CAP-MBR-MEM-016` and `CAP-MBR-MEM-017` because officers' actions and exports use the same scope.
