---
id: CAP-PFM-NOT-002
title: Publish a notification with its business change
feature: FEAT-PFM-NOT-02
product_area: platform
module: notifications
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - NT-ACC-0200
  - PLAT-M23-S06
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/proposals/notifications/23-decisions.md#D-01'
  - 'services-platform:docs/milestones/plans/PLAT-M23-plan.humanized.md#2. Scope'
  - >-
    services-platform:docs/milestones/plans/PLAT-M23-plan.humanized.md#PLAT-M23-S06
purpose: >-
  Applications announce business events without separating notification
  publication from the transaction that records the event.
outcomes:
  - A business transaction publishes a durable notification request.
  - A repeated idempotency key returns the existing submission.
concepts:
  - CON-PFM-NOT-003
actors:
  - ACT-PFM-030
  - ACT-PFM-031
surfaces:
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: platform.notifications.publish
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-PFM-NOT-002-01
    statement: >-
      Publishing inserts a queue entry in the producer's transaction; its
      availability follows the same commit or rollback as the business change.
    kind: invariant
    enforced_at:
      - database
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js#leaves
      nothing to claim when the producer’s transaction rolled back, and one row
      when it committed
    origin: >-
      services-platform:docs/milestones/plans/PLAT-M23-plan.humanized.md#2.
      Scope
  - id: BR-CAP-PFM-NOT-002-02
    statement: >-
      The producer capability role inserts only caller-owned queue fields in its
      own workspace; it cannot set claim or ordering fields, inspect or mutate
      existing entries, or access other notification tables.
    kind: permission
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/db/notification-submissions.table.integration.spec.js#may
      not read, change or delete the queue it writes
    origin: none
  - id: BR-CAP-PFM-NOT-002-03
    statement: >-
      The combination of submitting service, idempotency key and request kind
      identifies one request; Repeating it returns the same identifier with a
      duplicate receipt and leaves one queue entry.
    kind: invariant
    enforced_at:
      - database
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/requests/submit-notification-request.integration.spec.js#answers
      the same request id and a duplicate for a repeat, writing one row
    origin: none
  - id: BR-CAP-PFM-NOT-002-04
    statement: >-
      Queue payloads are objects limited to 32 KB; Entries identify a publish or
      cancel operation, a service submitter, a three-segment definition key and
      a derived version-five identifier; Occurrence cancellation uses the
      publish identifier but a separate entry.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/db/notification-submissions.table.integration.spec.js#refuses
      a payload over 32 KB
    origin: none
  - id: BR-CAP-PFM-NOT-002-05
    statement: >-
      Submission validates the complete envelope before writing or nudging
      dispatch; invalid or extra fields produce path-specific refusals; audience
      reasons occupy one line and at most 200 characters.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/requests/submit-notification-request.integration.spec.js#refuses
      a malformed envelope naming each path, with nothing written and nobody
      nudged
    origin: none
  - id: BR-CAP-PFM-NOT-002-06
    statement: >-
      HTTP publication and cancellation require a Rhythm system caller with
      publish permission; console callers are rejected even with all Platform
      permissions; IAM invocation requires the assigned invoke policy.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#refuses
      the system door and Rhythm HQ’s routes to a console caller, even one
      holding every platform key
    origin: none
  - id: BR-CAP-PFM-NOT-002-07
    statement: >-
      A receipt supplies the identifier derived from service and key and
      indicates duplication; Processing later determines the outcome displayed
      in History.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/requests/submit-notification-request.integration.spec.js#writes
      the queue row the library writes and answers the receipt, nudging the
      dispatcher afterwards
    origin: none
  - id: BR-CAP-PFM-NOT-002-08
    statement: >-
      Cancellation identifies the request to withdraw; unknown identifiers are
      not found, and a reason is required.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#answers
      a cancel of an unknown request 404 and one without a reason 400
    origin: none
  - id: BR-CAP-PFM-NOT-002-09
    statement: >-
      A post-commit nudge starts dispatch; Minute-by-minute scheduling supplies
      another chance when a nudge is lost, without imposing a delivery deadline.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: >-
      services-platform:modules/notifications/services/dispatch/dispatch-notifications.ts:dispatchNotifications
    origin: none
  - id: BR-CAP-PFM-NOT-002-10
    statement: >-
      IAM submissions use the service named by the event and return the same
      publish or cancel receipt as HTTP.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/requests/submit-notification-request.integration.spec.js#submits
      as the service the event names
    origin: none
scenarios:
  - id: SC-CAP-PFM-NOT-002-01
    given: >-
      The responsibilities integration assigns Jane Batch posting within its
      business transaction.
    when: That transaction commits and publication repeats using its event key.
    then: >-
      The queue contains one entry and the repeated submission returns the
      original identity as a duplicate.
    provenance: test
    source: >-
      react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0200.spec.ts#NT-ACC-0200
  - id: SC-CAP-PFM-NOT-002-02
    given: A console administrator possesses all Platform permission keys.
    when: The administrator invokes system publication.
    then: System-route access rejects the console caller.
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js#refuses
      the system door and Rhythm HQ’s routes to a console caller, even one
      holding every platform key
edge_cases: []
constraints: []
depends_on:
  - CAP-PFM-NOT-001
related:
  - CAP-PFM-NOT-003
  - CAP-PFM-NOT-006
  - CAP-PFM-NOT-008
journeys:
  - JNY-PFM-NOT-005
open_questions: []
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:docs/proposals/notifications/23-decisions.md;
    services-platform:modules/notifications/tests/integration/services/dispatch/dispatch-notifications.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/requests/submit-notification-request.integration.spec.js;
    services-platform:modules/notifications/services/dispatch/dispatch-notifications.ts
  - >-
    Pinned evidence inventory 2:
    services-platform:docs/milestones/plans/PLAT-M23-plan.humanized.md;
    services-platform:modules/notifications/tests/integration/db/notification-submissions.table.integration.spec.js;
    services-platform:modules/notifications/tests/e2e/notifications.e2e.spec.js;
    react-console:acceptance/notifications/playwright/journeys/pipeline/NT-ACC-0200.spec.ts
controls: [TC-AVL-001, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Publish a notification with its business change

## Purpose

Applications announce business events without separating notification publication from the transaction that records the event.

## How It Works

A producer prepares a publish or cancellation envelope for its own workspace. Validation happens before queue insertion. The producer inserts notification work through a restricted database role alongside its business update. Commit makes both available; rollback makes neither available. Repeating the key returns the existing request identity. A post-commit nudge accelerates dispatch, with scheduled dispatch available for recovery. The receipt acknowledges submission rather than promising that an audience was reached. History later supplies the processing outcome.

The rule rows define this outcome; the acceptance set identifies existing proof and proposed checks. Implementation evidence alone does not approve the observed details.

## Relationships

Registration supplies the event contract that later work validates and renders.
