---
id: ATS-PFM-NOT-008
scope: CAP-PFM-NOT-008
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-008-01
    title: Publish acknowledged entries and retain rejected work
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-01
    preconditions: 'Queue rows in two workspaces, more than one batch in one.'
    steps: >-
      Compare nudged and scheduled dispatch, reject one queue publication and
      execute two concurrent dispatch runs.
    expected: >-
      Accepted entries publish and disappear; rejected work is retried. Claims
      are exclusive, and nudges visit only their selected workspace.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#publishes
        a pointer for a committed row and deletes the row
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#keeps
        a row the queue refused, and offers it again on the next run
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#does
        not offer a row another run holds
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#publishes
        more rows than one batch holds, in rounds
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#drains
        the tenant a nudge names and leaves the other tenant’s rows alone
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#walks
        every tenant on the scheduled run
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/submissions.integration.spec.js#skips
        the rows another run holds, taking the next oldest
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/submissions.integration.spec.js#offers
        a row again once its claim has lapsed
      - TST-PFM-NOT-075
      - TST-PFM-NOT-096
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#publishes
      a pointer for a committed row and deletes the row
    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-008-02
    title: Recover publication interrupted before deletion
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-02
    preconditions: Prepare a row published but not deleted.
    steps: Run the dispatcher a second time and process both items.
    expected: Recovery republishes the surviving entry but creates only one request.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#leaves
        work for the next run when a run died between the publish and the
        delete, and the repeat makes nothing twice
      - >-
        react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0206.spec.ts#NT-ACC-0206
      - TST-PFM-NOT-017
      - TST-PFM-NOT-075
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#leaves
      work for the next run when a run died between the publish and the delete,
      and the repeat makes nothing twice
    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-008-03
    title: Make request redelivery harmless
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-03
    preconditions: Prepare a request item already processed.
    steps: Deliver the identical item a second time.
    expected: 'No additional request, notice or delivery records are stored.'
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/orchestration/orchestrate-request.integration.spec.js#recognizes
        a redelivered item by its record, writes nothing more and nudges the
        dispatcher
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/requests.integration.spec.js#answers
        the record already there for a redelivered request, unchanged, and says
        it wrote nothing
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js#answers
        the instance already there for a repeated key, unchanged, and says it
        created nothing
      - TST-PFM-NOT-079
      - TST-PFM-NOT-089
      - TST-PFM-NOT-093
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/orchestration/orchestrate-request.integration.spec.js#recognizes
      a redelivered item by its record, writes nothing more and nudges the
      dispatcher
    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-008-04
    title: Recover stale sending and lost delivery work
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-05
    preconditions: >-
      Prepare a delivery sending for 20 minutes, one sending for 5; a queued
      delivery whose work vanished two hours ago; an expired queue row.
    steps: Execute workspace reconciliation.
    expected: >-
      The twenty-minute send fails as ambiguous, the five-minute send remains
      unchanged, lost work is requeued and the stale queue entry becomes an
      expired request.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#settles
        a delivery left sending past the ambiguity window failed ambiguous, and
        the instance with it, never resending
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#leaves
        a delivery that has been sending for less than the window
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#queues
        a delivery again whose work row was lost after the hour
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#records
        a queue row nobody processed before its payload’s expiry as an expired
        request and deletes the row
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#runs
        the same reconcile once per tenant per night through the engine’s state
        machine, run in process
      - TST-PFM-NOT-100
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#settles
      a delivery left sending past the ambiguity window failed ambiguous, and
      the instance with it, never resending
    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-008-05
    title: Apply retention and partition boundaries
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-06
    preconditions: Prepare retained records on both sides of the thirteen-month cutoff.
    steps: >-
      Execute retention cleanup and try inserting an item into an unprepared
      month.
    expected: >-
      Aged records are deleted in batches, younger records survive and an insert
      without a matching partition fails.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#computes
        the cutoff as thirteen calendar months before now
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#deletes
        requests, deliveries, events, instance keys and challenges past it and
        keeps what is younger
      - >-
        services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#works
        through a backlog in batches, a batch at a time
      - >-
        services-platform:modules/notifications/tests/unit/registry-tables.spec.js#registers
        the instances as monthly-partitioned on created_at, thirteen months kept
        and two ahead
      - >-
        services-platform:modules/notifications/tests/integration/db/notification-instances.table.integration.spec.js#refuses
        a row of a month with no partition rather than absorbing it in a default
        partition
      - TST-PFM-NOT-070
      - TST-PFM-NOT-100
      - TST-PFM-NOT-112
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js#computes
      the cutoff as thirteen calendar months before now
    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-008-06
    title: Distinguish workspace deletion from reset
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-07
    preconditions: Two workspaces with rows in every table.
    steps: >-
      Delete the first workspace's notification data and reset the second
      workspace's operational history.
    expected: >-
      Purge removes all scoped rows; reset retains configuration, preferences
      and usage. Both preserve another workspace's data.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/tenant-purges.integration.spec.js#purges
        every table of the tenant by repeated calls and leaves nothing, then
        finds nothing to remove
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/tenant-purges.integration.spec.js#removes
        the operational records and leaves the configuration and the metering as
        they were
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/tenant-purges.integration.spec.js#reaches
        no other tenant
      - >-
        react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0206.spec.ts#NT-ACC-0206
      - TST-PFM-NOT-017
      - TST-PFM-NOT-098
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/tenant-purges.integration.spec.js#purges
      every table of the tenant by repeated calls and leaves nothing, then finds
      nothing to remove
    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-008-07
    title: Exclude sensitive values from captured normal-path logs
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-08
    preconditions: Prepare a capturing logger.
    steps: >-
      Process supplied body text, a link and an address while capturing pipeline
      logs.
    expected: >-
      Captured log entries contain none of the supplied content, links, data,
      addresses or credentials.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/orchestration/logging.integration.spec.js#writes
        ids, keys and outcomes and never a title, a body, a link, a data value
        or an e-mail address
      - >-
        react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0207.spec.ts#NT-ACC-0207
      - TST-PFM-NOT-018
      - TST-PFM-NOT-078
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/orchestration/logging.integration.spec.js#writes
      ids, keys and outcomes and never a title, a body, a link, a data value or
      an e-mail address
    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-008-08
    title: Enforce notification row visibility
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-09
      - INV-DOM-PFM-NOT-003-09
    preconditions: Notices in two workspaces.
    steps: >-
      Retrieve through the parent table, through a partition, and with no
      organization set.
    expected: >-
      Each workspace sees its own rows; execution without a workspace sees no
      scoped rows.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/unit/registry-tables.spec.js#exempts
        exactly the catalog among the module’s tables as a tenantless registry,
        with a reason
      - >-
        services-platform:modules/notifications/tests/unit/registry-tables.spec.js#gives
        every other table a tenant_id for the policy to bind
      - >-
        services-platform:modules/notifications/tests/integration/db/notification-instances.table.integration.spec.js#shows
        a tenant its own instances and not another’s, through the parent
      - >-
        services-platform:modules/notifications/tests/integration/db/notification-instances.table.integration.spec.js#shows
        no instances at all to a statement that names no tenant
      - TST-PFM-NOT-070
      - TST-PFM-NOT-112
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/unit/registry-tables.spec.js#exempts
      exactly the catalog among the module’s tables as a tenantless registry,
      with a reason
    category: permission
    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/notifications/tests/unit/registry-tables.spec.js#exempts
      exactly the catalog among the module’s tables as a tenantless registry,
      with a reason
  - id: ATS-PFM-NOT-008-09
    title: Detect dispatcher silence
    level: unit
    proves:
      - BR-CAP-PFM-NOT-008-10
    preconditions: Synthesized notification deployment with an operations paging topic.
    steps: >-
      Inspect the configured run-count metric, fifteen-minute period, below-one
      threshold, missing-data handling and paging target.
    expected: >-
      The alarm treats no run or missing data as a breach and targets operations
      paging.
    negative: false
    data_needs: >-
      Use isolated deployment configuration rather than publishing metrics to a
      shared environment.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/notifications/aws/logic/notifications-functions.lambda.ts:createDispatcherSilenceAlarm
    category: failure
  - id: ATS-PFM-NOT-008-10
    title: Detect absent nightly reconciliation
    level: unit
    proves:
      - BR-CAP-PFM-NOT-008-11
    preconditions: Synthesized notification deployment with an operations paging topic.
    steps: >-
      Inspect the schedule-count alarm with two daily evaluation periods, two
      required breaches, missing-data handling and paging target.
    expected: >-
      Two consecutive days below one scheduled run trigger the configured
      operations-paging alarm.
    negative: false
    data_needs: >-
      Use isolated deployment configuration rather than publishing metrics to a
      shared environment.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/notifications/aws/logic/notifications-reconcile.lambda.ts:createNotificationsReconcileFunctions
    category: failure
  - id: ATS-PFM-NOT-008-11
    title: Recovery does not restore withdrawn work or duplicate delivery
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-008-02
    exercises:
      - CAP-PFM-NOT-008
      - CAP-PFM-NOT-002
      - CAP-PFM-NOT-006
      - CAP-PFM-NOT-007
    preconditions: >-
      Use a committed personal request, a lost dispatch acknowledgement and a
      queued delivery that is later withdrawn.
    steps: >-
      Repeat dispatch and request processing, withdraw the queued work, then run
      nightly reconciliation and read the personal inbox.
    expected: >-
      The occurrence has one recipient notice. Reconciliation does not send
      withdrawn work, and inbox applicability agrees with the retained request
      history.
    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-008-keep-notifications-whole-when-part-of-the-pipeline-fails.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-002-tell-people-that-something-happened-with-one-call.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-006-withdraw-or-expire-a-notice-that-no-longer-applies.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-007-deliver-each-notice-on-the-channels-it-may-use.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-002
      - CAP-PFM-NOT-006
      - CAP-PFM-NOT-007
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js;
    react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0206.spec.ts;
    services-platform:modules/notifications/tests/integration/services/persistence/requests.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/reconcile/reconcile-tenant.integration.spec.js;
    services-platform:modules/notifications/tests/integration/db/notification-instances.table.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/orchestration/logging.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/notifications/tests/integration/services/persistence/submissions.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/orchestration/orchestrate-request.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js;
    services-platform:modules/notifications/tests/unit/registry-tables.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/tenant-purges.integration.spec.js;
    react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0207.spec.ts
