# 权限、租户能力与数据范围职责边界说明

> 适用范围：`leaudit-platform` 当前 `RBAC + 租户主数据 + 多租户业务挂载` 体系  
> 更新日期：2026-05-21  
> 文档定位：专门解决“角色权限配置”和“租户管理里的功能开关/承载能力”看起来重复的问题，明确三者各自负责什么、不负责什么。

---

## 1. 先看结论

当前系统里确实存在“语义重叠感”，但不应该把这三类配置看成同一层：

1. `角色权限`
   - 控制“人能不能操作”
2. `租户功能开关`
   - 控制“某租户是否开放某类业务能力”
3. `租户业务承载能力`
   - 控制“某租户能不能作为某类业务的数据归属方/挂载方”
4. `数据范围`
   - 控制“当前人最终能看到哪些租户、哪些数据”

如果只看页面字段，不看后端职责边界，就会误以为“租户页和角色权限页都在管入口模块、文档上传、知识库”。  
这正是当前用户感知混乱的根因。

---

## 2. 三层控制分别管什么

## 2.1 角色权限：管人

角色权限回答的问题是：

- 这个用户能不能进入某页面
- 这个用户能不能新建、编辑、删除、发布、导出
- 这个用户能不能调用某个接口

典型例子：

- `entry_module:create`
- `entry_module:update`
- `documents:upload:create`
- `rag:dataset:manage`
- `rbac:tenants:update`

结论：

- 角色权限只决定“用户有没有操作资格”
- 不决定“数据应该挂在哪个租户下”
- 也不决定“某个租户是否承载某类业务”

---

## 2.2 租户功能开关：管租户是否开放业务能力

租户功能开关回答的问题是：

- 这个租户是否启用了入口模块能力
- 这个租户是否启用了文档上传能力
- 这个租户是否启用了知识库能力

当前主字段：

- `sys_tenant_feature_flags.feature_key`
- 前端对应 `feature_keys`

当前真实语义应理解为：

- “该租户是否开放该业务能力”
- 更偏向租户产品能力配置，不是用户授权配置

结论：

- 功能开关不授予用户权限
- 它只决定“租户是否开放某业务”

---

## 2.3 租户业务承载能力：管数据能不能落到该租户

租户承载能力回答的问题是：

- 新建入口模块时，这个租户能不能被选为适用租户
- 新建文档、模板、知识库时，这个租户能不能作为归属租户

当前主字段：

- `can_host_entry_module`
- `can_host_documents`
- `can_host_rag`
- `can_host_templates`

这类字段和 `feature_keys` 很像，但不是一回事：

- `feature_keys` 更偏“租户是否开放该业务”
- `can_host_*` 更偏“租户能否被选为该业务的数据挂载对象”

结论：

- 承载能力不等于操作权限
- 也不完全等于功能开关
- 它主要服务于“业务归属”和“数据落点”控制

---

## 2.4 数据范围：管最终可见边界

数据范围回答的问题是：

- 这个用户最终能看到哪些租户的数据
- 这个用户能看全部、部门、自身、公共、关系型资源中的哪些部分

它最终会把下面几件事合并起来：

1. 用户身份
2. 角色权限
3. 用户归属租户
4. 目标资源归属租户
5. 模块策略

结论：

- 数据范围是最终访问边界
- 它不是租户主数据页里的布尔开关
- 也不是角色权限页里的单个 permission 就能表达清楚的

## 2.5 三层控制的防护性区别

这三层不是“配置位置不同的同一件事”，而是三道不同的防护闸：

1. `租户功能开关`
   - 属于业务准入防护
   - 防的是“该租户根本不该开放这类业务，却仍被看见或被使用”
2. `角色权限`
   - 属于操作授权防护
   - 防的是“租户虽然开放了该业务，但不是任何人都能新增、编辑、删除、发布”
3. `数据范围`
   - 属于数据边界防护
   - 防的是“有权限的人在操作时串看、串改到不属于自己范围的数据”

可以直接这样理解：

- `租户功能开关 = 模块级总闸`
- `角色权限 = 人的操作闸`
- `数据范围 = 数据行级边界闸`

任意一层缺失，风险都不同：

1. 只有功能开关，没有角色权限
   - 结果：租户开了功能后，可能任何角色都能乱用
2. 只有角色权限，没有功能开关
   - 结果：本不该承载该业务的租户，仍可能被误开放入口或误创建数据
