---
id: ATS-PFM-SEC-016
scope: CAP-PFM-SEC-016
product_area: platform
module: security
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-SEC-016-01
    title: Require both staff status and operation permission
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-01
    preconditions: >-
      Prepare a customer administrator and staff callers with and without the
      relevant keys.
    steps: 'Request staff listing, role listing and staff invitation resend.'
    expected: >-
      Reject customer authority as non-staff and staff callers missing a key;
      serve authorized staff.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#refuses
        an administrator of a customer, holding every key at that customer, as
        403
      - TST-PFM-SEC-056
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#refuses
        a staff member who holds another HQ key but not hq.staff.read as 403
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        refused 403 and told they are not staff, rather than that a permission
        is missing
      - TST-PFM-SEC-058
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        refused 403, for want of the one key the route names
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#refuses
      an administrator of a customer, holding every key at that customer, as 403
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#refuses
        an administrator of a customer, holding every key at that customer, as
        403
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#refuses
        a staff member who holds another HQ key but not hq.staff.read as 403
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        refused 403 and told they are not staff, rather than that a permission
        is missing
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        refused 403, for want of the one key the route names
  - id: ATS-PFM-SEC-016-02
    title: Restrict the staff invitation role
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-03
    preconditions: >-
      Prepare a customer role and a caller lacking authority to assign Rhythm
      Administrator.
    steps: >-
      Attempt those invitations, then invite a Support Representative with
      sufficient authority.
    expected: >-
      Reject invalid or unauthorized roles; allow the authorized staff
      invitation.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/security/services/invitations/invite-staff.ts#assertStaffRole
    category: permission
    test_references: []
  - id: ATS-PFM-SEC-016-03
    title: Resend only outstanding staff invitations
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-04
    preconditions: Prepare invited and accepted staff members and an invited customer member.
    steps: Request HQ resend for all three.
    expected: >-
      Replace the invited staff link; direct the accepted member toward password
      recovery; return not found for the customer member without delivery.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        answered a new invitation, superseding the one that was outstanding
      - TST-PFM-SEC-058
      - >-
        services-platform:modules/security/tests/integration/services/invitations/resend-staff-invitation.integration.spec.js#is
        refused, and told to reset the password instead of inviting the person
        again
      - TST-PFM-SEC-119
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        not found, so the route reports nothing about who exists
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        sent nothing, where the staff member was sent a second link
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
      answered a new invitation, superseding the one that was outstanding
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        answered a new invitation, superseding the one that was outstanding
      - >-
        services-platform:modules/security/tests/integration/services/invitations/resend-staff-invitation.integration.spec.js#is
        refused, and told to reset the password instead of inviting the person
        again
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        not found, so the route reports nothing about who exists
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-resend.e2e.spec.js#is
        sent nothing, where the staff member was sent a second link
  - id: ATS-PFM-SEC-016-04
    title: Show staff lifecycle actions in the console
    level: browser
    proves:
      - BR-CAP-PFM-SEC-016-05
    preconditions: Load the mocked staff page.
    steps: 'Suspend, reinstate and revoke through its controls.'
    expected: >-
      Show the mocked status changes and require a revocation confirmation
      naming the member. This binding does not establish backend propagation.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        react-console:e2e/mock/hq-ops.mock.spec.ts#resends an invitation,
        suspends and reinstates, and names the last administrator
      - TST-PFM-SEC-023
      - >-
        react-console:e2e/mock/hq-ops.mock.spec.ts#revokes access behind a
        confirmation that names the person
    provenance: test
    source: >-
      react-console:e2e/mock/hq-ops.mock.spec.ts#resends an invitation, suspends
      and reinstates, and names the last administrator
    category: interaction
    test_references:
      - >-
        react-console:e2e/mock/hq-ops.mock.spec.ts#resends an invitation,
        suspends and reinstates, and names the last administrator
      - >-
        react-console:e2e/mock/hq-ops.mock.spec.ts#revokes access behind a
        confirmation that names the person
  - id: ATS-PFM-SEC-016-05
    title: Prevent deletion of the sole administrator
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-06
      - INV-DOM-PFM-SEC-003-12
    preconditions: 'Begin with one active Rhythm Administrator, then add another.'
    steps: Attempt deletion before and after the second administrator exists.
    expected: >-
      Initially refuse and preserve the grant; subsequently allow deletion while
      retaining one administrator.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#refuses
        the delete as the staff guard does, naming its machine code
      - TST-PFM-SEC-117
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#leaves
        their Rhythm Administrator grant standing, so somebody can still hand
        the role out
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#leaves
        Rhythm with the one administrator the guard counted
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#refuses
      the delete as the staff guard does, naming its machine code
    category: invariant
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#refuses
        the delete as the staff guard does, naming its machine code
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#leaves
        their Rhythm Administrator grant standing, so somebody can still hand
        the role out
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#leaves
        Rhythm with the one administrator the guard counted
  - id: ATS-PFM-SEC-016-06
    title: Read staff-role permissions from the catalog
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-07
    preconditions: Load the shared catalog and HQ role listing.
    steps: >-
      Read each staff role and attempt emergency access with Support and
      Engineer.
    expected: >-
      The HQ listing returns reconciled staff roles with their stored permission
      keys. The partial integration fixture proves policy expansion and omission
      of an unreconciled role, rather than the full released key set. Backend
      emergency tests deny Support and Engineer.
    negative: false
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#answers
        the HQ roles the catalog holds, each with a name, a description and its
        permission keys
      - TST-PFM-SEC-056
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#answers
        every HQ role it names as one of the staff roles, and nothing else
      - >-
        services-platform:modules/security/tests/integration/services/catalog/hq-roles.integration.spec.js#answers
        the keys of a role's attached policy, which is where Rhythm Support's
        come from
      - TST-PFM-SEC-094
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#answers
      the HQ roles the catalog holds, each with a name, a description and its
      permission keys
    category: rule
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#answers
        the HQ roles the catalog holds, each with a name, a description and its
        permission keys
      - >-
        services-platform:modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js#answers
        every HQ role it names as one of the staff roles, and nothing else
      - >-
        services-platform:modules/security/tests/integration/services/catalog/hq-roles.integration.spec.js#answers
        the keys of a role's attached policy, which is where Rhythm Support's
        come from
  - id: ATS-PFM-SEC-016-07
    title: Reconcile founders without duplicate activation
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-08
    preconditions: Prepare an empty environment and an already invited founder.
    steps: Run founding-staff reconciliation twice in each setup.
    expected: >-
      Create or activate the membership once, supersede an existing invitation,
      retain one platform assignment and avoid duplicate activation audit
      events.
    negative: false
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#answers
        the first run as created
      - TST-PFM-SEC-176
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#answers
        the second run as unchanged
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#supersedes
        the invitation, so its link no longer works
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#leaves
        exactly one assignment: Rhythm Administrator at platform scope, active
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#answers
      the first run as created
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#answers
        the first run as created
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#answers
        the second run as unchanged
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#supersedes
        the invitation, so its link no longer works
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#leaves
        exactly one assignment: Rhythm Administrator at platform scope, active
  - id: ATS-PFM-SEC-016-08
    title: Administer Rhythm staff memberships with maintain console memberships
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-009-07
      - BR-CAP-PFM-SEC-016-06
    exercises:
      - CAP-PFM-SEC-016
      - CAP-PFM-SEC-009
    preconditions: >-
      A customer administrator and a sole Rhythm Administrator each have active
      memberships.
    steps: Attempt a removal that would strand each administration boundary.
    expected: >-
      Each removal is refused within its own boundary without treating one
      administrator as the other boundary’s replacement.
    negative: true
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities,
      and records owned by this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/services/membership/lockout.ts:assertNotLockedOut
      - >-
        services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#refuses
        the delete as the staff guard does, naming its machine code
  - id: ATS-PFM-SEC-016-09
    title: >-
      Administer Rhythm staff memberships with control staff access to customer
      workspaces
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-016-05
      - BR-CAP-PFM-SEC-017-08
    exercises:
      - CAP-PFM-SEC-016
      - CAP-PFM-SEC-017
    preconditions: A Rhythm Administrator has standing access to a customer.
    steps: >-
      Suspend the staff membership and inspect customer entry and its standing
      assignments; reinstate through authorized staff administration.
    expected: >-
      Suspension ends usable customer entry; fresh access follows the retained
      or reconciled assignment state rather than staff status alone.
    negative: false
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities,
      and records owned by this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/aws/api/hq-staff.routes.ts:hqStaffRoutes
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#gives
        each Rhythm Administrator one active customer-level standing grant in
        every customer but Rhythm’s own
  - id: ATS-PFM-SEC-016-10
    title: >-
      Administer Rhythm staff memberships with publish shared access definitions
      from releases
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-013-07
      - BR-CAP-PFM-SEC-016-08
    exercises:
      - CAP-PFM-SEC-016
      - CAP-PFM-SEC-013
    preconditions: Founding staff are configured and the shared role catalog is present.
    steps: >-
      Reconcile the release and founding staff twice, then read HQ role
      permissions and memberships.
    expected: >-
      Founders and catalog roles retain identity without duplicate activation or
      customer-console powers from HQ grants.
    negative: false
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities,
      and records owned by this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/unit/services/catalog/reconcile-catalog.spec.ts#publishes
        the eight roles the catalog ships, in the order it ships them
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js#leaves
        exactly one assignment: Rhythm Administrator at platform scope, active
