Files
group_fqcd_jr/docs/36-PR7合并记录与权限号段修正.md
lzf_0626 36c7a9d8d2 文档:审查报告入库 + 全量校对补注
## 新入库(`docs/演示用/`)

- `代码库全面审查报告-2026-09-14.md`
- `代码修改方案-2026-09-14.md`
- `记忆系统排查报告-2026-09-14.md`
- `记忆系统修复文档-2026-09-14.md`
- `文档一致性审计报告-2026-09-14.md`
- `多Worker接入方案-2026-09-14.md`

## 全量校对(32 个既有文档 + `AGENTS.md`)

跨 39 个文件、**1125 insertions / 148 deletions**。

⚠️ **这批改动同样不是本次会话写的**。我抽样核对过性质:是**实质内容补充**而不是
格式/换行转换。例如 `docs/44-演示流程.md` 新增两条"2026-09-14 补注":

- `启动金融Agent平台.bat` 只在**桌面**上,仓库里只有 `启动平台.bat` 这一份
  (两份由同一个 `tools/make_launcher_bat.py` 产出,改完 `start.ps1` 重跑它一起更新);
- `advisor_t`(9020) 与 `offsite_t`(9006) **不在 `tools/seed_test_rbac.py` 的演示用户里**
  (那里只有 `cust_t`/`risk_t`/`admin_t`/`review_t` 四个),由 `grant_*.py` 系列创建,
  **重跑种子不会重建它们** —— 换机器时这两个账号登录失败,要先查 `sys_user` 有没有这两行,
  而不是查密码。

这两条都是对的地方,与我这一路踩到的现象一致(我确实用到了 `advisor_t`/`offsite_t`)。

**我没有逐字审阅全部 39 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
2026-09-14 20:36:00 +08:00

11 KiB
Raw Permalink Blame History

PR #7 合并记录与权限号段修正

