---
id: FAS-PFM-SEC-06
title: Portal Membership and Identity acceptance
feature: FEAT-PFM-SEC-06
product_area: platform
module: security
revision: 2
status: proposed
product_intent: proposed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
capabilities:
  - CAP-PFM-SEC-019
  - CAP-PFM-SEC-020
scenarios:
  - id: FAS-PFM-SEC-06-01
    title: Confirm signup before matching a portal member to a contact
    kind: behaviour
    exercises:
      - CAP-PFM-SEC-019
      - CAP-PFM-SEC-020
    relies_on:
      - ATS-PFM-SEC-019-04
      - ATS-PFM-SEC-020-01
    preconditions: Prepare an active workspace portal pool and a new mailbox awaiting confirmation.
    steps:
      - do: >-
          Confirm signup, request contact matching and read the created membership and matching
          result.
        expect: 'One active membership exists, and contact matching remains within that member’s workspace.'
        provenance: proposed
        source:
          - >-
            services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
            the Portal Member role, active, at the tenant’s scope
          - >-
            services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#links
            the one contact holding the member’s own address without asking
    expected: 'One active membership exists, and contact matching remains within that member’s workspace.'
    negative: false
    data_needs: >-
      Disposable customer, workspace and membership fixtures owned by the run; fixed clocks, failure
      controls or provider prerequisites as required by the case.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
        the Portal Member role, active, at the tenant’s scope
      - >-
        services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#links
        the one contact holding the member’s own address without asking
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#links
          the one contact holding the member’s own address without asking
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#links
          the one contact holding the member’s own address without asking
    execution_gap: FND-PFM-SEC-078
  - id: FAS-PFM-SEC-06-02
    title: Fresh authorization uses the verified contact link
    kind: interaction
    exercises:
      - CAP-PFM-SEC-019
      - CAP-PFM-SEC-020
    relies_on:
      - ATS-PFM-SEC-019-08
      - ATS-PFM-SEC-020-06
      - ATS-PFM-SEC-020-08
    preconditions: Prepare an active portal member and a selected contact with deliverable possession proof.
    steps:
      - do: >-
          Complete verification, sign in again and inspect fresh authorization in the intended
          workspace and separate identity boundaries.
        expect: >-
          The fresh portal context contains the current linked contact; another workspace or console
          identity does not inherit it.
        provenance: proposed
        source:
          - >-
            services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
            the tenant's portal and lists the tenant's members, the console users left out
          - >-
            services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
            a wrong code, saying how many attempts are left
          - >-
            services-platform:modules/security/tests/integration/services/authorizer-portal-contact.integration.spec.js#carries
            the linked contact as `cid`, read from the user’s row under the deployed role
    expected: >-
      The fresh portal context contains the current linked contact; another workspace or console
      identity does not inherit it.
    negative: true
    data_needs: >-
      Disposable customer, workspace and membership fixtures owned by the run; fixed clocks, failure
      controls or provider prerequisites as required by the case.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of the
        tenant's portal and lists the tenant's members, the console users left out
      - >-
        services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
        a wrong code, saying how many attempts are left
      - >-
        services-platform:modules/security/tests/integration/services/authorizer-portal-contact.integration.spec.js#carries
        the linked contact as `cid`, read from the user’s row under the deployed role
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
          the tenant's portal and lists the tenant's members, the console users left out
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
        - >-
          services-platform:modules/security/tests/integration/services/authorizer-portal-contact.integration.spec.js#carries
          the linked contact as `cid`, read from the user’s row under the deployed role
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
          the tenant's portal and lists the tenant's members, the console users left out
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
        - >-
          services-platform:modules/security/tests/integration/services/authorizer-portal-contact.integration.spec.js#carries
          the linked contact as `cid`, read from the user’s row under the deployed role
    execution_gap: FND-PFM-SEC-078
  - id: FAS-PFM-SEC-06-03
    title: Replaced and expired codes cannot establish a link
    kind: lifecycle
    exercises:
      - CAP-PFM-SEC-019
      - CAP-PFM-SEC-020
    relies_on:
      - ATS-PFM-SEC-019-04
      - ATS-PFM-SEC-020-05
      - ATS-PFM-SEC-020-06
    preconditions: Prepare a confirmed but unlinked member able to receive challenge codes.
    steps:
      - do: >-
          Start a challenge, replace it and try the first code; expire the replacement and then try
          its code.
        expect: Both attempts are refused and neither creates the contact link.
        provenance: proposed
        source:
          - >-
            services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
            the Portal Member role, active, at the tenant’s scope
          - >-
            services-platform:modules/security/tests/integration/services/persistence/portal-contact-challenges.integration.spec.js#records
            the member, the contact, the channel and a ten-minute expiry, with the digest of the
            code and never the code
          - >-
            services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
            a wrong code, saying how many attempts are left
    expected: Both attempts are refused and neither creates the contact link.
    negative: false
    data_needs: >-
      Disposable customer, workspace and membership fixtures owned by the run; fixed clocks, failure
      controls or provider prerequisites as required by the case.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
        the Portal Member role, active, at the tenant’s scope
      - >-
        services-platform:modules/security/tests/integration/services/persistence/portal-contact-challenges.integration.spec.js#records
        the member, the contact, the channel and a ten-minute expiry, with the digest of the code
        and never the code
      - >-
        services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
        a wrong code, saying how many attempts are left
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/persistence/portal-contact-challenges.integration.spec.js#records
          the member, the contact, the channel and a ten-minute expiry, with the digest of the code
          and never the code
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/persistence/portal-contact-challenges.integration.spec.js#records
          the member, the contact, the channel and a ten-minute expiry, with the digest of the code
          and never the code
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
    execution_gap: FND-PFM-SEC-078
  - id: FAS-PFM-SEC-06-04
    title: Foreign memberships and candidate proofs remain private
    kind: permission
    exercises:
      - CAP-PFM-SEC-019
      - CAP-PFM-SEC-020
    relies_on:
      - ATS-PFM-SEC-019-08
      - ATS-PFM-SEC-020-04
    preconditions: Prepare two portal members with candidate offers in different workspaces.
    steps:
      - do: >-
          Attempt the foreign membership read and exchange candidate tokens and verification
          channels between the two members.
        expect: >-
          The reads and exchanged proofs are refused without disclosing the foreign contact
          identity.
        provenance: proposed
        source:
          - >-
            services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
            the tenant's portal and lists the tenant's members, the console users left out
          - >-
            services-platform:modules/security/tests/unit/services/portal/candidate-token.spec.ts#refuses
            a token with one character changed
    expected: The reads and exchanged proofs are refused without disclosing the foreign contact identity.
    negative: true
    data_needs: >-
      Disposable customer, workspace and membership fixtures owned by the run; fixed clocks, failure
      controls or provider prerequisites as required by the case.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of the
        tenant's portal and lists the tenant's members, the console users left out
      - >-
        services-platform:modules/security/tests/unit/services/portal/candidate-token.spec.ts#refuses
        a token with one character changed
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
          the tenant's portal and lists the tenant's members, the console users left out
        - >-
          services-platform:modules/security/tests/unit/services/portal/candidate-token.spec.ts#refuses
          a token with one character changed
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/e2e/invitations.e2e.spec.js#invites a member of
          the tenant's portal and lists the tenant's members, the console users left out
        - >-
          services-platform:modules/security/tests/unit/services/portal/candidate-token.spec.ts#refuses
          a token with one character changed
    execution_gap: FND-PFM-SEC-078
  - id: FAS-PFM-SEC-06-05
    title: Replay preserves an established portal identity
    kind: regression
    exercises:
      - CAP-PFM-SEC-019
      - CAP-PFM-SEC-020
    relies_on:
      - ATS-PFM-SEC-019-04
      - ATS-PFM-SEC-020-06
      - ATS-PFM-SEC-020-07
    preconditions: Prepare a confirmed member whose contact verification has succeeded.
    steps:
      - do: >-
          Repeat confirmation and verification, then attempt challenge creation and candidate
          decline after linking.
        expect: >-
          The same membership and contact link remain; replay creates neither a second member nor a
          second link.
        provenance: proposed
        source:
          - >-
            services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
            the Portal Member role, active, at the tenant’s scope
          - >-
            services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
            a wrong code, saying how many attempts are left
          - >-
            services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#is
            linked to a contact CRM created from the sign-up details
    expected: >-
      The same membership and contact link remain; replay creates neither a second member nor a
      second link.
    negative: false
    data_needs: >-
      Disposable customer, workspace and membership fixtures owned by the run; fixed clocks, failure
      controls or provider prerequisites as required by the case.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
        the Portal Member role, active, at the tenant’s scope
      - >-
        services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
        a wrong code, saying how many attempts are left
      - >-
        services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#is
        linked to a contact CRM created from the sign-up details
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#is
          linked to a contact CRM created from the sign-up details
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/provisioning/post-confirmation.integration.spec.js#grants
          the Portal Member role, active, at the tenant’s scope
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#refuses
          a wrong code, saying how many attempts are left
        - >-
          services-platform:modules/security/tests/integration/services/portal/contact-match.integration.spec.js#is
          linked to a contact CRM created from the sign-up details
    execution_gap: FND-PFM-SEC-078
