---
id: ATS-PFM-NOT-020
scope: CAP-PFM-NOT-020
product_area: platform
module: notifications
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-NOT-020-01
    title: Suppress permanently bounced recipients
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-01
    preconditions: Prepare a contact whose notice bounced permanently.
    steps: Submit a further notice for the bounced recipient.
    expected: >-
      Bounce detail is retained, the address becomes suppressed and subsequent
      mail is withheld before submission.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#marks
        the bounced message bounced_permanent with the diagnostic
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#suppresses
        the address for the tenant
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
        the next notice to that address suppressed, with the reason, and sends
        nothing
      - TST-PFM-NOT-030
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#marks
      the bounced message bounced_permanent with the diagnostic
    category: interaction
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-02
    title: Deduplicate canonical address suppression
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-02
    preconditions: Prepare a address suppressed for a bounce.
    steps: Suppress it a second time in another spelling for a complaint.
    expected: Repetition adds no entry and preserves the existing bounce reason.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js#canonicalizes
        the address, so the same mailbox written two ways is one suppression
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js#records
        nothing for an address already suppressed, so a repeated bounce adds no
        second row
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js#keeps
        the reason of the suppression already in force when it is suppressed
        again
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#says
        nothing was recorded the second time, the list being one row per address
      - TST-PFM-NOT-025
      - TST-PFM-NOT-050
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js#canonicalizes
      the address, so the same mailbox written two ways is one suppression
    category: invariant
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-03
    title: Validate manual suppression input
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-03
    preconditions: Prepare a administrator with the create permission.
    steps: Add an address with a note; with a empty note; add “not an address”.
    expected: >-
      A valid address records manual suppression with its note; blank notes are
      omitted; invalid addresses report their field.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#records
        it as a manual suppression with the note staff gave
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#stores
        the address in its canonical form, lowercase
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#names
        emailAddress in the refusal of an address that is not one
      - >-
        services-platform:modules/communications/tests/unit/aws/api/communications-schemas.spec.ts#takes
        no note when the box was left blank, rather than refusing the
        suppression
      - TST-PFM-NOT-025
      - TST-PFM-NOT-066
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#records
      it as a manual suppression with the note staff gave
    category: validation
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-04
    title: Record deliberate suppression release
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-04
    preconditions: Prepare a suppressed address.
    steps: >-
      Release it without a reason; with one; release it a second time; suppress
      it a second time.
    expected: >-
      Missing reasons reject without release. Valid release retains actor,
      reason and history; another release is not found, and later suppression
      can succeed.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#refuses
        a release that does not say why as 400
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#leaves
        the suppression active after a release that does not say why
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#records
        who released it and why
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#keeps
        the row for the history, read back when the released are asked for
      - >-
        services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#answers
        404 for releasing the same suppression twice, there being no active one
        left
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js#lets
        the address be suppressed again afterwards
      - TST-PFM-NOT-025
      - TST-PFM-NOT-050
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js#refuses
      a release that does not say why as 400
    category: transition
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-05
    title: Withhold reserved-domain recipients everywhere
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-06
      - INV-DOM-PFM-NOT-005-07
    preconditions: >-
      Prepare a request to a reserved-domain address, a public address and a
      test mailbox.
    steps: Submit the recipient fixture to the sending consumer.
    expected: >-
      The demonstration address is withheld with its reason and never submitted;
      remaining eligible recipients send.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js#withholds
        its message with the reserved-domain reason, so the history says why it
        was not sent
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js#makes
        no submission attempt for it
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js#sends
        the public and test-mailbox recipients of the same request
      - TST-PFM-NOT-037
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js#withholds
      its message with the reserved-domain reason, so the history says why it
      was not sent
    category: boundary
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
    invariant_source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js#withholds
      its message with the reserved-domain reason, so the history says why it
      was not sent
  - id: ATS-PFM-NOT-020-06
    title: Apply the configured nonproduction policy
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-07
    preconditions: >-
      Prepare a development environment; a request to an ordinary address, a
      test mailbox and a blind copy outside the allow list.
    steps: Execute delivery under nonproduction policy.
    expected: >-
      Only the allowed test mailbox receives this fixture's mail; other
      recipients are withheld for environment policy.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#queues
        the deliverable contact, withholds the suppressed one and the one this
        environment may not e-mail
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js#sends
        the deliverable BCC copy and drops the one this environment may not
        e-mail
      - TST-PFM-NOT-030
      - TST-PFM-NOT-039
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#queues
      the deliverable contact, withholds the suppressed one and the one this
      environment may not e-mail
    category: edge
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-07
    title: Use shared sending until workspace verification
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-08
    preconditions: >-
      Prepare workspaces with verified, pending, failed and unconfigured sender
      identities.
    steps: Send from each; change the setting and send within and after one minute.
    expected: >-
      Only the verified identity uses its own address in this fixture;
      configured display names remain, and changed verification affects
      selection after cache expiry.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#sends
        from the association’s own address
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#sends
        from the shared Rhythm sender, so the tenant’s mail never waits for the
        customer
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#sends
        from the shared Rhythm sender rather than from an address SES would
        refuse
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#keeps
        answering the same thing for the minute it is held, although the setting
        has changed underneath
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#asks
        again once the minute is up, so a customer who published sees it take
        effect
      - TST-PFM-NOT-059
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js#sends
      from the association’s own address
    category: interaction
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-08
    title: Validate sender strategy and domain claims
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-09
    preconditions: Prepare a organization asking for its own sender.
    steps: >-
      Attempt a second identity, missing required domain, prohibited
      address-only domain, mismatched and subdomain addresses, and verified
      state without verification time.
    expected: All invalid identity records fail without persistence.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/db/communication-sender-identities.table.integration.spec.js#refuses
        a second sender identity for the same tenant, by the unique rule’s name
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js#refuses
        the domain strategy with no domain to prove
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js#refuses
        a domain given to the address strategy, which proves the address alone
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js#refuses
        an address outside the domain the strategy proves, naming the domain
      - >-
        services-platform:modules/communications/tests/integration/db/communication-sender-identities.table.integration.spec.js#refuses
        an address at a subdomain of it, which SES does not cover either
      - >-
        services-platform:modules/communications/tests/integration/db/communication-sender-identities.table.integration.spec.js#refuses
        a verified setting that says nothing about when it was verified
      - TST-PFM-NOT-029
      - TST-PFM-NOT-048
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-sender-identities.table.integration.spec.js#refuses
      a second sender identity for the same tenant, by the unique rule’s name
    category: validation
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-09
    title: Provision or adopt the requested provider identities
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-10
    preconditions: >-
      Prepare a organization that asked for the both strategy; one that asked
      for none.
    steps: Provision each; provision the first a second time.
    expected: >-
      The combined strategy creates domain and address identities with tags and
      DNS instructions in pending state. Retry adopts them; a workspace
      requesting none creates none.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#creates
        the domain identity and the address identity, in that order
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#stores
        the CNAME records the customer must publish, one per DKIM token
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#leaves
        the setting pending, because nothing it did proves the customer
        published anything
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#adopts
        the identities the first run made rather than failing on them
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#creates
        nothing in SES for it
      - TST-PFM-NOT-058
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#creates
      the domain identity and the address identity, in that order
    category: rule
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-10
    title: Preserve first verification through later provider states
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-11
    preconditions: Prepare a pending sender identity.
    steps: Refresh while DNS is absent; after it is published; after it breaks.
    expected: >-
      State progresses from pending to verified with its first instant, then can
      lose verification without clearing that instant.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#is
        verified as soon as any identity will send, because that is when SES
        accepts mail from the address
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#is
        pending while an identity is still waiting for the customer’s DNS
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js#records
        when the association’s own sender started working, the first time it is
        verified
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js#never
        clears that instant when a later reading is no longer verified
      - TST-PFM-NOT-048
      - TST-PFM-NOT-058
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#is
      verified as soon as any identity will send, because that is when SES
      accepts mail from the address
    category: transition
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-11
    title: Remove scoped sender and stored communication data
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-12
    preconditions: >-
      Prepare a organization with a sender identity and stored mail; another
      whose id begins the identical way.
    steps: >-
      Purge the first workspace, then exercise a provider refusal during
      identity removal.
    expected: >-
      Provider identities, setting and scoped objects disappear while another
      workspace's storage survives. Provider removal refusal fails the operation
      for operator attention.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#deletes
        every identity the setting named
      - >-
        services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#fails
        for an operator rather than reporting a sender gone that can still send
      - >-
        services-platform:modules/communications/tests/integration/services/storage/tenant-objects.integration.spec.js#leaves
        the objects of a tenant whose id merely begins like the purged one,
        which is what the trailing slash is for
      - >-
        services-platform:modules/communications/tests/integration/services/storage/tenant-objects.integration.spec.js#removes
        the tenant’s rendered bodies and the requests they came from, both
        prefixes in one call
      - TST-PFM-NOT-058
      - TST-PFM-NOT-061
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js#deletes
      every identity the setting named
    category: failure
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-12
    title: Withhold future messages after bounce feedback
    level: api
    proves:
      - AS-JNY-PFM-NOT-006-03
    preconditions: Prepare a permanently bounced contact.
    steps: Submit another workflow notice for the previously bounced address.
    expected: The next request retains a suppressed withholding outcome.
    negative: true
    data_needs: 'The lifecycle suite''s organization type, contacts and recording provider.'
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
        the next notice to that address suppressed, with the reason, and sends
        nothing
      - TST-PFM-NOT-030
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
      the next notice to that address suppressed, with the reason, and sends
      nothing
    category: interaction
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
  - id: ATS-PFM-NOT-020-13
    title: Persist verification refresh without rewriting first success
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-14
    preconditions: >-
      Prepare a sender setting with provider identities and a previous verified
      time.
    steps: Refresh from a changed provider response.
    expected: >-
      State and latest-check time change, while first verification time remains
      unchanged.
    negative: false
    data_needs: A sender setting with provider identities and a previous verified time.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:refreshSenderIdentityVerification
    category: transition
  - id: ATS-PFM-NOT-020-14
    title: Remove adopted identities and verify provider absence
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-15
    preconditions: Prepare a sender setting linked to an adopted provider identity.
    steps: Purge the workspace sender identities.
    expected: >-
      Purge deletes the adopted identity and checks absence; a surviving
      identity makes the operation fail.
    negative: false
    data_needs: A sender setting linked to an adopted provider identity.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:purgeSenderIdentityIdentities
    category: transition
  - id: ATS-PFM-NOT-020-15
    title: Sender and recipient safeguards remain visible through the mail lifecycle
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-020-01
    exercises:
      - CAP-PFM-NOT-020
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-019
    preconditions: 'Use published content, a pending custom sender and one eligible address.'
    steps: >-
      Submit and send using shared sending, refresh the custom sender to
      verified, report a permanent bounce, then submit another occurrence and
      inspect both messages.
    expected: >-
      The first occurrence retains its selected sender and provider report.
      Later work uses the refreshed sender after cache expiry and withholds the
      protected address with a retained reason.
    negative: false
    data_needs: >-
      A suite-owned workspace, named staff accounts and unique occurrence keys;
      external adapters record attempts without shared writes.
    bound_to: []
    provenance: proposed
    source:
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-020-protect-deliverability-and-the-associations-sending-address.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-017-ask-platform-to-send-an-association-e-mail.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-018-deliver-association-e-mail-reliably-and-within-sending.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-019-record-what-became-of-each-e-mail.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-019
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js;
    services-platform:modules/communications/tests/e2e/suppressions.e2e.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/reserved-domain-suppression.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/sender-identity/tenant-sender-address.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/persistence/sender-identities.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/storage/tenant-objects.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/communications/tests/integration/services/persistence/suppressions.integration.spec.js;
    services-platform:modules/communications/tests/unit/aws/api/communications-schemas.spec.ts;
    services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js;
    services-platform:modules/communications/tests/integration/db/communication-sender-identities.table.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/sender-identity/provision-sender-identity.integration.spec.js;
    services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-081'
  validation: covered
  permission: 'gap: FND-PFM-NOT-081'
  edge: covered
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-PFM-NOT-081'
  invariant: covered
  interaction: covered
