Skip to content

moderation(L6): command handler (9040-9044) + mod-queue read endpoints#1599

Merged
tlongwell-block merged 2 commits into
eva/community-moderationfrom
quinn/mod-l6-build
Jul 7, 2026
Merged

moderation(L6): command handler (9040-9044) + mod-queue read endpoints#1599
tlongwell-block merged 2 commits into
eva/community-moderationfrom
quinn/mod-l6-build

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

Phase-1 Community Moderation — Lane 6 (L6)

The moderation command handler (kinds 9040–9044) and the three HTTP mod-queue read endpoints. Rebased clean onto integrated base 0bff742f (carries L1 #1592, L5 #1590, L4 #1594).

Command handler — handlers/moderation_commands.rs

Kind Action
9040 ban
9041 unban
9042 timeout
9043 untimeout
9044 resolve-report
  • Every command routes authorization through the single authorize_moderation_action capability helper (L2) — never an inline role check — then does its DB write (L1), records the audit row, and best-effort sends the relay-signed notice DM (L5).
  • Freshness gate rejects stale/replayed commands (never stored).
  • 9040 ban drives live enforcement via the paired AppState::disconnect_pubkey_clusterwide (L4): one call closes this pod's fenced sockets and fans the disconnect out cross-pod, so a live ban takes effect immediately, everywhere (decision 4). The DB ban row is the durable backstop for a dropped fan-out.

Read endpoints — api/bridge.rs + router.rs

  • GET /moderation/reports, /moderation/audit, /moderation/restricted.
  • Each mirrors the existing count_events shape: HOST-bound tenant, NIP-98 auth + replay check, then the mod-authz gate (ViewQueue) rather than plain relay membership. Reads resolve tenant-scoped rows (L1) only — never a global lookup.

Known dependency (external-blocked, not a defect here)

L2's authorize_moderation_action body is still todo!() (moderation_authz.rs:91), human-gated on Tyler setting Mari's model. The authz-gated paths compile against the pinned signature but will panic at runtime until L2 lands. Handler unit tests cover the pure helpers (tag parse, expiration vocab, report-tag validation), which do not hit authz.

Validation (base 0bff742f, toolchain 1.95.0)

  • cargo fmt --check
  • cargo check -p buzz-relay
  • cargo clippy -p buzz-relay
  • cargo test -p buzz-relay --lib ✅ — 492 passed / 0 failed / 2 ignored (7 L6 handler tests included)
  • git diff --check ✅ · pre-push hook (just test-unit + desktop/mobile/tauri) ✅

Diff is three buzz-relay lane files only (+774 / −4). state.rs reverts to base (the L4 stub is dropped; the real disconnect_pubkey_clusterwide is used).

npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm and others added 2 commits July 7, 2026 15:04
Phase-1 community moderation lane L6: the moderation command dispatch and
the three HTTP read endpoints that back the mod queue.

Command handler (handlers/moderation_commands.rs), kinds 9040-9044:
- 9040 ban, 9041 unban, 9042 timeout, 9043 untimeout, 9044 resolve-report.
- Every command routes authorization through the single
  `authorize_moderation_action` capability helper (L2) — never an inline
  role check — then performs its DB write (L1), records the audit row, and
  best-effort sends the relay-signed notice DM (L5).
- Freshness gate rejects stale/replayed commands (never stored).
- 9040 ban drives live enforcement via the paired
  `AppState::disconnect_pubkey_clusterwide` (L4): one call closes this pod's
  fenced sockets and fans the disconnect out cross-pod, so a live ban takes
  effect immediately, everywhere (decision 4). The DB ban row is the durable
  backstop for a dropped fan-out.

Read endpoints (api/bridge.rs) + routes (router.rs):
- GET /moderation/reports, /moderation/audit, /moderation/restricted.
- Each mirrors the existing count_events shape: HOST-bound tenant, NIP-98
  auth + replay check, then the mod-authz gate (ViewQueue) rather than
  plain relay membership. Reads resolve tenant-scoped rows (L1) only.

Depends on L2's `authorize_moderation_action` body, still `todo!()` and
human-gated: the authz-gated paths compile against the pinned signature but
panic at runtime until L2 lands. Handler unit tests cover the pure helpers
(tag parse, expiration vocab, report-tag validation) which do not hit authz.

Validation on base 0bff742: fmt --check, check -p buzz-relay, clippy
-p buzz-relay, test -p buzz-relay --lib all green (492 passed / 0 failed;
7 L6 handler tests included). git diff --check clean. Diff is three
buzz-relay lane files only.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Two small fixes in handle_resolve from Eva's #1599 review, plain delta on
8db239b.

F1 — orphan audit row on a lost resolve race: insert_audit ran before
resolve_moderation_report, so two mods resolving the same report left an
audit row behind the failed resolve. Check the already-fetched
report.status == "open" before insert_audit and error early. The DB's
WHERE status='open' stays the real guard; a comment notes the residual
tiny window (audit row + failed resolve only) is tolerated.

F2 — resolution rows indistinguishable from enforcement rows: a one-click
resolve with action=ban wrote an audit row "ban", and the client's paired
9040 wrote a second "ban" enforcement row — double-count, decision-vs-
enforcement ambiguity. Prefix the resolution decision row `resolve:ban`,
`resolve:delete`, etc. dismiss_report and escalate stay unprefixed
(escalate must remain queryable for the platform-safety lane). Module doc
records the audit vocab.

Validation on base 0bff742 (toolchain 1.95.0): fmt --check, clippy
-p buzz-relay --all-targets -D warnings, test -p buzz-relay --lib all
green (492 passed / 0 failed / 2 ignored). git diff --check clean. Delta
is moderation_commands.rs only, +30/-2.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block tlongwell-block merged commit 4e48fe2 into eva/community-moderation Jul 7, 2026
24 of 28 checks passed
@tlongwell-block tlongwell-block deleted the quinn/mod-l6-build branch July 7, 2026 19:32
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