---
id: ATS-PFM-NOT-015
scope: CAP-PFM-NOT-015
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-015-01
    title: Validate choices against required delivery
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-02
    preconditions: Prepare registered definitions covering all preference modes.
    steps: >-
      Attempt console disablement, mandatory e-mail disablement, complete
      channel-configurable disablement and a disallowed digest; then retain one
      permitted channel.
    expected: >-
      Each invalid choice leaves preferences unchanged; a valid remaining
      channel is accepted.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        to turn the console off
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        to turn e-mail off for a mandatory definition
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        to leave a channel-configurable definition with every channel off
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#accepts
        a channel-configurable definition with one channel on, the negative
        control of that refusal
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        a daily digest for a definition that is not digestible
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        a category that does not exist
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        a definition scope with no registered definition
      - TST-PFM-NOT-091
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
      to turn the console off
    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-015-02
    title: Reject missing and stale optimistic versions
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-03
    preconditions: 'Prepare a stored choice, channel setting and personal setting.'
    steps: >-
      Attempt another write using an outdated version, then omit the required
      update version.
    expected: Every invalid version update fails and leaves the prior setting intact.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        a stale version with a 409 and leaves the choice as it was
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
        to overwrite a choice without naming the version
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#refuses
        a stale version with a 409 and leaves the row as it was
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#refuses
        a stale version with a 409 and leaves the row as it was
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        a stale version with a 409 and leaves the row as it was
      - TST-PFM-NOT-081
      - TST-PFM-NOT-082
      - TST-PFM-NOT-091
      - TST-PFM-NOT-099
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js#refuses
      a stale version with a 409 and leaves the choice as it was
    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-015-03
    title: Preserve console defaults and channel validation
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-04
    preconditions: Prepare a organization that wrote nothing.
    steps: >-
      Retrieve the defaults; switch the console off; set a connection on e-mail;
      set a fractional ceiling.
    expected: >-
      Initial configuration enables console and e-mail and disables chat and
      text; invalid changes fail.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#answers
        the defaults for a tenant that wrote nothing: the console and e-mail on,
        chat and SMS off
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#refuses
        to switch the console off
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#refuses
        a connection on a channel that has none
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#refuses
        a ceiling that is not a whole number of segments
      - TST-PFM-NOT-081
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#answers
      the defaults for a tenant that wrote nothing: the console and e-mail on,
      chat and SMS off
    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-015-04
    title: Prevent weakening mandatory definitions
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-05
    preconditions: Prepare a mandatory definition.
    steps: >-
      Try disabling the mandatory definition, changing its requirement, removing
      e-mail, adding an unsupported route and excluding console from permitted
      channels.
    expected: 'All weakening changes fail, while retaining the original setting succeeds.'
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        to turn a mandatory definition off
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        to make a mandatory definition required
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        to narrow a mandatory definition below e-mail
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        permitted channels the definition does not support
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
        permitted channels without the console
      - TST-PFM-NOT-082
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#refuses
      to turn a mandatory definition off
    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-015-05
    title: Validate stored wording overrides without runtime effect
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-06
    preconditions: Prepare a definition with English console and e-mail templates.
    steps: >-
      Draft text; publish; publish a second; publish text reading an undeclared
      field or a 141-character title; deactivate.
    expected: >-
      At most one stored version is active. Invalid content fails publication;
      deactivation clears the active override without changing current pipeline
      wording.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#starts
        a draft at version 1 for the definition, channel and locale
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#supersedes
        the previous active version when another is published
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#leaves
        exactly one active version per definition, channel and locale
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#refuses
        to publish wording that names a field the definition does not declare
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#refuses
        to publish an in-app title that renders over 140 characters
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#takes
        the active version out of use so the product’s wording applies again,
        and audits it
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#publishes
        an e-mail override that changes only the subject, the HTML and text
        inheriting from the product
      - TST-PFM-NOT-097
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#starts
      a draft at version 1 for the definition, channel and locale
    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-015-06
    title: Audit successful workspace configuration writes
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-07
    preconditions: Prepare a administrator.
    steps: >-
      Store a channel setting twice, a notice setting twice and publish text;
      make a rejected store.
    expected: >-
      Creation and update record before-and-after information for every audited
      family; rejected work adds no audit entry.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#audits
        the first write as a create and an update with the before and after
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#audits
        nothing for a refused write
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js#audits
        the first write as a create and the second as an update, with before and
        after
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#publishes
        a draft: active, stamped with the time and the person, and audited
      - TST-PFM-NOT-081
      - TST-PFM-NOT-082
      - TST-PFM-NOT-097
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js#audits
      the first write as a create and an update with the before and after
    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-015-07
    title: Validate stored quiet hours and digest time
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-08
    preconditions: Prepare a recipient with no settings.
    steps: >-
      Retrieve the defaults; store quiet hours with no end, the identical start
      and end, a weekday 8, a weekday twice.
    expected: >-
      Defaults omit quiet hours and select 08:00 digest time; every invalid time
      or weekday update fails.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#answers
        nothing for a person who wrote none, whose defaults are no quiet hours
        and a digest at eight
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#refuses
        quiet hours with a start and no end
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#refuses
        quiet hours that begin and end together
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#refuses
        a weekday outside 1 to 7
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#refuses
        a weekday twice
      - TST-PFM-NOT-099
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js#answers
      nothing for a person who wrote none, whose defaults are no quiet hours and
      a digest at eight
    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-015-08
    title: Enforce identity uniqueness and terminal revocation
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-09
    preconditions: Prepare a recipient with a linked Slack identity and a pending phone.
    steps: >-
      Link a second Slack identity; link a number another person has; verify the
      phone; revoke; move a revoked identity.
    expected: >-
      Duplicate identities fail, verification activates the identity, revocation
      frees the current channel slot and no transition reopens revoked state.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#refuses
        a second current identity on a channel for the same person
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#refuses
        a phone number that another person already holds, by the table’s own
        index
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#verifies
        a pending phone: active, verified now, the failure streak cleared
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#revokes
        an identity with a reason and frees the channel for a new link
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#never
        moves a revoked identity
      - TST-PFM-NOT-088
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#refuses
      a second current identity on a channel for the same person
    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-015-09
    title: Expire and exhaust phone challenges
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-10
    preconditions: Prepare a open challenge.
    steps: >-
      Answer five wrong codes next the right one; response the right code after
      expiry; open a second challenge for the number.
    expected: >-
      Five wrong codes spend the challenge; expired codes fail; a new challenge
      replaces the earlier one; plaintext codes are never stored.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#opens
        a challenge holding the digest of the code and never the code
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#replaces
        the person’s earlier challenge for the same number, so one is open at a
        time
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#spends
        the challenge after five wrong codes, refusing even the right one
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#refuses
        a right code after the challenge expired
      - TST-PFM-NOT-088
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js#opens
      a challenge holding the digest of the code and never the code
    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-015-10
    title: Reserve text segments atomically within allowance
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-11
    preconditions: Prepare a organization with a ceiling of ten segments.
    steps: >-
      Reserve a fitting amount and an excessive amount, race reservations at the
      limit and repeat with a zero allowance.
    expected: >-
      Fitting reservations succeed; over-limit work fails and stamps the ceiling
      event. Concurrent edge reservations admit only available segments; zero
      admits none.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js#reserves
        segments within the ceiling and answers the month’s total
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js#refuses
        a send that would cross the ceiling, leaving the total and stamping when
        it was stopped
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js#lets
        only the sends that fit through when they race at the edge
      - >-
        services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js#reserves
        nothing for a ceiling of zero, the tenant having asked for no texts
      - TST-PFM-NOT-095
    provenance: test
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js#reserves
      segments within the ceiling and answers the month’s total
    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-015-11
    title: Accept an empty override and a category digest choice
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-02
    preconditions: >-
      A registered channel-configurable definition and a category without a
      supplied definition row.
    steps: >-
      Save the definition preference with no channel switches; save a
      category-level daily digest; attempt a nonempty override with every
      supplied switch disabled.
    expected: >-
      The empty override and category daily digest are stored; the nonempty
      all-disabled override is rejected.
    negative: false
    data_needs: Create isolated personal preferences and a registered definition.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/notifications/services/persistence/preferences.ts:refuseInvalid
    category: edge
  - id: ATS-PFM-NOT-015-12
    title: Stored configuration does not change current rendered delivery
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-13
    exercises:
      - CAP-PFM-NOT-015
      - CAP-PFM-NOT-007
      - CAP-PFM-NOT-004
    preconditions: >-
      Save personal channel choices, a published wording override and a disabled
      optional definition setting.
    steps: >-
      Process a fresh occurrence and inspect its inbox content and selected
      channels alongside the stored configuration.
    expected: >-
      The stored versions remain available, while the occurrence retains product
      wording and console-only personal delivery.
    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-015-let-the-organization-and-its-people-choose-how-they-hear.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
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-004-word-each-notice-for-the-person-who-receives-it.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-007
      - CAP-PFM-NOT-004
  - id: ATS-PFM-NOT-015-13
    title: Settle a collected digest window once
    category: transition
    level: api
    proves:
      - BR-CAP-PFM-NOT-015-12
    preconditions: >-
      Create one open daily window for Jane with two contribution identities;
      retain another untouched window.
    steps: >-
      Begin delivery, record completion and try to settle the same window again.
      Read both windows and their contribution totals.
    expected: >-
      Jane’s first window remains delivered after the repeated settlement; its
      contributions remain available and the untouched window stays open.
    negative: true
    data_needs: 'Suite-owned recipient, category and two distinct digest windows.'
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/notifications/services/persistence/digests.ts:DIGEST_TRANSITIONS
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/notifications/tests/integration/services/persistence/preferences.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/channel-settings.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/user-settings.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/definition-settings.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/identities.integration.spec.js;
    services-platform:modules/notifications/tests/integration/services/persistence/sms-usages.integration.spec.js
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-076'
  validation: covered
  permission: not-applicable
  edge: covered
  failure: 'gap: FND-PFM-NOT-076'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  permission: >-
    This capability declares no independent access key; callers enter through
    the producing or reading capability.
  failure: The suite lacks an isolated dependency failure and recovery case.
  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-076