coverage_notes:
  permission: >-
    The suite does not isolate every declared access key and refusal
    independently.
  concurrency: >-
    The suite lacks a complete race case with controlled interleaving and
    post-race assertions.
  transition: >-
    The lifecycle matrix records every transition as requiring independent
    verification of its guard and effects; existing scenarios remain useful
    candidates.
findings:
  - FND-PFM-NOT-081
transition_coverage:
  - state_model: STATE-PFM-NOT-013
    transition_index: 1
    from: not suppressed
    to: active
    trigger: 'Staff add protection, or provider feedback triggers it.'
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#suppresses
      the address for the tenant
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-013
    transition_index: 2
    from: active
    to: active
    trigger: Protection is requested for an already protected address.
    source: >-
      services-platform:modules/communications/services/persistence/suppressions.ts:suppressAddress
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-013
    transition_index: 3
    from: active
    to: released
    trigger: Staff release address protection.
    source: >-
      services-platform:modules/communications/services/persistence/suppressions.ts:releaseSuppression
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-013
    transition_index: 4
    from: released
    to: active
    trigger: New provider feedback or a staff addition protects a released address.
    source: >-
      services-platform:modules/communications/services/persistence/suppressions.ts:suppressAddress
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 1
    from: none
    to: pending
    trigger: Workspace setup requests a custom sender setting.
    source: 'rhythm-v2-docs:specs/platform/notifications/FINDINGS.md:FND-PFM-NOT-049'
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 2
    from: pending
    to: verified
    trigger: An HQ sender read refreshes provider verification.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:refreshSenderIdentityVerification
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 3
    from: pending
    to: failed
    trigger: An HQ sender read finds failed verification.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:verificationStateOf
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 4
    from: verified
    to: pending
    trigger: An HQ sender read finds verification no longer ready.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:verificationStateOf
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 5
    from: verified
    to: none
    trigger: Workspace deletion removes the sender.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:purgeSenderIdentityIdentities
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 6
    from: failed
    to: verified
    trigger: An HQ sender read observes recovered verification.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:verificationStateOf
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 7
    from: failed
    to: pending
    trigger: An HQ sender read observes verification still awaiting completion.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:verificationStateOf
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 8
    from: verified
    to: failed
    trigger: An HQ sender read observes provider verification failure.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:verificationStateOf
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 9
    from: pending
    to: none
    trigger: Workspace deletion removes the pending sender.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:purgeSenderIdentityIdentities
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-014
    transition_index: 10
    from: failed
    to: none
    trigger: Workspace deletion removes the failed sender.
    source: >-
      services-platform:modules/communications/services/sender-identity/provision-sender-identity.ts:purgeSenderIdentityIdentities
    existing_candidates:
      - ATS-PFM-NOT-020-04
      - ATS-PFM-NOT-020-10
      - ATS-PFM-NOT-020-13
      - ATS-PFM-NOT-020-14
    decision: 'gap: independent guard and side-effect proof has not been verified'
