---
id: CAP-PFM-SEC-009
title: Maintain console memberships
feature: FEAT-PFM-SEC-03
product_area: platform
module: security
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-11
    Final acceptance)
  - >-
    services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-13/14
    Final acceptance)
  - >-
    services-platform:docs/security/security-administration.md#16 (Andrew accepted the delete,
    2026-10-12)
  - 'services-platform:docs/security/security-administration.md#2. Capability checklist (U1 to U7)'
purpose: >-
  Security administrators maintain colleagues’ profiles and membership lifecycle while preserving
  customer administration and historical attribution.
outcomes:
  - 'Administrators find memberships and inspect profile, roles, access and history.'
  - 'Lifecycle actions distinguish temporary suspension, membership revocation and record deletion.'
  - Refused changes preserve the customer’s remaining security administration.
concepts:
  - CON-PFM-SEC-003
actors:
  - ACT-PFM-001
  - ACT-PFM-006
surfaces:
  - console
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: platform.users.read
    where_checked: api
    scope: tenant
  - key: platform.security-access.manage
    where_checked: api
    scope: tenant
  - key: platform.users.create
    where_checked: api
    scope: tenant
  - key: platform.users.update
    where_checked: api
    scope: tenant
  - key: platform.users.delete
    where_checked: api
    scope: tenant
  - key: platform.users.bulk
    where_checked: api
    scope: tenant
  - key: platform.users.manage-identities
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-PFM-SEC-009-01
    proved_by: [ATS-PFM-SEC-009-01, TST-PFM-SEC-051]
    statement: >-
      Customer user lists contain that customer’s console memberships with roles, surname ordering,
      name/e-mail search, status, role and invitation filters and pages of at most 100; revoked
      memberships are hidden by default, and platform staff authority alone cannot access another
      customer’s list.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses a platform-scope
      caller the users of a customer they are not of, because this route is console
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-13/14
      Final acceptance)
  - id: BR-CAP-PFM-SEC-009-02
    proved_by: []
    statement: >-
      Membership detail includes profile, assignments with role and grantor, and the latest invitation’s
      delivery information; missing and outside-customer identities share the refusal.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/console-users.integration.spec.js#answers
      the person, the grant with the role it names and who granted it, the roles they hold, and their
      invitation
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-13/14
      Final acceptance)
  - id: BR-CAP-PFM-SEC-009-03
    proved_by: [ATS-PFM-SEC-006-13, ATS-PFM-SEC-009-02, TST-PFM-SEC-051]
    statement: >-
      Versioned profile edits change only first name, last name, display name, time zone and locale,
      preserving e-mail, status and customer ownership.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/console-users.integration.spec.js#leaves
      the e-mail address, the status and the wall exactly as they were
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-11
      Final acceptance)
  - id: BR-CAP-PFM-SEC-009-04
    proved_by: [ATS-PFM-SEC-009-03, ATS-PFM-SEC-009-12, ATS-PFM-SEC-009-16, TST-PFM-SEC-035, TST-PFM-SEC-060, TST-PFM-SEC-212]
    statement: >-
      Deactivation commits suspended membership and withdrawn invitations, then attempts pool disablement
      and flush submission; fresh membership reads refuse it, cached decisions can persist, pool failure
      is reported without reversing suspension, and self-suspension or revocation fails.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/security/services/invitations/suspend-user.ts:suspendUser'
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-11
      Final acceptance)
  - id: BR-CAP-PFM-SEC-009-05
    proved_by: [ATS-PFM-SEC-008-21, ATS-PFM-SEC-009-04, ATS-PFM-SEC-009-13, ATS-PFM-SEC-009-17, TST-PFM-SEC-212]
    statement: >-
      Reactivation applies only to suspended memberships, requires widening authority over their retained
      grants and attempts pool enablement without reviving withdrawn invitations; an enablement failure
      is reported while the membership change remains committed.
    kind: permission
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/security/services/invitations/suspend-user.ts:reinstateUser'
    origin: >-
      services-platform:docs/security/security-administration.md#Phase 6 (reinstatement is judged as
      widening)
  - id: BR-CAP-PFM-SEC-009-06
    proved_by: [ATS-PFM-SEC-009-05, ATS-PFM-SEC-009-14, ATS-PFM-SEC-009-15, TST-PFM-SEC-060, TST-PFM-SEC-212]
    statement: >-
      Revocation ends membership, outstanding invitations and held assignments; returning requires a
      fresh invitation rather than reinstatement.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/services/invitations/suspend-revoke.spec.js#ends the
      membership, its invitations and every assignment it held in one transaction
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-11
      Final acceptance)
  - id: BR-CAP-PFM-SEC-009-07
    proved_by: [ATS-PFM-SEC-009-06, ATS-PFM-SEC-009-18, ATS-PFM-SEC-009-20, ATS-PFM-SEC-010-20, ATS-PFM-SEC-016-08, TST-PFM-SEC-035, TST-PFM-SEC-117, TST-PFM-SEC-121, TST-PFM-SEC-128, TST-PFM-SEC-212]
    statement: >-
      A guarded write that would reduce a non-Rhythm customer from one or more qualifying administrators
      to none rolls back; qualifying administrators are active customer console users with active,
      unconditional customer-wide roles containing the security-administrator policy, and an already-zero
      count does not trigger refusal.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/security/services/membership/lockout.ts:assertNotLockedOut'
    origin: 'services-platform:docs/security/security-administration.md#4. Delegation, escalation and lockout'
  - id: BR-CAP-PFM-SEC-009-08
    proved_by: [ATS-PFM-SEC-009-07, ATS-PFM-SEC-009-19, ATS-PFM-SEC-011-08, TST-PFM-SEC-069]
    statement: >-
      Deletion requires delete and access-management authority, preserves name attribution and refuses
      referenced identities, self-deletion, loss of the customer’s last qualifying administrator or loss
      of Rhythm’s last Rhythm Administrator.
    kind: lifecycle
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/services/invitations/user-deletion.spec.js#refuses a
      delete that would leave the customer with nobody who can administer its security
    origin: >-
      services-platform:docs/security/security-administration.md#16 (Andrew accepted the delete,
      2026-10-12)
  - id: BR-CAP-PFM-SEC-009-09
    proved_by: [ATS-PFM-SEC-009-08, TST-PFM-SEC-114]
    statement: >-
      Revocation and deletion commit durable pool-account work for retried background disablement or
      removal; the response can precede completion of that work.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/dispatch/pool-account-removals.integration.spec.js#removes
      the account on the second delivery, and tells SQS the item is done
    origin: >-
      services-platform:docs/security/security-administration.md#16 (Andrew accepted the delete,
      2026-10-12)
  - id: BR-CAP-PFM-SEC-009-10
    proved_by: [ATS-PFM-SEC-009-09, TST-PFM-SEC-069]
    statement: >-
      Console memberships belong to one customer and portal memberships to one workspace; the same
      address can identify distinct memberships across those boundaries, same-boundary duplicates fail
      and ordinary edits cannot move lifecycle.
    kind: invariant
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/users.e2e.spec.js#refuses a duplicate e-mail in the
      same wall (400) and a wall the access type does not allow (400)
    origin: 'services-platform:docs/security/security-administration.md#1. What the area is for (decision U7)'
  - id: BR-CAP-PFM-SEC-009-11
    proved_by: [ATS-PFM-SEC-009-10, TST-PFM-SEC-069, TST-PFM-SEC-204]
    statement: >-
      Canonical sign-in linking requires identity-management authority and a pool account that exists, is
      enabled and carries the membership’s verified matching address.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/services/identity/link-cognito-identity.spec.js#never
      links on the request's say-so: the pool user must exist, be enabled, and carry the user's verified
      e-mail
    origin: none
  - id: BR-CAP-PFM-SEC-009-12
    proved_by: []
    statement: >-
      The user view separates Profile, Roles, Access and History, keeps editing on a separate page and
      hides Edit when user-management permission is absent.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: >-
      react-console:e2e/mock/security-view-edit.mock.spec.ts#somebody who may not manage users gets no
      Edit, and the edit page sends them to the view
    origin: >-
      services-platform:docs/security/security-administration.md#14 Acceptance checkpoints (2026-10-13/14
      Final acceptance)
