DIVE-3560: fix buzz dms list — query relay-emitted kind:39000, not the never-emitted kind:41001 - #7
Merged
Merged
Conversation
…e never-emitted kind:41001
`dms list` queried {kinds:[41001], "#p":[me]}. KIND_DM_CREATED (41001) is
declared in buzz-core but no code path in this repo ever emits it, so the verb
returned [] for every account on every relay while channels list, channels
members and messages get all saw the same conversation. An empty list rather
than an error is why this read as "no DMs exist" instead of "my predicate
matched nothing" — and it is why the buzz plugin's inbound DM bridge
(5dive-plugins block#44) discovers zero DM targets on a real box.
The relay-emitted truth is kind:39000 NIP-29 group metadata, which
emit_group_discovery_events tags with t=<channel_type> plus one p tag per
participant for DMs. kind:39000 is absent from required_scope_for_kind's
allowlist, so client ingest rejects it as "restricted: unknown event kind" —
only the relay can author one. t=dm is therefore relay-attested channel_type,
not the attacker-controlled name/membership heuristic rejected on this row,
which matters because consumers skip the @-mention test on the DM path.
- dm_list_filter(): extracted so a regression to a kind nothing emits is a red
test, not a silent empty list.
- parse_query_events(): a relay error object is an error, not an empty inbox.
The old unwrap_or_default() made a dead relay indistinguishable from no DMs.
- dms_from_group_metadata(): keeps only t=dm with a non-empty d tag that lists
us; a valueless ["t"] tag no longer masks a later ["t","dm"]; newest revision
per channel wins; sorted newest first.
- Annotated KIND_DM_CREATED as reserved-and-unemitted.
Grading: 358/0 buzz-cli tests (9 new), 249/0 buzz-core, clippy clean, fmt clean.
9 mutants all killed, each asserted to have applied, baseline restored
byte-identical: accept any channel_type; drop the participant re-check; accept
an empty uuid; degrade a relay error to []; keep the oldest revision; query
41001 again (it survived until dm_list_filter was extracted and tested);
valueless-tag masking; unscoped #p; limit cap removed.
NOT VERIFIED: the e2e arm test_dms_list_query_shape_returns_the_dm (with a
kind:41001 negative control) is #[ignore] per repo convention and needs a
running relay. No relay, no docker and no reachable box from this seat, so it
is unexecuted here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
buzz dms listqueried{kinds:[41001], "#p":[me]}.KIND_DM_CREATED = 41001is declared incrates/buzz-core/src/kind.rsand emitted by nothing — the constant appears in its own declaration and one membership list, nowhere else in the tree.handle_dm_opencreates the channel and then publishes NIP-29 group discovery, never a 41001. So the verb has returned[]for every account on every relay since it was written, whilechannels list,channels membersandmessages getall saw the same conversation. That is why the buzz plugin's inbound DM bridge (5dive-plugins#44) discovers zero DM targets on a live box: a user DMs an agent and gets silence.The fix
Query the relay-emitted truth: kind:39000 NIP-29 group metadata, which
emit_group_discovery_eventstags withd= channel uuid,t= channel_type, and — for DMs only — oneptag per participant. Filter tot == "dm". Thedtag is exactly whatmessages get/send --channelalready takes.dm_list_filter()extracted so a regression to a kind nothing emits is a red test rather than a silent empty list.parse_query_events(): a relay error object is now an error. The oldunwrap_or_default()made a dead relay, a wrong predicate and an empty inbox one observation.dms_from_group_metadata(): keepst=dmwith a non-emptydthat lists us (re-checked locally, so a relay ignoring#pcannot widen a consumer's poll set); a valueless["t"]tag no longer masks a later["t","dm"]; newest revision per uuid wins; sorted newest first.KIND_DM_CREATEDannotated reserved-and-unemitted.Why
t=dmand not a name/membership heuristickind:39000 is absent from
required_scope_for_kind's match inhandlers/ingest.rs, whose default arm isErr("restricted: unknown event kind"). A client cannot publish one — only the relay keypair authors it. Sot=dmis relay-attestedchannel_type, not a channel NAME or a membership SHAPE, both of which any user can manufacture. That distinction is load-bearing: consumers skip the @-mention test on the DM path, so whatever marks a conversation as a DM is what authorises unsolicited input into a session.Threat note: anyone may legitimately
dms open --pubkey <agent>, so stranger DMs are open by design. What a forgery would buy is aiming the DM marker at a public channel and making the agent answer every message there unprompted — that is the attack the ingest allowlist closes.Grading
358/0
buzz-cli(9 new tests), 249/0buzz-core, clippy 0 warnings,cargo fmtclean — measured on this head. 9 mutants all killed, each asserted to have actually applied, baseline restored byte-identical after each: accept any channel_type; drop the participant re-check; accept an empty uuid; degrade a relay error to[]; keep the oldest revision; query 41001 again; valueless-tag masking; unscoped#p; limit cap removed.Worth flagging for reviewers: with the filter left inline, re-introducing the original defect (kind back to 41001) kept all 8 tests green — a pure mapper cannot see the query it was handed. Extracting
dm_list_filter()and asserting on it is what kills that mutant.Not verified
test_dms_list_query_shape_returns_the_dm(with a kind:41001 negative control) is#[ignore]per repo convention and needs a running relay. No relay and no docker on the authoring seat, so it is unexecuted — CI or a relay box grades it. The originating row's DONE line — a DM answered in the same DM — also stays unmet until a fixed binary is built, released and installed.DIVE-3560.