---
id: ATS-PFM-SEC-010
scope: CAP-PFM-SEC-010
product_area: platform
module: security
revision: 3
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-SEC-010-01
    title: Role writes require management authority
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-01
    preconditions: Prepare a reader and an access manager.
    steps: Attempt the same grant with both identities.
    expected: The reader receives 403 without a write; the authorized manager succeeds.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader’s
        attempt to grant as 403, and grants nothing
      - TST-PFM-SEC-051
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#grants a role by its
        built-in key and answers the grant with the role it names
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader’s
      attempt to grant as 403, and grants nothing
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader’s
        attempt to grant as 403, and grants nothing
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#grants a role by its
        built-in key and answers the grant with the role it names
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader’s
        attempt to grant as 403, and grants nothing
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader’s
        attempt to grant as 403, and grants nothing
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-02
    title: 'Delegation keys, scopes and self-widening'
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-02
      - INV-DOM-PFM-SEC-003-11
    preconditions: Prepare an administrator with limited workspace authority.
    steps: 'Grant within that authority, beyond its keys, at an unmanaged scope and to the administrator.'
    expected: >-
      The permitted grant succeeds; failures identify missing authority or self-widening without
      creating grants.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
        grants more than the caller may do there, and names what was missing
      - TST-PFM-SEC-060
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
        a scope the caller does not manage, and says which scope rather than which role
      - TST-PFM-SEC-213
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#lists
        every bad scope at once, so an administrator fixes the invitation in one go
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
        an edit that reaches the editor's own grant, whatever they hold and whoever else holds it
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
      grants more than the caller may do there, and names what was missing
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
        grants more than the caller may do there, and names what was missing
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
        a scope the caller does not manage, and says which scope rather than which role
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#lists
        every bad scope at once, so an administrator fixes the invitation in one go
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
        an edit that reaches the editor's own grant, whatever they hold and whoever else holds it
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
        grants more than the caller may do there, and names what was missing
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
        grants more than the caller may do there, and names what was missing
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-03
    title: Supported resource scope
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-03
      - INV-DOM-PFM-SEC-003-07
    preconditions: 'Prepare live, draft and archived custom record types.'
    steps: >-
      Grant on those, an unsupported resource kind, a missing type and a different customer’s
      workspace.
    expected: Live and draft types accept grants; the remaining cases fail with no assignment written.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
        a role on a draft custom object, which is live though not yet active
      - TST-PFM-SEC-129
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
        a resource type that is not on the allow-list, naming the field, and writes nothing
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
        an archived custom object, naming the object key, and writes nothing
      - >-
        services-platform:modules/security/tests/e2e/resource-scoped-grants.e2e.spec.js#refuses a
        tenant of another customer, naming it, and writes no grant
      - TST-PFM-SEC-063
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses a scope the
        screens do not offer as 400
      - TST-PFM-SEC-051
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
      a role on a draft custom object, which is live though not yet active
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
        a role on a draft custom object, which is live though not yet active
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
        a resource type that is not on the allow-list, naming the field, and writes nothing
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
        an archived custom object, naming the object key, and writes nothing
      - >-
        services-platform:modules/security/tests/e2e/resource-scoped-grants.e2e.spec.js#refuses a
        tenant of another customer, naming it, and writes no grant
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses a scope the
        screens do not offer as 400
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
        a role on a draft custom object, which is live though not yet active
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
        a role on a draft custom object, which is live though not yet active
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-04
    title: Customer security cannot amend staff authority
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-04
      - INV-DOM-PFM-SEC-003-07
    preconditions: Prepare platform and ordinary assignments.
    steps: >-
      Shorten, condition or move the platform assignment, then try promoting the ordinary assignment
      to platform.
    expected: Each attempt fails with HQ guidance and leaves the assignments unchanged.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a window that would have ended the staff grant, naming where such a change is made
      - TST-PFM-SEC-122
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        taking the staff grant’s scope off the platform
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        the promotion, because granting staff authority is Rhythm HQ’s act
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#leaves
        the grant exactly as it was granted, so the authority is still in force
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
      a window that would have ended the staff grant, naming where such a change is made
    category: invariant
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a window that would have ended the staff grant, naming where such a change is made
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        taking the staff grant’s scope off the platform
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        the promotion, because granting staff authority is Rhythm HQ’s act
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#leaves
        the grant exactly as it was granted, so the authority is still in force
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a window that would have ended the staff grant, naming where such a change is made
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a window that would have ended the staff grant, naming where such a change is made
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-05
    title: Membership and validity timing
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-05
      - INV-DOM-PFM-SEC-003-08
    preconditions: Prepare active and invited members.
    steps: Assign roles to both and another assignment beginning tomorrow.
    expected: >-
      Active membership receives an active grant, invited membership a pending grant, and the future
      window grants no current access.
    negative: false
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
        a role at customer scope, active at once for an active person, and audits it as widening
      - TST-PFM-SEC-128
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#leaves
        a grant to somebody who has not accepted yet pending, so it begins when they do
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#answers a grant whose
        window opens tomorrow as scheduled, in the grant and in the list
      - TST-PFM-SEC-051
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
      a role at customer scope, active at once for an active person, and audits it as widening
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
        a role at customer scope, active at once for an active person, and audits it as widening
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#leaves
        a grant to somebody who has not accepted yet pending, so it begins when they do
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#answers a grant whose
        window opens tomorrow as scheduled, in the grant and in the list
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
        a role at customer scope, active at once for an active person, and audits it as widening
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
        a role at customer scope, active at once for an active person, and audits it as widening
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-06
    title: Duplicate assignment concurrency
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-06
      - INV-DOM-PFM-SEC-003-09
    preconditions: Prepare a member eligible for a role.
    steps: >-
      Grant the same role, scope and window sequentially and concurrently, then revoke and grant
      again.
    expected: >-
      Duplicates return already-assigned and preserve one live row; replacement after revocation
      succeeds.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a second grant of the same role at the same scope, and writes no second row
      - TST-PFM-SEC-128
      - >-
        services-platform:modules/security/tests/integration/services/membership/concurrent-grants.integration.spec.js#grants
        once and refuses the other as already_assigned
      - TST-PFM-SEC-123
      - >-
        services-platform:modules/security/tests/integration/services/membership/concurrent-grants.integration.spec.js#is
        granted, because a revoked row is history and leaves room for the grant
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
      a second grant of the same role at the same scope, and writes no second row
    category: concurrency
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a second grant of the same role at the same scope, and writes no second row
      - >-
        services-platform:modules/security/tests/integration/services/membership/concurrent-grants.integration.spec.js#grants
        once and refuses the other as already_assigned
      - >-
        services-platform:modules/security/tests/integration/services/membership/concurrent-grants.integration.spec.js#is
        granted, because a revoked row is history and leaves room for the grant
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a second grant of the same role at the same scope, and writes no second row
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a second grant of the same role at the same scope, and writes no second row
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-07
    title: Withdrawal history and administrator protection
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-07
    preconditions: Prepare an ordinary grant and the last qualifying administrator.
    steps: Withdraw each grant.
    expected: >-
      The ordinary assignment is retained as revoked history; removing the last administrator is
      refused.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
      - TST-PFM-SEC-128
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        the withdrawal that would leave the customer with no security administrator, and withdraws
        nothing
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#withdraws a grant,
        which the list then leaves out
      - TST-PFM-SEC-051
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
      the grant, keeps the row, and audits what was withdrawn
    category: invariant
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        the withdrawal that would leave the customer with no security administrator, and withdraws
        nothing
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#withdraws a grant,
        which the list then leaves out
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-08
    title: Atomic role-scope reconciliation
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-08
    preconditions: Prepare a customer-wide Read Only assignment.
    steps: 'Select two workspaces, return to customer-wide scope and try an outside-customer workspace.'
    expected: >-
      The expected two and then one scope rows remain; matching rows retain identity, revocation
      submits a flush request and the outside workspace fails.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
        the customer grant into one active grant per tenant asked for, revoking the customer grant
      - TST-PFM-SEC-130
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#keeps
        a grant that already matches by its id, revoking only the tenant left out
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#submits
        an authorizer-cache flush naming the person after a change that revoked a grant
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#refuses
        a tenant of another customer, naming it, and changes nothing
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#leaves
        a resource grant untouched, and out of the answer, when the role’s scope is set
      - TST-PFM-SEC-129
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
      the customer grant into one active grant per tenant asked for, revoking the customer grant
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
        the customer grant into one active grant per tenant asked for, revoking the customer grant
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#keeps
        a grant that already matches by its id, revoking only the tenant left out
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#submits
        an authorizer-cache flush naming the person after a change that revoked a grant
      - >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#refuses
        a tenant of another customer, naming it, and changes nothing
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#leaves
        a resource grant untouched, and out of the answer, when the role’s scope is set
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
        the customer grant into one active grant per tenant asked for, revoking the customer grant
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
        the customer grant into one active grant per tenant asked for, revoking the customer grant
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-09
    title: Deny weakening uses widening checks
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-09
    preconditions: Prepare a deny-bearing grant and an editor lacking the denied key.
    steps: 'Narrow or shorten the grant and add conditions, then extend it and remove a restriction.'
    expected: >-
      The tested weakening operations fail and are recorded; the tested strengthening operations
      succeed.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a narrowing of the scope for an editor who is not allowed the key the grant was denying
      - TST-PFM-SEC-122
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a validity window that would end a grant carrying a deny, for the same editor
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a condition added to a grant carrying a deny, for the same editor
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#allows
        an editor allowed nothing to extend the grant, because a grant that lasts longer denies for
        longer
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
      a narrowing of the scope for an editor who is not allowed the key the grant was denying
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a narrowing of the scope for an editor who is not allowed the key the grant was denying
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a validity window that would end a grant carrying a deny, for the same editor
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a condition added to a grant carrying a deny, for the same editor
      - >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#allows
        an editor allowed nothing to extend the grant, because a grant that lasts longer denies for
        longer
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a narrowing of the scope for an editor who is not allowed the key the grant was denying
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
        a narrowing of the scope for an editor who is not allowed the key the grant was denying
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-10
    title: Effective-permission attribution
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-11
    preconditions: Prepare a role with its own deny and a member without grants.
    steps: 'Explain both members’ access, then request explanation as a reader.'
    expected: >-
      Allowed keys identify their assignment, role and policy; the denied key is absent, the
      grantless member has no permissions and the reader is refused.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#answers
        the key the role allows, with the grant, the role and the policy that name it
      - TST-PFM-SEC-125
      - >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#leaves
        out a key the role's own deny takes back, although its policy still names it
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#answers nothing for
        somebody holding no grant, rather than failing
      - TST-PFM-SEC-051
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader,
        who may read the person but not explain their access
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#answers
      the key the role allows, with the grant, the role and the policy that name it
    category: rule
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#answers
        the key the role allows, with the grant, the role and the policy that name it
      - >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#leaves
        out a key the role's own deny takes back, although its policy still names it
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#answers nothing for
        somebody holding no grant, rather than failing
      - >-
        services-platform:modules/security/tests/e2e/console-users.e2e.spec.js#refuses the reader,
        who may read the person but not explain their access
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#answers
        the key the role allows, with the grant, the role and the policy that name it
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js#answers
        the key the role allows, with the grant, the role and the policy that name it
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-11
    title: Staff-only and unknown roles are unavailable
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-12
    preconditions: Prepare a customer membership.
    steps: Try staff-only roles by key and identifier and an unknown built-in key.
    expected: >-
      No grant is written; unavailable roles return not found and unknown-key refusal identifies the
      requested key.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a staff-only role for a customer's user as not found, by its key or its id, and grants
        nothing
      - TST-PFM-SEC-128
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a built-in role key Rhythm does not have, naming it, and grants nothing
      - >-
        services-platform:modules/security/tests/e2e/definitions.e2e.spec.js#refuses to grant a
        staff-only role to a member, by its key or its id, and grants nothing
      - TST-PFM-SEC-052
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
      a staff-only role for a customer's user as not found, by its key or its id, and grants nothing
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a staff-only role for a customer's user as not found, by its key or its id, and grants
        nothing
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a built-in role key Rhythm does not have, naming it, and grants nothing
      - >-
        services-platform:modules/security/tests/e2e/definitions.e2e.spec.js#refuses to grant a
        staff-only role to a member, by its key or its id, and grants nothing
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a staff-only role for a customer's user as not found, by its key or its id, and grants
        nothing
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
        a staff-only role for a customer's user as not found, by its key or its id, and grants
        nothing
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-12
    title: Membership and assignment history
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-13
    preconditions: Prepare a member.
    steps: 'Grant, move and withdraw a role, then inspect history.'
    expected: >-
      Membership entries identify each changed assignment and assignment history carries the
      relevant widening classification.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#lists
        the grant, the move and the withdrawal, each naming the grant it was about
      - TST-PFM-SEC-126
      - >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#still
        writes the grant's own row, the one marked widening
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#lists
      the grant, the move and the withdrawal, each naming the grant it was about
    category: interaction
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#lists
        the grant, the move and the withdrawal, each naming the grant it was about
      - >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#still
        writes the grant's own row, the one marked widening
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#lists
        the grant, the move and the withdrawal, each naming the grant it was about
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/membership/grant-history.integration.spec.js#lists
        the grant, the move and the withdrawal, each naming the grant it was about
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-13
    title: Grant presentation and role membership
    level: browser
    proves:
      - AS-JNY-PFM-SEC-003-02
      - AS-JNY-PFM-SEC-004-03
    preconditions: Prepare a security administrator and colleague.
    steps: 'Grant from the user page, inspect the role’s Members view and withdraw the grant.'
    expected: >-
      Presentation names the grantor and lists the colleague while assigned; withdrawal removes that
      membership from the live view.
    negative: false
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        react-console:e2e/security/users.spec.ts#gives a role, says who granted it, and takes it
        away again
      - TST-PFM-SEC-035
      - >-
        react-console:e2e/security/roles.spec.ts#is given to a person, who then shows on its Members
        tab
      - TST-PFM-SEC-034
    provenance: test
    source: >-
      react-console:e2e/security/users.spec.ts#gives a role, says who granted it, and takes it away
      again
    category: interaction
    test_references:
      - >-
        react-console:e2e/security/users.spec.ts#gives a role, says who granted it, and takes it
        away again
      - >-
        react-console:e2e/security/roles.spec.ts#is given to a person, who then shows on its Members
        tab
    action_evidence:
      provenance: test
      source: >-
        react-console:e2e/security/users.spec.ts#gives a role, says who granted it, and takes it
        away again
    assertion_evidence:
      provenance: test
      source: >-
        react-console:e2e/security/users.spec.ts#gives a role, says who granted it, and takes it
        away again
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-010-14
    title: 'Role assignment: scheduled to live'
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-05
    state_model: STATE-PFM-SEC-006
    transition:
      from: scheduled
      to: live
      index: 2
    preconditions: >-
      Prepare a run-owned role assignment in scheduled state with the authority and inputs required
      by the cited transition.
    steps:
      - do: Advance the clock to the assignment’s start and read effective permissions.
        expect: >-
          After the stated action, observe the role assignment in live state. This case does not
          require an additional history event unless the transition explicitly defines one.
        provenance: proposed
        source:
          - 'services-platform:modules/security/db/security-assignments.table.ts (valid_from)'
          - >-
            services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
            services/persistence/assignments.ts; services/membership/manage-assignments.ts;
            services/invitations/widening.ts.
    expected: >-
      After the stated action, observe the role assignment in live state. This case does not require
      an additional history event unless the transition explicitly defines one.
    negative: false
    data_needs: >-
      A disposable customer, workspace and named actor; a controllable clock where the transition
      depends on time.
    bound_to: []
    provenance: proposed
    source:
      - 'services-platform:modules/security/db/security-assignments.table.ts (valid_from)'
      - >-
        services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
        services/persistence/assignments.ts; services/membership/manage-assignments.ts;
        services/invitations/widening.ts.
    guard_cases: Request-time evaluation derives usability without changing stored status.
    side_effects_to_verify: []
    transition_assertion:
      state: live
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_from)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_from)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_from)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-15
    title: 'Role assignment: live to ended'
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-05
    state_model: STATE-PFM-SEC-006
    transition:
      from: live
      to: ended
      index: 3
    preconditions: >-
      Prepare a run-owned role assignment in live state with the authority and inputs required by
      the cited transition.
    steps:
      - do: Advance the clock to the assignment’s end and attempt the previously allowed operation.
        expect: >-
          After the stated action, observe the role assignment in ended state. This case does not
          require an additional history event unless the transition explicitly defines one.
        provenance: proposed
        source:
          - 'services-platform:modules/security/db/security-assignments.table.ts (valid_until)'
          - >-
            services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
            services/persistence/assignments.ts; services/membership/manage-assignments.ts;
            services/invitations/widening.ts.
    expected: >-
      After the stated action, observe the role assignment in ended state. This case does not
      require an additional history event unless the transition explicitly defines one.
    negative: false
    data_needs: >-
      A disposable customer, workspace and named actor; a controllable clock where the transition
      depends on time.
    bound_to: []
    provenance: proposed
    source:
      - 'services-platform:modules/security/db/security-assignments.table.ts (valid_until)'
      - >-
        services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
        services/persistence/assignments.ts; services/membership/manage-assignments.ts;
        services/invitations/widening.ts.
    guard_cases: Request-time evaluation derives expiry without a status write.
    side_effects_to_verify:
      - An earlier signed context must respect the assignment’s end
    transition_assertion:
      state: ended
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_until)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_until)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/db/security-assignments.table.ts (valid_until)'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-16
    title: 'Role assignment: live to revoked'
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-07
    state_model: STATE-PFM-SEC-006
    transition:
      from: live
      to: revoked
      index: 4
    preconditions: >-
      Prepare a run-owned role assignment in live state with the authority and inputs required by
      the cited transition.
    steps:
      - do: Withdraw the active assignment and inspect its retained row and current access.
        expect: >-
          After the stated action, observe the role assignment in revoked state. This case does not
          require an additional history event unless the transition explicitly defines one.
        provenance: proposed
        source:
          - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
          - >-
            services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
            services/persistence/assignments.ts; services/membership/manage-assignments.ts;
            services/invitations/widening.ts.
    expected: >-
      After the stated action, observe the role assignment in revoked state. This case does not
      require an additional history event unless the transition explicitly defines one.
    negative: false
    data_needs: >-
      A disposable customer, workspace and named actor; a controllable clock where the transition
      depends on time.
    bound_to: []
    provenance: proposed
    source:
      - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
      - >-
        services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
        services/persistence/assignments.ts; services/membership/manage-assignments.ts;
        services/invitations/widening.ts.
    guard_cases: 'Manual withdrawal requires scope authority, refuses derived rows and passes lockout checks.'
    side_effects_to_verify:
      - Record assignment and membership history
      - Submit a post-commit flush request
    transition_assertion:
      state: revoked
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-17
    title: 'Role assignment: scheduled to revoked'
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-07
    state_model: STATE-PFM-SEC-006
    transition:
      from: scheduled
      to: revoked
      index: 6
    preconditions: >-
      Prepare a run-owned role assignment in scheduled state with the authority and inputs required
      by the cited transition.
    steps:
      - do: Withdraw a future assignment before its start and inspect the retained row.
        expect: >-
          After the stated action, observe the role assignment in revoked state. This case does not
          require an additional history event unless the transition explicitly defines one.
        provenance: proposed
        source:
          - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
          - >-
            services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
            services/persistence/assignments.ts; services/membership/manage-assignments.ts;
            services/invitations/widening.ts.
    expected: >-
      After the stated action, observe the role assignment in revoked state. This case does not
      require an additional history event unless the transition explicitly defines one.
    negative: false
    data_needs: >-
      A disposable customer, workspace and named actor; a controllable clock where the transition
      depends on time.
    bound_to: []
    provenance: proposed
    source:
      - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
      - >-
        services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
        services/persistence/assignments.ts; services/membership/manage-assignments.ts;
        services/invitations/widening.ts.
    guard_cases: Apply the manual withdrawal checks used for a currently usable row.
    side_effects_to_verify: []
    transition_assertion:
      state: revoked
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:revokeAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-18
    title: 'Role assignment: live to live'
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-09
    state_model: STATE-PFM-SEC-006
    transition:
      from: live
      to: live
      index: 7
    preconditions: >-
      Prepare a run-owned role assignment in live state with the authority and inputs required by
      the cited transition.
    steps:
      - do: >-
          Amend the assignment window within delegated authority and read its identity, version and
          history.
        expect: >-
          After the stated action, observe the role assignment in live state. This case does not
          require an additional history event unless the transition explicitly defines one.
        provenance: proposed
        source:
          - 'services-platform:modules/security/services/persistence/assignments.ts:amendAssignment'
          - >-
            services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
            services/persistence/assignments.ts; services/membership/manage-assignments.ts;
            services/invitations/widening.ts.
    expected: >-
      After the stated action, observe the role assignment in live state. This case does not require
      an additional history event unless the transition explicitly defines one.
    negative: false
    data_needs: >-
      A disposable customer, workspace and named actor; a controllable clock where the transition
      depends on time.
    bound_to: []
    provenance: proposed
    source:
      - 'services-platform:modules/security/services/persistence/assignments.ts:amendAssignment'
      - >-
        services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
        services/persistence/assignments.ts; services/membership/manage-assignments.ts;
        services/invitations/widening.ts.
    guard_cases: >-
      Widening and weakened denies need delegation authority; derived changes require the source
      capability.
    side_effects_to_verify:
      - Record widening or narrowing in history
    transition_assertion:
      state: live
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:amendAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:amendAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/persistence/assignments.ts:amendAssignment'
        - >-
          services-platform at ad3f6a65: modules/security/db/security-assignments.table.ts;
          services/persistence/assignments.ts; services/membership/manage-assignments.ts;
          services/invitations/widening.ts.
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-19
    title: Assign roles within a controlled scope with offer and manage an invitation
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-008-05
      - BR-CAP-PFM-SEC-010-02
    exercises:
      - CAP-PFM-SEC-010
      - CAP-PFM-SEC-008
    preconditions: An access manager can delegate one role but lacks another role’s powers.
    steps: 'Invite with the permitted role, then attempt another invitation carrying the unavailable role.'
    expected: The first creates pending assignments; the second creates neither invitation nor assignment.
    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/tests/e2e/invitations.e2e.spec.js#refuses a role that
        grants more than the caller may do there, and names what was missing
      - >-
        services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
        a scope the caller does not manage, and says which scope rather than which role
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
          grants more than the caller may do there, and names what was missing
        - >-
          services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
          a scope the caller does not manage, and says which scope rather than which role
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
          grants more than the caller may do there, and names what was missing
        - >-
          services-platform:modules/security/tests/unit/services/invitations/widening.spec.ts#refuses
          a scope the caller does not manage, and says which scope rather than which role
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-20
    title: Assign roles within a controlled scope with maintain console memberships
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-009-07
      - BR-CAP-PFM-SEC-010-07
    exercises:
      - CAP-PFM-SEC-010
      - CAP-PFM-SEC-009
    preconditions: Only one active qualifying customer administrator remains.
    steps: >-
      Try withdrawing the qualifying assignment and suspending its holder; read membership and
      assignments after each refusal.
    expected: Both operations leave the active administrator and qualifying assignment intact.
    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/services/membership/lockout.ts:assertNotLockedOut'
      - >-
        services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/membership/lockout.ts:assertNotLockedOut'
        - >-
          services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
          the grant, keeps the row, and audits what was withdrawn
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/security/services/membership/lockout.ts:assertNotLockedOut'
        - >-
          services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
          the grant, keeps the row, and audits what was withdrawn
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-21
    title: >-
      Assign roles within a controlled scope with model customer responsibilities with roles and
      policies
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-07
      - BR-CAP-PFM-SEC-012-09
    exercises:
      - CAP-PFM-SEC-010
      - CAP-PFM-SEC-012
    preconditions: A custom role is assigned to an active member.
    steps: 'Try archiving the role, withdraw its assignment, and archive again.'
    expected: >-
      The held role cannot be archived; after withdrawal it can leave the active list while history
      remains.
    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/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
        the grant, keeps the row, and audits what was withdrawn
      - >-
        services-platform:modules/security/tests/integration/services/definitions/manage-roles.integration.spec.js#refuses
        to archive a role somebody still holds, names them, and leaves it active
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
          the grant, keeps the row, and audits what was withdrawn
        - >-
          services-platform:modules/security/tests/integration/services/definitions/manage-roles.integration.spec.js#refuses
          to archive a role somebody still holds, names them, and leaves it active
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#revokes
          the grant, keeps the row, and audits what was withdrawn
        - >-
          services-platform:modules/security/tests/integration/services/definitions/manage-roles.integration.spec.js#refuses
          to archive a role somebody still holds, names them, and leaves it active
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-010-22
    title: >-
      Assign roles within a controlled scope with derive member access from appointments and
      eligibility
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-08
      - BR-CAP-PFM-SEC-014-04
    exercises:
      - CAP-PFM-SEC-010
      - CAP-PFM-SEC-014
    preconditions: One member has a manual grant and a linked contact has source-owned access.
    steps: 'Change the manual role scope, try editing the derived assignment, then synchronize its source.'
    expected: >-
      Manual scope changes preserve the derived row; direct derived editing is refused and source
      synchronization alone changes it.
    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/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
        the customer grant into one active grant per tenant asked for, revoking the customer grant
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        an amendment 409 derived_assignment, naming where to change it
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
          the customer grant into one active grant per tenant asked for, revoking the customer grant
        - >-
          services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
          an amendment 409 derived_assignment, naming where to change it
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js#turns
          the customer grant into one active grant per tenant asked for, revoking the customer grant
        - >-
          services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
          an amendment 409 derived_assignment, naming where to change it
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
evidence:
  - >-
    Pinned services-platform references for the structured rows:
    modules/security/tests/e2e/console-users.e2e.spec.js;
    modules/security/tests/e2e/definitions.e2e.spec.js;
    modules/security/tests/e2e/invitations.e2e.spec.js;
    modules/security/tests/e2e/resource-scoped-grants.e2e.spec.js;
    modules/security/tests/integration/services/membership/amend-grant.integration.spec.js;
    modules/security/tests/integration/services/membership/concurrent-grants.integration.spec.js;
    modules/security/tests/integration/services/membership/effective-permissions.integration.spec.js;
    modules/security/tests/integration/services/membership/grant-history.integration.spec.js;
    modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js;
    modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js;
    modules/security/tests/integration/services/membership/set-role-scope.integration.spec.js;
    modules/security/tests/unit/services/invitations/widening.spec.ts.
  - >-
    Pinned react-console references for the structured rows: e2e/security/roles.spec.ts;
    e2e/security/users.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-067'
  permission: covered
  edge: 'gap: FND-PFM-SEC-067'
  failure: 'gap: FND-PFM-SEC-067'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  validation: No scenario isolates this category. The referenced finding retains the missing design proof.
  edge: No scenario isolates this category. The referenced finding retains the missing design proof.
  failure: No scenario isolates this category. The referenced finding retains the missing design proof.
