moderation(L5): relay-signed moderation notice DMs#1590
Merged
Conversation
Fill send_moderation_notice per plan §0.3: reuse the participant-hash
DM model to get/create a two-party {relay mod key, recipient} thread,
emit kind:39000 discovery (hidden/t=dm/p via emit_group_discovery_events)
and a relay-signed kind:0 "{host} Moderation" profile on first use, then
insert a relay-signed kind:9 notice (h=<dm_channel_id>) and fan it out via
dispatch_persistent_event.
Idempotent per (source id, recipient): each notice carries an e-tag with
its report/action id; an existing relay-authored notice for that id in the
DM short-circuits, so a crash-retry is a safe no-op.
Privacy: bodies are built only from the notice's own sanitized fields
(status/summary/public_reason) — never reporter identities or raw notes.
Unit tests cover source_id selection and body rendering per variant.
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
… unconditional discovery Fold Wren's + Eva's #1590 review into the L5 notice lane (single-file, no DB migration per the agreed constraint): - Unhide-after-open_dm: open_dm only clears hidden_at for created_by (the relay key), so a recipient who hid the "{host} Moderation" thread never saw a later ban/resolution notice. Call unhide_dm(community, dm, recipient) after open_dm so a fresh notice resurfaces the thread — closed-loop trust. - Opaque source tag: the source is a report/action DB-row UUID, not a 32-byte event id, so the ['e', <uuid>] tag was semantically wrong and lies about referencing an event. Replace with a custom ['moderation_source', <uuid>] tag and match it in Rust via a new notice_already_sent() helper scoped to the recipient's own DM thread — no EventQuery.e_tags, no shared-file edit, no migration. - Unconditional discovery + profile: the kind:0 profile and kind:39000 discovery were gated on was_created. Because discovery is ?-propagated, a first-send failure left the retry with was_created == false and the thread permanently undiscoverable. Both are replaceable events, so emit them on every send — cheap, and replace_addressable_event makes it idempotent. - Docs: idempotency is crash-retry safe, not concurrency-safe; hard per-source serialization is a noted follow-up (deferred per Eva/Wren). clippy clean; 4 unit tests pass. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Wren's #1590 re-review doc cleanups + Eva's non-blocking window catch, all one-file: - send_moderation_notice rustdoc no longer reads concurrency-absolute: "Crash-retry safe per (action/report id, recipient) ... concurrent duplicate sends are not serialized in v1." - ReportResolved.status field doc pinned to `resolved | dismissed` (the scaffold vocab); the defensive `escalated` match arm stays. - notice_already_sent now passes limit: Some(1000). Matching is post-query in Rust, so the default 100-row window could let an old source id fall out of view and re-send a duplicate on crash-retry; 1000 is the existing query clamp and a practical per-user ceiling — no migration, no shared-file edit. clippy clean; 4 unit tests pass. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
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.
L5 — Relay-signed moderation notice DMs
Fills the
moderation_notices.rsscaffold (contract tip4c58489b) per plan §0.3. One lane file changed.What it does
send_moderation_notice(tenant, state, recipient, notice):state.db.open_dm({relay mod key, recipient}), participant-hash idempotent ⇒ one thread per (community, user).emit_group_discovery_eventson first use, which already emits kind:39000 withhidden/t=dm/pfor DM channels (no reimplementation)."{host} Moderation"(replaceable) on first use.h=<dm_channel_id>+ anetag naming the source id, inserted and fanned out viadispatch_persistent_event(same path as every other relay-authored event).Idempotency
Per (source id, recipient): the
e-tagged source id is queried before insert; an existing relay-authored notice for that id short-circuits. A crash-retry between insert and fan-out is a safe no-op.Privacy (module invariant)
Bodies are built only from the notice's own sanitized fields (
status/summary/public_reason). Never reporter identities, other reporters, or raw report notes.Notes for reviewers
ModerationNoticeenum shape is unchanged from the scaffold (pinned contract)."{Community}"resolves totenant.host()— thecommunitiestable has no display-name column; host is the community's public identity.Verification
cargo check -p buzz-relay✅cargo clippy -p buzz-relay --all-targets✅ zero warningscargo test -p buzz-relay --lib moderation_notices✅ 4/4 (source_id selection + per-variant body rendering / privacy)Base:
eva/community-moderation. Trailers: Co-authored-by + Signed-off-by Tyler Longwell.