合并时间:2026-09-12 合并提交:4413644(merge: 合并 ZSY 的客服 Agent 接入(访客身份、画像候选、转人工工单)—— PR #7) 来源:origin/ZSY_develop = f68b052 → qyqy_develop 规模:90 文件、+7897 / −73 门禁:全部通过(见 §2),数据库侧无迁移、表结构未变


⚠️ 口径修正(2026-09-14):本文的"权限条数"是当时快照,现已增长

本文正文出现的 40 条 / 45 条 / 38 条都是 2026-09-12 当天的实测值,不是当前值。 现状(直接查 tools/seed_test_rbac.py 的 PERMISSIONS):

口径 值
权限号段 9001–9065(共 65 条)
区间归属 9001-9017 一期公共;9018-9034 客服二期/投顾;9041-9046 产品治理与候选审核;9047-9050 风控告警;9051-9056 推广/NL2SQL/探针;9057-9059 投顾客户范围;9060-9065 账户与交易看板
从未存在 9035-9040、4041-4046(后者是 9041-9046 的笔误)

⚠️ 本文 §4 的号段表到 9046 结束是正确的(那正是当时的上界),但不要据此认为 9046 是终点。 权威出处是 tools/seed_test_rbac.py 的 PERMISSIONS(该脚本是 DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099 的重建语义: 没并进种子的权限码,重建一次就没了,表现是"接口突然 403"而无报错线索)。 一致性由 python tools/check_rbac_seed_consistency.py 及其单测守着。


1. 合并内容

ZSY 的客服 Agent 接入线,含三块新能力与一次合规清理:

块 内容
访客身份 VisitorTokenIssuer(/api/v1/auth/visitor-tokens)、security.py 的 visitor:true 分支、auth.py 跳过 IdentityService.resolve()
画像候选(二期) 候选生成 Worker、用户确认 / 管理员审核四个端点、Milvus + Neo4j 投影适配器、memory_sync_outbox 消费者
转人工工单 工单只读管理端点、转接上下文构造、会话凭据最小化(conversation_privacy.py)
合规清理(9aaacc2) 删除生产死代码 knowledge_tool_service.py 与硬编码 Milvus 字段名的 milvus_knowledge_adapter.py(9 文件 +189/−483)

评审往返见 docs/33(v1 回复)、docs/34(v2 回复)、docs/35(v3 回执核实)。


2. 合并后门禁实测

项 结果
git merge f68b052 --no-ff ✅ 无冲突
ruff check app tests tools ✅ 干净
mypy app ✅ 244 文件 0 错(基线 228,+16 为新模块)
python tools/check_authoritative_docs.py ✅ 49 份文档无撞号
pytest tests/unit tests/contract ✅ 1275 passed, 2 skipped, 0 failed(基线 1207,+68 为新用例)
alembic current ✅ 已在 20260911_merge_adv_risk_heads(head),upgrade head 无操作 → 无新迁移
python tools/audit_schema.py ✅ 89 business tables, no missing or unexpected tables

说明:f68b052 单独一棵树跑文档守卫会报 21/22 撞车 —— 那是我方基线的问题 (投顾那两份的改名在我方 5018f11,不在 ZSY 的基线上)。合并后消失:21/22 归 风控与四大 Agent,投顾挪到 30/31。不是 ZSY 引入的。


3. 合并时顺手修掉的两处

3.1 tests/unit/core/test_security.py 的密钥路径漂移(2 行)

第 48、108 行原本硬编码 Path("config/jwt/jwt-private.pem"),漏了 dev/:

  • tools/generate_jwt_keys.py:107 的默认 --out-dir 是 config/jwt/dev;
  • app/core/config.py:26-27 的默认值也是 config/jwt/dev/...;
  • 同一个文件第 13 行已定义 DEV_KEY_DIR = Path("config/jwt/dev"),注释写着 「路径只在这里定义一次……避免多处硬编码各自漂移」。

后果:在没有 config/jwt/jwt-private.pem 的环境里必红 2 个用例(本机即如此)。 已改为 (DEV_KEY_DIR / "jwt-private.pem"),与第 132 行写法一致。全仓这种写法只此 2 处, 其余 12 处引用都是 config/jwt/dev/。

3.2 权限号段冲突(我方环境的历史遗留,ZSY 不受影响)

问题:库里有一批 9020-9035,是本方 tools/grant_advisor_role.py 用旧号段建的; 而投顾线(bbf623a)后来把 9020-9034(15 个)写进了公共种子 seed_test_rbac.py。 两套 id→code 映射不同:

id 种子(投顾线) 库里(旧 grant_advisor_role.py)
9020 investment-goal:write:self asset-allocation:generate:self
9023 product-recommendation:generate:self investment-goal:review
9031 product-recommendation:review product-governance:read

后果:种子的清理是 DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099, 但角色绑定只清 role_id IN (9001,9002,9003) —— advisor(9004) 的绑定不在清理范围内。 所以跑一次种子会把 9020-9034 换成种子的语义,而 advisor 的绑定仍指向这些 id ⇒ advisor 会拿到错误的权限组合,且不会报错。

处置(已改代码,见下):权限号段整体上移到 9041-9046,与种子的 9001-9034 和 库里的旧数据都不冲突;并把"权限码的定义以种子为准"写进了三个文件的文档字符串。

ZSY 侧不受影响:他的库只有 9001-9018,没有这批旧数据。


4. 代码改动清单

文件 改动
tests/unit/core/test_security.py 2 行改用 DEV_KEY_DIR(§3.1)
tools/seed_test_rbac.py PERMISSIONS 增 9041-9046(投顾治理类 3 个 + 客服二期 3 个);CUSTOMER_PERMISSIONS 增 9044;文档字符串写清「DELETE 重建」的两个后果
tools/grant_advisor_role.py ADVISOR_PERMISSIONS 由 16 个收敛为种子里缺的 3 个(9041-9043);文档字符串记录号段冲突与处置顺序
tools/grant_customer_service_phase2_permissions.py 权限 id 9036-9038 → 9044-9046;文档字符串改为「已并入种子,本脚本仅作幂等补齐」

新增的 6 个权限码:

id 权限码 授给
9041 product-governance:read admin(治理类)
9042 product-governance:review admin
9043 product-governance:sync admin
9044 memory:candidate:confirm customer(确认自己的候选)+ admin
9045 memory:candidate:review admin
9046 handover:read admin

一致性自检(只读):当时种子 40 条权限、id 唯一、无重复;两个 grant_*.py 的每一条 (id, code) 都与种子逐字一致;CUSTOMER_PERMISSIONS 引用的 id 全部存在。 (⚠️ 2026-09-14:现为 65 条,号段 9001-9065。)


5. 本机 RBAC 对齐:最终执行记录

分两轮完成。第一轮原计划是"种子 + 三个 grant 脚本",但种子当场跑不通(见 §5.1), 于是先改用不依赖种子的方案把功能补齐;随后按项目方决定修好种子,第二轮完整跑通。

5.1 第一轮撞到的破坏:seed_test_rbac.py 在这台环境上跑不通(已修)

sqlalchemy.exc.IntegrityError: (1451, 'Cannot delete or update a parent row: a foreign key
constraint fails (`jr`.`advisor_profile_tag`, CONSTRAINT `fk_advisor_profile_tag_customer`
FOREIGN KEY (`customer_id`) REFERENCES `sys_user` (`id`))')
[SQL: DELETE FROM sys_user WHERE id IN (9001,9002,9003)]

投顾线引入的 advisor_profile_tag 有 FK 指向 sys_user,而库里有数据引用 9001-9003, 于是种子最后那步 DELETE FROM sys_user 被外键拒绝。因为 commit() 在最后,种子是原子的 (失败即完整回滚,当时实测:权限仍 38 条、三个用户密码完好),但任何人在这台环境、 或任何有投顾数据的环境上跑种子都会失败 —— 而且外部表现只是"什么都没发生"。

修法(已落地):把 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 对齐后的最终实测状态

⚠️ 下表是 2026-09-12 当天的实测快照。权限总数与绑定数此后已随新增权限码变动 (现共 65 条,见文件头 ⚠️ 块);测试用例数也已增长(tests/unit tests/contract 不再只有 1275 项)。

项 值(2026-09-12)
权限总数 45 条 → 现 65 条
绑定数 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

执行顺序(有依赖,不能颠倒):

清 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 条)。


6. 遗留事项

项 归属 状态
品牌名(COMPANY 南方科技 → 奶龙基金责任有限公司) ZSY 上报项目方 走 (b):保持现状,定后单行 revert,须与 customer_service_rules.py:116/129 一起处理
候选流程缺走 RBAC 的集成用例(服务层测试测不出"权限码不存在") ZSY 他认账,承诺补
只读真机冒烟(用真实令牌打 §19 新增端点,403 当失败) 待 ZSY 回话 已提议
环境数据不可跨环境 全员 第 4 次踩坑,已写进种子文档字符串
docs/05 §19 端点编号 已解决 A039/A040,全表 62 个编号唯一
seed_test_rbac.py 被投顾 FK 挡住 ✅ 已修 改 sys_user 为 UPSERT 且不覆盖密码,见 §5.1;重跑种子不再弄丢演示密码
本机缺 9018 knowledge:query / 9019 knowledge:manage ✅ 已补 见 §5.2,客服检索工具的必需权限已就位