L2: community moderation authorization seam (authorize_moderation_action)#1604
Merged
Merged
Conversation
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>
8059413
into
eva/community-moderation
24 of 28 checks passed
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.
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 (wastodo!(), the last runtime block: every authz-gated path panicked until this landed).Authority model
relay_members.role) authorize every action in any channel of their community.DeleteMessage/Kickwithinchannel_id. No current call site passes achannel_id— this branch is the contract seam for the futurevalidate_admin_eventwiring (plan §1), built to contract now so that's a one-line consult, not a rewrite.Tenant fence
Both role reads (
get_relay_member,get_member_role) filter ontenant.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.