---
id: ATS-PFM-NOT-018
scope: CAP-PFM-NOT-018
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-018-01
    title: Dispatch committed pointers with recoverable claims
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-01
    preconditions: 'Pointer rows in two workspaces, more than one batch.'
    steps: >-
      Dispatch accepted pointers, reject one queue publish, compete two runs and
      end a run at its time budget.
    expected: >-
      Acknowledged pointers publish in ascending creation order and are deleted;
      refused work returns later. Exclusive concurrent claims prevent duplicate
      taking, and time-limited remaining work waits for another run.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#publishes
        every row of the tenant it was given, oldest first
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#publishes
        a pointer and nothing of the request: the version, the kind, the tenant
        and the key
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#leaves
        the refused row and deletes the accepted one
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#offers
        it again once its claim has lapsed, and nothing on the row says it was
        ever refused
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#finds
        nothing for the second run, because the first run holds the claim
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#stops
        before it enters a tenant, so the run ends inside the function’s timeout
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/request-submissions.integration.spec.js#never
        gives the same row to two runs claiming at the same moment
      - TST-PFM-NOT-033
      - TST-PFM-NOT-044
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js#publishes
      every row of the tenant it was given, oldest first
    category: concurrency
    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-018-02
    title: Skip final and temporarily ambiguous submissions
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-02
      - INV-DOM-PFM-NOT-005-04
    preconditions: Prepare a request item processed once.
    steps: >-
      Replay the processed request and attempt to claim another message whose
      provider handoff may still be in flight.
    expected: >-
      Final messages are not resubmitted; the potentially in-flight message
      remains untouched during the ambiguity interval.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#sends
        nothing a second time
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#writes
        no second message and leaves the one there is as it was
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#answers
        in_flight for a submission that may still be with the provider, so it is
        never sent twice
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#skips
        a message already settled, so a repeated queue item never sends twice
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#never
        sends again when the run is repeated
      - TST-PFM-NOT-030
      - TST-PFM-NOT-036
      - TST-PFM-NOT-042
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#sends
      nothing a second time
    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/db/communication-messages.table.ts;
      services-platform:modules/communications/services/persistence/messages.ts:claimMessageForSubmission
  - id: ATS-PFM-NOT-018-03
    title: Back off throttled sends and expose exhaustion
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-03
    preconditions: Prepare a request of three recipients and a throttling provider.
    steps: >-
      Throttle the initial receive, allow recovery, and exhaust a different
      request's recipient on its final receive.
    expected: >-
      Throttled drafts return with backoff and eligible sends complete in the
      retry fixture. At exhaustion the refused message fails and the item goes
      to dead-letter handling; remaining drafts are attempted only while
      invocation time remains.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#stops
        the rest of the request for this receive rather than pressing an unwell
        provider
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#gives
        the item back with the backoff of this receive, which SQS times and
        Postgres does not
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#sends
        all three on the next receive, each exactly once
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#defers
        nothing: what the provider refused is failed as attempts_exhausted
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#still
        sends the e-mails the failure did not reach, so one bad recipient does
        not hold the rest
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/send-handler.integration.spec.js#extends
        the item’s visibility by the backoff of this receive, so SQS waits that
        long and not less
      - TST-PFM-NOT-036
      - TST-PFM-NOT-038
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#stops
      the rest of the request for this receive rather than pressing an unwell
      provider
    category: failure
    binding_scope: >-
      Each binding supplies its own assertions; the aggregate references do not
      imply that one executable file proves every step.
    findings:
      - FND-PFM-NOT-052
  - id: ATS-PFM-NOT-018-04
    title: Redrive only exhausted sends
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-04
    preconditions: >-
      Prepare a dead-lettered request with one exhausted, one sent and one
      rejected e-mail.
    steps: Reoffer the exhausted item through operator redrive.
    expected: Only the exhausted message is submitted again for processing.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#offers
        the exhausted e-mail to the provider again
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#never
        offers one the provider already took: the request is settled, not
        re-sent
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#leaves
        a message the provider refused for good alone, so a redrive never sends
        what was refused
      - TST-PFM-NOT-036
      - TST-PFM-NOT-042
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js#offers
      the exhausted e-mail to the provider again
    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-018-05
    title: Apply expiry and recipient checks immediately before send
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-05
      - INV-DOM-PFM-NOT-005-05
    preconditions: >-
      Prepare draft messages that are expired, newly suppressed after acceptance
      or addressed to a reserved domain.
    steps: Attempt provider-submission claims for all three messages.
    expected: >-
      Stale messages expire without attempts; newly suppressed and
      reserved-domain addresses are withheld with their respective reasons.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#expires
        a message whose expiry has passed, without an attempt
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#withholds
        a message whose recipient was suppressed since acceptance, state and
        reason written together
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#withholds
        a draft to a reserved domain, as one accepted before the rule would be,
        under a policy that delivers to everybody
      - TST-PFM-NOT-042
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#expires
      a message whose expiry has passed, without an attempt
    category: edge
    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_withheld_check
  - id: ATS-PFM-NOT-018-06
    title: Recover uncertain submission after the wait
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-06
    preconditions: Prepare a message left submitting.
    steps: >-
      Attempt a claim before the ambiguity interval ends and another after it
      ends.
    expected: >-
      The message is initially left alone; after the interval it is claimed
      again and its unknown attempt is closed.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#answers
        in_flight for a submission that may still be with the provider, so it is
        never sent twice
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#claims
        it again once the ambiguity window has passed, the unknown attempt
        closed
      - TST-PFM-NOT-042
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#answers
      in_flight for a submission that may still be with the provider, so it is
      never sent twice
    category: boundary
    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-018-07
    title: Allow cancellation only before submission
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-07
    preconditions: 'Prepare a draft, a withheld and a submitted message.'
    steps: >-
      Try cancellation in every fixture state and repeat the accepted draft
      cancellation.
    expected: >-
      The draft becomes cancelled. Other states and repeated cancellation are
      rejected.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/messages.e2e.spec.js#answers
        200 and the message cancelled
      - >-
        services-platform:modules/communications/tests/e2e/messages.e2e.spec.js#refuses
        cancelling the same message twice as 400, it no longer being queued
      - >-
        services-platform:modules/communications/tests/e2e/messages.e2e.spec.js#refuses
        cancelling a message that was withheld rather than drafted as 400
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#refuses
        a message that has already been submitted, which cannot be recalled
      - TST-PFM-NOT-023
      - TST-PFM-NOT-042
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/messages.e2e.spec.js#answers
      200 and the message cancelled
    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-018-08
    title: Separate transactional and Marketing work
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-08
    preconditions: >-
      Prepare a marketing and a transactional request; an execution without the
      marketing queue.
    steps: 'Dispatch both, next without the marketing queue.'
    expected: >-
      Each classification uses its own queue; Marketing remains pending when its
      queue is unavailable.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#publishes
        a marketing request to the marketing queue
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#publishes
        each request to one queue only
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#leaves
        the marketing row for the next run, never publishing it to the other
        lane
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/lanes.spec.js#puts
        a purpose whose classification is marketing on the marketing lane
      - TST-PFM-NOT-034
      - TST-PFM-NOT-068
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#publishes
      a marketing request to the marketing queue
    category: boundary
    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-018-09
    title: Reserve lane capacity with one-way borrowing
    level: unit
    proves:
      - BR-CAP-PFM-NOT-018-09
    preconditions: Initialize lane token buckets using a known provider sending rate.
    steps: >-
      Consume Marketing capacity, exhaust transactional capacity and inspect a
      lane without a previously loaded rate.
    expected: >-
      Marketing cannot exceed sixty percent, transactional traffic borrows
      unused capacity and an unrated lane remains unlimited.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js#lets
        the marketing lane take its 60 % and no more in one second
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js#lets
        the transactional lane borrow the marketing lane’s unused tokens once
        its own are gone
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js#never
        lets the marketing lane borrow the transactional lane’s reserved tokens
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js#is
        not limited at all
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#lets
        transactional mail borrow the marketing lane’s unused tokens once its
        own are gone
      - TST-PFM-NOT-034
      - TST-PFM-NOT-067
    provenance: test
    source: >-
      services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js#lets
      the marketing lane take its 60 % and no more in one second
    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-018-10
    title: Delay rate-limited work without spending retries
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-10
    preconditions: Prepare a request larger than the bucket.
    steps: 'Process limited-budget work, replenish tokens and process the remainder.'
    expected: >-
      Available capacity sends eligible messages; remaining drafts return with
      delay and later complete without repeating final messages.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#sends
        the messages its tokens cover
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#requeues
        the item rather than giving it back to the queue to spend a receive
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#sends
        what was held and finishes
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#sleeps
        for the refill instead of giving the item back
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#acknowledges
        the original instead of reporting it a failure, even on its last receive
      - TST-PFM-NOT-034
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#sends
      the messages its tokens cover
    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-018-11
    title: Pause only Marketing near account quota
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-11
    preconditions: 'Prepare a account at 89, 90 and next 80 per cent of its quota.'
    steps: Evaluate quota guard separately at each configured quota fixture.
    expected: >-
      The lane starts running, pauses by disabling its consumer at threshold
      while transactional mail continues, then resumes and processes held work.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js#does
        not pause the marketing lane just below 90 %
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js#pauses
        the marketing lane at exactly 90 %
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js#never
        pauses the transactional lane
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js#releases
        the pause when the use falls below 90 %
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#disables
        the marketing consumer’s queue mapping when it pauses and enables it
        when it resumes
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#goes
        on sending transactional mail while marketing is paused
      - TST-PFM-NOT-034
      - TST-PFM-NOT-040
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js#does
      not pause the marketing lane just below 90 %
    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-018-12
    title: Validate and attach unsubscribe metadata
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-12
    preconditions: >-
      Prepare transactional and Marketing envelopes with present and absent
      subscription and unsubscribe metadata.
    steps: Submit every envelope and deliver only the accepted classifications.
    expected: >-
      Classification mismatches reject submission without queuing. Marketing
      messages contain unsubscribe headers; transactional messages do not.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#refuses
        a marketing request without a subscription type, with the library’s
        message
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#refuses
        a marketing request without unsubscribe targets, with the library’s
        message
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#refuses
        a transactional request that carries a subscription type, with the
        library’s message
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#adds
        List-Unsubscribe-Post for the https target
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#goes
        out with no unsubscribe header, its headers exactly as they were
      - TST-PFM-NOT-034
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js#refuses
      a marketing request without a subscription type, with the library’s
      message
    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-018-13
    title: Submit each deliverable recipient in the normal lifecycle
    level: api
    proves:
      - AS-JNY-PFM-NOT-006-02
    preconditions: Prepare a accepted request.
    steps: Repeat dispatch and consumption after the first completed run.
    expected: >-
      Each eligible contact receives one provider submission in the exercised
      path.
    negative: false
    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#publishes
        the request once, whatever its recipients turn out to be
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#never
        sends again when the run is repeated
      - TST-PFM-NOT-030
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#publishes
      the request once, whatever its recipients turn out to be
    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-018-14
    title: Late protection and feedback constrain retry outcomes
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-018-05
    exercises:
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-019
      - CAP-PFM-NOT-020
    preconditions: >-
      Accept messages for two eligible recipients, then suppress one before
      claiming it and throttle the other once.
    steps: >-
      Run the send consumer, retry after backoff, report delivery and inspect
      attempts and final outcomes.
    expected: >-
      The newly protected message has no provider attempt. The other message
      submits through the retry path and retains provider delivery separately
      from dispatch state.
    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-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-019
      - CAP-PFM-NOT-020
  - id: ATS-PFM-NOT-018-15
    title: Read supported communication queue items and reject malformed versions
    category: boundary
    level: unit
    proves:
      - BR-CAP-PFM-NOT-018-01
    preconditions: >-
      Prepare current request pointers, send items, earlier camel-case items,
      legacy message items and malformed input.
    steps: Serialize and parse each item shape using the communication queue helpers.
    expected: >-
      Current and supported earlier item shapes round-trip into the current
      contract. The parser identifies legacy message work and returns no
      supported item for malformed or unknown-version input.
    negative: true
    data_needs: Pure queue item inputs in one process.
    bound_to:
      - >-
        services-platform:modules/communications/tests/unit/services/dispatch/send-queue.spec.js#reads
        back exactly what the dispatcher published
      - TST-PFM-NOT-122
    provenance: test
    source: >-
      services-platform:modules/communications/tests/unit/services/dispatch/send-queue.spec.js#reads
      back exactly what the dispatcher published
    binding_scope: >-
      This suite checks communication queue serialization and parsing without
      dispatch claims, queue publication or provider sending.
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/communications/tests/integration/services/dispatch/dispatch-communications.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/process-communication-request.integration.spec.js;
    services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js;
    services-platform:modules/communications/tests/e2e/messages.e2e.spec.js;
    services-platform:modules/communications/tests/unit/services/dispatch/lanes.spec.js;
    services-platform:modules/communications/tests/integration/services/persistence/lane-budgets.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/communications/tests/integration/services/persistence/request-submissions.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/send-handler.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/marketing-lane.integration.spec.js;
    services-platform:modules/communications/tests/unit/services/dispatch/lane-bucket.spec.js
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-079'
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: covered
  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.