scenarios:
  - id: SC-CAP-PFM-SEC-009-01
    given: The customer has one qualifying security administrator.
    when: An administrator tries to remove that person’s Administrator assignment.
    then: The change is refused because it would remove the customer’s remaining security administration.
    provenance: test
    source: >-
      react-console:e2e/security/users.spec.ts#refuses to remove the Administrator role from the last
      person who holds it
  - id: SC-CAP-PFM-SEC-009-02
    given: A colleague should temporarily lose membership access.
    when: An administrator deactivates the colleague.
    then: >-
      Fresh membership checks refuse the suspended account; cached authorization has its existing
      freshness limit, and later reinstatement is available.
    provenance: test
    source: >-
      react-console:e2e/security/users.spec.ts#deactivating really shuts somebody out, and reactivating
      lets them back in
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-PFM-SEC-008
  - CAP-PFM-SEC-010
  - CAP-PFM-SEC-011
  - CAP-PFM-SEC-016
journeys:
  - JNY-PFM-SEC-003
open_questions: []
evidence:
  - >-
    Pinned services-platform references for the structured rows:
    docs/security/security-administration.md; modules/security/services/invitations/suspend-user.ts;
    modules/security/services/membership/lockout.ts;
    modules/security/tests/e2e/console-users.e2e.spec.js; modules/security/tests/e2e/users.e2e.spec.js;
    modules/security/tests/integration/services/dispatch/pool-account-removals.integration.spec.js;
    modules/security/tests/integration/services/membership/console-users.integration.spec.js;
    modules/security/tests/unit/services/identity/link-cognito-identity.spec.js;
    modules/security/tests/unit/services/invitations/suspend-revoke.spec.js;
    modules/security/tests/unit/services/invitations/user-deletion.spec.js.
  - >-
    Pinned react-console references for the structured rows: e2e/mock/security-view-edit.mock.spec.ts;
    e2e/security/users.spec.ts.