evidence:
  - >-
    Pinned services-platform references for the structured rows:
    modules/security/services/invitations/invite-staff.ts;
    modules/security/tests/e2e/hq-staff-and-roles.e2e.spec.js;
    modules/security/tests/e2e/hq-staff-resend.e2e.spec.js;
    modules/security/tests/integration/services/catalog/hq-roles.integration.spec.js;
    modules/security/tests/integration/services/invitations/delete-user.integration.spec.js;
    modules/security/tests/integration/services/invitations/resend-staff-invitation.integration.spec.js;
    modules/security/tests/integration/services/provisioning/ensure-founding-staff.integration.spec.js.
  - >-
    Pinned react-console references for the structured rows:
    e2e/mock/hq-ops.mock.spec.ts.
  - >-
    Testing back-fill reviews the pinned suite assertions, related capabilities,
    domain invariants, state transitions and workflow failure boundaries. New
    unbound rows remain proposed.
coverage:
  rule: covered
  transition: covered
  validation: 'gap: FND-PFM-SEC-073'
  permission: covered
  edge: 'gap: FND-PFM-SEC-073'
  failure: 'gap: FND-PFM-SEC-073'
  boundary: 'gap: FND-PFM-SEC-073'
  concurrency: 'gap: FND-PFM-SEC-073'
  invariant: covered
  interaction: covered