coverage:
  rule: 'gap: FND-PFM-NOT-069'
  transition: covered
  validation: 'gap: FND-PFM-NOT-069'
  permission: covered
  edge: 'gap: FND-PFM-NOT-069'
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  rule: >-
    The specification does not isolate a general positive outcome outside the
    narrower category cases.
  validation: Malformed input is not isolated in a dedicated category scenario.
  edge: Exceptional but valid input needs a dedicated expected-outcome case.
findings:
  - FND-PFM-NOT-069
transition_coverage: []
workflow_coverage:
  - workflow: WF-PFM-NOT-002
    capabilities:
      - CAP-PFM-NOT-002
      - CAP-PFM-NOT-008
      - CAP-PFM-NOT-009
    scenario_candidate: ATS-PFM-NOT-008-11
    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-003
    capabilities:
      - CAP-PFM-NOT-003
      - CAP-PFM-NOT-004
      - CAP-PFM-NOT-005
      - CAP-PFM-NOT-006
      - CAP-PFM-NOT-007
      - CAP-PFM-NOT-008
    scenario_candidate: ATS-PFM-NOT-008-11
    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-005
    capabilities:
      - CAP-PFM-NOT-007
      - CAP-PFM-NOT-008
    scenario_candidate: ATS-PFM-NOT-008-11
    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-007
    capabilities:
      - CAP-PFM-NOT-008
      - CAP-PFM-NOT-006
    scenario_candidate: ATS-PFM-NOT-008-11
    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-008-11
    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-003-09
    scenarios:
      - ATS-PFM-NOT-008-08
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-003-staff-notification.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
---

