Skip to content

L2: community moderation authorization seam (authorize_moderation_action)#1604

Merged
tlongwell-block merged 1 commit into
eva/community-moderationfrom
dawn/mod-l2-authz
Jul 7, 2026
Merged

L2: community moderation authorization seam (authorize_moderation_action)#1604
tlongwell-block merged 1 commit into
eva/community-moderationfrom
dawn/mod-l2-authz

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

Phase-1 community moderation authorization seam (L2). Implements authorize_moderation_action (moderation_authz.rs) — the single capability helper every L6 moderation command + the queue bridge route through (was todo!(), the last runtime block: every authz-gated path panicked until this landed).

Authority model

  • Community owner/admin (tenant-scoped relay_members.role) authorize every action in any channel of their community.
  • Channel owner/admin keep channel-local authority for DeleteMessage/Kick within channel_id. No current call site passes a channel_id — this branch is the contract seam for the future validate_admin_event wiring (plan §1), built to contract now so that's a one-line consult, not a rewrite.
  • Guard rail: an admin cannot ban/timeout the owner or a fellow admin; only the owner actions an admin. Scoped to ban/timeout only (restriction-lifting stays unguarded — rationale in the code comment; ruled by Eva). Trips on target role, never a missing row, so non-members stay bannable.

Tenant fence

Both role reads (get_relay_member, get_member_role) filter on tenant.community() in SQL — authority never crosses tenants.

Tests

Policy factored into a pure decide_authority, exhaustively unit-tested (7 tests, run in the normal sweep — no Postgres needed). buzz-relay --lib: 499/499.

Base: eva/community-moderation @ 4e48fe2d. One file, +251/-7.

Implements `authorize_moderation_action` (the capability helper, plan §1),
the single seam every moderation decision routes through so a future
Moderator tier is a policy change, not a rewrite (decision 1).

Authority model:
- Community owner/admin (tenant-scoped `relay_members.role`) authorize
  every action in any channel of their community — the bridge
  `validate_admin_event` is missing today.
- Channel owner/admin keep channel-local authority for DeleteMessage/Kick
  within `channel_id`. No current call site passes a `channel_id` (every
  L6 handler + the queue bridge pass `None`), so this branch is the
  contract seam for the future `validate_admin_event` wiring — building it
  to contract now makes that a one-line consult, not a helper rewrite.
- Guard rail: an admin cannot ban/timeout the community owner or a fellow
  admin; only the owner may action an admin. Scoped to ban/timeout only —
  restriction-lifting (unban/untimeout) is unguarded because a banned
  admin can't self-unban (blocked at the auth seam before any command
  runs), so the only reachable case is lifting a fellow admin's
  restriction, which is benign, audited, and owner-reversible. The guard
  trips on a target *role* of owner/admin, never on a missing row, so a
  drive-by spammer who already left is still bannable.

Tenant fence: both role reads (`get_relay_member`, `get_member_role`)
filter on `tenant.community()` in SQL — authority never crosses tenants.

The policy is factored into a pure `decide_authority` from resolved roles,
exhaustively unit-tested (7 tests: owner all-actions, admin against
non-privileged and privileged targets, non-member bannable, guard scope,
channel-role delete/kick-only, member/stranger denied). The async wrapper
is thin I/O glue that reads only the roles a given path needs.

Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
@tlongwell-block tlongwell-block merged commit 8059413 into eva/community-moderation Jul 7, 2026
24 of 28 checks passed
@tlongwell-block tlongwell-block deleted the dawn/mod-l2-authz branch July 7, 2026 19:53
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