findings:
  - FND-PFM-NOT-079
transition_coverage:
  - state_model: STATE-PFM-NOT-011
    transition_index: 1
    from: draft
    to: submitting
    trigger: The consumer claims a rendered message.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:claimMessageForSubmission
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 2
    from: draft
    to: withheld
    trigger: The final send check rejects the address or environment policy.
    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
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 3
    from: draft
    to: expired
    trigger: The final send check detects expiry.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:claimMessageForSubmission
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 4
    from: draft
    to: cancelled
    trigger: Staff request cancellation.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:cancelMessage
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 5
    from: submitting
    to: submitted
    trigger: >-
      Provider acceptance or qualifying feedback resolves an unfinished
      submission.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:recordMessageSubmitted;
      services-platform:modules/communications/services/feedback/process-ses-event.ts:processSesEvent
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 6
    from: submitting
    to: draft
    trigger: A throttle or temporary failure permits another receive.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:recordMessageSubmissionFailed
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 7
    from: submitting
    to: failed
    trigger: A permanent refusal or final-receive failure ends the attempt.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:recordMessageSubmissionFailed
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 8
    from: submitting
    to: submitting
    trigger: A later claim encounters an old unresolved submission.
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/messages.integration.spec.js#claims
      it again once the ambiguity window has passed, the unknown attempt closed
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-011
    transition_index: 9
    from: failed
    to: draft
    trigger: An operator redrives exhausted request work.
    source: >-
      services-platform:modules/communications/services/persistence/messages.ts:retryExhaustedMessages
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-015
    transition_index: 1
    from: unlimited
    to: running
    trigger: The quota guard obtains its first rate observation.
    source: >-
      services-platform:modules/communications/services/dispatch/quota-guard.ts:runQuotaGuard
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-015
    transition_index: 2
    from: running
    to: paused
    trigger: Daily usage reaches ninety percent of the account quota.
    source: >-
      services-platform:modules/communications/services/dispatch/quota-guard.ts:runQuotaGuard
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-015
    transition_index: 3
    from: paused
    to: running
    trigger: Daily usage drops below the pause threshold.
    source: >-
      services-platform:modules/communications/services/dispatch/quota-guard.ts:runQuotaGuard
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-015
    transition_index: 4
    from: unlimited
    to: paused
    trigger: The first quota observation already exceeds the daily threshold.
    source: >-
      services-platform:modules/communications/services/dispatch/quota-guard.ts:runQuotaGuard
    existing_candidates:
      - ATS-PFM-NOT-018-04
      - ATS-PFM-NOT-018-11
    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-018-14
    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-018-14
    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-012
    capabilities:
      - CAP-PFM-NOT-018
    scenario_candidate: ATS-PFM-NOT-018-14
    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-04
    scenarios:
      - ATS-PFM-NOT-018-02
    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-05
    scenarios:
      - ATS-PFM-NOT-018-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.
