---
id: ATS-PFM-NOT-009
scope: CAP-PFM-NOT-009
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-009-01
    title: Require personal notification permission
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-01
    preconditions: >-
      Prepare a clerk in a built-in role with no administrative permission; a
      caller without the use permission.
    steps: Retrieve the inbox as each.
    expected: >-
      A built-in clerk role can read its inbox; a caller without the permission
      fails every personal route.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/unit/permissions.spec.js#puts
        use at tier read, so every built-in console role carries it, and open to
        customers
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#refuses
        every own-notification route to a caller without
        platform.notifications.use
      - >-
        react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0001.spec.ts#NT-ACC-0001
      - TST-PFM-NOT-005
      - TST-PFM-NOT-069
      - TST-PFM-NOT-109
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/unit/permissions.spec.js#puts
      use at tier read, so every built-in console role carries it, and open to
      customers
    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-009-02
    title: Confine personal operations to the signed-in user
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-02
    preconditions: >-
      Create an item owned by Jane, plus a colleague account and a portal
      account.
    steps: >-
      The colleague reads, marks and archives Jane's notice by id; the portal
      account reads its inbox.
    expected: >-
      Other users' items are not found and remain unchanged; portal callers are
      rejected.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#finds
        nothing of another person’s notification, whoever asks
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#finds
        nothing of another person’s notification, changing nothing
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#refuses
        a portal account every own-notification route, the portal being held
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#never
        lists another person’s notifications
      - >-
        react-console:acceptance/notifications/playwright/journeys/history/NT-ACC-0100.spec.ts#NT-ACC-0100
      - TST-PFM-NOT-002
      - TST-PFM-NOT-069
      - TST-PFM-NOT-073
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#finds
      nothing of another person’s notification, whoever asks
    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-009-03
    title: Filter and page the personal inbox
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-03
    preconditions: >-
      Prepare a recipient with notices across categories, some read, one
      archived.
    steps: >-
      Retrieve the list by default; unread only; one category; since an instant;
      archived; page through; send a bad cursor.
    expected: >-
      Views match their filters in descending creation order, with complete
      nonduplicated paging; an invalid cursor fails.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#lists
        the caller’s own notifications newest first with every element mapped
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#filters
        by unread, category and since, and pages by keyset
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#lists
        the archived by their own filter and leaves them out of the default list
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#refuses
        a bad filter, cursor or page size with a 400 naming the parameter
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.queries.integration.spec.js#pages
        by keyset: each page continues where the last stopped, with no instance
        twice or lost
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#refuses
        a page size outside 1 to 100
      - TST-PFM-NOT-069
      - TST-PFM-NOT-073
      - TST-PFM-NOT-090
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#lists
      the caller’s own notifications newest first with every element mapped
    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-009-04
    title: Match unread lists to unread totals
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-04
      - INV-DOM-PFM-NOT-003-08
    preconditions: >-
      Create two categories containing unread, read, archived and superseded
      items.
    steps: Retrieve the summary and the unread view.
    expected: 'Only unread, unarchived and applicable items contribute to either result.'
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#counts
        the unread, in all and by category
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#leaves
        out a notification that no longer applies
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.queries.integration.spec.js#leaves
        out of the unread view what the unread count leaves out: a notice that
        no longer applies
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.queries.integration.spec.js#counts
        the unread and unarchived instances, in all and by category
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#counts
        the unread in the summary
      - TST-PFM-NOT-069
      - TST-PFM-NOT-073
      - TST-PFM-NOT-090
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#counts
      the unread, in all and by category
    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/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#leaves
      out a notification that no longer applies
  - id: ATS-PFM-NOT-009-05
    title: Update personal read and archive state
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-05
    preconditions: Give Jane a single inbox item in unread state.
    steps: 'Set it read twice, unread, archive, unarchive.'
    expected: >-
      First-read time is preserved, unread restores counting, archive moves the
      item out of normal views and restoration returns it with the corresponding
      count.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
        one read and unread, archives and unarchives it, answering it each time
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js#keeps
        the first read stamp when it is marked read again
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#archives
        it, taking it out of the default list and into the archived one, and
        brings it back
      - >-
        react-console:components/notifications/__tests__/notifications.test.tsx#marks
        a row read from its menu: the route is called and the unread count drops
        by one
      - >-
        react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0003.spec.ts#NT-ACC-0003
      - TST-PFM-NOT-007
      - TST-PFM-NOT-021
      - TST-PFM-NOT-069
      - TST-PFM-NOT-073
      - TST-PFM-NOT-089
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    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-009-06
    title: Limit bulk reading to eligible personal items
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-06
    preconditions: >-
      Prepare two categories with unread items before and after a cutoff, plus
      an archived item and a colleague-owned item.
    steps: Set all read before the instant; for one category; for everything; repeat.
    expected: >-
      Only matching unread items change and contribute to the returned count.
      Archived and colleague items remain untouched; repetition changes zero
      items.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js#marks
        every unread instance read, but neither an archived instance nor another
        person’s
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#marks
        only the notifications before an instant read
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#marks
        only a category read
      - >-
        services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js#marks
        everything unread read, the one that no longer applies included, and a
        repeat marks nothing
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
        everything read, optionally before an instant, answering how many
      - TST-PFM-NOT-069
      - TST-PFM-NOT-073
      - TST-PFM-NOT-089
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js#marks
      every unread instance read, but neither an archived instance nor another
      person’s
    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-009-07
    title: Send a personal pipeline test
    level: browser
    proves:
      - BR-CAP-PFM-NOT-009-07
    preconditions: Use signed-in sessions for Jane and another staff member.
    steps: Select Send Me a Test in Jane's session.
    expected: Jane receives the test item in her inbox and no other user receives it.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#puts
        a test notification in the caller’s own list, through the whole
        pipeline, and nobody else’s
      - >-
        services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#sends
        the default message when none is given
      - >-
        react-console:components/notifications/__tests__/notifications.test.tsx#Send
        Me a Test asks for a test, which arrives in the inbox
      - >-
        react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0006.spec.ts#NT-ACC-0006
      - TST-PFM-NOT-010
      - TST-PFM-NOT-021
      - TST-PFM-NOT-069
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#puts
      a test notification in the caller’s own list, through the whole pipeline,
      and nobody else’s
    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-009-08
    title: Open an assignment item and update its count
    level: browser
    proves:
      - AS-JNY-PFM-NOT-001-01
      - AS-JNY-PFM-NOT-001-02
    preconditions: Use Jane's clerk account and the Northeast Batch posting responsibility.
    steps: Run the journey's steps one to four.
    expected: >-
      One unread item contains its content and action. Opening reaches My
      Responsibilities and reduces unread count by one.
    negative: false
    data_needs: The journey's own personas and Northeast unit.
    bound_to:
      - >-
        react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0001.spec.ts#NT-ACC-0001
      - >-
        react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0002.spec.ts#NT-ACC-0002
      - >-
        react-console:components/notifications/__tests__/notifications.test.tsx#marks
        a row read from its menu: the route is called and the unread count drops
        by one
      - TST-PFM-NOT-005
      - TST-PFM-NOT-006
      - TST-PFM-NOT-021
    provenance: test
    source: >-
      react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0001.spec.ts#NT-ACC-0001
    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-009-09
    title: Preserve existing attention and assignment behavior
    level: browser
    proves:
      - AS-JNY-PFM-NOT-001-04
    preconditions: Prepare the relevant milestone assignment walkthrough's data.
    steps: >-
      Save an assignment from responsibility Settings and inspect the header
      bell.
    expected: >-
      Assignment and Needs Your Attention retain their behavior; the intended
      notification section is still absent from the bell.
    negative: false
    data_needs: The journey's own personas and Northeast unit.
    bound_to:
      - >-
        react-console:acceptance/notifications/playwright/journeys/cross-application/NT-ACC-0300.spec.ts#NT-ACC-0300
      - TST-PFM-NOT-001
    provenance: test
    source: >-
      react-console:acceptance/notifications/playwright/journeys/cross-application/NT-ACC-0300.spec.ts#NT-ACC-0300
    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-009-10
    title: Inbox organization and explanation follow responsibility withdrawal
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-009-05
    exercises:
      - CAP-PFM-NOT-009
      - CAP-PFM-NOT-010
      - CAP-PFM-NOT-006
      - CAP-PFM-NOT-005
      - CAP-PFM-NOT-014
    preconditions: >-
      Assign Jane a scoped responsibility and publish two matching
      contributions.
    steps: >-
      Read the explanation, mark the folded notice read and unread, end the
      assignment, and compare inbox lists with unread totals.
    expected: >-
      The explanation retains the selection reason. Read and archive choices
      affect only Jane, while the superseded assignment stays readable outside
      applicable unread results.
    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-009-work-through-my-own-notifications.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-010-understand-why-i-was-told.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-005-fold-quick-repeats-into-one-notice.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-014-tell-staff-about-changes-to-their-own-responsibilities.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-010
      - CAP-PFM-NOT-006
      - CAP-PFM-NOT-005
      - CAP-PFM-NOT-014
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/notifications/tests/unit/permissions.spec.js;
    react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0001.spec.ts;
    react-console:acceptance/notifications/playwright/journeys/history/NT-ACC-0100.spec.ts;
    services-platform:modules/notifications/tests/integration/services/persistence/instances.integration.spec.js;
    react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0003.spec.ts;
    react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0002.spec.ts
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js;
    services-platform:modules/notifications/tests/integration/services/centre/notification-centre.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/instances.queries.integration.spec.js;
    react-console:components/notifications/__tests__/notifications.test.tsx;
    react-console:acceptance/notifications/playwright/journeys/inbox/NT-ACC-0006.spec.ts;
    react-console:acceptance/notifications/playwright/journeys/cross-application/NT-ACC-0300.spec.ts
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-070'
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-PFM-NOT-070'
  boundary: 'gap: FND-PFM-NOT-070'
  concurrency: 'gap: FND-PFM-NOT-070'
  invariant: covered
  interaction: covered