controls: [TC-AVL-001, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-DAT-001, TC-DAT-003, TC-DAT-004, TC-IAM-001, TC-IAM-002, TC-IAM-003, 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-PI-001, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-SEC-03-01, FAS-PFM-SEC-03-02, FAS-PFM-SEC-03-03, FAS-PFM-SEC-03-04, FAS-PFM-SEC-03-05]
suites: [TST-PFM-SEC-035, TST-PFM-SEC-051, TST-PFM-SEC-060, TST-PFM-SEC-069, TST-PFM-SEC-114, TST-PFM-SEC-117, TST-PFM-SEC-121, TST-PFM-SEC-128, TST-PFM-SEC-204, TST-PFM-SEC-212]
---
# Maintain console memberships

## Purpose

Administrators keep the customer’s colleagues recognizable and manage access as people’s responsibilities change. The membership record connects those actions to the identity and history staff will need later.

## How It Works

The administrator finds a colleague through the user list and opens the person’s record. The view brings together profile information, roles, access explanation and history. Editing profile information uses a separate page and can fail if the saved version has changed. Lifecycle actions appear alongside that profile work but express different decisions. Deactivation supports a temporary interruption; revocation ends membership until a new invitation. The result can report a pool problem even though the membership change has committed, which staff need to investigate rather than assume the database action failed. Deletion is reserved for an unwanted record and remains subject to references and administrator safeguards. Background pool-account work can continue after the response. Staff consult security history when they need to explain why a colleague lost access or why an attempted change was refused.

## Relationships

- Invitation handling establishes and restores memberships.
- Scoped role management changes the held assignments.
- Security history preserves lifecycle decisions and refusals.
- HQ provides the corresponding administration surface for Rhythm staff.
