---
id: ATS-PFM-SEC-014
scope: CAP-PFM-SEC-014
product_area: platform
module: security
revision: 3
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
scenarios:
  - id: ATS-PFM-SEC-014-01
    title: Derived access uses the contact claim
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-01
      - INV-DOM-PFM-SEC-003-06
    preconditions: Prepare a derived contact assignment and a linked portal member.
    steps: 'Evaluate the member, remove the link and evaluate a fresh member context.'
    expected: >-
      The linked claim receives scoped access; the newly unlinked claim does not. The test does not
      establish immediate replacement of a cached contact claim.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by
        the authorizer inside its validity, for the tenant of the grant
      - TST-PFM-SEC-053
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused once
        the member is no longer linked to the contact, so the link is what carries the grant
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses another
        unit, which the grant does not name
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by the
      authorizer inside its validity, for the tenant of the grant
    category: invariant
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by
        the authorizer inside its validity, for the tenant of the grant
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused once
        the member is no longer linked to the contact, so the link is what carries the grant
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses another
        unit, which the grant does not name
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by
        the authorizer inside its validity, for the tenant of the grant
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by
        the authorizer inside its validity, for the tenant of the 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-014-02
    title: Desired-set validation is atomic
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-02
      - INV-DOM-PFM-SEC-003-07
    preconditions: Prepare a desired set containing one invalid entry.
    steps: >-
      Try unsupported source, customer/platform scope, another workspace, archived role and
      foreign-customer role.
    expected: Each request fails without inserting any desired entry.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a source that is not on its allow-list
      - TST-PFM-SEC-135
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a customer scope
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a platform scope
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a scope in another tenant
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        an archived role, which grants nothing
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a role of another customer
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a whole desired set when one entry is refused, inserting none of the others
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
      a source that is not on its allow-list
    category: validation
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a source that is not on its allow-list
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a customer scope
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a platform scope
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a scope in another tenant
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        an archived role, which grants nothing
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a role of another customer
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a whole desired set when one entry is refused, inserting none of the others
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a source that is not on its allow-list
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a source that is not on its allow-list
    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-014-03
    title: Exact and repeatable synchronization
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-03
      - INV-DOM-PFM-SEC-003-09
    preconditions: Prepare an existing derived grant.
    steps: 'Synchronize the same set, extend its validity and synchronize an empty set.'
    expected: >-
      Identical synchronization changes nothing; extension retains identity and records widening,
      while removal records revocation.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
        nothing and writes nothing when called again with the same desired set
      - TST-PFM-SEC-135
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#extends
        the window in place, one version on, as a widening amendment
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#revokes
        every row of the key when the desired set is empty, and answers each
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
      nothing and writes nothing when called again with the same desired set
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
        nothing and writes nothing when called again with the same desired set
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#extends
        the window in place, one version on, as a widening amendment
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#revokes
        every row of the key when the desired set is empty, and answers each
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
        nothing and writes nothing when called again with the same desired set
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
        nothing and writes nothing when called again with the same desired set
    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-014-04
    title: Manual administration cannot alter derivation
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-04
      - INV-DOM-PFM-SEC-003-10
    preconditions: Prepare a derived assignment.
    steps: Attempt ordinary amendment and revocation.
    expected: Both return 409 with source guidance and leave the assignment intact.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        an amendment 409 derived_assignment, naming where to change it
      - TST-PFM-SEC-135
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a revoke 409 derived_assignment
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#leaves
        the row exactly as the reconciler wrote it after both refusals
      - TST-PFM-SEC-103
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
      an amendment 409 derived_assignment, naming where to change it
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        an amendment 409 derived_assignment, naming where to change it
      - >-
        services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
        a revoke 409 derived_assignment
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#leaves
        the row exactly as the reconciler wrote it after both refusals
    action_evidence:
      provenance: test
      source: >-
        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: test
      source: >-
        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: >-
      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-014-05
    title: Expiry against an earlier signed context
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-05
    preconditions: Prepare a derived assignment reaching its end time.
    steps: Call the dispatcher after expiry with a still-valid context signed before it.
    expected: Request-time evaluation refuses the expired assignment.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses the
        same still-unexpired context once valid_until has passed, because validity is read per
        request
      - TST-PFM-SEC-053
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused by
        the authorizer itself after valid_until, for want of a grant in force
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses the same
      still-unexpired context once valid_until has passed, because validity is read per request
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses the
        same still-unexpired context once valid_until has passed, because validity is read per
        request
      - >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused by
        the authorizer itself after valid_until, for want of a grant in force
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses the
        same still-unexpired context once valid_until has passed, because validity is read per
        request
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#refuses the
        same still-unexpired context once valid_until has passed, because validity is read per
        request
    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-014-06
    title: Eligibility role-binding restrictions
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-07
    preconditions: Prepare custom/built-in roles and bindings of different kinds.
    steps: 'Bind them, try four-minute, five-minute and thirty-one-day intervals and reuse a binding.'
    expected: >-
      The valid custom binding succeeds; built-ins, wrong kinds, out-of-range intervals and
      duplicate bindings fail.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a built-in
        role as a conflict, because no tenant’s rule may assign Rhythm’s role
      - TST-PFM-SEC-064
      - >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a binding
        of another consumer kind as a conflict, naming the kind
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a TTL under five minutes
      - TST-PFM-SEC-075
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a TTL over thirty days
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a second role naming the same binding
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a built-in
      role as a conflict, because no tenant’s rule may assign Rhythm’s role
    category: rule
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a built-in
        role as a conflict, because no tenant’s rule may assign Rhythm’s role
      - >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a binding
        of another consumer kind as a conflict, naming the kind
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a TTL under five minutes
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a TTL over thirty days
      - >-
        services-platform:modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js#refuses
        a second role naming the same binding
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a built-in
        role as a conflict, because no tenant’s rule may assign Rhythm’s role
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/role-eligibility.e2e.spec.js#refuses a built-in
        role as a conflict, because no tenant’s rule may assign Rhythm’s role
    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-014-07
    title: Eligibility outcomes preserve manual work
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-08
    preconditions: >-
      Prepare eligible, ineligible and indeterminate contacts, a manual assignment and an
      organization.
    steps: Reconcile repeatedly under each outcome.
    expected: >-
      Derived grants are created, confirmed or revoked as appropriate; indeterminate and manual rows
      remain intact and organizations receive no derived grants.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#creates
        one active, tenant-scoped, contact-keyed row of the role through the function, keyed by
        binding and contact
      - TST-PFM-SEC-103
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#revokes
        it through the function when the subject is no longer eligible, keeping the row as history
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#leaves
        the key exactly as it was and names the role it could not decide
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#never
        touches the manual assignment: no derivation, no window, its first version
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#evaluates
        nothing and creates nothing, because eligibility derives roles for contacts only
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#creates
      one active, tenant-scoped, contact-keyed row of the role through the function, keyed by
      binding and contact
    category: interaction
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#creates
        one active, tenant-scoped, contact-keyed row of the role through the function, keyed by
        binding and contact
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#revokes
        it through the function when the subject is no longer eligible, keeping the row as history
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#leaves
        the key exactly as it was and names the role it could not decide
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#never
        touches the manual assignment: no derivation, no window, its first version
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#evaluates
        nothing and creates nothing, because eligibility derives roles for contacts only
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#creates
        one active, tenant-scoped, contact-keyed row of the role through the function, keyed by
        binding and contact
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js#creates
        one active, tenant-scoped, contact-keyed row of the role through the function, keyed by
        binding and contact
    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-014-08
    title: Reconciliation before portal password tokens
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-09
    preconditions: Prepare a linked eligible portal member and a failing evaluator.
    steps: Sign in successfully under each evaluator outcome.
    expected: >-
      Successful evaluation produces its assignment before tokens return; evaluator failure still
      returns tokens without invented grants.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#has
        created the derived assignment of the rule-bound role by the time it answers
      - TST-PFM-SEC-104
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#signs
        a member in even when the evaluator fails, which it was asked
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#has
      created the derived assignment of the rule-bound role by the time it answers
    category: interaction
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#has
        created the derived assignment of the rule-bound role by the time it answers
      - >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#signs
        a member in even when the evaluator fails, which it was asked
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#has
        created the derived assignment of the rule-bound role by the time it answers
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#has
        created the derived assignment of the rule-bound role by the time it answers
    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-014-09
    title: Bounded queued reconciliation and redelivery
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-11
    preconditions: Prepare an authorized staff caller.
    steps: 'Request 101 contacts, queue the same contact twice and redeliver its work.'
    expected: >-
      The excessive request fails, one waiting item is retained and redelivery creates no duplicate
      assignment.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#refuses
        more than 100 subjects
      - TST-PFM-SEC-113
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#queues
        one row and answers its work id, and a second request for the waiting subject answers the
        same id
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#grants
        nothing twice on a redelivery: the same row, confirmed again
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#refuses
      more than 100 subjects
    category: concurrency
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#refuses
        more than 100 subjects
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#queues
        one row and answers its work id, and a second request for the waiting subject answers the
        same id
      - >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#grants
        nothing twice on a redelivery: the same row, confirmed again
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#refuses
        more than 100 subjects
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js#refuses
        more than 100 subjects
    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-014-10
    title: Eligibility binding ownership and kind
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-12
    preconditions: Prepare bindings from two customers and multiple consumer kinds.
    steps: >-
      Save policy conditions using the customer’s assignment-condition binding, a foreign binding
      and a wrong-kind binding.
    expected: Only the valid owned reference is stored; other references are refused.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#stores
        a rule naming a security.assignment_condition binding of one of the customer’s tenants
      - TST-PFM-SEC-099
      - >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#refuses
        a binding of another customer’s tenant, naming it, and stores no policy
      - >-
        services-platform:modules/security/tests/e2e/eligibility-conditions.e2e.spec.js#refuses the
        customer’s administrator a binding of another kind as eligibility_binding_unknown, naming it
      - TST-PFM-SEC-054
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#stores
      a rule naming a security.assignment_condition binding of one of the customer’s tenants
    category: validation
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#stores
        a rule naming a security.assignment_condition binding of one of the customer’s tenants
      - >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#refuses
        a binding of another customer’s tenant, naming it, and stores no policy
      - >-
        services-platform:modules/security/tests/e2e/eligibility-conditions.e2e.spec.js#refuses the
        customer’s administrator a binding of another kind as eligibility_binding_unknown, naming it
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#stores
        a rule naming a security.assignment_condition binding of one of the customer’s tenants
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js#stores
        a rule naming a security.assignment_condition binding of one of the customer’s tenants
    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-014-11
    title: >-
      Derive member access from appointments and eligibility with assign roles within a controlled
      scope
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-08
      - BR-CAP-PFM-SEC-014-04
    exercises:
      - CAP-PFM-SEC-014
      - CAP-PFM-SEC-010
    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.
  - id: ATS-PFM-SEC-014-12
    title: >-
      Derive member access from appointments and eligibility with register and authenticate portal
      members
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-09
      - BR-CAP-PFM-SEC-019-05
    exercises:
      - CAP-PFM-SEC-014
      - CAP-PFM-SEC-019
    preconditions: A confirmed portal member links to a contact with an eligibility-derived role.
    steps: 'Sign in by password, change eligibility, and sign in again; repeat with an evaluator failure.'
    expected: >-
      Reconciliation updates derived access before tokens return; evaluator failure is logged
      without blocking valid sign-in.
    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/derived-assignments/sign-in-reconcile.integration.spec.js#signs
        a member in even when the evaluator fails, which it was asked
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
        the Portal Member role, active, at the tenant’s scope
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#signs
          a member in even when the evaluator fails, which it was asked
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js#signs
          a member in even when the evaluator fails, which it was asked
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-014-13
    title: >-
      Derive member access from appointments and eligibility with match portal memberships to crm
      contacts
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-014-01
      - BR-CAP-PFM-SEC-020-08
    exercises:
      - CAP-PFM-SEC-014
      - CAP-PFM-SEC-020
    preconditions: An unlinked portal member has a contact carrying derived authority.
    steps: Complete contact possession proof and request a fresh authorization context.
    expected: >-
      The linked contact claim enables its applicable derived assignments; a console context carries
      no contact claim.
    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/e2e/derived-assignments.e2e.spec.js#is refused once
        the member is no longer linked to the contact, so the link is what carries the grant
      - 'services-platform:docs/security/portal-contact-claim.md#When a change is seen'
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused
          once the member is no longer linked to the contact, so the link is what carries the grant
        - 'services-platform:docs/security/portal-contact-claim.md#When a change is seen'
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is refused
          once the member is no longer linked to the contact, so the link is what carries the grant
        - 'services-platform:docs/security/portal-contact-claim.md#When a change is seen'
    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/derived-assignments.e2e.spec.js;
    modules/security/tests/e2e/eligibility-conditions.e2e.spec.js;
    modules/security/tests/e2e/role-eligibility.e2e.spec.js;
    modules/security/tests/integration/db/security-roles-eligibility.table.integration.spec.js;
    modules/security/tests/integration/services/definitions/eligibility-conditions.integration.spec.js;
    modules/security/tests/integration/services/derived-assignments/reconcile.integration.spec.js;
    modules/security/tests/integration/services/derived-assignments/sign-in-reconcile.integration.spec.js;
    modules/security/tests/integration/services/dispatch/derived-assignment-reconciles.integration.spec.js;
    modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js.
  - >-
    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: 'gap: FND-PFM-SEC-071'
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-PFM-SEC-071'
  failure: 'gap: FND-PFM-SEC-071'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  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.
  rule: >-
    At least one required rule has no scenario. The rule table and finding identify the outstanding
    proof.
