Files
YG_FT/docs/租户与用户权限体系梳理及纠偏流程.md
wuyongtao 6f0e82f351 feat: 平台治理与权限体系完善,存储进度/GPU预留/审批中心与日志整合
- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请
- 存储: MinIO 存储进度迁移、对象存储安全加固与测试
- 计算: GPU 资源预留、compute 轮询与同步增强
- 权限: permission v2 迁移、权限安全验收测试
- 日志: 后端运行日志中文说明、操作日志整合
- 数据处理/评测: 数据转换与模型评测优化

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 09:49:48 +08:00

629 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 租户与用户权限体系梳理及纠偏流程
> 版本v1.0
> 梳理日期2026-08-20
> 适用范围YG_FT 平台后端、前端、Compute Agent、MinIO、PostgreSQL 及离线部署目录
> 目的:明确租户、用户、角色、权限、资源和审批之间的层级关系,识别当前实现中的概念冲突,并为后续优化提供唯一设计口径。
## 一、结论摘要
当前系统已经具备用户登录、租户字段、租户成员、资源 ACL、审批、GPU 分配和资源级动作校验,但概念来源还没有完全收敛,主要表现为:
1. **用户和租户没有混为一个数据库对象,但业务流程中边界不清**:用户表保存 `tenant_id`,同时又通过 `tenant_members` 表表达租户关系,导致“主租户”和“成员关系”的判定规则并存。
2. **平台角色、租户角色、页面权限码、资源动作权限同时存在,但职责没有彻底分离**`users.role``users.permissions``tenant_members.role``roles.permissions` 可能产生不一致。
3. **资源归属字段不统一**:部分资源使用独立的 `created_by`,部分任务把创建者放在 `payload.created_by`,部分资源的 `tenant_id` 允许为空历史数据因此出现“ACL 已授权但列表不可见”的问题。
4. **租户创建、用户创建、成员加入和资源授权是四条不同流程,目前在页面上有交叉**:创建用户时直接写入租户成员,租户的 `owner_user_id` 却没有同步建立 owner 成员关系。
5. **项目表和项目字段仍在数据库及部分接口中保留**,但当前产品已经取消项目作为用户可见的业务隔离层,继续把 `project_id` 当权限条件会造成新的歧义。
6. **正确的目标模型应当是**:平台管理员管理平台身份和租户;用户通过租户成员关系进入一个或多个租户;资源属于一个租户并有一个所有者;资源使用通过 ACL 动作权限高风险动作通过审批GPU 和配额是资源使用前的运行时约束。
本文件的纠偏原则是:**先统一口径,再做兼容迁移;先保证后端判定一致,再调整前端菜单和数据库约束;历史字段暂不删除,但不再新增新的业务依赖。**
## 二、目标层级模型
```text
平台 Platform
├── 平台身份与平台角色
│ ├── platform_admin平台管理员可管理租户、用户、策略和全部资源
│ └── platform_user普通平台用户不因登录自动获得资源权限
├── 租户 Tenant
│ ├── 租户基本信息、状态、配额、留存策略
│ ├── 租户成员 TenantMember
│ │ ├── owner租户所有者
│ │ ├── admin租户管理员
│ │ ├── member普通成员
│ │ └── viewer只读成员
│ └── 租户内资源
│ ├── 数据集
│ ├── 基座模型
│ ├── 训练任务与训练模型
│ ├── 评测任务
│ ├── 推理任务
│ └── 数据处理/转换任务
├── 资源授权 Resource ACL
│ ├── principaluser 或 tenant role
│ ├── read查看
│ ├── write编辑
│ ├── execute训练、推理、评测等使用
│ ├── download下载或导出文件
│ ├── delete删除
│ └── admin资源授权和完整管理
├── 运行时资源
│ ├── Compute Node
│ ├── GPU
│ ├── GPU assignment用户获批可使用哪些 GPU
│ ├── GPU reservation任务当前占用哪些 GPU
│ └── Tenant quota reservation租户当前预留了多少运行资源
└── 审批与审计
├── resource access申请资源 ACL
├── gpu.assign申请算力卡
├── tenant.quota.update申请租户配额变更
├── 高风险删除/导出/合并操作
└── 全部结果写入审计日志
```
### 2.1 四个必须分开的概念
| 概念 | 正确含义 | 不应承担的职责 |
|---|---|---|
| 用户 User | 可登录平台的人或服务账号 | 不代表租户,也不自动代表资源权限 |
| 租户 Tenant | 资源、成员、配额和审计的隔离边界 | 不直接作为登录账号 |
| 角色 Role | 某个范围内的一组职责 | 不应直接等同于某个用户的全部资源权限 |
| 权限 Permission | 对页面或资源的具体操作能力 | 不应只靠前端按钮控制 |
## 三、当前实现盘点
### 3.1 用户身份层
当前主要实现位置:
- `users` 表:保存账号、密码哈希、平台角色、状态、页面权限 JSON、主租户字段。
- `sessions` 表:保存登录会话、过期时间和注销时间。
- `backend/app/core/auth.py`:解析 Token、校验用户状态、校验页面权限和租户上下文。
- `/users` 接口:当前只有管理员可以创建、修改、禁用和删除用户。
当前用户对象包含:
```text
id / username / display_name / status
role / protected / permissions
tenant_id
```
存在的混淆:
- `role=operator/viewer/user` 是平台用户字段,但 `tenant_members.role` 还有 `owner/admin/member/viewer`,两套角色名称不在同一层级。
- `role=user` 在创建接口中被接受,但设计文档主要定义的是 `admin/operator/viewer/guest`,前端又同时显示“普通用户”。
- 创建用户时直接生成 `tenant_members` 活跃成员记录;这相当于“创建用户”和“加入租户”一次完成,没有邀请、待确认或成员审批边界。
- `users.permissions` 是用户级 JSON`roles` 表只提供查询接口,当前没有形成“角色变更自动计算权限”的唯一来源。
- 非管理员创建/更新用户时会被剥离 `compute``user-settings`,但初始化 SQL 中的 `u_operator` 种子数据仍包含 `compute`,代码默认值和 SQL 种子不一致。
### 3.2 租户层
当前主要实现位置:
- `tenants`保存租户名称、状态、owner_user_id、配额和留存策略。
- `tenant_members` 表:保存租户与用户的多对多关系、租户角色、状态、邀请人和有效期。
- `tenant_membership()``user_tenant_ids()`:判定当前用户的租户范围。
- `/tenants/{tenant_id}/members`:管理员或租户 owner/admin 管理成员。
当前逻辑:
```text
当前用户租户范围 = users.tenant_id + tenant_members 中 status=active 的 tenant_id
当前请求租户 = X-Tenant-ID仅在用户属于该租户时生效
资源租户 = 资源 tenant_id部分任务还会从 payload 中读取 tenant_id
```
主要问题:
1. `users.tenant_id``tenant_members` 都能表达租户关系,但没有明确谁是主数据、谁是兼容字段。
2. `tenants.owner_user_id` 创建租户时写入,但创建租户流程没有自动写入对应的 `tenant_members(role='owner')`
3. 平台管理员创建普通用户时直接加入租户,租户管理员也可以直接添加成员,邀请/加入申请流程虽已具备接口,但不是主流程。
4. `tenant_id` 在资源表中有的为 `NOT NULL`,有的允许 `NULL`;历史资源的 `NULL` 租户需要特殊兼容判断。
5. 租户删除是状态删除但成员、资源、ACL、审批、MinIO 对象和本地缓存的后续处理没有统一的生命周期编排入口。
### 3.3 页面权限层
页面权限由两部分组成:
```text
前端:路由 meta.permission + 侧边栏过滤 + 按钮显示
后端MODULE_PERMISSIONS + get_current_user / require_admin
```
页面权限码包括:
```text
dashboard, fine-tune, model-eval, model-inference,
model-manage, dataset, data-process, data-convert,
compute, hardware, logs, user-settings
```
问题:
- 页面权限码是“能否进入模块”,不是“能否使用某一条数据或 GPU”但历史实现中一度把 `compute` 页面权限阻断了普通用户读取自己获批 GPU 的接口,造成“有 GPU 分配但训练页面看不到”的问题。
- 普通用户的 `compute` 管理菜单应保持隐藏,但训练、推理、评测所需的个人节点/GPU查询应当是独立的自助只读接口。
- 前端权限控制和后端权限控制分别维护,按钮隐藏不能作为安全边界。
### 3.4 资源权限层
当前资源权限主要由以下条件组合:
```text
平台管理员旁路
资源属于当前租户
且(资源所有者 == 当前用户
或 ACL 明确授权
或当前用户是租户 owner/admin
且满足具体动作 read/write/execute/download/delete/admin
```
已接入资源类型包括:
```text
dataset / model / trained_model / fine-tune / eval / inference
data_process / data_convert / compare / project历史兼容
```
当前差异:
| 资源 | 当前所有者来源 | 当前租户来源 | 主要风险 |
|---|---|---|---|
| datasets | `created_by` | `tenant_id`,部分历史为空 | 列表、详情、训练权限可能不一致 |
| models | `created_by` | `tenant_id`,基座模型存在平台共享规则 | 基座模型共享与训练产物共享容易混淆 |
| trained_models | `created_by` | `tenant_id` | 血缘来源、默认授权需统一 |
| fine_tune_tasks | 多数在 `payload.created_by` | 多数在 payload 或派生字段 | 结构化查询和授权容易漏字段 |
| eval_tasks | `created_by` | `tenant_id` | 与数据集/模型执行权限必须同时校验 |
| compare_tasks | 表字段和 payload 并存 | 表字段和 payload 并存 | 推理任务与模型使用权限容易分裂 |
| data_process_tasks | `created_by` | `tenant_id` | 处理结果派生数据集授权不明确 |
| data_convert_tasks | `created_by` | `tenant_id` | 历史表结构和初始化脚本存在演进痕迹 |
| projects | `create_by` 等历史字段 | `tenant_id` | 当前已取消项目业务层,但表和接口仍在 |
### 3.5 GPU 与运行时权限层
GPU 相关关系实际上有三层:
```text
gpus
└── gpu_assignments管理员批准“用户可以使用哪张卡”
└── gpu_allocations / gpu_reservations任务运行时“当前占用了哪张卡”
```
正确判定顺序应为:
1. 用户拥有租户成员资格且账号 active。
2. 用户已经获得该 GPU 的分配或管理员旁路。
3. 节点 online、enabledGPU 存在且没有被其他任务占用。
4. 租户配额可以完成原子预留。
5. 训练、推理或评测任务提交到指定节点。
目前已经有分配申请、审批后写入 `gpu_assignments`、运行时预留和释放逻辑但仍需把“审批结果、GPU 分配、配额预留、节点故障回收”统一成一个状态机。
## 四、当前数据库关系与问题
### 4.1 当前表的职责分组
```text
身份
├── users
├── sessions
└── roles
租户
├── tenants
├── tenant_members
└── tenant_quota_reservations
资源权限
├── acls
├── resource_access_requests
├── approval_templates
├── approval_instances
└── approval_steps
业务资源
├── datasets / dataset_files / dataset_file_versions / dataset_records
├── models / trained_models / model_lineage / model_artifacts
├── fine_tune_tasks / eval_tasks / compare_tasks
├── data_process_tasks / data_convert_tasks
└── projects / project_members历史兼容
运行资源
├── compute_nodes
├── gpus
├── gpu_assignments
├── gpu_allocations
├── gpu_reservations
└── resource_replicas / storage_objects / storage_cache_jobs
审计
└── audit_logs
```
### 4.2 数据库设计中的主要问题
#### 问题 A租户归属字段可空且没有统一外键
`models``trained_models``datasets``eval_tasks``compare_tasks`、数据处理相关表的 `tenant_id` 演进过程不同,部分字段仍允许空值。多数资源表也没有对 `tenants(id)` 的外键约束。
后果:
- 资源可能被创建但没有租户归属。
- 列表接口和详情接口的可见性不同。
- ACL 授权是否跨租户无法仅靠数据库判断。
- 历史数据需要特殊分支,增加权限代码复杂度。
#### 问题 B所有者字段不统一
当前同时存在 `created_by``owner_id``create_by``payload.created_by`。这会导致:
- 列表判断资源所有者时需要按资源类型写特殊逻辑。
- 用户删除时无法保证所有资源都能被一致地软删除。
- 审计目标和资源所有者可能不是同一个字段。
#### 问题 C角色权限没有单一来源
当前存在以下可能来源:
```text
users.role
users.permissions(JSON)
tenant_members.role
roles.permissions(JSON)
前端 PermissionCode
后端 ALL_PERMISSIONS / MODULE_PERMISSIONS
```
如果更新了角色但没有同步用户权限 JSON页面和接口可能出现不同结果如果只修改前端权限码后端仍可能拒绝如果只修改 `tenant_members.role`,并不会自动改变平台页面权限。
#### 问题 D资源 ACL 不是所有资源的唯一授权来源
基座模型在当前设计中对租户用户默认共享 read/execute资源所有者又有隐式权限租户管理员又有管理旁路ACL 只是其中一层。这个设计可以成立,但必须在文档和代码中严格区分:
```text
平台共享规则 ≠ ACL 授权
资源所有权 ≠ 租户管理员权限
页面权限 ≠ 资源动作权限
GPU 分配 ≠ 资源 ACL
```
#### 问题 E项目字段与当前产品边界不一致
`projects``project_members``project_id` 仍在数据库中存在,但当前产品已经决定不把项目作为用户可见的隔离层。它们应被标记为 legacy不应再参与新资源的权限判定也不应在新建页面继续要求填写。
## 五、目标唯一口径
### 5.1 用户与租户的最终关系
```text
一个用户可以属于多个租户
一个租户可以拥有多个用户
用户在一次请求中只能选择一个 active_tenant
资源必须归属于 active_tenant
```
推荐保留:
- `users.tenant_id`:暂时保留为兼容字段,表示用户的 primary tenant不再作为多租户关系的唯一来源。
- `tenant_members`:升级为租户成员关系的唯一权威来源。
- `X-Tenant-ID`:明确表达当前请求租户,必须来自 active membership。
最终判定:
```text
有效租户 = tenant_members(status=active, 未过期)
primary tenant = users.tenant_id仅兼容和默认登录上下文
当前租户 = 请求 X-Tenant-ID否则 primary tenant
```
### 5.2 平台角色与租户角色
推荐收敛为两层:
| 层级 | 字段/表 | 允许的角色 | 作用 |
|---|---|---|---|
| 平台层 | `users.platform_role`,兼容当前 `users.role` | `platform_admin``platform_user` | 管理租户、平台配置、平台用户和全局策略 |
| 租户层 | `tenant_members.role` | `owner``admin``member``viewer` | 管理本租户成员、资源和租户级审批 |
不建议继续把 `operator``user``member` 混在一个角色字段中。过渡期可以保留旧值,但新增代码只允许通过映射函数转换:
```text
admin/protected -> platform_admin
operator/user -> platform_user + tenant_members.member
viewer -> platform_user + tenant_members.viewer
```
### 5.3 页面权限与资源动作
页面权限只解决“能否进入模块”:
```text
module permission: dataset / fine-tune / model-inference / ...
```
资源动作解决“能否操作某个资源”:
```text
read / write / execute / download / delete / admin
```
统一授权函数输入应固定为:
```text
authorize(
actor_id,
active_tenant_id,
resource_type,
resource_id,
action,
)
```
推荐判定顺序:
1. 会话有效且用户 active。
2. 当前租户 active用户是该租户成员。
3. 平台管理员旁路,或资源属于当前租户。
4. 资源所有者拥有默认动作集合。
5. 租户 owner/admin 按租户范围拥有管理动作。
6. ACL 明确授予当前用户或租户角色对应动作。
7. 对 download、delete、admin 等高风险动作执行额外审批检查。
### 5.4 资源标准字段
所有新资源应统一具备:
```text
id
tenant_id NOT NULL
created_by NOT NULL
created_at
updated_at
deleted_at
deleted_by
```
历史 `owner_id``create_by``payload.created_by` 只在迁移阶段读取,最终写入标准字段。任务配置 JSON 可以保留业务参数,但不再存放权限事实。
## 六、标准业务流程
### 6.1 创建租户流程
```text
平台管理员
├─ 创建租户基本信息
├─ 设置租户配额和状态=active
├─ 指定或创建租户 owner
├─ 写入 tenant_members(tenant_id, owner_user_id, owner)
├─ 初始化审批策略
└─ 记录 tenant.create / tenant.member.add 审计
```
纠偏要求:`tenants.owner_user_id``tenant_members(role='owner')` 必须在同一事务中维护;不能只写一个。
### 6.2 创建用户与加入租户流程
#### 平台管理员创建用户
```text
平台管理员
├─ 创建 users 记录
├─ 选择初始平台角色platform_user 或 platform_admin
├─ 选择 primary tenant
├─ 写入 tenant_members
│ ├─ owner 仅平台管理员明确指定时允许
│ └─ 普通用户默认 member 或 viewer
├─ 生成页面权限快照(过渡期)
└─ 记录 user.create / tenant.member.add 审计
```
#### 已存在用户加入新租户
```text
租户 owner/admin 或平台管理员
├─ 发出邀请或创建加入申请
├─ tenant_members.status = pending
├─ 被邀请用户接受
├─ tenant_members.status = active
└─ 用户选择 X-Tenant-ID 后访问该租户资源
```
纠偏要求:不能把“创建登录用户”和“拥有某租户全部资源”理解为同一件事;新用户默认没有任何资源 ACL也没有 GPU 分配。
### 6.3 创建业务资源流程
```text
登录用户
├─ 会话校验
├─ 确定 active_tenant
├─ 校验租户 active 和用户成员状态
├─ 写入资源 tenant_id + created_by
├─ 写入资源业务表
├─ 写入 MinIO 对象元数据(如有文件)
├─ 生成资源血缘/版本快照
└─ 记录资源创建审计
```
数据处理结果生成数据集时,必须继承任务的租户和创建者;不能因为是系统生成就把 `tenant_id``created_by` 留空。
### 6.4 资源查看和授权流程
```text
用户请求资源列表/详情
├─ 页面权限检查
├─ active_tenant 检查
├─ 资源 tenant_id 检查
├─ 所有者 / 租户角色 / ACL 判定
├─ 返回资源元数据
└─ 文件、版本、MinIO 对象再次按同一资源动作校验
```
资源授权:
```text
资源所有者或租户管理员/平台管理员
├─ 选择用户或租户角色
├─ 选择 read/write/execute/download 等动作
├─ 设置有效期
├─ 写入 ACL 或创建访问审批
└─ 记录授权来源、授权人和审计
```
### 6.5 训练、推理、评测使用资源流程
```text
创建任务
├─ 页面权限fine-tune / model-inference / model-eval
├─ active_tenant 和任务资源归属校验
├─ 基座模型:平台共享规则或 model.execute
├─ 训练模型trained_model.execute
├─ 数据集dataset.execute
├─ GPU assignment用户是否获批使用所选卡
├─ 节点GPU 所属节点是否 online/enabled
├─ 原子预留 GPU 和租户配额
├─ MinIO/节点缓存准备
├─ 提交计算任务
└─ 成功、失败、取消、超时、节点故障统一释放预留
```
### 6.6 高风险操作审批流程
```text
用户发起高风险操作
├─ 检查当前用户是否有基础资源权限
├─ 检查是否为管理员旁路
├─ 匹配 tenant + action + resource_type 审批策略
├─ 创建 pending 审批实例
├─ 审批人从当前会话确定
├─ approved -> 幂等执行具体效果
│ ├─ ACL 授权
│ ├─ GPU 分配
│ └─ 配额更新
├─ rejected/cancelled/expired -> 不产生资源效果
└─ 全流程记录审计
```
## 七、纠偏开发计划
### 阶段 0冻结口径
1. 书面确认:不再把项目作为新业务隔离层。
2. 确认平台角色只有平台管理员和平台用户两类;租户职责由 `tenant_members.role` 表达。
3. 确认 `tenant_members` 是租户关系权威表,`users.tenant_id` 仅作为 primary tenant 兼容字段。
4. 确认所有资源必须有 `tenant_id``created_by`、生命周期字段。
5. 确认基座模型共享不等于训练模型和数据集共享。
### 阶段 1身份和租户纠偏
1. 为平台角色建立统一映射,停止新增 `role='user'` 的特殊逻辑。
2. 用户创建、租户创建、成员邀请、成员接受、成员移除统一走成员服务。
3. 创建租户时同步 owner 成员记录。
4. 租户成员变更、用户禁用、用户软删除时统一撤销会话、GPU 分配、ACL、待审批流程。
5. 新增当前租户查询和切换接口,前端显示“当前租户”,避免仅显示用户主租户。
### 阶段 2资源数据纠偏
1. 对所有资源表补齐并回填 `tenant_id``created_by``created_at``updated_at`
2. 将任务 payload 中的创建者和租户迁移到结构化字段。
3. 对历史 `tenant_id IS NULL` 数据按以下顺序处理:
- 能从创建者找到租户的,回填创建者所属租户。
- 系统生成数据集能从源任务找到租户的,继承源任务租户。
- 仍无法确定归属的,进入待治理清单,不自动公开。
- 管理员明确授权的历史资源可临时通过用户级 ACL 访问,直到完成归属迁移。
4. 为资源和租户建立必要索引;在数据清洗完成后再逐步收紧 `NOT NULL` 和外键。
### 阶段 3权限判定收敛
1. 建立统一 `authorize()` 服务所有列表、详情、文件、版本、下载、MinIO、缓存和任务接口调用同一套判定。
2. 页面权限只用于模块入口;资源动作必须在后端独立判断。
3. 统一基座模型、训练模型、数据集和任务的血缘权限来源。
4. 统一 GPU assignment、GPU reservation 和租户配额 reservation 的状态流转。
5. 审批实例增加幂等键和过期处理任务,避免重复审批和重复执行。
### 阶段 4数据库和初始化脚本
1. 将完整初始化 SQL 与迁移 SQL 的职责分开:新库只执行一份完整脚本,旧库执行版本化迁移。
2. 初始化 SQL 中统一种子用户、种子角色、默认租户和租户成员关系,不能出现 operator 仍拥有普通用户不应有的 `compute` 权限而代码又剥离该权限的矛盾。
3. 给所有需要的关联增加外键或由应用层统一保证引用完整性。
4.`tenant_id + status``created_by + deleted_at`、ACL 主体、审批状态和 GPU 状态建立索引。
5. 主工程与 `docker/offline/src` 的完整 SQL、迁移、代码和部署文档做 SHA-256 一致性校验。
### 阶段 5前端流程纠偏
1. 用户管理页面分别显示平台角色、所属租户、租户角色、页面权限和资源权限,不再把它们放在一个“权限”字段中。
2. 租户详情页显示成员、租户角色、配额使用、审批策略和资源统计。
3. 资源详情页显示资源所属租户、创建者、授权主体、动作、有效期和授权来源。
4. 训练/推理/评测页面只显示当前用户有 `execute` 权限且已获批 GPU 的资源。
5. 对“无权访问”“未授权”“审批中”“资源已删除”“租户已停用”分别展示原因,不统一显示为数据为空或 500。
## 八、建议的验收矩阵
| 场景 | 预期结果 |
|---|---|
| 用户未加入租户访问租户资源 | 403 |
| 用户属于租户但没有资源 ACL | 资源列表不可见,详情 403 |
| 用户获得 dataset.read | 数据集可见,可预览,但不能训练或上传 |
| 用户获得 dataset.execute | 数据集可用于训练/评测,但不能编辑或下载 |
| 用户获得 dataset.download | 可以下载文件,但不能训练 |
| 用户获得 trained_model.execute | 可以推理/评测,不能导出 |
| 用户获得 trained_model.download | 可以导出或下载,不能执行推理 |
| 用户获批 GPU 但没有数据集 execute | 不能创建训练任务 |
| 用户有数据集 execute 但没有 GPU assignment | 不能创建训练任务 |
| 管理员撤销 ACL | 列表、详情、执行、下载均立即重新校验 |
| 用户被禁用 | 会话失效,成员和 ACL 不能继续生效 |
| 租户被停用 | 租户资源不可创建和执行,运行任务进入待处理/失败策略 |
| 历史资源 tenant_id 为空且无 ACL | 普通用户不可见 |
| 历史资源 tenant_id 为空且有管理员直接用户 ACL | 仅指定用户在有效期内可见和按动作使用 |
| 资源删除 | 资源不可见ACL、审批、对象清理流程被触发 |
| MinIO 不可用 | 资源元数据权限仍可判断,涉及对象的操作返回明确存储故障 |
## 九、纠偏完成判定
满足以下条件,才认为租户与用户权限完成统一,而不是“接口能够调用”:
- `tenant_members` 成为租户关系唯一权威来源,`users.tenant_id` 只作为兼容主租户。
- 平台角色和租户角色职责分离,角色权限不再通过多个 JSON/字段重复维护。
- 新资源全部具备非空租户和创建者,历史资源有明确归属或进入治理清单。
- 所有资源接口、文件接口、MinIO 接口和任务接口使用统一动作授权。
- 数据集、基座模型、训练模型和任务之间的派生关系不会自动扩大访问范围。
- 用户创建、成员邀请、资源授权、GPU 申请、配额申请和审批都能追溯到用户、租户、资源、动作和请求 ID。
- 初始化 SQL、迁移 SQL、在线数据库和离线源码目录的表结构与权限逻辑一致。
- 双用户、跨租户、ACL 撤销、权限过期、用户禁用、租户停用、GPU 冲突和 MinIO 故障场景全部通过验证。
## 十、2026-08-20 本轮改造结果
本轮已按目标层级模型完成一轮可运行的后端、数据库和前端收敛:
1. `users.platform_role` 已作为平台角色标准字段,值为 `platform_admin``platform_user`;历史 `users.role` 继续返回,供旧客户端兼容。
2. `tenant_members` 已作为租户成员关系的权威来源。创建租户时会在同一事务中写入 `owner` 成员;创建用户时单独使用 `tenant_role` 写入成员角色,不再因为平台角色是管理员就自动成为租户 owner。
3. 用户接口返回 `platform_role``tenant_memberships``tenant_count`,组织页面已区分平台身份与租户成员关系。
4. `fine_tune_tasks` 已增加结构化 `tenant_id``created_by``deleted_at``deleted_by`;训练列表、详情和 owner 判定优先使用结构化字段,历史 payload 仅作回退。
5. 新建数据集、训练、评测、推理任务会绑定当前认证租户;普通用户不能仅通过请求体伪造其他租户。平台管理员仍可为指定租户创建资源。
6. 普通用户的资源列表继续通过租户成员、所有者和 ACL 统一过滤;项目不再出现在前端路由、组织页面和新权限判断中。
7. 租户删除已补充 `deleted_at/deleted_by` 软删除,并同步禁用该租户的成员关系。
8. `000_full_init.sql` 已包含本轮标准字段、索引、owner 成员修复和无项目业务依赖的种子数据;`docker/offline/src/backend/app/db/sql` 仅保留这一份完整初始化脚本。
9. `docker/offline/src` 已同步当前 backend、frontend、compute 源码和构建后的 `frontend-dist`
本轮已在线验证:数据库字段存在、普通用户身份返回、用户租户成员关系返回、租户 owner 自动建立、租户软删除、跨租户伪造创建被 403 拒绝;前端 `npm run build` 通过。项目表、项目字段及旧项目 API 仅保留历史兼容读取,不再作为新业务隔离条件。
## 十一、下一步开发计划
1. 为资源详情和资源授权页面补充租户、创建者、授权来源、有效期和动作权限的可视化信息。
2. 将数据处理、数据转换等派生资源的租户和创建者继承改为统一服务,并清理历史 `tenant_id IS NULL` 待治理清单。
3. 将 GPU assignment、GPU reservation、租户配额 reservation 和审批实例整理为同一状态机,补充节点故障回收验收。
4. 执行双租户、双用户、ACL 过期/撤销、用户禁用、租户停用、MinIO 故障和多 GPU 冲突的完整回归测试。