Skip to content

feat(workflow): implement the send_dm action (WF-07) - #2614

Draft
SeanGearin wants to merge 1 commit into
block:mainfrom
SeanGearin:feat-workflow-send-dm
Draft

feat(workflow): implement the send_dm action (WF-07)#2614
SeanGearin wants to merge 1 commit into
block:mainfrom
SeanGearin:feat-workflow-send-dm

Conversation

@SeanGearin

@SeanGearin SeanGearin commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Design discussion: #2628 — opened per CONTRIBUTING's significant-change guidance. This PR is in draft pending that discussion; the implementation is here for reference.

Problem

The send_dm workflow action is fully wired through the schema
(ActionDef::SendDm) and template resolution, but its dispatch arm is a
maintainer TODO — crates/buzz-workflow/src/executor.rs:582
// TODO (WF-07): emit DM event. — so every workflow step that uses it fails
with WorkflowError::NotImplemented("SendDm").

Scope is deliberately send_dm only; the neighbouring SetChannelTopic TODO
at executor.rs:588 is untouched (one action per PR).

Fix

Mirrors the two patterns the codebase already establishes:

  • Emission follows the sibling SendMessage arm exactly: the executor
    loads the run + workflow (community-scoped, same wrong-community rationale
    as SendMessage) to obtain the owner pubkey, then calls a new
    ActionSink::send_dm. The relay-side RelayActionSink::send_dm builds a
    kind:9 event, signs with the relay keypair, attributes the owner via p
    tag, persists via insert_event_with_thread_metadata (top-level: depth 0),
    and runs dispatch_persistent_event under the run's owning community —
    the same flow as send_message in crates/buzz-relay/src/workflow_sink.rs.
  • DM encoding: Buzz DMs are private channels (channel_type='dm'), not
    NIP-17 wraps. The sink opens (or reuses) the owner↔recipient DM channel via
    the same idempotent open_dm path the kind:41010 handle_dm_open command
    handler uses, and mirrors that handler's post-open side effects: on create —
    channels-created metric, membership-cache invalidation, dm_created system
    message, group discovery events, membership notifications; on re-open — the
    owner's NIP-DV visibility snapshot refresh.

Details:

  • The recipient is p-tagged on the message event in addition to the owner
    attribution tag: agent wake (event_mentions_agent in buzz-acp) and push
    delivery are p-tag gated, so an un-tagged DM would never notify anyone.
  • New pure helper resolve_dm_recipient normalizes the to field to
    lowercase hex before the sink call (the executor-side analogue of
    resolve_send_message_channel). It accepts hex or bech32 npub — the
    | npub template filter produces the latter — and rejects empty, malformed,
    and unresolved {{...}} literal values.
  • Self-DM (to == workflow owner) is rejected as InvalidInput rather than
    attempting a 1-participant channel (create_dm requires >= 2 participants).
  • Unlike SendMessage, the DM body is deliberately not written to relay logs
    (DMs are private; logs are not).
  • buzz:workflow tag is carried, preserving the recursive-trigger guard.

Design note for reviewers: signing follows the established workflow pattern —
relay keypair signs, owner attributed via p tag. If WF-07 intended
owner-key-signed DMs or a dedicated workflow identity instead, the sink method
is the single place to change.

Test evidence

  • cargo fmt -p buzz-workflow -p buzz-relay — clean.
  • cargo clippy -p buzz-workflow -p buzz-relay --all-targets — no warnings.
  • -p buzz-workflowfmt/clippy/nextest all green (includes this PR's
    new resolve_dm_recipient unit tests).
  • -p buzz-relayfmt + clippy green; nextest passes except 8
    environment-dependent failures
    in api::media::* /
    api::admin::feedback_* fixtures (they want the sidecar/storage setup — the
    genuinely DB-gated tests show as ignored, requires Postgres in the same
    run, so these are a different class). They fail identically on unpatched
    main
    , and none are in files this change touches (the diff only
    modifies workflow_sink.rs, action_sink.rs, executor.rs). Reproduce:
    cargo nextest run -p buzz-relay on main shows the same 8.
  • New unit tests (executor.rs, same pure-helper pattern as the
    resolve_send_message_channel tests): hex accepted, npub normalized to
    hex, uppercase normalized, whitespace trimmed, empty/whitespace rejected,
    malformed + unresolved-template literals rejected, and
    resolve_step_templates resolving both send_dm fields from a parsed YAML
    step.
  • New Postgres-gated integration test (workflow_sink.rs, mirroring
    workflow_send_message_p_tags_mentioned_member, run with
    cargo test -p buzz-relay --lib workflow_sink -- --ignored):
    workflow_send_dm_opens_channel_and_p_tags_recipient asserts the emitted
    event lands in a channel_type='dm' / visibility='private' channel, both
    participants are p-tagged, a second send reuses the same channel
    (idempotent open), and self-DM is rejected.

Links

  • TODO being implemented: crates/buzz-workflow/src/executor.rs:582
    (TODO (WF-07): emit DM event)
  • Sibling emission path mirrored: RelayActionSink::send_message
    (crates/buzz-relay/src/workflow_sink.rs)
  • DM open path reused: buzz_db::dm::open_dm /
    handle_dm_open (crates/buzz-relay/src/handlers/command_executor.rs)

@SeanGearin
SeanGearin requested a review from a team as a code owner July 23, 2026 20:13
Signed-off-by: Sean Gearin <sgearin@gmail.com>
@SeanGearin

Copy link
Copy Markdown
Contributor Author

Converted to draft pending the design discussion in #2628, per CONTRIBUTING's guidance to open an issue first for significant changes. I'll mark it ready once the approach — in particular the signing-key question raised there — is settled.

Wirenut33 added a commit to Wirenut33/buzz that referenced this pull request Aug 2, 2026
Narrow the upstream pull request to the unique atomic workflow deletion repair; send_dm remains under discussion in PR block#2614 and issue block#2628.

Co-authored-by: Michael Morale <michaelmorale1@gmail.com>
Signed-off-by: Michael Morale <michaelmorale1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant