Files
group_xinghuo_jinrong/docs/memory/tests/2026-09-11-risk-agent-e2e/TEST-LOG-2026-09-11-RISK-001.md
T
zhanghongyu_0626 793c0307f8 feat(risk): Enhance risk management functionality and access control
- Updated `RiskListAccess` and `ThresholdWriteAccess` to enforce access control in the risk repository and threshold repository, ensuring only authorized roles can perform sensitive operations.
- Introduced new methods in `RiskRepository` for counting pending alerts and listing alerts with access checks, improving data security and compliance.
- Enhanced the `chat.py` and `deps.py` files to integrate compliance roles into the risk management matrix, allowing for more granular access control.
- Updated documentation to reflect the new testing baseline of 825 passed tests, indicating improved stability and functionality across the application.

This update significantly strengthens the risk management capabilities, ensuring robust access control and compliance with organizational policies.
2026-09-11 17:07:22 +08:00

19 KiB
Raw Blame History

企业级测试日志 · TEST-2026-09-11-RISK-001

风险 Agent(风控监测)端到端沙盘:表域 / 权限 / 触发方式 / 对话线 / UX 静态契约分析


1. 文档元数据

字段 值
测试记录编号 TEST-2026-09-11-RISK-001
缺陷/变更标题 风险 Agent 全功能端到端体检;发现仓储层单层防线(高危)×1 + 角色矩阵/契约口径 ×11
文档版本 v1.2
创建日期 2026-09-11
最后更新 2026-09-11
关联分支 merger
关联拍板 / TODO 风险 Agent(RISK-001~008)· TODO 133-159(前端逐功能详测未覆盖项)
风险等级 MEDIUM(F1 仓储层单层防线为高危,但仅在「直调仓储」路径可达,API 层已拦)
缺陷类型 纵深防御缺口 ×1 + 角色矩阵路径缺口 ×3 + 前端/后端口径 ×5 + 运维可见性 ×2
发现阶段 沙盘验证(真实 MySQL + 真实 DeepSeek)+ 前端静态/契约分析
修复阶段 已修复 v1.1–v1.3(F1–F11 + F12 对话线 deny)· 827 pytest 绿 · v1.2 复跑 50 PASS

2. 组织与责任

字段 值
所属系统 JinRong 金融四 Agent 智能管家
所属模块 风险 Agent(风控监测线)
子模块 / 服务 risk_repository · threshold_repository · risk/engine · risk/rules · alert_service · aml_service · escalation_service · agent_behavior_service · trade_gateway · app/api/risk.py · app/api/simulate.py · app/api/chat.py
发现人 Andrew(Claude Code 沙盘)
测试执行人 Andrew(Claude Code 沙盘)
修改人 (待指派)
评审人 (待模块负责人确认)
发布建议 后端逻辑本身可发布(规则引擎 + 鉴权均为确定性通过);F1/F5 建议合入前修复

3. 环境与基线

字段 值
测试环境 development · 本机 Windows 11
Python 3.13
数据库 MySQL jinrong_core(只读)+ jinrong_agent(业务,已灌演示种子)
Redis Docker 6380(Pub/Sub risk:pub:alert;所有 Redis 操作降级优雅,本测不依赖)
LLM DeepSeek deepseek-chat(真实 Key;仅风险对话线 /api/chat 使用)
前端 React + TypeScript(静态/契约分析,未启动浏览器 E2E)
测试基线 沙盘驱动脚本独立运行,不跑 pytest
沙盘结果 48 PASS / 0 WARN / 0 FAIL

4. 测试目标与方法

目标:摸清风险 Agent 整体功能,端到端实测①表域(各角色可见客户范围)②权限(角色 × 端点矩阵)③触发方式(4 类全跑)④用户使用体验(UX 问题尽量试出)。

方法:fastapi.testclient.TestClient 进程内 + issue_dev_token() 签发 7 角色 JWT + X-Agent-Type: risk 头 + 真实 MySQL 双库 + 真实 DeepSeek。规则引擎是纯函数(不依赖 LLM),对话线走真实 DeepSeek。另叠加「直调仓储探针」(不经过 API 层)坐实单层防线结论;前端做静态/契约比对(走读源码,未跑浏览器)。

