---
id: ATS-PFM-NOT-016
scope: CAP-PFM-NOT-016
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-016-01
    title: Block Platform-rendered mail without publication
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-01
    preconditions: Prepare a purpose with starter text and a record that owns no template.
    steps: Submit the owning record's workflow e-mail request.
    expected: >-
      The missing-template request is blocked, and starter content is not
      submitted to the provider.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#offers
        starter text that the purpose itself never sends
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
        configuration_blocked behind the queue, naming the purpose and the
        record that owns no template
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#sends
        nothing at all
      - TST-PFM-NOT-030
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#offers
      starter text that the purpose itself never sends
    category: failure
    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-016-02
    title: Expose only association-editable purposes
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-02
      - INV-DOM-PFM-NOT-004-03
    preconditions: >-
      Prepare a Platform-rendered, a caller-rendered and an owned-template
      purpose.
    steps: >-
      Retrieve the list of purposes; create, preview, test-send and check
      readiness for the other two.
    expected: >-
      Only the Platform-rendered purpose is listed; all template paths reject
      the other purpose types by name.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#does
        not offer a purpose its caller renders, which has no template to edit
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#describes
        the records the purpose offers a template, from the registry
      - >-
        services-platform:modules/communications/tests/integration/services/templates/caller-rendered-purposes.integration.spec.js#refuse
        a template for the caller-rendered purpose
      - >-
        services-platform:modules/communications/tests/integration/services/templates/caller-rendered-purposes.integration.spec.js#refuse
        a preview of it
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#is
        not offered by the purpose list
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#refuses
        a template for it
      - TST-PFM-NOT-026
      - TST-PFM-NOT-062
      - TST-PFM-NOT-063
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#does
      not offer a purpose its caller renders, which has no template to edit
    category: boundary
    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/communications/tests/integration/services/templates/caller-rendered-purposes.integration.spec.js#refuse
      a template for the caller-rendered purpose
  - id: ATS-PFM-NOT-016-03
    title: Validate template fields while retaining unfinished drafts
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-03
    preconditions: Prepare a association administrator.
    steps: >-
      Create templates with no name, an unrecognized purpose, a bad reply-to,
      eleven copies, repeated copies; next a draft that does not validate.
    expected: >-
      Invalid fields produce specific rejections, copies are canonical and
      distinct, and unfinished content saves with validation findings.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        a body with no name as 400
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        a purpose Rhythm does not know as 400, naming it
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#refuses
        a reply-to address that is no address, naming the field
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#refuses
        more copies than one message may carry, naming the field
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#keeps
        a copied address only once, however many times it was given
      - >-
        services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js#refuses
        more than ten copies
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#saves
        a draft that does not validate, recording why it cannot be published
      - TST-PFM-NOT-026
      - TST-PFM-NOT-039
      - TST-PFM-NOT-052
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
      a body with no name as 400
    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-016-04
    title: Count only active templates toward the purpose limit
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-04
      - INV-DOM-PFM-NOT-004-07
    preconditions: Prepare a purpose filled to its limit with archived templates.
    steps: >-
      Create within the allowance, populate all active slots and attempt a
      further creation.
    expected: >-
      Creation within the allowance succeeds; exceeding it rejects the write and
      stores no template.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#creates
        a template for a purpose whose cap is filled with archived ones
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#refuses
        a template once the cap is filled with ACTIVE ones
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#writes
        nothing when it refuses, so the purpose holds what it held before
      - TST-PFM-NOT-052
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#creates
      a template for a purpose whose cap is filled with archived ones
    category: boundary
    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/communications/tests/integration/services/persistence/templates.integration.spec.js#refuses
      a template once the cap is filled with ACTIVE ones
  - id: ATS-PFM-NOT-016-05
    title: Reject stale drafts and archived edits
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-05
    preconditions: Prepare a template edited by two administrators.
    steps: >-
      Replace the draft at the version read; replace it a second time at the old
      version; edit and publish an archived template.
    expected: >-
      The first valid edit succeeds; its stale competitor and both archived
      operations fail.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        a stale version as 409
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#leaves
        the draft of the edit that won after the stale one is refused
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        editing an archived template as 409
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        publishing an archived template as 409
      - TST-PFM-NOT-026
      - TST-PFM-NOT-052
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
      a stale version as 409
    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-016-06
    title: Report unsafe and invalid content locations
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-06
      - INV-DOM-PFM-NOT-004-02
    preconditions: >-
      Prepare content containing undeclared fields, template inclusion,
      Handlebars syntax and a script element.
    steps: Run validation and rendering preview for every content fixture.
    expected: >-
      Every content problem includes its location, and invalid content is not
      rendered.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#refuses
        content that reads an undeclared variable and includes a file
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#names
        both problems in the refusal, so the screen can show them
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#is
        told it is Handlebars, with the line and column of the tag
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#is
        one problem naming what was found and where
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#says
        the draft does not validate
      - TST-PFM-NOT-026
      - TST-PFM-NOT-030
      - TST-PFM-NOT-065
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#refuses
      content that reads an undeclared variable and includes a file
    category: validation
    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/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#refuses
      content that reads an undeclared variable and includes a file
  - id: ATS-PFM-NOT-016-07
    title: Freeze published content across later edits
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-07
      - INV-DOM-PFM-NOT-004-01
    preconditions: Prepare a valid draft.
    steps: Publish; edit the draft; publish a second time; publish an invalid draft.
    expected: >-
      Version one and its hash stay immutable; later draft editing leaves it
      unchanged; publication makes version two current; invalid publication
      fails.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#froze
        the draft as version 1, with the content hash and the variables it reads
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#answers
        200 for the second publish, numbering it 2
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#points
        the template at the version published last
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#leaves
        the published version untouched when the draft changes afterwards
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        publishing a draft that does not validate as 400
      - >-
        services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js#leaves
        neither the version nor the pointer behind when the transaction it ran
        in rolls back
      - TST-PFM-NOT-026
      - TST-PFM-NOT-052
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#froze
      the draft as version 1, with the content hash and the variables it reads
    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/communications/tests/e2e/templates.e2e.spec.js#froze
      the draft as version 1, with the content hash and the variables it reads
  - id: ATS-PFM-NOT-016-08
    title: Save one record-owned template with partial updates
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-09
      - INV-DOM-PFM-NOT-004-04
    preconditions: Prepare a organization type that owns no template.
    steps: >-
      Save its first template; save part of it; save the identical content; save
      invalid content; dry-run; clear twice; save from two screens at once.
    expected: >-
      Initial save publishes version one; a changed partial save preserves other
      fields and publishes version two. Identical, invalid and dry-run saves add
      nothing. Clearing archives once, and concurrent creation yields one
      template.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#creates
        the record’s template and publishes version 1
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#keeps
        every field the partial content left out
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#clears
        an optional field when the screen sends null for it
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#publishes
        no new version
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#writes
        nothing for the refused first save: the record still owns none
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#writes
        nothing: the record owns no template afterwards
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#answers
        a second clear harmlessly, so a repeated record save is safe
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#ends
        with one template for the owner, because the unique index refuses the
        second insert
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#publishes
        no second version for a save that changes nothing
      - TST-PFM-NOT-030
      - TST-PFM-NOT-064
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#creates
      the record’s template and publishes version 1
    category: interaction
    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/communications/db/communication-templates.table.ts:communication_templates_owner_uidx
  - id: ATS-PFM-NOT-016-09
    title: Preserve owned-template purpose and owner
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-10
      - INV-DOM-PFM-NOT-004-05
    preconditions: Prepare a template a record owns.
    steps: >-
      Attempt owner and purpose changes through ordinary template editing, then
      archive through that interface.
    expected: >-
      Edits retain both identity fields; archiving clears the owning record's
      active content.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#drops
        the purpose and the owner from the body before the operation sees them
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#leaves
        the template owned by the record that composed it
      - >-
        services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#allows
        archiving it, which is the same act as clearing it
      - TST-PFM-NOT-064
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#drops
      the purpose and the owner from the body before the operation sees them
    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/communications/tests/integration/services/templates/owned-templates.integration.spec.js#drops
      the purpose and the owner from the body before the operation sees them
  - id: ATS-PFM-NOT-016-10
    title: Keep Marketing-version content immutable and hidden
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-11
      - INV-DOM-PFM-NOT-004-08
    preconditions: Prepare a Marketing message version.
    steps: >-
      Store its template; store the identical content; store different content;
      list staff templates.
    expected: >-
      Initial storage publishes one version, repetition returns it, changed
      published content fails and staff lists omit it.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#creates
        the template for the message version and publishes its version
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#answers
        the same version for the same content, and writes nothing
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#refuses
        different content for a message version that is published, as an illegal
        operation
      - >-
        services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#keeps
        its templates out of the template list
      - TST-PFM-NOT-063
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#creates
      the template for the message version and publishes its version
    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/communications/tests/integration/services/templates/message-version-templates.integration.spec.js#refuses
      different content for a message version that is published, as an illegal
      operation
  - id: ATS-PFM-NOT-016-11
    title: Preview through the delivery renderer without sending
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-12
    preconditions: Prepare a published template and the message delivery rendered from it.
    steps: >-
      Preview with the identical data; preview content with a problem; count the
      rows and the queue.
    expected: >-
      Subject and bodies match delivery output. Findings and empty fields are
      returned without rows, queue work or dispatch nudges.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#renders
        the same subject, character for character
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#renders
        the same HTML body, character for character
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#is
        an answer and not a refusal: the content is the author’s to fix
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#names
        that empty custom field, and only it, in emptyFields
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#leaves
        every communications table with exactly the rows the notice left
      - >-
        services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#puts
        nothing on the send queue
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#rendered
        the draft rather than a version, with sample data
      - TST-PFM-NOT-026
      - TST-PFM-NOT-065
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js#renders
      the same subject, character for character
    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-016-12
    title: Restrict marked test delivery destinations
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-13
    preconditions: Prepare a author with the test-send permission.
    steps: >-
      Submit marked tests to the requesting author's address, a test mailbox and
      a colleague's address.
    expected: >-
      Author and test-mailbox targets queue [TEST] messages; a colleague target
      is rejected.
    negative: true
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
        an address that is neither a test mailbox nor the caller’s own as 403
      - >-
        services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#allows
        a test mailbox, which the environment delivers to whoever asked
      - >-
        services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#allows
        the requesting user’s own address, read from the user
      - >-
        services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#allows
        no other address, however ordinary it looks
      - >-
        services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#marks
        the subject [TEST] and renders the registry’s sample values for the
        record it names
      - >-
        services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#marks
        the stored subject as a test
      - TST-PFM-NOT-026
      - TST-PFM-NOT-055
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#refuses
      an address that is neither a test mailbox nor the caller’s own as 403
    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-016-13
    title: Render declared records and custom fields
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-15
    preconditions: >-
      Prepare a contact custom field defined by the association; a contact that
      omits a described field.
    steps: >-
      Publish content referencing both fields and process a message from that
      version.
    expected: >-
      Custom-field values render, omitted fields render empty and references
      unavailable to the record type are rejected.
    negative: false
    data_needs: Use only records created and owned by this suite.
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js#renders
        the contact's value for the custom field into the stored HTML body
      - >-
        services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js#renders
        the omitted field as absent rather than as a value
      - >-
        services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js#refuses
        a template that reads a field crm.contacts does not have at all
      - >-
        services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js#refuses
        the same template for a tenant that has not defined the field, naming
        the path it reads
      - TST-PFM-NOT-056
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js#renders
      the contact's value for the custom field into the stored HTML body
    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-016-14
    title: Require publication before workflow delivery
    level: api
    proves:
      - AS-JNY-PFM-NOT-006-01
    preconditions: Prepare a organization type that owns no template.
    steps: Submit the workflow's notification e-mail.
    expected: The request records a blocked outcome and no mail is sent.
    negative: true
    data_needs: 'The lifecycle suite''s organization type, contacts and recording provider.'
    bound_to:
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#sends
        nothing at all
      - >-
        services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
        configuration_blocked behind the queue, naming the purpose and the
        record that owns no template
      - TST-PFM-NOT-030
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#sends
      nothing at all
    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-016-15
    title: Publication controls acceptance and later message history
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-NOT-016-07
    exercises:
      - CAP-PFM-NOT-016
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-019
    preconditions: >-
      Use one record-owned draft and two recipients; retain the initial
      published snapshot when one becomes available.
    steps: >-
      Submit before publication, publish corrected content, submit again, edit
      the draft and inspect request and message history.
    expected: >-
      The first request is blocked. The later request pins the published
      version; draft editing does not rewrite its retained message content.
    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-016-compose-the-e-mails-an-association-sends.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-017-ask-platform-to-send-an-association-e-mail.md#frontmatter
      - >-
        rhythm-v2-docs:specs/platform/notifications/planning/capabilities/CAP-PFM-NOT-019-record-what-became-of-each-e-mail.md#frontmatter
    interaction_with:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-019
  - id: ATS-PFM-NOT-016-16
    title: Preview record-owned content and retain the saved version
    category: interaction
    level: browser
    proves:
      - BR-CAP-PFM-NOT-016-12
    preconditions: >-
      Create an organization type with saved compliance content and a contact
      used for preview.
    steps: >-
      Preview the selected organization and contact, add an unavailable field to
      the unsaved draft and preview again.
    expected: >-
      The preview shows recipient values and a located validation problem; the
      saved content does not acquire the invalid field.
    negative: false
    data_needs: Dev fixture staff and unique records created by the browser suite.
    bound_to:
      - 'react-console:e2e/dev/organization-types.dev.spec.ts'
      - TST-PFM-NOT-117
    provenance: test
    source: 'react-console:e2e/dev/organization-types.dev.spec.ts'
    execution_note: Only template composition and preview assertions belong to this module.
  - id: ATS-PFM-NOT-016-17
    title: Select published scheduled-report content during schedule setup
    category: interaction
    level: browser
    proves:
      - BR-CAP-PFM-NOT-016-01
    preconditions: >-
      Use a dev administrator and deployed schedule routes; run both with an
      existing publication and with none.
    steps: >-
      Open schedule setup; when no publication exists, create and publish report
      content, then choose the template and run the schedule.
    expected: >-
      Setup selects published content. The fallback creation and publication
      return success when needed; run history reaches Sent, Failed or Skipped,
      so this case does not establish provider delivery.
    negative: false
    data_needs: Dev fixture staff and unique records created by the browser suite.
    bound_to:
      - 'react-console:e2e/dev/query-schedules.dev.spec.ts'
      - TST-PFM-NOT-118
    provenance: test
    source: 'react-console:e2e/dev/query-schedules.dev.spec.ts'
    execution_note: >-
      The template-creation assertions execute only when no published template
      exists; unavailable fixtures or routes skip the suite.
  - id: ATS-PFM-NOT-016-18
    title: Distinguish empty preview values from zero and false
    category: edge
    level: unit
    proves:
      - BR-CAP-PFM-NOT-016-12
    preconditions: >-
      Prepare declared paths whose values include text, zero, false, whitespace,
      null and absent properties, plus lists with mixed values.
    steps: Calculate empty fields for scalar paths and paths crossing lists.
    expected: >-
      Zero, false, whitespace and populated paths stay outside the empty-field
      result. Missing values are named, and a list path appears once only when
      every matching value is empty.
    negative: false
    data_needs: Pure scalar and list inputs in one process.
    bound_to:
      - >-
        services-platform:modules/communications/tests/unit/services/templates/empty-merge-fields.spec.js#names
        only the empty ones, in the order the paths were given
      - TST-PFM-NOT-121
    provenance: test
    source: >-
      services-platform:modules/communications/tests/unit/services/templates/empty-merge-fields.spec.js#names
      only the empty ones, in the order the paths were given
    binding_scope: >-
      This suite checks the empty-field helper used by preview; it does not
      render a template, persist content or call a provider.
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/templates/message-version-templates.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/templates/render-preview.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/requests/record-merge-fields.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/communications/tests/e2e/templates.e2e.spec.js;
    services-platform:modules/communications/tests/integration/services/persistence/templates.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js
  - >-
    Pinned evidence inventory 3:
    services-platform:modules/communications/tests/integration/services/templates/caller-rendered-purposes.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/template-copies.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js