# Recover notification processing and retain its history: 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-008-01` | `ATS-PFM-NOT-008-01`, `ATS-PFM-NOT-008-11` |
| `BR-CAP-PFM-NOT-008-02` | `ATS-PFM-NOT-008-02` |
| `BR-CAP-PFM-NOT-008-03` | `ATS-PFM-NOT-008-03` |
| `BR-CAP-PFM-NOT-008-05` | `ATS-PFM-NOT-008-04` |
| `BR-CAP-PFM-NOT-008-06` | `ATS-PFM-NOT-008-05` |
| `BR-CAP-PFM-NOT-008-07` | `ATS-PFM-NOT-008-06` |
| `BR-CAP-PFM-NOT-008-08` | `ATS-PFM-NOT-008-07` |
| `BR-CAP-PFM-NOT-008-09` | `ATS-PFM-NOT-008-08` |
| `BR-CAP-PFM-NOT-008-10` | `ATS-PFM-NOT-008-09` |
| `BR-CAP-PFM-NOT-008-11` | `ATS-PFM-NOT-008-10` |

## Interactions With Related Capabilities

Use a committed personal request, a lost dispatch acknowledgement and a queued delivery that is later withdrawn. Repeat dispatch and request processing, withdraw the queued work, then run nightly reconciliation and read the personal inbox.

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.