coverage_notes:
  failure: The suite lacks an isolated dependency failure and recovery case.
  boundary: >-
    The suite does not independently prove both sides of every size, time or
    workspace limit.
  concurrency: >-
    The suite lacks a complete race case with controlled interleaving and
    post-race assertions.
  transition: >-
    The lifecycle matrix records every transition as requiring independent
    verification of its guard and effects; existing scenarios remain useful
    candidates.
findings:
  - FND-PFM-NOT-070
transition_coverage:
  - state_model: STATE-PFM-NOT-004
    transition_index: 1
    from: unread
    to: read
    trigger: 'The person opens a notice, marks it read, or invokes bulk reading.'
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-004
    transition_index: 2
    from: read
    to: unread
    trigger: The person chooses to mark the notice unread.
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-004
    transition_index: 3
    from: unread
    to: archived unread
    trigger: The person archives an unread notice.
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-004
    transition_index: 4
    from: read
    to: archived read
    trigger: The person archives a read notice.
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-004
    transition_index: 5
    from: archived unread
    to: unread
    trigger: The person restores an unread archived notice.
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-004
    transition_index: 6
    from: archived read
    to: read
    trigger: The person restores a read archived notice.
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#marks
      one read and unread, archives and unarchives it, answering it each time
    existing_candidates:
      - ATS-PFM-NOT-009-05
    decision: 'gap: independent guard and side-effect proof has not been verified'
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-009-10
    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-08
    scenarios:
      - ATS-PFM-NOT-009-04
    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.
---

# Manage a personal notification inbox: 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-009-01` | `ATS-PFM-NOT-009-01`, `ATS-PFM-NOT-009-10` |
| `BR-CAP-PFM-NOT-009-02` | `ATS-PFM-NOT-009-02` |
| `BR-CAP-PFM-NOT-009-03` | `ATS-PFM-NOT-009-03` |
| `BR-CAP-PFM-NOT-009-04` | `ATS-PFM-NOT-009-04` |
| `BR-CAP-PFM-NOT-009-05` | `ATS-PFM-NOT-009-05` |
| `BR-CAP-PFM-NOT-009-06` | `ATS-PFM-NOT-009-06` |
| `BR-CAP-PFM-NOT-009-07` | `ATS-PFM-NOT-009-07` |
| `BR-CAP-PFM-NOT-009-09` | `FND-PFM-NOT-070` |

## Interactions With Related Capabilities

Assign Jane a scoped responsibility and publish two matching contributions. Read the explanation, mark the folded notice read and unread, end the assignment, and compare inbox lists with unread totals.

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.