invariant_proofs:
  - scenario: ATS-PFM-SEC-010-02
    proves:
      - INV-DOM-PFM-SEC-003-11
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#refuses a role that
      grants more than the caller may do there, and names what was missing
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-010-03
    proves:
      - INV-DOM-PFM-SEC-003-07
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#grants
      a role on a draft custom object, which is live though not yet active
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-010-04
    proves:
      - INV-DOM-PFM-SEC-003-07
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/amend-grant.integration.spec.js#refuses
      a window that would have ended the staff grant, naming where such a change is made
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-010-05
    proves:
      - INV-DOM-PFM-SEC-003-08
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#grants
      a role at customer scope, active at once for an active person, and audits it as widening
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-010-06
    proves:
      - INV-DOM-PFM-SEC-003-09
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/membership/manage-assignments.integration.spec.js#refuses
      a second grant of the same role at the same scope, and writes no second row
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
permission_proof_review:
  - key: platform.security-access.manage
    scenario_candidates:
      - ATS-PFM-SEC-010-01
      - ATS-PFM-SEC-010-02
      - ATS-PFM-SEC-010-04
      - ATS-PFM-SEC-010-09
      - ATS-PFM-SEC-010-11
      - ATS-PFM-SEC-010-18
      - ATS-PFM-SEC-010-19
    qualification: >-
      These are candidate scenarios for the authority boundary, not an exhaustive allowed/refused
      matrix for this key. The finding retains combinations not established by the executable
      assertions.
    gap: FND-PFM-SEC-067
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-010-give-and-take-away-roles-at-the-right-scope.md
  - key: platform.security-access.explain
    scenario_candidates:
      - ATS-PFM-SEC-010-01
      - ATS-PFM-SEC-010-02
      - ATS-PFM-SEC-010-04
      - ATS-PFM-SEC-010-09
      - ATS-PFM-SEC-010-11
      - ATS-PFM-SEC-010-18
      - ATS-PFM-SEC-010-19
    qualification: >-
      These are candidate scenarios for the authority boundary, not an exhaustive allowed/refused
      matrix for this key. The finding retains combinations not established by the executable
      assertions.
    gap: FND-PFM-SEC-067
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-010-give-and-take-away-roles-at-the-right-scope.md
  - key: platform.users.read
    scenario_candidates:
      - ATS-PFM-SEC-010-01
      - ATS-PFM-SEC-010-02
      - ATS-PFM-SEC-010-04
      - ATS-PFM-SEC-010-09
      - ATS-PFM-SEC-010-11
      - ATS-PFM-SEC-010-18
      - ATS-PFM-SEC-010-19
    qualification: >-
      These are candidate scenarios for the authority boundary, not an exhaustive allowed/refused
      matrix for this key. The finding retains combinations not established by the executable
      assertions.
    gap: FND-PFM-SEC-067
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-010-give-and-take-away-roles-at-the-right-scope.md
examiner: codex
examined_at: '2026-10-23'
final_text: OpenAI
---
# Assign roles within a controlled scope: Capability Test Specification

