fix(cli): dms list must query the relay-emitted kind:39000, not kind:41001 - #6847
fix(cli): dms list must query the relay-emitted kind:39000, not kind:41001#6847lodar wants to merge 1 commit into
dms list must query the relay-emitted kind:39000, not kind:41001#6847Conversation
…d: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 reads as "no DMs exist" instead of "my predicate
matched nothing".
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
rather than a client-supplied name/membership heuristic, 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.
Adds an end-to-end interop arm in buzz-test-client that pins the wire kind, so
the relay and the CLI cannot drift apart again.
Signed-off-by: lodar <markounik@gmail.com>
Chessing234
left a comment
There was a problem hiding this comment.
the diagnosis is airtight and the trust paragraph is the best part — "kind:39000 is not in required_scope_for_kind's allowlist, so only the relay can author one, therefore t=dm is relay-attested rather than a client-supplied name" is exactly the argument that needed making, given that the DM path is what lets a consumer skip the @-mention test. extracting dm_list_filter so a regression to an unemitted kind is a red test rather than a silent [], and making parse_query_events treat a relay error object as an error, are both the right lessons to take from the bug rather than just fixing it.
two things.
--limit no longer counts what the user thinks it counts. the filter sends {"kinds":[39000], "#p":[me], "limit": n} and the t=dm filter runs client-side afterwards. so the relay's limit is applied to kind:39000 events before anything narrows to DMs, and dms list --limit 50 means "the newest 50 group-metadata events that p-tag me, of which some subset are DMs". today that's probably harmless — the pr says 39000 carries p tags "for DMs only", so #p already does most of the work — but that's a property of emit_group_discovery_events right now, not a guarantee, and the failure mode if it changes is silent truncation of the DM list rather than an error. worth either saying in the doc comment that #p is load-bearing for the limit's meaning, or over-fetching and truncating after the t=dm filter so the number the user passed is the number of DMs they get.
does the CLI's own read path accept a kind it can't author? the trust argument turns on required_scope_for_kind rejecting client-authored 39000 with "restricted: unknown event kind". if that check also runs over query results anywhere in BuzzClient::query or the SDK's ingest, the CLI would drop the very events this fix depends on. i assume it's an authoring gate only, since you measured this against a live relay — but the doc comment currently asserts the rejection without saying which direction it applies to, and that's the one sentence a future reader will lean on hardest. worth naming the function that enforces it and stating it's inbound-publish only.
smaller: "a valueless ["t"] tag no longer masks a later ["t","dm"]" is a good catch and exactly the kind of thing that deserves the unit test it presumably has. and annotating KIND_DM_CREATED as reserved-and-unemitted rather than deleting it is the right call — worth making that comment say why it's reserved (so a future relay change doesn't re-use the number) rather than only that it's unused.
Summary
dms listqueried{kinds:[41001], "#p":[me]}.KIND_DM_CREATED(41001) is declared inbuzz-core, but no code path in this repo ever emits it — so the verb returns[]for every account on every relay, whilechannels list,channels membersandmessages getall see the same conversation. Returning an empty list rather than an error is why this reads as "no DMs exist" instead of "my predicate matched nothing".The relay-emitted truth is kind:39000 NIP-29 group metadata:
emit_group_discovery_eventstags it witht=<channel_type>plus oneptag per participant for DMs. kind:39000 is absent fromrequired_scope_for_kind's allowlist, so client ingest rejects it as "restricted: unknown event kind" — only the relay can author one.t=dmis therefore a relay-attested channel type rather than a client-supplied name or membership heuristic, which matters because consumers skip the @-mention test on the DM path.Key decisions:
dm_list_filter()is extracted, so a regression back to a kind nothing emits is a red test rather than a silent empty list.parse_query_events()treats a relay error object as an error. The oldunwrap_or_default()made a dead relay indistinguishable from an empty inbox.dms_from_group_metadata()keeps onlyt=dmevents with a non-emptydtag that lists us; a valueless["t"]tag no longer masks a later["t","dm"]; the newest revision per channel wins; results sort newest first.KIND_DM_CREATEDis annotated as reserved-and-unemitted rather than deleted, so nothing downstream breaks on the constant disappearing.Related issue
No issue opened for this. Closest existing reports: #5137, #5424 and #2897 — all "
dms listreturns empty for an active conversation".Two open PRs already attack the same defect inside
dms.rs: #5725 and #5139. This one differs in resolving against the relay-attested kind:39000 discovery event, in distinguishing a relay error from an empty inbox, and in pinning the wire kind with a regression arm. If a maintainer prefers either of those directions, say so and I will close this in their favour.Testing
crates/buzz-cli/src/commands/dms.rscovering the filter shape, non-DM channel types, missing/valuelessttags, non-participation, empty channel uuid, newest-revision-wins ordering, malformed tags, and the relay-error-is-not-an-empty-list case.crates/buzz-test-client/tests/e2e_nostr_interop.rsthat pins the wire kind so the relay and the CLI cannot drift apart again. Note it is#[ignore]d, following that file's existing convention — it is a manual harness against a live relay, not an unattended gate, so please do not read it as CI coverage.rustfmt --checkclean on all three changed files under the pinned 1.95.0 toolchain; lint, unit tests, the Windows build and the cross-compile matrix ran green on this exact tree in my own CI before opening.No UI surface is touched.
The branch is based on
f88cda9eb, which is behindmain— none of the three files have been touched upstream since, so it applies cleanly. Happy to rebase on request.