invariant_proofs:
  - scenario: ATS-PFM-SEC-014-01
    proves:
      - INV-DOM-PFM-SEC-003-06
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/derived-assignments.e2e.spec.js#is allowed by the
      authorizer inside its validity, for the tenant of the grant
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-014-02
    proves:
      - INV-DOM-PFM-SEC-003-07
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
      a source that is not on its allow-list
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-014-03
    proves:
      - INV-DOM-PFM-SEC-003-09
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#changes
      nothing and writes nothing when called again with the same desired set
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-014-04
    proves:
      - INV-DOM-PFM-SEC-003-10
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/persistence/assignments.derived.integration.spec.js#refuses
      an amendment 409 derived_assignment, naming where to change it
    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-roles.manage
    scenario_candidates:
      - ATS-PFM-SEC-014-04
      - ATS-PFM-SEC-014-11
    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-071
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-014-grant-roles-automatically-from-appointments-and-eligibility.md
  - key: platform.eligibility.reconcile
    scenario_candidates:
      - ATS-PFM-SEC-014-04
      - ATS-PFM-SEC-014-11
    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-071
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-014-grant-roles-automatically-from-appointments-and-eligibility.md
examiner: codex
examined_at: '2026-10-23'
final_text: OpenAI
---
# Derive member access from appointments and eligibility: Capability Test Specification

