---
id: STATE-PFM-OPS-003
title: Requester notice delivery
product_area: platform
module: operations
revision: 1
status: draft
object: DOM-PFM-OPS-001
design_intent: confirmed
origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D11 and #E5 (approved K8)'
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
  - name: pending
    meaning: 'A claim exists, but its sender has not recorded a final delivery result.'
  - name: delivered
    meaning: Delivery was recorded with its provider reference when available.
  - name: suppressed
    meaning: The environment withheld delivery and the notice counts as handled.
  - name: failed
    meaning: The send attempt failed and another invocation may reclaim it.
transitions:
  - from: pending
    to: delivered
    trigger: The sender records acceptance by the mail provider.
    guard: The updater receives the row identity without checking claim ownership or present state.
    side_effects:
      - Record handling time and any provider reference.
    who: the notify-requester capability
    provenance: observed
    source: 'services-platform:modules/jobs/services/persistence/job-notices.ts:recordJobNoticeOutcome'
  - from: pending
    to: suppressed
    trigger: The sender records environment suppression.
    guard: The updater receives the row identity without checking claim ownership or present state.
    side_effects:
      - Record the handling time.
    who: the notify-requester capability
    provenance: observed
    source: 'services-platform:modules/jobs/services/notices/notify-job-requester.ts:notifyJobRequester'
  - from: pending
    to: failed
    trigger: The sender cannot deliver or resolve required delivery inputs.
    guard: The updater receives the row identity without checking claim ownership or present state.
    side_effects:
      - Store the shortened error.
      - Log delivery failure.
      - Leave the job outcome unaffected.
    who: the notify-requester capability
    provenance: observed
    source: 'services-platform:modules/jobs/services/notices/notify-job-requester.ts:notifyJobRequester'
  - from: failed
    to: pending
    trigger: Delivery is invoked again for a failed notice.
    guard: The claim matches a pending or failed row.
    side_effects:
      - Increment claim attempts.
      - Clear the previous claim error.
    who: the notify-requester capability
    provenance: test
    source: >-
      services-platform:modules/jobs/tests/integration/services/persistence/job-notices.integration.spec.js#is
      claimed again, because a notice nobody received is worse than one that arrives twice
  - from: pending
    to: pending
    trigger: Another invocation claims an already pending notice.
    guard: The existing row remains eligible for claiming.
    side_effects:
      - Increment claim attempts.
      - 'Permit another sender, with duplicate delivery possible.'
    who: the notify-requester capability
    provenance: observed
    source: 'services-platform:modules/jobs/services/persistence/job-notices.ts:claimJobNotice'
invalid:
  - from: delivered
    to: pending
    why: Delivered rows reject another claim; the unguarded outcome updater can still overwrite them.
  - from: suppressed
    to: pending
    why: Suppressed rows reject another claim; the unguarded outcome updater can still overwrite them.
concurrency: >-
  Claiming serializes the database statement but does not reserve a sender: pending rows remain claimable.
  Outcome writes have no ownership or current-state condition. Duplicate delivery and overwritten results
  remain possible, and another invocation is not guaranteed.
invariants:
  - INV-DOM-PFM-OPS-001-11
evidence:
  - >-
    services-platform at ad3f6a6: modules/jobs/db/job-notices.table.ts, services/persistence/job-notices.ts,
    services/notices/notify-job-requester.ts, services/notices/job-notice-email.ts and their suites.
  - >-
    codex examination 2026-10-23: checked the pinned source anchors, planning references and enforcement
    boundaries; unresolved limits are recorded in module findings.
---

# Requester notice delivery

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> pending: delivery claim
  pending --> delivered
  pending --> suppressed: delivery policy suppresses
  pending --> failed
  failed --> pending: subsequent claim
  pending --> pending: competing claim
  delivered --> [*]
  suppressed --> [*]
```

## Transitions Explained

Delivery first claims a job-and-outcome row, then resolves the requester, sends or suppresses the message, and records the result. Delivered and suppressed outcomes stop later claims. Pending and failed outcomes can be claimed again if another delivery invocation occurs.

## What Cannot Happen

A delivery exception does not propagate to the job. Claim refusal protects ordinary sequential replay, but the outcome updater itself does not protect terminal notice states. Another sender can overwrite a result using the same row identity.

## Concurrency and Timing

The protocol allows two claimants to send and leaves an interruption window between send and outcome persistence. No independent retry sweep guarantees that failed or abandoned notices receive another invocation.