3. 只有前两层，没有数据范围
   - 结果：拥有权限的用户仍可能看到其他租户数据，形成越权或串租户泄漏

所以最终判断某个操作是否允许，不能只看一层，而应按下面顺序串起来判断：

1. 目标租户是否开放该业务
2. 当前用户是否具备该业务动作权限
3. 当前用户的数据范围是否允许访问该目标租户或目标资源

---

## 3. 为什么当前会让人觉得重复

当前用户会觉得重复，原因不是理解错了，而是系统现状确实还没完全收口：

1. 前端租户页把：
   - `启用租户`
   - `公共租户`
   - `功能开关`
   - `承载能力`
   混在同一个表单区域里
2. 业务名称上和角色权限页高度重合
3. 部分下游模块还没有把 `can_host_*` 做成强执行链路
4. `feature_keys` 已经有使用点，但并不是所有模块都用同一模式接入

因此现在最准确的判断不是“完全重复”，而是：

- 设计上应该分层
- 落地上还处于过渡态
- 页面表达还不够清楚

补一句更贴近当前现状的话：

- 当前系统已经有三层模型雏形
- 但还没有在所有模块做到“租户能力先拦、权限再拦、数据范围最后收口”的稳定统一执行顺序

---

## 4. 当前代码里的真实分工

## 4.1 已经落到真实用途的部分

当前已经能确认的真实用途：

1. `feature_keys`
   - 已被入口模块租户选择器消费
   - `home.entry_module` 已有真实用途
2. `leaudit_entry_module_tenants`
   - 已作为入口模块适用租户关系表使用
3. 用户当前上下文
   - 已开始统一输出 `tenant_code / tenant_name`
4. 首页入口模块
   - 已按用户租户 + 模块租户关系过滤

## 4.2 仍未完全闭环的部分

以下部分仍需继续补强：

1. `can_host_documents`
2. `can_host_rag`
3. `can_host_templates`
4. `feature_keys` 的统一消费规范

结论：

- 现在租户页里的部分字段已经不是摆设
- 但也还没有做到所有字段都形成“写入即生效”的全链路闭环

---

## 5. 最终建议的收口规则

## 5.1 角色权限页只做“人”的授权

角色权限页只保留：

- 页面访问权限
- 菜单权限
- 按钮权限
- 接口动作权限
- 数据范围策略

不要在角色权限页里表达：

- 某租户是否开放业务
- 某租户是否能承载业务数据

## 5.2 租户管理页只做“租户侧能力配置”

租户管理页应拆成 3 组：

1. 基础状态
   - `启用租户`
   - `公共租户`
2. 功能开关
   - 某租户是否开放某业务能力
3. 业务承载能力
   - 某租户能否作为某业务的数据归属方

并且页面必须明确提示：

- 这里不授予用户操作权限
- 用户是否能操作，仍由角色权限决定

## 5.3 数据范围执行器单独收口

数据范围不要继续分散在各业务模块里各写一套：

- 应把用户权限
- 用户租户
- 资源租户
- 公共数据策略
- 关系型特例

统一并入执行器。

---

## 6. 推荐页面文案

### 功能开关

说明：

- 决定该租户是否开放某类业务能力
- 不直接授予用户页面或按钮操作权限

### 业务承载能力

说明：

- 决定该租户能否作为该类业务的数据归属租户
- 会影响新建/编辑时该租户是否可被选中

### 角色权限

说明：

- 控制当前用户能不能访问页面、调用接口、执行动作
- 与租户侧能力配置是两层控制

---

## 7. 后续工程动作建议

优先级建议：

1. 前端租户页分组与文案改清楚
2. 为 `can_host_documents / can_host_rag / can_host_templates` 补下游强校验
3. 梳理 `feature_keys` 的统一消费规范
4. 统一把“租户是否可选”沉到服务层，不让前端自己猜
5. 最后再把数据范围执行器接到更多业务域

---

## 8. 一句话原则

后续所有实现都应遵守下面这个原则：

- `角色权限` 决定“谁能操作”
- `租户功能开关` 决定“租户开不开这类业务”
- `租户承载能力` 决定“数据能不能挂到这个租户”
- `数据范围` 决定“最终能看到哪些数据”

只要这四层不再混写，后面的权限和多租户改造就不会继续失控。
