## 新入库(`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 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
11 KiB
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) |
|---|---|
| 权限总数 | |
| 绑定数 | 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,客服检索工具的必需权限已就位 |