transition_coverage:
  - state_model: STATE-PFM-NOT-006
    transition_index: 1
    from: pending verification
    to: active
    trigger: A caller invokes dormant identity activation.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:transitionIdentity
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 2
    from: pending verification
    to: revoked
    trigger: The pending link is removed or abandoned.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 3
    from: active
    to: unreachable
    trigger: A channel reports loss of reachability.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 4
    from: active
    to: opted out
    trigger: The person or carrier withdraws consent.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 5
    from: unreachable
    to: active
    trigger: The channel reports restored reachability or another verification.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 6
    from: opted out
    to: active
    trigger: The person restores consent.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-006
    transition_index: 7
    from: active
    to: revoked
    trigger: The person or administrator removes the active link.
    source: >-
      services-platform:modules/notifications/services/persistence/identities.ts:TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-007
    transition_index: 1
    from: draft
    to: active
    trigger: An administrator publishes edited wording.
    source: >-
      services-platform:modules/notifications/tests/integration/services/persistence/template-overrides.integration.spec.js#leaves
      exactly one active version per definition, channel and locale
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-007
    transition_index: 2
    from: active
    to: superseded
    trigger: Another override becomes active for the same selection.
    source: >-
      services-platform:modules/notifications/services/persistence/template-overrides.ts
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-007
    transition_index: 3
    from: active
    to: deactivated
    trigger: An administrator withdraws the active wording.
    source: >-
      services-platform:modules/notifications/services/persistence/template-overrides.ts
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-008
    transition_index: 1
    from: open
    to: delivering
    trigger: A caller begins delivery at the end of a window.
    source: >-
      services-platform:modules/notifications/services/persistence/digests.ts:DIGEST_TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-008
    transition_index: 2
    from: open
    to: empty
    trigger: A caller closes a window as empty.
    source: >-
      services-platform:modules/notifications/services/persistence/digests.ts:DIGEST_TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-008
    transition_index: 3
    from: delivering
    to: delivered
    trigger: The channel confirms digest sending.
    source: >-
      services-platform:modules/notifications/services/persistence/digests.ts:DIGEST_TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-008
    transition_index: 4
    from: delivering
    to: failed
    trigger: The channel reports terminal digest failure.
    source: >-
      services-platform:modules/notifications/services/persistence/digests.ts:DIGEST_TRANSITIONS
    existing_candidates:
      - ATS-PFM-NOT-015-05
      - ATS-PFM-NOT-015-08
    decision: 'gap: independent guard and side-effect proof has not been verified'