workflow_coverage:
  - workflow: WF-PFM-NOT-011
    capabilities:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-020
      - CAP-PFM-NOT-016
    scenario_candidate: ATS-PFM-NOT-020-15
    coverage: 'gap: FND-PFM-NOT-084'
    reason: >-
      The proposed related-capability sequence does not independently isolate
      every workflow hop, failure seam and interleaving.
  - workflow: WF-PFM-NOT-013
    capabilities:
      - CAP-PFM-NOT-019
      - CAP-PFM-NOT-020
    scenario_candidate: ATS-PFM-NOT-020-15
    coverage: 'gap: FND-PFM-NOT-084'
    reason: >-
      The proposed related-capability sequence does not independently isolate
      every workflow hop, failure seam and interleaving.
  - workflow: WF-PFM-NOT-015
    capabilities:
      - CAP-PFM-NOT-020
    scenario_candidate: ATS-PFM-NOT-020-15
    coverage: 'gap: FND-PFM-NOT-084'
    reason: >-
      The proposed related-capability sequence does not independently isolate
      every workflow hop, failure seam and interleaving.
  - workflow: WF-PFM-NOT-016
    capabilities:
      - CAP-PFM-NOT-008
      - CAP-PFM-NOT-020
    scenario_candidate: ATS-PFM-NOT-020-15
    coverage: 'gap: FND-PFM-NOT-084'
    reason: >-
      The proposed related-capability sequence does not independently isolate
      every workflow hop, failure seam and interleaving.