驱动脚本:scripts/dev/sandbox_risk_test.py(新增,独立 TestClient(app),main() 逐节跑 A~D,打印 [PASS]/[WARN]/[FAIL] + 关键字段,存在 FAIL 即非零退出)。


5. 角色与权限矩阵

Token 映射(issue_dev_token 签发,HS256 dev):

账号 roles token_type 用途
STAFF-30001 risk_officer staff 全量 + 处置 + 扫描 + 对话线
STAFF-31001 risk_manager staff 台账全量只读 / 对话线拒绝
STAFF-40001 compliance staff 台账强制 aml 收敛
STAFF-10086 advisor staff 名下客户适当性
STAFF-10087 advisor staff 非名下客户(NOT_ASSIGNED)
CUST-9527 customer customer 本人适当性 / 本人交易
STAFF-90001 risk_officer, risk_demo staff 模拟交易(risk_demo)
SVC-RISK-01 service_risk service 矩阵放行但无只读路径(F2)

角色 × 端点结果总览(结构化断言 status/error_code):

端点 risk_officer risk_manager compliance advisor customer 无 token / 错配
GET /api/risk/alerts ✅ 全量 ✅ 全量只读 ✅ 强制 aml ❌ 403 ROLE ❌ 403 ROLE 401 / 403 MISMATCH
POST /api/risk/alerts/{id}/handle ✅ 状态机 ❌ 403 ❌ 403 ❌ 403 ❌ 403 401
POST /api/risk/suitability/check ✅ 全量 SCOPE SCOPE 名下 OK / 非名下 NOT_ASSIGNED 本人 OK / 他人 NOT_OWNER 401
POST /api/risk/aml/scan ✅ 成功+幂等 ❌ 403 ❌ 403 ❌ 403 ❌ 403 401
POST /api/simulate/trade ❌ 403(无 risk_demo) ❌ 403 ❌ 403 ❌ 403 ✅ 本人 OK 401
POST /api/chat(risk) ✅ 200+渲染 ❌ 403(显式拒) ❌ 403 MISMATCH ❌ 403 MISMATCH ❌ 403 MISMATCH 401

边界补充:handle 二次处置 → 409 STATE_CONFLICT;非法 handler_result → 422;缺单 → 404;simulate trade_type=convert → 400 BAD_REQUEST;page_size>100 → 422;JWT 通道 X-Agent-Type 缺失 → 401 AUTH_401_MISSING_AGENT_TYPE、错配 → 403 AUTH_403_AGENT_MISMATCH。


6. 测试执行记录(明细)

6.1 权限矩阵(A 节)

序号 用例 结果 关键断言
A-1 risk_officer GET /alerts PASS 全量返回,含 pending_review 与已处置行
A-2 risk_manager GET /alerts PASS 全量只读(同 risk_officer)
A-3 compliance GET /alerts PASS 返回行 alert_type 全部为 aml(表域收敛)
A-4 advisor GET /alerts PASS 403 AUTH_403_ROLE
A-5 customer GET /alerts PASS 403 AUTH_403_ROLE
A-6 无 token GET /alerts PASS 401 AUTH_401_MISSING_DEBUG_HEADERS
A-7 JWT 缺 X-Agent-Type PASS 401 AUTH_401_MISSING_AGENT_TYPE
A-8 JWT X-Agent-Type 错配 PASS 403 AUTH_403_AGENT_MISMATCH
A-9 handle 成功 + 状态机流转 PASS pending_review → confirmed_suspicious,status 单向
A-10 handle 二次处置 PASS 409 STATE_CONFLICT
A-11 handle 非法 handler_result PASS 422(Literal 校验)
A-12 handle 缺单 PASS 404 NOT_FOUND
A-13 suitability 本人 OK PASS 返回匹配结果
A-14 suitability 他人 NOT_OWNER PASS 403 AUTH_403_NOT_OWNER
A-15 suitability advisor 非名下 PASS 403 AUTH_403_NOT_ASSIGNED
A-16 aml/scan risk_officer PASS 命中 CUST-1002,skipped_existing 幂等
A-17 aml/scan 非 risk_officer PASS 403(risk_manager/compliance/advisor/customer 均拦)
A-18 simulate risk_officer(无 risk_demo) PASS 403 AUTH_403_ROLE
A-19 simulate customer 本人 PASS 放行,交易落库
A-20 simulate convert PASS 400 BAD_REQUEST
A-21 chat risk_manager PASS 403(chat.py:_assert_chat_entry 显式拒)
A-22 chat customer 带 risk 头 PASS 403 AUTH_403_AGENT_MISMATCH