workflow_coverage: []
domain_invariant_coverage: []
---

# Store notification preferences and channel configuration: 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-015-02` | `ATS-PFM-NOT-015-01`, `ATS-PFM-NOT-015-11` |
| `BR-CAP-PFM-NOT-015-03` | `ATS-PFM-NOT-015-02` |
| `BR-CAP-PFM-NOT-015-04` | `ATS-PFM-NOT-015-03` |
| `BR-CAP-PFM-NOT-015-05` | `ATS-PFM-NOT-015-04` |
| `BR-CAP-PFM-NOT-015-06` | `ATS-PFM-NOT-015-05` |
| `BR-CAP-PFM-NOT-015-07` | `ATS-PFM-NOT-015-06` |
| `BR-CAP-PFM-NOT-015-08` | `ATS-PFM-NOT-015-07` |
| `BR-CAP-PFM-NOT-015-09` | `ATS-PFM-NOT-015-08` |
| `BR-CAP-PFM-NOT-015-10` | `ATS-PFM-NOT-015-09` |
| `BR-CAP-PFM-NOT-015-11` | `ATS-PFM-NOT-015-10` |
| `BR-CAP-PFM-NOT-015-13` | `FND-PFM-NOT-076` |

## Interactions With Related Capabilities

Save personal channel choices, a published wording override and a disabled optional definition setting. Process a fresh occurrence and inspect its inbox content and selected channels alongside the stored configuration.

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.
