diff --git a/tools/grant_advisor_role.py b/tools/grant_advisor_role.py index 2599a7c..56001e8 100644 --- a/tools/grant_advisor_role.py +++ b/tools/grant_advisor_role.py @@ -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。 diff --git a/tools/grant_customer_service_phase2_permissions.py b/tools/grant_customer_service_phase2_permissions.py index 22144fd..3e26905 100644 --- a/tools/grant_customer_service_phase2_permissions.py +++ b/tools/grant_customer_service_phase2_permissions.py @@ -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` 建的)。 diff --git a/tools/seed_test_rbac.py b/tools/seed_test_rbac.py index e863501..9cf0731 100644 --- a/tools/seed_test_rbac.py +++ b/tools/seed_test_rbac.py @@ -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)