coverage:
  rule: covered
  transition: 'gap: FND-PFM-NOT-077'
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  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-077
transition_coverage:
  - state_model: STATE-PFM-NOT-009
    transition_index: 1
    from: draft only
    to: published
    trigger: 'An author publishes, or an owning record saves its initial content.'
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#froze
      the draft as version 1, with the content hash and the variables it reads
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-009
    transition_index: 2
    from: published
    to: published
    trigger: An author republishes or an owner saves changed content.
    source: >-
      services-platform:modules/communications/tests/integration/services/templates/owned-templates.integration.spec.js#keeps
      every field the partial content left out
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-009
    transition_index: 3
    from: published
    to: archived
    trigger: An author archives the template or its owner clears the setting.
    source: >-
      services-platform:modules/communications/tests/e2e/templates.e2e.spec.js#keeps
      it readable by id, because it is kept for history
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
  - state_model: STATE-PFM-NOT-009
    transition_index: 4
    from: draft only
    to: archived
    trigger: An author archives an unpublished template or its owner clears it.
    source: >-
      services-platform:modules/communications/services/persistence/templates.ts:archiveTemplate
    existing_candidates: []
    decision: 'gap: independent guard and side-effect proof has not been verified'
workflow_coverage:
  - workflow: WF-PFM-NOT-011
    capabilities:
      - CAP-PFM-NOT-017
      - CAP-PFM-NOT-018
      - CAP-PFM-NOT-020
      - CAP-PFM-NOT-016
    scenario_candidate: ATS-PFM-NOT-016-15
    coverage: 'gap: FND-PFM-NOT-084'
    reason: >-
      The proposed related-capability sequence does not independently isolate
      every workflow hop, failure seam and interleaving.
  - workflow: WF-PFM-NOT-014
    capabilities:
      - CAP-PFM-NOT-016
    scenario_candidate: ATS-PFM-NOT-016-15
    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-004-01
    scenarios:
      - ATS-PFM-NOT-016-07
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-02
    scenarios:
      - ATS-PFM-NOT-016-06
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-03
    scenarios:
      - ATS-PFM-NOT-016-02
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-04
    scenarios:
      - ATS-PFM-NOT-016-08
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-05
    scenarios:
      - ATS-PFM-NOT-016-09
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-07
    scenarios:
      - ATS-PFM-NOT-016-04
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
  - invariant: INV-DOM-PFM-NOT-004-08
    scenarios:
      - ATS-PFM-NOT-016-10
    coverage: specified
    source: >-
      rhythm-v2-docs:specs/platform/notifications/design/objects/DOM-PFM-NOT-004-association-e-mail-template.md
    verification_note: >-
      The candidate supplies the fixture outcome; individual binding limits
      remain applicable.