coverage_notes:
  validation: >-
    No dedicated scenario isolates this category; the coverage finding records
    the missing proof.
  edge: >-
    No dedicated scenario isolates this category; the coverage finding records
    the missing proof.
  failure: >-
    No dedicated scenario isolates this category; the coverage finding records
    the missing proof.
  boundary: >-
    No dedicated scenario isolates this category; the coverage finding records
    the missing proof.
  concurrency: >-
    No dedicated scenario isolates this category; the coverage finding records
    the missing proof.
invariant_proofs:
  - scenario: ATS-PFM-SEC-016-05
    proves:
      - INV-DOM-PFM-SEC-003-12
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/invitations/delete-user.integration.spec.js#refuses
      the delete as the staff guard does, naming its machine code
permission_proof_review:
  - key: hq.staff.read
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
  - key: hq.staff.invite
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
  - key: hq.staff.disable
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
  - key: hq.staff.update
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
  - key: hq.roles.read
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
  - key: hq.roles.manage
    scenario_candidates:
      - ATS-PFM-SEC-016-01
      - ATS-PFM-SEC-016-02
    qualification: >-
      Permission-rule scenarios cover the declared authority boundaries;
      exhaustive operation-by-key combinations remain an executable coverage
      gap.
    gap: FND-PFM-SEC-073
---
# Administer Rhythm staff memberships: Capability Test Specification

## Coverage

| Rule | Proved by |
|---|---|
| `BR-CAP-PFM-SEC-016-01` | `ATS-PFM-SEC-016-01` |
| `BR-CAP-PFM-SEC-016-03` | `ATS-PFM-SEC-016-02` |
| `BR-CAP-PFM-SEC-016-04` | `ATS-PFM-SEC-016-03` |
| `BR-CAP-PFM-SEC-016-05` | `ATS-PFM-SEC-016-04`, `ATS-PFM-SEC-016-09` |
| `BR-CAP-PFM-SEC-016-06` | `ATS-PFM-SEC-016-05`, `ATS-PFM-SEC-016-08` |
| `BR-CAP-PFM-SEC-016-07` | `ATS-PFM-SEC-016-06` |
| `BR-CAP-PFM-SEC-016-08` | `ATS-PFM-SEC-016-07`, `ATS-PFM-SEC-016-10` |

The coverage map distinguishes a dedicated scenario from an unresolved category. Proposed scenarios define proof to build; bindings retain the exact assertion references and identify their executable suite.

## Interactions With Related Capabilities

- Maintain console memberships: Attempt a removal that would strand each administration boundary.
- Control staff access to customer workspaces: Suspend the staff membership and inspect customer entry and its standing assignments; reinstate through authorized staff administration.
- Publish shared access definitions from releases: Reconcile the release and founding staff twice, then read HQ role permissions and memberships.