evidence:
  - >-
    specs/platform/security/planning/capabilities/CAP-PFM-SEC-019-let-members-sign-up-and-sign-in-on-a-workspace-s-portal.md
  - >-
    specs/platform/security/planning/capabilities/CAP-PFM-SEC-020-link-a-portal-member-to-their-contact-record.md
  - >-
    Capability test scenario references identify the outcomes this suite relies on. The complete
    feature chains remain proposed until a suite executes every step.
examiner: codex
examined_at: '2026-10-23'
final_text: OpenAI
---
# Portal Membership and Identity acceptance: Feature Acceptance Suite

## What the Feature Must Do as a Whole

The suite follows portal signup from mailbox confirmation to workspace membership and contact verification. It checks that identity and possession proof stay tied to the intended member throughout retries and refusal.

## Interactions Between Capabilities

Verification changes the contact claim used by fresh authorization. Replaced or expired challenges must leave the member unlinked, and confirmation or verification replay must preserve the established identity. The referenced capability scenarios supply detailed observations. These ordered feature cases remain proposed until a registered suite executes the whole chain and its result is retained.

## Regression Record

| Revision | Change | Scenario | Why |
|---|---|---|---|
| 1 | initial | all | Establish the feature’s cumulative acceptance baseline. |
| 2 | testing examination continuation | all | Verify references, write final feature text and keep execution gaps explicit. |
