---
id: ATS-PFM-NOT-017
scope: CAP-PFM-NOT-017
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-017-01
    title: Commit a communication pointer with its business change
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-01
    preconditions: Prepare a caller transaction that submits a request.
    steps: >-
      Submit once in a transaction that rolls back and once in a transaction
      that commits.
    expected: >-
      Rollback leaves no pointer; commit leaves one entry referring to the
      stored object.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#leaves
        no queue row behind when the caller’s transaction rolls back
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#keeps
        the queue row when the caller’s transaction commits
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#points
        the row at the request object, which was written before it
      - TST-PFM-NOT-028
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#leaves
      no queue row behind when the caller’s transaction rolls back
    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-017-02
    title: Restrict callers to scoped pointer insertion
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-02
    preconditions: Prepare the communications capability role.
    steps: >-
      Insert into another organization; store the claim column; read, change or
      delete a row; read messages and templates.
    expected: >-
      Forbidden access fails; allowed pointer insertion succeeds only in the
      caller's workspace.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#inserts
        a queue row into its own tenant naming all ten pointer columns, which is
        the whole capability
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
        insert one into another tenant: the forced policy refuses it
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
        write the claim column, so a caller can never hide its own row from the
        dispatcher
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
        read back even the rows it wrote
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
        delete a row, which is the dispatcher’s statement
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
        read the messages table, which holds the rendered e-mail
      - TST-PFM-NOT-028
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#inserts
      a queue row into its own tenant naming all ten pointer columns, which is
      the whole capability
    category: permission
    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-017-03
    title: Deduplicate ordinary request retries and recovery
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-03
      - INV-DOM-PFM-NOT-005-02
    preconditions: Prepare a submitted request.
    steps: >-
      Submit the same key while its request waits, during the handoff, after
      processing and after removal of the request row.
    expected: >-
      Repetition returns the same identifier with a duplicate indicator,
      producing one provider send in the exercised recovery path.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
        answered duplicate, under the id the first submission named
      - >-
        services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
        still answered duplicate, because the request object says the id was
        submitted before
      - >-
        services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
        accepted once and sends its one e-mail once, though the queue carried it
        twice
      - >-
        services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
        accepted and its e-mail sent, which is what makes the retry worth taking
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#answers
        the derived id both times, so a retrying caller is never told an id that
        does not exist
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#answers
        the same request id, marked duplicate, when the idempotency key is
        repeated
      - TST-PFM-NOT-024
      - TST-PFM-NOT-028
      - TST-PFM-NOT-057
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
      answered duplicate, under the id the first submission named
    category: invariant
    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/requests/submit-request.integration.spec.js#is
      still answered duplicate, because the request object says the id was
      submitted before
  - id: ATS-PFM-NOT-017-04
    title: Validate before consuming the submission key
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-04
    preconditions: Prepare the request primitive.
    steps: Submit an envelope with a bad path; next the identical key correctly.
    expected: >-
      The invalid request reports its field path and stores nothing; corrected
      input using the key is accepted.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#answers
        invalid in front of the caller for an envelope it can judge there,
        naming the problem by path
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#takes
        nothing for that request, so its key is still free
      - >-
        services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#answers
        a broken envelope as an invalid outcome, not an exception, naming the
        path
      - >-
        services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#refuses
        a malformed submission before anything is written, naming the caller’s
        own defect
      - TST-PFM-NOT-024
      - TST-PFM-NOT-028
      - TST-PFM-NOT-055
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#answers
      invalid in front of the caller for an envelope it can judge there, naming
      the problem by path
    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-017-05
    title: Retain all non-sending outcomes and reasons
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-05
    preconditions: >-
      Requests with no template, an unpublished template, data the contract
      rejects and only suppressed recipients.
    steps: Process each submitted fixture and retrieve its recorded request outcome.
    expected: >-
      Missing template, missing publication, invalid fields and fully suppressed
      recipients produce their corresponding recorded outcomes without sending.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#blocks
        a request that names no template, recording the reason staff can act on
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#blocks
        a request naming a template that was never published
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#takes
        data the purpose’s contract does not accept, and stores it invalid
        behind the queue
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#withholds
        every message of a request to a suppressed address, which is the
        suppressed outcome
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
        it invalid where staff can read it, naming the paths
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#stores
        why nothing was sent where staff can read it
      - TST-PFM-NOT-024
      - TST-PFM-NOT-030
      - TST-PFM-NOT-036
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#blocks
      a request that names no template, recording the reason staff can act on
    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-017-06
    title: Pin published content and derive message identities
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-06
    preconditions: >-
      Prepare a record whose template is published; three recipients, one
      suppressed.
    steps: Send the constructed envelope through the request primitive.
    expected: >-
      The accepted request pins its version. The receipt names message
      identities before persistence; processing queues two messages and
      withholds one.
    negative: false
    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#accepts
        the request with the owner’s published version pinned
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#names
        the messages in the receipt, under ids derived from the request, before
        any of them exists
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#escapes
        the contact’s name in the HTML it rendered and stored
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#writes
        the message under the id derived from the request, never a generated one
      - TST-PFM-NOT-030
      - TST-PFM-NOT-036
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#accepts
      the request with the owner’s published version pinned
    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-017-07
    title: Reject missing and mismatched stored objects
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-07
      - INV-DOM-PFM-NOT-005-03
    preconditions: >-
      Prepare a row whose object names another organization; a row whose object
      is gone.
    steps: Evaluate both stored-object fixtures.
    expected: >-
      Both requests become invalid with object-specific problems and produce no
      sends.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js#refuses
        an object that names another tenant, which is what the grant cannot
        prevent
      - >-
        services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js#refuses
        an object that names another request, even under the right key
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#stores
        the request invalid, naming the object rather than crashing
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#sends
        nothing for it
      - TST-PFM-NOT-036
      - TST-PFM-NOT-060
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js#refuses
      an object that names another tenant, which is what the grant cannot
      prevent
    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/storage/communication-objects.integration.spec.js#refuses
      an object that names another tenant, which is what the grant cannot
      prevent
  - id: ATS-PFM-NOT-017-08
    title: Override caller values for reserved rendering fields
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-08
    preconditions: Prepare a request whose data names another association.
    steps: Process acceptance and inspect the resulting rendered message.
    expected: >-
      Rendering uses the actual association name and generated message
      reference.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/communication-reads.integration.spec.js#sets
        the association over whatever the caller sent under that name, so a
        caller cannot rename the association
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#fills
        the platform’s variables: the association and the message’s own
        reference
      - TST-PFM-NOT-030
      - TST-PFM-NOT-031
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/communication-reads.integration.spec.js#sets
      the association over whatever the caller sent under that name, so a caller
      cannot rename the association
    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-017-09
    title: Send valid caller-rendered content unchanged
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-09
    preconditions: Prepare a invoice e-mail with an attachment.
    steps: >-
      Try valid caller content, missing caller content, caller content naming a
      template and attachment-bearing Platform-rendered content.
    expected: >-
      Valid supplied content sends with derived text and its attachment;
      incompatible or invalid cases are rejected.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#accepts
        the request without resolving any template
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#sends
        the caller's subject and HTML as given, the Liquid in it untouched
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#derives
        the text part from the HTML when the caller sent none
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#hands
        the transport the attachment, its bytes decoded from the request
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#refuses
        a caller-rendered request without content, writing nothing
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#refuses
        a caller-rendered request that names a template
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#refuses
        attachments on a purpose Platform renders
      - TST-PFM-NOT-032
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#accepts
      the request without resolving any template
    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-017-10
    title: Allow display configuration without caller-selected addresses
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-10
    preconditions: 'Prepare a request with a display name, reply-to and copies.'
    steps: Process the configured message through delivery.
    expected: >-
      Delivery uses the workspace sender address, supplied display name and
      reply-to, and only copies that pass recipient checks.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#sends
        from the tenant's sender address, under the caller's display name
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#replies
        to the caller's reply-to address, canonical
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#copies
        the deliverable CC, canonical, and never the suppressed one
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js#still
        sends to the contact
      - TST-PFM-NOT-032
      - TST-PFM-NOT-039
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#sends
      from the tenant's sender address, under the caller's display name
    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-017-11
    title: Use pinned Marketing content or record blocking
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-11
    preconditions: Prepare a Marketing message version and its template.
    steps: >-
      Send it after a newer version exists; send a absent version, another
      purpose's, an unowned one and a newer contract's.
    expected: >-
      Valid pinned content sends; invalid version, owner or contract cases block
      with reasons and create no message.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#still
        sends a version that is no longer its template’s current one, and sends
        the pinned content
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#blocks
        a version that does not exist, naming it
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#blocks
        a version of another purpose
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#blocks
        a version of the purpose whose template nobody owns
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#blocks
        a version built for a newer contract than the request’s
      - TST-PFM-NOT-035
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#still
      sends a version that is no longer its template’s current one, and sends
      the pinned content
    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-017-12
    title: Deny direct association sends from console callers
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-14
    preconditions: Prepare a console caller and a system caller.
    steps: Call submission from both the console and system sessions.
    expected: >-
      The console request fails; an authorized system caller receives the
      submission receipt.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#refuses
        a console caller the system route that asks for a communication
      - >-
        services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#answers
        a receipt naming the request the caller may read it under, and the
        messages it will have
      - TST-PFM-NOT-024
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#refuses
      a console caller the system route that asks for a communication
    category: permission
    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-017-13
    title: Retain rollback objects until lifecycle cleanup
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-15
    preconditions: Prepare a request object store and a caller transaction.
    steps: 'Store a request, store its pointer, next roll back the caller transaction.'
    expected: >-
      No pointer or queued work survives rollback, while its stored object
      awaits lifecycle removal.
    negative: false
    data_needs: A request object store and a caller transaction.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:docs/communications/architecture.md#18.12 One S3 write
      inside the caller's transaction
    category: edge
  - id: ATS-PFM-NOT-017-14
    title: Enforce rendering and version consistency
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-16
      - INV-DOM-PFM-NOT-005-09
    preconditions: Message persistence with a valid request and optional published version.
    steps: >-
      Insert Platform transactional content without a version, Platform test
      content without a version, caller content without a version, caller
      content with a version and an unsupported rendering kind.
    expected: >-
      Platform transactional content without a version fails. Draft Platform
      test and version-less caller content succeed. Caller content with a
      version and unsupported rendering kinds fail.
    negative: true
    data_needs: Use isolated messages and a published version owned by the suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#refuses
        a transactional message Platform rendered, by the version check’s name
      - >-
        services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#accepts
        the same message rendered by the caller, which is the negative control
      - >-
        services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#accepts
        a test send Platform rendered from a draft, as before
      - >-
        services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#refuses
        it, by the caller-rendered check’s name
      - >-
        services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#refuses
        a rendering that is neither platform nor caller
      - TST-PFM-NOT-027
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#refuses
      a transactional message Platform rendered, by the version check’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.
    invariant_source: >-
      services-platform:modules/communications/db/communication-messages.table.ts:communication_messages_caller_rendered_check
  - id: ATS-PFM-NOT-017-15
    title: 'Accepted content, safeguards and feedback remain connected'
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-017-06
    exercises:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-019
      - CAP-PFM-NOT-020
    preconditions: >-
      Use published compliance content, one eligible contact and one suppressed
      address.
    steps: >-
      Commit the request with its business change, process and send it, report
      provider delivery and read the request with its messages.
    expected: >-
      The eligible contact has one submitted message pinned to the publication.
      The protected address has a withholding reason, and provider delivery
      changes the message history without changing the receipt identity.
    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-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
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-020-protect-deliverability-and-the-associations-sending-address.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-019
      - CAP-PFM-NOT-020
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js;
    services-platform:modules/communications/tests/e2e/requests.e2e.spec.js;
    services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js;
    services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/communication-reads.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js;
    services-platform:docs/communications/architecture.md
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-078'
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-PFM-NOT-078'
  invariant: covered
  interaction: covered