---

# Send association e-mail with retry and capacity control: 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-018-01` | `ATS-PFM-NOT-018-01`, `ATS-PFM-NOT-018-14` |
| `BR-CAP-PFM-NOT-018-02` | `ATS-PFM-NOT-018-02` |
| `BR-CAP-PFM-NOT-018-03` | `ATS-PFM-NOT-018-03` |
| `BR-CAP-PFM-NOT-018-04` | `ATS-PFM-NOT-018-04` |
| `BR-CAP-PFM-NOT-018-05` | `ATS-PFM-NOT-018-05` |
| `BR-CAP-PFM-NOT-018-06` | `ATS-PFM-NOT-018-06` |
| `BR-CAP-PFM-NOT-018-07` | `ATS-PFM-NOT-018-07` |
| `BR-CAP-PFM-NOT-018-08` | `ATS-PFM-NOT-018-08` |
| `BR-CAP-PFM-NOT-018-09` | `ATS-PFM-NOT-018-09` |
| `BR-CAP-PFM-NOT-018-10` | `ATS-PFM-NOT-018-10` |
| `BR-CAP-PFM-NOT-018-11` | `ATS-PFM-NOT-018-11` |
| `BR-CAP-PFM-NOT-018-12` | `ATS-PFM-NOT-018-12` |

## Interactions With Related Capabilities

Accept messages for two eligible recipients, then suppress one before claiming it and throttle the other once. Run the send consumer, retry after backoff, report delivery and inspect attempts and final outcomes.

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.
