---
id: STATE-PFM-NOT-003
title: Notice delivery progress
object: DOM-PFM-NOT-003
product_area: platform
module: notifications
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
- name: pending
  meaning: A notice begins with this value during creation.
- name: no channel
  meaning: No channel has sent; all selected channels ended without delivery.
- name: routed
  meaning: A non-console channel is waiting and none is sending or sent.
- name: delivering
  meaning: A non-console channel is sending or has been accepted.
- name: delivered
  meaning: Delivery completed successfully with no failed channel and no active work.
- name: partially delivered
  meaning: Successful and failed deliveries coexist after active work ends.
- name: failed
  meaning: All attempted channels ended in failure or bounce.
- name: superseded
  meaning: The notice remains readable after withdrawal, but no longer contributes to unread totals.
- name: expired
  meaning: The request ended before anything sent; the notice stays readable outside unread totals.
- name: cancelled
  meaning: The vocabulary reserves this value, but current processing does not assign it.
transitions:
- from: pending
  to: delivered
  trigger: Creation records a delivered console item.
  guard: Console delivery is selected and no other channel is queued.
  side_effects:
  - Compute the initial summary within the creation transaction.
  who: Notification processing worker
  provenance: observed
  source: services-platform:modules/notifications/instance-status.ts:deriveInstanceStatus; services-platform:modules/notifications/services/persistence/instances.ts:refreshInstanceStatus
- from: pending
  to: routed
  trigger: Creation queues a chat or text channel.
  guard: Queued delivery exists; an HQ alert has only its system Slack delivery.
  side_effects:
  - Create the work needed for channel dispatch.
  who: Notification processing worker
  provenance: test
  source: services-platform:modules/notifications/tests/integration/services/system/system-audience.integration.spec.js#is
    accepted as one request, one instance with no recipient and one queued slack_system delivery with
    the Block Kit payload
- from: routed
  to: delivering
  trigger: A consumer claims a delivery for sending.
  guard: The queued-state conditional update must succeed.
  side_effects: []
  who: Channel delivery worker
  provenance: observed
  source: services-platform:modules/notifications/services/dispatch/deliver-delivery.ts:deliverQueuedDelivery
- from: delivering
  to: delivered
  trigger: Delivery completes with a successful channel result.
  guard: No failed or bounced channel remains and active work has ended.
  side_effects: []
  who: Channel delivery worker
  provenance: observed
  source: services-platform:modules/notifications/instance-status.ts:deriveInstanceStatus
- from: delivering
  to: partially delivered
  trigger: A channel fails while another has delivered.
  guard: All channel work is terminal.
  side_effects: []
  who: Channel delivery worker; nightly reconciliation for an ambiguous send
  provenance: test
  source: services-platform:modules/notifications/tests/unit/instance-status.spec.js#is partially delivered
    when one delivered and another failed, none still active
- from: delivering
  to: failed
  trigger: All selected delivery attempts fail.
  guard: Every attempted channel is failed or bounced.
  side_effects:
  - Count terminal HQ alert failure for operational monitoring.
  who: Channel delivery worker
  provenance: observed
  source: services-platform:modules/notifications/services/dispatch/deliver-delivery.ts:deliverQueuedDelivery
- from: routed
  to: no channel
  trigger: The remaining queued channel is skipped.
  guard: No channel has sent.
  side_effects: []
  who: Channel delivery worker
  provenance: observed
  source: services-platform:modules/notifications/instance-status.ts:deriveInstanceStatus
- from: failed
  to: routed
  trigger: HQ retries an exhausted delivery.
  guard: The failed delivery carries the attempts-exhausted reason.
  side_effects:
  - Return the delivery to dispatchable work.
  who: Rhythm notification operator
  provenance: test
  source: services-platform:modules/notifications/tests/integration/services/dispatch/deliver-delivery.integration.spec.js#queues
    a delivery that ran out of receives again, with its work row, and nothing else
- from: delivered
  to: superseded
  trigger: Withdrawal reaches a notice with console delivery.
  guard: The request remains open and no other open request owns the folded notice.
  side_effects:
  - End unsent channels without recalling prior sends.
  who: Notification processing worker
  provenance: test
  source: services-platform:modules/notifications/tests/integration/services/orchestration/cancel-and-supersede.integration.spec.js#cancels
    the queued delivery and its work row, and does not recall one that already left
- from: routed
  to: superseded
  trigger: Withdrawal reaches a notice before channel sending.
  guard: The request remains open and no other open request owns the folded notice.
  side_effects:
  - Cancel the queued channel when its consumer checks the notice.
  who: Notification processing worker; chat delivery consumer
  provenance: test
  source: services-platform:modules/notifications/tests/integration/services/dispatch/deliver-delivery.integration.spec.js#cancels,
    not sends, a delivery whose notification was superseded after it was queued
- from: routed
  to: expired
  trigger: A consumer or nightly sweep detects expiry.
  guard: No channel has already sent.
  side_effects:
  - End the remaining unsent deliveries.
  who: Nightly reconciliation worker; chat delivery consumer
  provenance: observed
  source: services-platform:modules/notifications/services/orchestration/expire-request.ts; services-platform:modules/notifications/services/reconcile/reconcile-tenant.ts:reconcileTenant
invalid:
- from: superseded
  to: delivered
  why: Summary derivation preserves explicit superseded, cancelled and expired states.
- from: expired
  to: routed
  why: Expiry prevents dispatch of the request's remaining work.
- from: delivered
  to: routed
  why: Successful terminal deliveries are not reopened by ordinary processing.
concurrency: Creation computes its summary atomically, but later delivery changes and summary refreshes
  can commit separately. A refresh failure can leave stale progress. Withdrawal can race with a send already
  in flight and cannot recall that send.
invariants:
- INV-DOM-PFM-NOT-003-07
- INV-DOM-PFM-NOT-003-08
evidence:
- 'services-platform at ad3f6a65: modules/notifications/instance-status.ts; notifications.settings.ts:INSTANCE_STATUSES;
  services/persistence/instances.ts; services/dispatch/deliver-delivery.ts.'
---
# Notice delivery progress

## The Lifecycle

```mermaid
stateDiagram-v2
    state "pending" as s0
    state "no channel" as s1
    state "routed" as s2
    state "delivering" as s3
    state "delivered" as s4
    state "partially delivered" as s5
    state "failed" as s6
    state "superseded" as s7
    state "expired" as s8
    state "cancelled" as s9
    s0 --> s4
    s0 --> s2
    s2 --> s3
    s3 --> s4
    s3 --> s5
    s3 --> s6
    s2 --> s1
    s6 --> s2
    s4 --> s7
    s2 --> s7
    s2 --> s8
```

## Transitions Explained

The transition rows describe stored state changes, their checks and their recorded effects. A queue receive, provider outcome and staff action can reach different lifecycle boundaries.

## What Cannot Happen

Summary derivation preserves explicit superseded, cancelled and expired states. Expiry prevents dispatch of the request's remaining work. Successful terminal deliveries are not reopened by ordinary processing.

## Concurrency and Timing

Creation computes its summary atomically, but later delivery changes and summary refreshes can commit separately. A refresh failure can leave stale progress. Withdrawal can race with a send already in flight and cannot recall that send.