---

# Author and publish association e-mail content: 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-016-01` | `ATS-PFM-NOT-016-01`, `ATS-PFM-NOT-016-15`, `ATS-PFM-NOT-016-17` |
| `BR-CAP-PFM-NOT-016-02` | `ATS-PFM-NOT-016-02` |
| `BR-CAP-PFM-NOT-016-03` | `ATS-PFM-NOT-016-03` |
| `BR-CAP-PFM-NOT-016-04` | `ATS-PFM-NOT-016-04` |
| `BR-CAP-PFM-NOT-016-05` | `ATS-PFM-NOT-016-05` |
| `BR-CAP-PFM-NOT-016-06` | `ATS-PFM-NOT-016-06` |
| `BR-CAP-PFM-NOT-016-07` | `ATS-PFM-NOT-016-07` |
| `BR-CAP-PFM-NOT-016-09` | `ATS-PFM-NOT-016-08` |
| `BR-CAP-PFM-NOT-016-10` | `ATS-PFM-NOT-016-09` |
| `BR-CAP-PFM-NOT-016-11` | `ATS-PFM-NOT-016-10` |
| `BR-CAP-PFM-NOT-016-12` | `ATS-PFM-NOT-016-11`, `ATS-PFM-NOT-016-16` |
| `BR-CAP-PFM-NOT-016-13` | `ATS-PFM-NOT-016-12` |
| `BR-CAP-PFM-NOT-016-15` | `ATS-PFM-NOT-016-13` |

## Interactions With Related Capabilities

Use one record-owned draft and two recipients; retain the initial published snapshot when one becomes available. Submit before publication, publish corrected content, submit again, edit the draft and inspect request and message history.

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.