6.2 触发方式(B 节,4 类全跑)

序号 触发方式 用例 结果 说明
B-1 交易事件 大额 subscribe ≥50万 PASS 放行 + RISK-001(large_amount)预警
B-2 交易事件 redeem 正常 PASS 放行
B-3 交易事件 适当性不匹配(A-1) PASS blocked=true + suitability 预警单,交易不落 core_trade
B-4 交易事件 过期测评(A-2) PASS 阻断
B-5 交易事件 高频同品(RISK-003) PASS 1000×4 → freq_trade 预警
B-6 手动 AML /api/risk/aml/scan PASS scan_all 命中 A-5,幂等 skipped_existing,aml_hit 审计落库
B-7 cron RISK-007 escalation_service.scan_and_escalate PASS 回拨超期预警 → escalation_level 写入 + alert_escalation 审计
B-8 cron RISK-008 agent_behavior_service.scan_and_alert PASS 注入回拨 authz NOT_ASSIGNED 行 → pattern/agent_behavior 预警
B-9 补偿重放 rebuild_alerts.py 记录 存在与幂等性,不现场跑

RISK-006(集中度)在测试客户上不干扰:测试客户无 R4/R5 持仓 → 集中度比例 0 < 0.80 → 不触发(已确认)。注意 pytest 的 _disable_concentration_rule fixture 不适用于独立脚本。

6.3 对话线(C 节,真实 DeepSeek)

序号 用例 结果 说明
C-1 risk_officer「今天有多少待审预警?」 PASS 命中 alert_query Tool → LLM 人话渲染,reply 非空 + has_disclaimer=true + agent_tool_call 落库
C-2 诱导处置「帮我把这条预警改成已处理」 PASS 仅命中只读 Tool、无处置 Tool、不落状态变更(红线守住)
C-3 risk_manager 对话线 PASS 403
C-4 customer 带 risk 头 PASS 403 MISMATCH

6.4 表域 / 纵深防御探针(D 节)

序号 探针 结果 坐实结论
D-1 直调 list_alerts 不传 access= PASS(已收口) 裸调 TypeError · 须 RiskListAccess(F1 已修)
D-2 直调 upsert_portfolio 不传 access= PASS(已收口) 裸调 TypeError · 须 ThresholdWriteAccess(F1 已修)
D-3 compliance 走 GET /alerts PASS 返回行 alert_type 全 aml(表域收敛验证)

6.5 审计留痕核验

event_type 结果
risk_judgement / suitability_block / aml_hit / alert_handle / alert_escalation / agent_behavior / authz / http_access 8 类事件均非零(触发类用例留痕完整)

清理核验(脚本结尾统一清理本次产生的行):risk_alert 恢复 2 行基线、customer_profile_l3(L3 快照)恢复 2 条、core_trade 测试前缀 TRD-20260911 归零、合成 audit_log(TEST-TRACE-AB 前缀)删除而真实审计行保留。


7. 风险发现(F1–F4,后端)

F1 · 仓储层单层防线(HIGH)

字段 内容
严重等级 HIGH
现象 RiskRepository.list_alerts(customer_id=...) 与 ThresholdRepository.upsert_portfolio(customer_id=...) 自身不注入角色/归属过滤,直调即可读任意客户台账、无鉴权直写阈值配置
根因 鉴权仅在 API 层(app/api/risk.py 入口)把关,仓储层是「信任调用方」的单层设计
影响 一旦有新的调用路径(cron、内部服务、未来复用仓储)直接引用仓储方法而未复刻 API 层鉴权,即造成行级越权读写
修复建议 ①仓储方法增加可选 scope/auth_context 参数做行级过滤;或 ②在 service 层统一收敛鉴权,仓储只做纯数据;③upsert_portfolio 增加调用方角色校验

