跳转至

GroupMembership 与群权限契约

依赖:shared.md。公网创建群与受信管理面成员变更是两个不可替代的边界。

pub trait GroupMembership: Send + Sync {
    async fn create_group(
        &self,
        actor: AuthenticatedActor,
        request: GroupCreateRequest,
    ) -> Result<GroupCreated, GroupMembershipError>;

    async fn admin_change_members(
        &self,
        principal: InternalPrincipal,
        change: AdminMembershipChange,
    ) -> Result<MembershipVersion, GroupMembershipError>;

    async fn authorize_send(
        &self,
        actor: AuthenticatedActor,
        conversation_id: ConversationId,
    ) -> Result<(), GroupMembershipError>;

    async fn authorize_history(
        &self,
        actor: AuthenticatedActor,
        conversation_id: ConversationId,
    ) -> Result<(), GroupMembershipError>;

    async fn members_page(
        &self,
        request: MemberPageRequest,
    ) -> Result<MemberPage, GroupMembershipError>;

    async fn freeze_delivery(
        &self,
        actor: AuthenticatedActor,
        conversation_id: ConversationId,
    ) -> Result<FrozenDeliveryInput, GroupMembershipError>;

    async fn recipients_for_version(
        &self,
        group_id: GroupId,
        membership_version: MembershipVersion,
        target_shard: MailboxShardId,
    ) -> Result<Vec<RecipientChunk>, GroupMembershipError>;
}

前置条件

  • 所有 actor 均来自已认证上下文;不得接受客户端自报的 owner、sender 或内部身份。
  • admin_change_members 只接受明确的 InternalPrincipal,不可由任何公网 opcode 直达。
  • authorize_send、authorize_history 和 members_page 必须由 writer 或存储读入口在每次请求中调用,网关认证不能替代此检查。

后置条件

  • wire GroupId 必须经 strip_kind 后再用 group_conversation_id 归一;群唯一性与授权键均使用 (tenant_id, canonical ConversationId),禁止 raw/canonical 两套键并存。
  • create_group 只创建不存在的 canonical 群,强制把 actor 作为 owner/creator 加入,并以原子方式验证全部 invitee 和成员上限;已有群返回 AlreadyExists,绝不追加成员。
  • 任何成员变更递增 MembershipVersion;freeze_delivery 在消息提交边界返回精确不可变版本,recipients_for_version 只能读取该版本。后续恢复不受当前成员集合影响,快照保留期覆盖全部日志恢复窗口。
  • 未授权的发送、历史和成员页均返回 PermissionDenied,且不得泄露任何成员或消息数据。

错误

  • MemberLimitExceeded 表示整批拒绝,禁止截断 invitee 列表后部分成功。
  • InternalIdentityRequired 表示调用边界错误,禁止降级到客户端创建群语义。