## Coverage

| Required rule | Scenario or finding |
|---|---|
| `BR-CAP-PFM-SEC-010-01` | `ATS-PFM-SEC-010-01` |
| `BR-CAP-PFM-SEC-010-02` | `ATS-PFM-SEC-010-02`, `ATS-PFM-SEC-010-19` |
| `BR-CAP-PFM-SEC-010-03` | `ATS-PFM-SEC-010-03` |
| `BR-CAP-PFM-SEC-010-04` | `ATS-PFM-SEC-010-04` |
| `BR-CAP-PFM-SEC-010-05` | `ATS-PFM-SEC-010-05`, `ATS-PFM-SEC-010-14`, `ATS-PFM-SEC-010-15` |
| `BR-CAP-PFM-SEC-010-06` | `ATS-PFM-SEC-010-06` |
| `BR-CAP-PFM-SEC-010-07` | `ATS-PFM-SEC-010-07`, `ATS-PFM-SEC-010-16`, `ATS-PFM-SEC-010-17`, `ATS-PFM-SEC-010-20`, `ATS-PFM-SEC-010-21` |
| `BR-CAP-PFM-SEC-010-08` | `ATS-PFM-SEC-010-08`, `ATS-PFM-SEC-010-22` |
| `BR-CAP-PFM-SEC-010-09` | `ATS-PFM-SEC-010-09`, `ATS-PFM-SEC-010-18` |
| `BR-CAP-PFM-SEC-010-11` | `ATS-PFM-SEC-010-10` |
| `BR-CAP-PFM-SEC-010-12` | `ATS-PFM-SEC-010-11` |
| `BR-CAP-PFM-SEC-010-13` | `ATS-PFM-SEC-010-12` |

The category map records specified proof. A covered category can contain proposed scenarios; it does not mean the checks have run. The bindings identify individual executable contributions, while findings retain missing rules, permissions, transitions and assertions.

## Interactions With Related Capabilities

The interaction scenarios connect this capability to the related actions and workflow handoffs named in their proof references. Each case observes the outcome after the connected action changes records or available authority. A collection of separately bound checks does not establish an ordered feature journey.

Source revisions and assertion references are recorded in frontmatter. Steps and expected observations carry separate evidence annotations. Proposed checks remain distinct from executable test statements, and static review supplies no run result.