## Coverage

| Required rule | Scenario or finding |
|---|---|
| `BR-CAP-PFM-SEC-014-01` | `ATS-PFM-SEC-014-01`, `ATS-PFM-SEC-014-13` |
| `BR-CAP-PFM-SEC-014-02` | `ATS-PFM-SEC-014-02` |
| `BR-CAP-PFM-SEC-014-03` | `ATS-PFM-SEC-014-03` |
| `BR-CAP-PFM-SEC-014-04` | `ATS-PFM-SEC-014-04`, `ATS-PFM-SEC-014-11` |
| `BR-CAP-PFM-SEC-014-05` | `ATS-PFM-SEC-014-05` |
| `BR-CAP-PFM-SEC-014-07` | `ATS-PFM-SEC-014-06` |
| `BR-CAP-PFM-SEC-014-08` | `ATS-PFM-SEC-014-07` |
| `BR-CAP-PFM-SEC-014-09` | `ATS-PFM-SEC-014-08`, `ATS-PFM-SEC-014-12` |
| `BR-CAP-PFM-SEC-014-11` | `ATS-PFM-SEC-014-09` |
| `BR-CAP-PFM-SEC-014-12` | `ATS-PFM-SEC-014-10` |
| `BR-CAP-PFM-SEC-014-13` | no scenario: `FND-PFM-SEC-71` |

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.