F2 · service_risk 矩阵放行却无只读路径(MEDIUM)

字段 内容
严重等级 MEDIUM
现象 AGENT_ACCESS_MATRIX["risk"].roles 含 service_risk,但路由实际判定 else → 403 AUTH_403_ROLE(无只读路径落到 service_risk)
根因 矩阵声明与路由实现不一致,service_risk 是「声明可用、实际不可达」
修复建议 要么为 service_risk 补一条只读路径(如仅 GET /alerts),要么从矩阵移除并文档化

F3 · compliance 台账 aml 收敛仅 debug 头可达(MEDIUM)

字段 内容
严重等级 MEDIUM
现象 compliance 的 aml 台账收敛在生产 JWT 通道(compliance + X-Agent-Type: risk)会被 403 AUTH_403_AGENT_MISMATCH 拦下,仅 dev debug 头(X-Debug-Role: compliance)可达
根因 AGENT_ACCESS_MATRIX["risk"] 未将 compliance 纳入 token 交叉校验放行集,compliance 无法通过生产 JWT 通道以 risk 态访问
修复建议 明确 compliance 是否应具备生产侧 aml 台账访问权;若要,把 compliance 纳入 risk 矩阵的 token_types/roles 交叉放行

F4 · risk_manager 未进生产 JWT 签发侧 / 权限表(候选)

字段 内容
严重等级 候选(未坐实生产侧,dev issue_dev_token 可手工签发任意角色)
现象 生产 JWT 签发侧 / 权限表未发现 risk_manager 角色种子,dev 通道可绕过
修复建议 核对生产签发侧角色种子,补齐 risk_manager(及其只读边界)

F12 · compliance 进入 risk 对话线(F3 修复副作用 · v1.2 复跑新增)

字段 内容
严重等级 LOW(无数据泄露;工具层 fail-closed)
现象 F3 把 compliance 纳入 risk 矩阵后,/api/chat 的 _assert_chat_entry 仅显式拒 risk_manager,未拒 compliance → compliance 带 X-Agent-Type: risk 可进入 risk 对话线,返回 200 + 完整助手回复(复跑实测「你好」→ 完整风控助手欢迎语)
根因 AGENT_ACCESS_MATRIX["risk"].roles 增补 compliance 被 HTTP 台账与对话线入口共用;对话线「FR-6 冻结口径仅 risk_officer」只对 risk_manager 做了显式 deny 兜底(chat.py:_assert_chat_entry),漏掉 compliance
影响 Tool 层 assert_tool_access 对 compliance fail-closed(AUTH_403_SCOPE),实际查不到业务数据,无泄露;但口径不一致:compliance 走对话线不享受 aml-only 收敛(若未来 Tool 层放开即泄露全量台账),且产生无效会话/LLM 消耗
修复建议 在 chat.py:_assert_chat_entry 将 compliance 与 risk_manager 同口径显式拒 v1.3 已修(chat.py + test_chat)

8. UX / 前端契约问题(F5–F11)

F5 · 「已处置」筛选恒 0 行(前端,严重)

RiskAlertsPage 筛选项 {value:'handled'} → ?status=handled → 后端 list_alerts 只透传 status 精确匹配,无 handled 特殊值 → 恒返回 0 行。前端把非 pending 归类为「已处置」的口径与后端无此枚举不一致。 修复建议:后端加 status=handled 聚合(映射到 confirmed_normal/confirmed_suspicious/reported),或前端改为多值精确筛选。

F6 · 状态/处置结果标签缺失(前端)

ALERT_STATUS_LABELS 缺 confirmed_normal/confirmed_suspicious/reported;HANDLER_RESULT_LABELS 使用已废弃的 confirmed_risk/false_positive,缺 confirmed_suspicious/reported → 已处置行在 UI 显示原始英文 key 或空。 修复建议:同步标签字典与后端 handler_result Literal 枚举。

F7 · countToday 口径漂移(前端)

countToday 用 UTC 日期 vs 服务端本地时间,且 pageSize=100 截断 → 今日待审数可能偏小或跨日漂移。 修复建议:由服务端提供 pending_count 统计字段,或对齐时区 + 去截断。

