fix: 种子改用 UPSERT 建用户且不覆盖密码,解除投顾 FK 导致的跑不通;记录 RBAC 对齐最终状态

This commit is contained in:
2026-09-12 12:22:32 +08:00
parent 39f7b81200
commit 4cc0ecdea9
2 changed files with 61 additions and 45 deletions
+34 -35
View File
@@ -105,27 +105,12 @@ ZSY 的客服 Agent 接入线,含三块新能力与一次合规清理:
---
## 5. 本机 RBAC 对齐:实际执行记录
## 5. 本机 RBAC 对齐:最终执行记录
代码侧已就绪。**原计划是"种子 + 三个 grant 脚本"的全套对齐,但实测发现种子在这台环境上
根本跑不通**(见 §5.1),于是改用**不依赖种子**的方案,已执行完毕:
分两轮完成。**第一轮原计划是"种子 + 三个 grant 脚本",但种子当场跑不通**(见 §5.1),
于是先改用不依赖种子的方案把功能补齐;随后按项目方决定修好种子,**第二轮完整跑通**。
| 步骤 | 命令 | 结果 |
|---|---|---|
| 1 | 清理 `advisor`(9004) 的旧绑定 | ✅ 删 10 条(防 id 语义漂移,见 §3.2) |
| 2 | `tools/grant_advisor_role.py` | ✅ **按权限码重建 advisor 的 10 条绑定** |
| 3 | `tools/grant_customer_service_phase2_permissions.py` | ✅ 新增权限 `9044-9046`;授权 `customer` +1、`admin` +3 |
| 4 | `tools/grant_risk_permissions.py` | ✅ 风控 4 个权限幂等确认(本就存在) |
| 5 | `pytest tests/integration/test_auth_login_mysql.py tests/integration/test_rbac_read_mysql.py` | ✅ **19 passed** |
对齐后的实测状态:权限 **41 条**;绑定数 `admin 40 / customer 11 / risk_operator 10 / advisor 10 / operator 1`。
三个新权限的绑定实测为:`memory:candidate:confirm` → `customer` + `admin`,
`memory:candidate:review` → `admin`,`handover:read` → `admin`。
**没有跑** `tools/set_user_password.py`:种子失败后事务完整回滚,`cust_t`/`risk_t`/`admin_t`/`advisor_t`
的 bcrypt 密码都还在(已用 `--list` 核实),无需重设。
### 5.1 ⚠️ 新发现的既有破坏:`seed_test_rbac.py` 在这台环境上已经跑不通
### 5.1 第一轮撞到的破坏:`seed_test_rbac.py` 在这台环境上跑不通(已修)
```
sqlalchemy.exc.IntegrityError: (1451, 'Cannot delete or update a parent row: a foreign key
@@ -135,26 +120,40 @@ FOREIGN KEY (`customer_id`) REFERENCES `sys_user` (`id`))')
```
投顾线引入的 `advisor_profile_tag` 有 FK 指向 `sys_user`,而库里有数据引用 `9001-9003`,
于是种子最后那步 `DELETE FROM sys_user` 被外键拒绝。
于是种子最后那步 `DELETE FROM sys_user` 被外键拒绝。**因为 `commit()` 在最后,种子是原子的**
(失败即完整回滚,已实测:权限仍 38 条、三个用户密码完好),但任何人在这台环境、
或任何有投顾数据的环境上跑种子都会失败 —— 而且外部表现只是"什么都没发生"。
**好消息是种子是原子的**:`session.commit()` 在最后,失败即完整回滚 —— 本次已实测确认
(权限仍 38 条、三个用户密码完好)。**但不能依赖这一点**:哪天有人把 `commit()` 挪到中间,
就会出现"权限被删、用户还在"的半成品状态。
**修法(已落地)**:把 `DELETE FROM sys_user` 换成「存在则 UPDATE 非密码字段、不存在才 INSERT」,
且**不覆盖 `password_hash`**。附带好处:重跑种子不再弄丢演示密码,也不需要事后补
`set_user_password.py` —— 实测两轮之后 `9001`/`9002`/`9003`/`9020` 的 bcrypt 密码都还在。
**影响**:任何人在这台环境、或任何有投顾数据的环境上跑种子都会失败。
考虑过但**未采用**:① 种子完全不碰 `sys_user`(把用户创建交给 `create_test_user.py`,代价是
"新建环境多一步");② 给该 FK 加 `ON DELETE CASCADE`(改已有约束,牵涉规则 4,不做)。
**待定修法(需项目方定,本次未动)**,三种取向:
### 5.2 对齐后的最终实测状态
1. **把 `DELETE FROM sys_user` 改为 UPSERT,且不覆盖 `password_hash`** —— 破坏最小:
用户不被删、密码不丢,FK 也不会被触发。**我倾向这个**;
2. 种子只重建权限与角色、不碰 `sys_user`,用户创建交给 `create_test_user.py`;
3. 给该 FK 加 `ON DELETE CASCADE` —— 会改已有约束,牵涉规则 4,**不建议**。
| 项 | 值 |
|---|---|
| 权限总数 | **45 条** |
| 绑定数 | `admin 44 / customer 20 / risk_operator 10 / advisor 10 / operator 1` |
| 原先缺的 `9018 knowledge:query` | ✅ 已补(种子重建带入) |
| 原先缺的 `9019 knowledge:manage` | ✅ 已补 |
| `9041-9043` 投顾治理类 | ✅ 就位 |
| `9044-9046` 客服二期 | ✅ 就位;实测 `memory:candidate:confirm` → `customer` + `admin`,另两个 → `admin` |
| 演示密码 | ✅ 四个账号 bcrypt 密码完好,**未重设** |
| `pytest tests/integration/test_auth_login_mysql.py tests/integration/test_rbac_read_mysql.py` | ✅ **19 passed** |
| `pytest tests/unit tests/contract` | ✅ **1275 passed, 2 skipped** |
### 5.2 仍然存在的既有环境缺口
执行顺序(有依赖,不能颠倒):
本机库里 **`9018 knowledge:query` 与 `9019 knowledge:manage` 仍然不存在** —— 本机 RBAC
数据比种子旧。前者是 `search_knowledge` 工具的必需权限,缺它会让客服 Agent 调检索工具时被拒。
**因为种子跑不通,这一项本次没能补上**,已列入 §6。与 ZSY 的合并无关,是我方环境的账。
```
清 advisor(9004) 旧绑定 → seed_test_rbac.py → grant_advisor_role.py
→ grant_customer_service_phase2_permissions.py → grant_risk_permissions.py
```
第一步必须做,原因见 §3.2;后两个 `grant_*.py` 里风控那 4 个权限是**必要的幂等补齐** ——
种子不重建 `1986...` 号段的风控权限,却会清掉 `risk_operator`/`admin` 对它们的绑定(本次实测补了 8 条)。
---
@@ -167,5 +166,5 @@ FOREIGN KEY (`customer_id`) REFERENCES `sys_user` (`id`))')
| 只读真机冒烟(用真实令牌打 §19 新增端点,403 当失败) | 待 ZSY 回话 | 已提议 |
| 环境数据不可跨环境 | 全员 | 第 4 次踩坑,已写进种子文档字符串 |
| `docs/05` §19 端点编号 | 已解决 | `A039`/`A040`,全表 62 个编号唯一 |
| **`seed_test_rbac.py` 在有投顾数据的环境跑不通** | **项目方定修法** | 见 §5.1,三种取向待选;种子本身是原子的,已实测回滚无损 |
| 本机缺 `9018 knowledge:query` / `9019 knowledge:manage` | 我方环境 | 见 §5.2,依赖 §5.1 的修法;缺 `knowledge:query` 会让客服检索工具被拒 |
| `seed_test_rbac.py` 被投顾 FK 挡住 | ✅ 已修 | 改 `sys_user` 为 UPSERT 且不覆盖密码,见 §5.1;重跑种子不再弄丢演示密码 |
| 本机缺 `9018 knowledge:query` / `9019 knowledge:manage` | ✅ 已补 | 见 §5.2,客服检索工具的必需权限已就位 |