chore: 权限号段根治——9041-9046 并进种子;修正 grant 脚本与种子 9020-9034 的 id 映射冲突

This commit is contained in:
2026-09-12 12:09:55 +08:00
parent 2bb056e516
commit cdb2de3e45
3 changed files with 65 additions and 39 deletions
+25 -28
View File
@@ -5,27 +5,33 @@
投顾这条线合并进来后,`bootstrap.py` 有 **10 处 `allowed_roles` 引用了 `advisor`**,
`financial_nl2sql_service.py:272` 还硬编码检查 `{"advisor","operator","admin","super_admin"}`,
`promotion_material_service.py:164` 直接按 `"advisor" in context.roles` 走业务分支 ——
但 `sys_role` 里**没有这个角色**,`sys_permission` 里也没有投顾那 16 个权限码
(`seed_test_rbac.py` 只建到 9019)。结果是:投顾登录后拿不到任何投顾权限,
但 `seed_test_rbac.py` **只重建 customer / risk_operator / admin 三个角色**,
`sys_role` 里没有 `advisor`。结果是:投顾登录后拿不到任何投顾权限,
所有投顾接口一律 403,而报错看起来像"权限配错了",实际是**角色根本不存在**。
## 号段为什么是 9041(重要,2026-09-12 修正)
投顾那 13 个业务权限码**已由投顾线(`bbf623a`)并进 `seed_test_rbac.py` 的 9020-9034**,
但那个种子**漏了 3 个治理类权限码**(`product-governance:*`),而治理接口直接要求它们。
本脚本现在只补这 3 个(**9041-9043**)+ 建角色 + 绑定。
⚠️ 本脚本**此前**用 9020-9035 定义过整套 16 个权限,与种子的 9020-9034 **id→code 映射不同**。
若库里还留着那批旧数据,跑一次种子会把 9020-9034 换成种子的语义,而 `advisor` 角色(9004)
的绑定**不在种子的清理范围内**(种子只清 role_id 9001-9003),于是它的绑定会指向**错误的权限码**。
**处置顺序:先跑 `seed_test_rbac.py`(对齐 9001-9034),再跑本脚本(补 9041-9043 并重建绑定)。**
## 权限怎么分
| 类别 | 权限码 | 给谁 |
|---|---|---|
| 投顾工作流(10) | `asset-allocation:generate:self`、`investment-goal:read:self` / `:review` / `:publish`、`portfolio-analysis:read:self`、`product-comparison:read:self`、`product-recommendation:read:self` / `:generate:self` / `:review` / `:publish` | `advisor` + `admin` |
| 治理类(6) | `asset-allocation:backtest`、`product-governance:read` / `:review` / `:sync`、`profile-governance:read` / `:review` | **只给 `admin`** |
| 投顾工作流(10) | `asset-allocation:generate:self`、`investment-goal:read:self` / `:review` / `:publish`、`portfolio-analysis:read:self`、`product-comparison:read:self`、`product-recommendation:read:self` / `:generate:self` / `:review` / `:publish` | `advisor` + `admin` —— **定义在种子的 9020-9034** |
| 治理类(3,本脚本建) | `product-governance:read` / `:review` / `:sync` | **只给 `admin`** |
| 治理类(种子已含) | `asset-allocation:backtest`、`profile-governance:read` / `:review` | **只给 `admin`** |
`review` / `publish` 也给投顾,与项目既有决策一致 —— 此前已裁定**不做双人复核**
(`admin` 发布配置时也是"创建人自审")。治理类不给投顾:那是平台侧的活。
## ⚠️ 与 `seed_test_rbac.py` 的冲突
那个脚本是 **DELETE 重建**语义,它的
`DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099` 会**清掉本脚本建的权限**
(本脚本用 9020-9035)。将来若要在种子里固化投顾权限,请把它并进
`seed_test_rbac.py` 的 `PERMISSIONS` 常量,而不是只跑本脚本。
本脚本自身**只增不删**:重复执行只补齐缺失项,不动任何已有绑定。
用法:
@@ -53,25 +59,16 @@ ADVISOR_ROLE_ID = 9004
ADVISOR_ROLE_CODE = "advisor"
ADVISOR_ROLE_NAME = "投资顾问"
#: 权限 id 从 9020 起,避开种子已用的 9001-9019。
#: 权限 id 从 9041 起。两个硬约束:
#: 1. 种子 `seed_test_rbac.py` 已占 9001-9034(含投顾线的 9020-9034);
#: 2. 库里曾有一批 9020-9035 是本脚本用**旧号段**建的,与种子的 9020-9034
#: **id→code 映射不同** —— 整体挪到 9041 之后,与两侧都不冲突。
#: 只定义种子里**缺**的这 3 个治理类权限码;其余 13 个由种子提供。
#: (id, permission_code, resource, action, data_scope)
ADVISOR_PERMISSIONS: tuple[tuple[int, str, str, str, str], ...] = (
(9020, "asset-allocation:generate:self", "asset-allocation", "generate", "self"),
(9021, "asset-allocation:backtest", "asset-allocation", "backtest", "all"),
(9022, "investment-goal:read:self", "investment-goal", "read", "self"),
(9023, "investment-goal:review", "investment-goal", "review", "all"),
(9024, "investment-goal:publish", "investment-goal", "publish", "all"),
(9025, "portfolio-analysis:read:self", "portfolio-analysis", "read", "self"),
(9026, "product-comparison:read:self", "product-comparison", "read", "self"),
(9027, "product-recommendation:read:self", "product-recommendation", "read", "self"),
(9028, "product-recommendation:generate:self", "product-recommendation", "generate", "self"),
(9029, "product-recommendation:review", "product-recommendation", "review", "all"),
(9030, "product-recommendation:publish", "product-recommendation", "publish", "all"),
(9031, "product-governance:read", "product-governance", "read", "all"),
(9032, "product-governance:review", "product-governance", "review", "all"),
(9033, "product-governance:sync", "product-governance", "sync", "all"),
(9034, "profile-governance:read", "profile-governance", "read", "all"),
(9035, "profile-governance:review", "profile-governance", "review", "all"),
(9041, "product-governance:read", "product-governance", "read", "all"),
(9042, "product-governance:review", "product-governance", "review", "all"),
(9043, "product-governance:sync", "product-governance", "sync", "all"),
)
#: 投顾拿哪些 —— 工作流那 10 个;治理类 6 个只给 admin。
@@ -39,14 +39,18 @@ if admin:
我方库里没有 `super_admin` 角色,`admin=True` 那一支由 `admin` 满足。
## ⚠️ 与 `seed_test_rbac.py` 的冲突(与投顾那次同源)
## ✅ 已并入种子(2026-09-12)
`seed_test_rbac.py` 是 **DELETE 重建**语义,它的
`DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099` 会**清掉本脚本建的权限**。
所以本脚本**只增不删**,用于局部补齐;若要长期固化,请把这三个权限并进
`seed_test_rbac.py` 的 `PERMISSIONS` 常量(投顾那 16 个也是同一个待办)。
这三个权限码**已经并进 `seed_test_rbac.py` 的 `PERMISSIONS`(id 9044-9046)**,
所以"换环境 / 重跑种子之后接口又 403"这个根问题已经解决。本脚本保留下来作为
**幂等补齐**:库里临时缺这几行、或只想跑这一个脚本时用它。
权限 id 从 **9036** 起:避开种子占用的 9001-9019,也避开投顾的 9020-9035。
权限 id 的号段现状:种子占 9001-9034,投顾治理类占 9041-9043,本脚本用 **9044-9046**。
⚠️ 注意 `seed_test_rbac.py` 是 **DELETE 重建**语义,它的
`DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099` 会删掉该号段内**所有**权限
再按常量重建 —— 所以**权限码的定义以种子为准**。以后新增权限码请先并进种子的
`PERMISSIONS`,不要只写一个脚本。
用法:
@@ -68,11 +72,13 @@ from app.infrastructure.db import SessionFactory
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(errors="replace") # type: ignore[union-attr]
#: 权限 id 从 9044 起:种子 `seed_test_rbac.py` 已占 9001-9034,投顾治理类用 9041-9043,
#: 本脚本再接 9044-9046。这三个权限码**已并进种子**,所以本脚本只做幂等补齐与授权。
#: (id, permission_code, resource, action, data_scope)
PHASE2_PERMISSIONS: tuple[tuple[int, str, str, str, str], ...] = (
(9036, "memory:candidate:confirm", "memory:candidate", "confirm", "self"),
(9037, "memory:candidate:review", "memory:candidate", "review", "all"),
(9038, "handover:read", "handover", "read", "all"),
(9044, "memory:candidate:confirm", "memory:candidate", "confirm", "self"),
(9045, "memory:candidate:review", "memory:candidate", "review", "all"),
(9046, "handover:read", "handover", "read", "all"),
)
#: 角色 id(`seed_test_rbac.py` 建的)。
+25 -2
View File
@@ -1,7 +1,15 @@
"""造 RBAC 测试数据,验证真实链路身份加载与越权拦截。
数据使用 9001/9002/9003 号段(角色、权限分别用 9001 起的独立号段),便于清理,
不影响业务数据。
数据使用 9001 起的号段(角色 9001-9003、权限 9001-9046),便于清理,不影响业务数据。
权限码是几条线叠加出来的:9001-9019 基础能力、9020-9034 投顾线、9041-9046 投顾治理类与客服二期。
⚠️ 本脚本是 **DELETE 重建**:`DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099`
会删掉该号段内**所有**权限(包括别的线用 `grant_*.py` 建的),再按 `PERMISSIONS` 重建,
并且**只清 role_id 9001-9003 的角色绑定**。两个已知后果:
1. 新增权限码必须并进本文件的 `PERMISSIONS`,否则重建一次就没了(表现为"接口突然 403");
2. `advisor`(9004)这类不在 `ROLES` 里的角色,其绑定**不会被清** —— 若它们引用的
permission_id 在重建时换了语义,绑定就会指向错误的权限码。改 id→code 映射前先确认。
为什么需要这份种子:权限码必须与**代码实际声明**一致——工具声明写在
`app/service/agent/bootstrap.py`(`check_suitability` 需 `suitability:read`、
@@ -68,12 +76,27 @@ PERMISSIONS: tuple[tuple[int, str, str, str, str], ...] = (
(9032, "product-recommendation:publish", "product-recommendation", "publish", "all"),
(9033, "asset-allocation:backtest", "asset-allocation", "backtest", "all"),
(9034, "product-comparison:read:self", "product-comparison", "read", "self"),
# ---- 9041-9046:投顾治理类 3 个 + 客服二期 3 个 ----
# 投顾线(`bbf623a`)已把 9020-9034 放进本种子,但漏了这 3 个治理类权限码,
# 而 `app/service/` 的治理接口直接要求它们 —— 缺了就是"实现了却一直 403"。
# 号段从 9041 起:库里曾有一批 9020-9035 是 `grant_advisor_role.py` 用旧号段建的,
# 与种子的 9020-9034 **id→code 映射不同**,挪到 9041 之后与两侧都不冲突。
(9041, "product-governance:read", "product-governance", "read", "all"),
(9042, "product-governance:review", "product-governance", "review", "all"),
(9043, "product-governance:sync", "product-governance", "sync", "all"),
# 客服二期(画像候选 + 转人工工单)的 3 个权限码。此前不在任何种子或迁移里,
# 属"环境数据":代码合过来后若库里缺这几行,候选确认/审核与工单查看 5 个接口一律 403。
(9044, "memory:candidate:confirm", "memory:candidate", "confirm", "self"),
(9045, "memory:candidate:review", "memory:candidate", "review", "all"),
(9046, "handover:read", "handover", "read", "all"),
)
# 客户:业务侧自助能力(自己的会话、反馈、转人工、自己的记忆画像)。
CUSTOMER_PERMISSIONS = (
9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 9011, 9018,
9020, 9021, 9022, 9023, 9024, 9025, 9026, 9034,
# 客服二期:客户确认/拒绝**自己**的画像候选(服务层按 customer_id 过滤,不越权)。
9044,
)
# 风控专员:业务侧只读 + 跨客户记忆 + 审计只读,不含配置写权限。
RISK_PERMISSIONS = (9001, 9002, 9003, 9010, 9011, 9012)