F8 · SSE 无心跳帧(确认)

风险对话线 SSE 长连接无心跳帧,断线感知弱、易被代理超时中断。 修复建议:加周期 heartbeat 注释帧(: 开头的 SSE 注释行)。

F9 · 三页原始 JSON 直出(前端)

simulate/suitability/aml 三页用 <pre>{JSON.stringify(result)}</pre> 直出,无结构化渲染、无错误友好提示、免责声明不常驻。 修复建议:结构化表格 + 错误码→用户可读文案映射 + 常驻免责声明。

F10 · cron/运维脚本无 UI(确认)

RISK-007(时效升级)、RISK-008(行为链)只能手跑 cron 脚本;agent_behavior 预警单归为 pattern 类型无法区分,customer_id 为「涉及客户众数」而非精确归属。 修复建议:①为升级/行为链预警提供管理端可见视图;②预警增加细分 subtype;③涉及客户精确列出(而非众数)。

F11 · 处置 409 后无自动刷新(前端,轻微)

处置 Modal 提交命中 409(他人已处置)后无自动刷新/提示,用户停留在过期状态。 修复建议:409 时自动重新拉取列表并 toast 提示。


9. 数据准备

数据集 是否重灌 说明
Core / Agent 否 已灌演示种子(客户/AML 名单/25 行 C×R 矩阵/risk_alert 2 行基线)
触发类数据 运行时写、结尾清 core_trade/risk_alert/risk_suitability_log/audit_log 用可识别前缀,结尾清理本次产生的预警/交易行,真实审计行保留

10. 结论与剩余风险

字段 结论
权限矩阵 22/22 PASS;ROLE / NOT_OWNER / NOT_ASSIGNED / SCOPE / MISMATCH / 401 / 409 / 404 / 422 均正确
触发方式 4 类全跑通(交易同步 / 手动 AML / cron 时效升级 / cron 行为链),补偿重放记录存在
对话线 真实 DeepSeek 渲染正常、免责声明常驻、诱导处置红线守住
表域 / 纵深防御 F1 已收口(仓储凭证)· F2 service_risk 只读 GET /alerts · F3 compliance 纳入 risk JWT 矩阵 · F4 STAFF-31001 种子
UX 问题 F5 handled 聚合筛选 · F6 标签字典 · F7 服务端 stats · F8 SSE : ping · F9 结构化结果页 · F10 仍 open(cron 无 UI)· F11 409 刷新
剩余风险 ①新仓储方法须继续带 access 凭证;②F10 运维预警无 UI + 行为链归属粗粒度
建议人工再验 复跑 sandbox_risk_test.py;前端台账「已处置」筛选 + 409 回归
是否可发布 后端+前端契约项 v1.3 已合入工作区 · 827 pytest · 复跑 50 PASS · merger 待 commit

11. 签核

角色 姓名 日期 意见
模块负责人 (待指派) ☐ 通过 ☐ 待改
发现人 / 测试 Andrew 2026-09-11 ☑ 沙盘 + 前端静态契约分析
修改人 Andrew 2026-09-11 ☑ F1–F11 子集已修 · 825 pytest

12. 修订历史

版本 日期 作者 说明
v1.0 2026-09-11 Andrew 首版:7 角色 × 权限矩阵(22 例)+ 4 类触发 + 真实 DeepSeek 对话线 + 仓储单层防线探针 + 前端静态契约 11 项发现;48 PASS / 0 WARN / 0 FAIL
v1.1 2026-09-11 Andrew 修复 F1–F11 子集(拍板:F2 只读 GET · F3 compliance 矩阵)· D 节探针改 TypeError · 825 pytest
v1.2 2026-09-11 Andrew 复跑 sandbox_risk_test.py(50 PASS / 0 WARN / 0 FAIL)· 修正沙盘 5 处陈旧断言(compliance×alerts/suitability/aml/chat + service_risk)+ 补 F5 handled/F7 stats 覆盖 · 新增 F12(compliance 进对话线,F3 副作用)
v1.3 2026-09-11 Andrew F12 已修:chat.py:_assert_chat_entry 拒 compliance · 单测 + 沙盘 A5 期望 403 · 827 pytest