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表示调用边界错误,禁止降级到客户端创建群语义。