domain_invariant_coverage:
  - invariant: INV-DOM-PFM-NOT-005-07
    scenarios:
      - ATS-PFM-NOT-020-05
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-005-association-communication.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
---

# Apply recipient protection and sender verification: Capability Test Specification

## Coverage

Each scenario names the guarantee it proves. Category coverage describes specified cases; executable bindings retain their narrower assertion boundaries. Lifecycle coverage remains a gap wherever guards and effects lack independent verification.

| Rule | Proof |
|---|---|
| `BR-CAP-PFM-NOT-020-01` | `ATS-PFM-NOT-020-01`, `ATS-PFM-NOT-020-15` |
| `BR-CAP-PFM-NOT-020-02` | `ATS-PFM-NOT-020-02` |
| `BR-CAP-PFM-NOT-020-03` | `ATS-PFM-NOT-020-03` |
| `BR-CAP-PFM-NOT-020-04` | `ATS-PFM-NOT-020-04` |
| `BR-CAP-PFM-NOT-020-06` | `ATS-PFM-NOT-020-05` |
| `BR-CAP-PFM-NOT-020-07` | `ATS-PFM-NOT-020-06` |
| `BR-CAP-PFM-NOT-020-08` | `ATS-PFM-NOT-020-07` |
| `BR-CAP-PFM-NOT-020-09` | `ATS-PFM-NOT-020-08` |
| `BR-CAP-PFM-NOT-020-10` | `ATS-PFM-NOT-020-09` |
| `BR-CAP-PFM-NOT-020-11` | `ATS-PFM-NOT-020-10` |
| `BR-CAP-PFM-NOT-020-12` | `ATS-PFM-NOT-020-11` |
| `BR-CAP-PFM-NOT-020-14` | `ATS-PFM-NOT-020-13` |
| `BR-CAP-PFM-NOT-020-15` | `ATS-PFM-NOT-020-14` |

## Interactions With Related Capabilities

Use published content, a pending custom sender and one eligible address. Submit and send using shared sending, refresh the custom sender to verified, report a permanent bounce, then submit another occurrence and inspect both messages.

The interaction case combines the producing, processing and reading outcomes named in its frontmatter. It remains proposed until an executable suite performs that complete sequence.
