From 78c87ae20e182fffdd99744d6c9ff99df82b159c Mon Sep 17 00:00:00 2001 From: Brad Groux <3053586+BradGroux@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:14:25 -0500 Subject: [PATCH] fix(sdk): preserve self-mention p tags in message and forum event builders (#4975) ## What users saw `buzz messages send` silently removed an explicitly supplied self-mention. The caller passed `--mention ` and received `accepted:true`, but the signed event had no matching `p` tag and `mention_pubkeys` was empty. ## Why it happened `nostr` 0.44 strips `p` tags matching the signer's pubkey by default. The codebase already opts out with `.allow_self_tagging()` for identity archive and unarchive requests, but the message and forum builders that accept mentions did not. The library therefore removed the tag during signing after the CLI had validated the explicit mention. ## What changed Added `.allow_self_tagging()` to all three event builders that accept mention tags: - `build_message` (kind 9) - `build_forum_post` (kind 45001) - `build_forum_comment` (kind 45003) An explicit mention now survives signing even when it matches the sender. ## How this was tested Added one regression test per builder. Each test signs with the same key included in the mention list and asserts that the resulting event preserves the self-referential `p` tag. Validation at `cd0f30bca`: ```text ./bin/cargo fmt --all -- --check cargo test -p buzz-sdk --lib cargo test -p buzz-cli --lib cargo clippy -p buzz-sdk -p buzz-cli --all-targets -- -D warnings ``` All 257 `buzz-sdk` tests and all 321 `buzz-cli` tests passed, and formatting and strict Clippy checks completed successfully. ## Scope and non-goals - Does not change mention validation, deduplication, or channel-member checks. - Does not change `normalize_mention_pubkeys`, which is not used by the messages-send path. - Does not add a dropped-mentions output field because the explicit tags are now preserved. Closes #4906. --------- Signed-off-by: Brad Groux Signed-off-by: npub17q2gdupkvswvk5kprwc7plergm4gn295uw6fe4mjyjv53ahuhtnq02jd3f Signed-off-by: Brad Groux <3053586+BradGroux@users.noreply.github.com> Signed-off-by: Wes Co-authored-by: npub17q2gdupkvswvk5kprwc7plergm4gn295uw6fe4mjyjv53ahuhtnq02jd3f Co-authored-by: Wes Co-authored-by: Carl --- crates/buzz-sdk/src/builders.rs | 59 +++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/crates/buzz-sdk/src/builders.rs b/crates/buzz-sdk/src/builders.rs index c3b4432101..948fa775f5 100644 --- a/crates/buzz-sdk/src/builders.rs +++ b/crates/buzz-sdk/src/builders.rs @@ -239,7 +239,9 @@ pub fn build_message( tags.push(tag(&["broadcast", "1"])?); } imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(9), content).tags(tags)) + Ok(EventBuilder::new(Kind::Custom(9), content) + .tags(tags) + .allow_self_tagging()) } /// Build an encrypted agent observer frame (kind 24200). @@ -290,7 +292,9 @@ pub fn build_forum_post( let mut tags = vec![tag(&["h", &channel_id.to_string()])?]; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45001), content).tags(tags)) + Ok(EventBuilder::new(Kind::Custom(45001), content) + .tags(tags) + .allow_self_tagging()) } /// Build a forum comment reply (kind 45003). @@ -306,7 +310,9 @@ pub fn build_forum_comment( thread_tags(thread_ref, &mut tags)?; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45003), content).tags(tags)) + Ok(EventBuilder::new(Kind::Custom(45003), content) + .tags(tags) + .allow_self_tagging()) } /// Build a diff/patch message (kind 40008). @@ -2253,6 +2259,53 @@ mod tests { assert!(has_tag(&ev, "h", &cid.to_string())); } + #[test] + fn message_preserves_self_mention_p_tag() { + // nostr 0.44 strips p tags matching the signer by default. + // build_message must opt in via allow_self_tagging() so that + // explicit self-mentions survive signing. See #4906. + let cid = uuid(); + let sender = keys(); + let self_pk = sender.public_key().to_hex(); + let builder = build_message(cid, "self-canary", None, &[&self_pk], false, &[]).unwrap(); + let ev = builder.sign_with_keys(&sender).expect("sign"); + assert!( + has_tag(&ev, "p", &self_pk), + "self-mention p tag must survive signing" + ); + } + + #[test] + fn forum_post_preserves_self_mention_p_tag() { + let cid = uuid(); + let sender = keys(); + let self_pk = sender.public_key().to_hex(); + let builder = build_forum_post(cid, "self-canary", &[&self_pk], &[]).unwrap(); + let ev = builder.sign_with_keys(&sender).expect("sign"); + assert!( + has_tag(&ev, "p", &self_pk), + "self-mention p tag must survive signing" + ); + } + + #[test] + fn forum_comment_preserves_self_mention_p_tag() { + let cid = uuid(); + let sender = keys(); + let self_pk = sender.public_key().to_hex(); + let root = event_id(); + let tr = ThreadRef { + root_event_id: root, + parent_event_id: root, + }; + let builder = build_forum_comment(cid, "self-canary", &tr, &[&self_pk], &[]).unwrap(); + let ev = builder.sign_with_keys(&sender).expect("sign"); + assert!( + has_tag(&ev, "p", &self_pk), + "self-mention p tag must survive signing" + ); + } + #[test] fn agent_observer_frame_happy_path() { let sender = keys();