coverage_notes:
  transition: >-
    The lifecycle matrix records every transition as requiring independent
    verification of its guard and effects; existing scenarios remain useful
    candidates.
  concurrency: >-
    The suite lacks a complete race case with controlled interleaving and
    post-race assertions.
findings:
  - FND-PFM-NOT-078
transition_coverage:
  - state_model: STATE-PFM-NOT-010
    transition_index: 1
    from: received
    to: accepted
    trigger: The consumer processes a request without an outcome.
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#accepts
      the request with the owner’s published version pinned
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-010
    transition_index: 2
    from: received
    to: configuration blocked
    trigger: Content resolution cannot produce a usable version.
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#blocks
      a request that names no template, recording the reason staff can act on
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-010
    transition_index: 3
    from: received
    to: suppressed
    trigger: Every recipient is excluded by protection checks.
    source: >-
      services-platform:modules/communications/services/requests/accept-communication-request.ts:acceptResolved
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-010
    transition_index: 4
    from: received
    to: invalid
    trigger: 'Stored content is absent, mismatched or invalid.'
    source: >-
      services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js#refuses
      an object that names another tenant, which is what the grant cannot
      prevent
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
workflow_coverage:
  - workflow: WF-PFM-NOT-010
    capabilities:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
    scenario_candidate: ATS-PFM-NOT-017-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-011
    capabilities:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-020
      - CAP-PFM-NOT-016
    scenario_candidate: ATS-PFM-NOT-017-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-02
    scenarios:
      - ATS-PFM-NOT-017-03
    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.
  - invariant: INV-DOM-PFM-NOT-005-03
    scenarios:
      - ATS-PFM-NOT-017-07
    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.
  - invariant: INV-DOM-PFM-NOT-005-09
    scenarios:
      - ATS-PFM-NOT-017-14
    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.
---

# Submit association communication requests: 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-017-01` | `ATS-PFM-NOT-017-01`, `ATS-PFM-NOT-017-15` |
| `BR-CAP-PFM-NOT-017-02` | `ATS-PFM-NOT-017-02` |
| `BR-CAP-PFM-NOT-017-03` | `ATS-PFM-NOT-017-03` |
| `BR-CAP-PFM-NOT-017-04` | `ATS-PFM-NOT-017-04` |
| `BR-CAP-PFM-NOT-017-05` | `ATS-PFM-NOT-017-05` |
| `BR-CAP-PFM-NOT-017-06` | `ATS-PFM-NOT-017-06` |
| `BR-CAP-PFM-NOT-017-07` | `ATS-PFM-NOT-017-07` |
| `BR-CAP-PFM-NOT-017-08` | `ATS-PFM-NOT-017-08` |
| `BR-CAP-PFM-NOT-017-09` | `ATS-PFM-NOT-017-09` |
| `BR-CAP-PFM-NOT-017-10` | `ATS-PFM-NOT-017-10` |
| `BR-CAP-PFM-NOT-017-11` | `ATS-PFM-NOT-017-11` |
| `BR-CAP-PFM-NOT-017-14` | `ATS-PFM-NOT-017-12` |
| `BR-CAP-PFM-NOT-017-15` | `ATS-PFM-NOT-017-13` |
| `BR-CAP-PFM-NOT-017-16` | `ATS-PFM-NOT-017-14` |

## Interactions With Related Capabilities

Use published compliance content, one eligible contact and one suppressed address. Commit the request with its business change, process and send it, report provider delivery and read the request with its 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.
