- 在 `risk_suitability_log` 表中新增 `check_source` 枚举值 `platform`,支持代销平台的适当性检查。 - 更新相关 SQL 脚本以适应新的数据结构,确保数据一致性。 - 修改文档以反映 API 的最新状态和测试基线,确保文档与实现保持一致。 - 测试基线更新至 530 passed, 0 skipped,确保系统稳定性。 此更新为代销平台提供了更全面的适当性检查能力,提升了系统的功能性与可维护性。
14 KiB
风控 Agent 模块边界与合并接缝标注
版本:v1.0 · 2026-09-07 18:20 适用分支:
risk-control-agent(当前唯一开发分支) 定位:给 AL-09 合并 main 用的裁决依据,也供日常改码判断"这个文件动了会不会影响宿主"。 关联:《修改报告-对齐main基准.md》(阶段一执行依据)、《实现方案-风控追加需求v1.1-C4C6.md》(阶段二编码依据)
0. 一句话结论
风控 Agent 按"独立封装模块"自治:模块内部(含自有鉴权)自己说了算,与宿主的耦合收敛到 4 个接缝;合并 main 时不逐文件融合,只在接缝处接线。
采用本方案后,main 侧 gateway/*、middleware/trace.py、utils/input_guard.py 等文件与模块私有实现允许并存——它们不是"重复实现",而是"宿主层"与"模块层"各自的实现,只要接缝不串、模块不被宿主覆盖即可。
前提(三条,缺一不可):
- 模块私有文件不被宿主同名文件覆盖(合并时以模块版本为准);
- 模块对宿主的依赖只走 §2 的 4 个接缝,不得直接 import 宿主私有实现;
- 接缝由
tests/test_module_boundary.py锁死,宿主一改破坏契约即红灯。
1. 模块边界(文件归属表)
1.1 A 类 · 模块私有(风控业务,合并时全量保留,不与 main 融合)
| 路径 | 说明 |
|---|---|
app/service/risk/ |
风控引擎、规则、预警服务、对话 Tool、L3 画像、AML(阶段 A/B/C 全部成果) |
app/service/suitability.py |
适当性服务(AL-05 换核后为 main 契约 + 我方兼容层) |
app/service/tool_service.py |
Tool 编排与文案汇总 |
app/repository/risk_repository.py |
风控读写仓储 |
app/repository/core_ro.py |
Core 只读(AL-03 已与 main 融合,归模块私有) |
app/repository/session_repository.py |
会话仓储(模块自用) |
app/api/risk.py、app/api/simulate.py |
风控 4 API + 交易网关模拟入口 |
app/gateway/trade_gateway.py |
交易网关(C6 需透传 actor_id) |
app/model/suitability.py |
适当性落库行构造(AL-04 引入,与 main 同名 → 冲突时以模块版为准) |
scripts/demo/*、scripts/core/* |
演示与 Core 种子脚本 |
tests/(除 test_module_boundary 外) |
模块测试;AL-09 后全量基线 530 passed 0 skipped |
1.2 B 类 · 模块私有基建(与宿主同类但模块内自用,允许与 main 并存)
| 模块侧(我方) | 宿主侧(main) | 并存是否安全 | 说明 |
|---|---|---|---|
app/service/auth_service.py + app/api/deps.py |
app/gateway/{jwt_service,auth_deps,rbac,ownership}.py |
✅ 安全 | 模块内 API 一律 Depends(get_auth_context) 走 deps.py,不碰 gateway |
app/utils/trace.py + app/api/audit_middleware.py |
app/middleware/trace.py |
✅ 安全 | 模块用 contextvar 取 trace;宿主用 TraceMiddleware,互不读写 |
app/service/input_guard.py |
app/utils/input_guard.py |
✅ 安全 | 模块版含 42 词表 + oversize 4000 + Redis 限流 + 留痕;宿主版仅 SQL 注入校验 |
app/utils/db.py(get_engine(db)) |
app/config/database.py(get_agent_engine/get_core_engine) |
⚠️ 有条件 | 二者都是双库,功能等价;但连接池分裂——模块 Side 不得 import config.database,见 §2-S4 |
⚠️ 唯一实质风险:B 类第 4 行。其余三行只要"各用各的"就不冲突。
1.3 C 类 · 公共层/接缝(合并时以 main 为主,模块做适配)
| 接缝 | 文件 | 处置 |
|---|---|---|
| S1 | app/main.py |
以 main 骨架为主,补挂模块 router 与中间件(见 §2-S1) |
| S2 | app/config/settings.py |
以 main 为主 + 必须保留模块私有字段(见 §2-S3) |
| S3 | app/utils/exceptions.py、app/utils/response.py |
以 main 为主,补齐模块用到的错误码与响应字段 |
| S4 | app/service/agent_service.py、app/service/memory_service.py |
以 main 为主(chat 主链路),模块 Tool 通过注册表挂载 |
1.4 D 类 · main 独有新增(合并时静默并入,模块不依赖)
app/api/auth.py、app/gateway/{auth_deps,jwt_service,ownership,rbac}.py、app/middleware/{__init__,trace}.py、
app/repository/{advisor_rel,agent,audit}_repository.py、app/utils/input_guard.py、app/model/schemas.py、
app/config/database.py、tests/test_wave0_*.py
处置:全部接纳,不删除(它们是宿主 Wave 0 的正当实现),但模块代码不得 import 其中任何一个——由 §3 防呆测试锁死。
2. 四个接缝(合并时唯一需要人工接线的地方)
S1 · 挂载点(app/main.py)
我方现状:挂 risk / simulate / chat 三个 router;两层 @app.middleware("http")(audit + trace)。
main 现状:add_middleware(TraceMiddleware) + 挂 auth / chat。
合并口径:
# 宿主部分保留 main 写法
app.add_middleware(TraceMiddleware)
app.include_router(auth_router)
app.include_router(chat_router)
# 模块部分:追加挂载,中间件改为模块内注册(见下)
app.include_router(risk_router)
app.include_router(simulate_router)
setup_risk_module(app) # AL-09 已完成:注册 audit + trace 两层 http 中间件
注意:main 用 add_middleware,我方用 @app.middleware("http")——两种注册方式可共存,但中间件执行顺序需实测确认(trace 必须在 audit 内层,保证 audit 能读到 trace_id)。这列为 AL-09 必测项。
S2 · AuthContext 契约(最关键的接缝)
| 字段/方法 | 模块侧(deps.AuthContext) | 宿主侧(schemas.AuthContext) | 差异后果 |
|---|---|---|---|
| 主体标识 | actor_id |
sub |
字段名不同,全量 auth.actor_id 引用会 AttributeError |
| trace | 无(走 contextvar current_trace) |
trace_id(必填) |
模块需从 contextvar 或适配器补齐 |
| agent_type | 无(单独传参) | agent_type(必填,4 值) |
取值集合双方一致:customer/advisor/analyst/risk ✅ |
has_role |
has_role(*roles) 多参 |
has_role(role) 单参 |
多参调用会 TypeError |
| 权限判断 | has_permission(perm) |
has_perm(perm)(支持 xx:* 通配) |
方法名不同 |
| 其余 | roles/customer_id/token_type/permissions/tenant_id/jti |
同左(tenant_id 默认 default,jti 非空) |
基本对齐 |
处置:写适配器,不改模块内部。 已预制 app/api/auth_adapter.py(当前未接线,AL-09 时接入):
from app.api.auth_adapter import from_host_auth
# 宿主 AuthContext(sub/trace_id/agent_type) → 模块 AuthContext(actor_id/...)
ctx = from_host_auth(host_ctx)
适配器保证 sub→actor_id、trace_id→contextvar、has_role 多参语义、权限前缀通配到 permissions 的展开。
S3 · settings 字段
- main 新增需吸收:
jwt_dev_algorithm(HS256)、jwt_dev_expire_hours(8)。jwt_dev_secret双方同名同值,无冲突。 - 模块私有,合并时一个都不能丢:
- 双库:
mysql_core_database - 中间件:
redis_url - 知识库:
milvus_uri、ollama_base_url、embed_model、embed_dim、embed_timeout_seconds - LLM:
deepseek_api_key、deepseek_base_url - 风控阈值 11 项:
risk_large_amount、risk_daily_total、risk_freq_count、risk_probe_window_minutes、risk_probe_count、risk_probe_amount、risk_small_amount、risk_small_count、risk_aml_default_threshold、guard_rate_limit_max、guard_rate_limit_window_seconds
- 双库:
- ⚠️ issuer 硬伤:模块
jwt_issuer = https://idp.jinrong.internal,宿主https://idp.jinrong.dev。 自治方案下不改任何一方,由 S2 适配器在模块入口完成映射;已签发 token 不受影响(模块继续认自己的 issuer)。
S4 · 引擎工厂
模块统一用 app/utils/db.py:get_engine(database)(含 dispose_engines 生命周期)。
宿主 app/config/database.py 提供 get_agent_engine() / get_core_engine()。
红线:模块代码一律不得 from app.config.database import ...,否则连接池分裂、测试 monkeypatch 失效。由防呆测试锁死。
AL-09 可选优化:让 config.database 内部转调 utils.db.get_engine,保留宿主 API 外观、共用一处连接池。
3. 防呆机制(tests/test_module_boundary.py)
新增 4 组断言,任何一条被破坏即测试失败:
- 模块私有文件存在性:A 类关键文件不得被误删。
- 禁止跨层 import:模块文件(A/B 类)中不得出现
from app.gateway.auth_deps、from app.config.database、from app.middleware.trace、from app.utils.input_guard(宿主版)的导入。 - AuthContext 契约:模块
AuthContext必须保留actor_id/roles/customer_id/token_type/permissions/tenant_id/jti字段与has_role(多参)/has_permission/is_customer方法——防合并时被宿主类替换。 - settings 私有字段:§2-S3 列出的模块私有配置必须齐全;
AGENT_TYPES必须等于("customer","advisor","analyst","risk")。
用法:AL-09 合并后立刻跑
pytest tests/test_module_boundary.py,全绿才说明模块没被宿主侵蚀。
4. 冲突裁决表(AL-09 用,试合并实测 20 个冲突文件)
实测命令:
git merge-tree --write-tree --name-only risk-control-agent 3995cb4(只读,可随时复算)
| # | 冲突文件 | 归属 | 裁决 |
|---|---|---|---|
| 1 | app/api/chat.py |
C 公共 | 以 main 为主;模块对话能力经 Tool 注册表与 agent_service 挂载,不占用此文件 |
| 2 | app/main.py |
C 接缝 S1 | 以 main 骨架为主 + 追加模块 router/中间件(见 §2-S1) |
| 3 | app/config/settings.py |
C 接缝 S3 | 以 main 为主 + 保留模块私有字段(见 §2-S3 清单) |
| 4 | app/service/agent_service.py |
C 公共 | 以 main 为主;注意入口函数差异:main run_chat(ctx,msg,cust)->(str,bool) vs 模块 chat(...),由适配层统一,勿直接替换 |
| 5 | app/service/memory_service.py |
C 公共 | 以 main 为主,补齐模块会话窗口所需方法 |
| 6 | app/utils/exceptions.py |
C 公共 | 以 main 为主,补齐模块错误码(AUTH_*、STATE_CONFLICT 等) |
| 7 | app/utils/response.py |
C 公共 | 以 main 为主,补齐模块响应字段 |
| 8 | app/repository/core_ro.py |
A 私有 | 以模块版为准(AL-03 已融合 main 四项 + 我方风控扩展 5 项,main 版缺风控扩展) |
| 9 | app/model/suitability.py |
A 私有 | 以模块版为准(AL-04 引入;main 版为纯落库映射,缺模块兼容层) |
| 10 | app/gateway/__init__.py |
B/D 混合 | 合并两侧导出:保留模块 trade_gateway 导出,不引入 main 的 gateway 鉴权导出 |
| 11 | AGENTS.md |
文档 | 以我方为准(含 memory 六件套指引),吸收 main 新增条目 |
| 12~18 | docs/memory/{MEMORY,TODO,REQUIREMENTS,FRAMEWORK,FLOW,ENVIRONMENT,ITERATION}.md |
文档 | 以我方为准(六件套是我方进度事实源),逐份吸收 main 新增实质内容 |
| 19 | scripts/core/02-seed-base.sql |
A 私有 | 手工融合:main 账号(STAFF-10086~50001 等)+ 模块独有 STAFF-90001(风控演示账号,main 无!)+ C5 前置 STAFF-31001/31002 |
| 20 | tests/conftest.py |
A 私有 | 以模块版为准(502 用例依赖),再把 main 的 wave0 fixture 增量并入 |
另有 13 个 main 独有文件不报冲突、静默并入 → 见 §1.4,全部接纳但模块不得 import。
5. 三处硬伤在自治方案下的处置
| 硬伤 | 原风险 | 自治方案下处置 |
|---|---|---|
JWT issuer 不一致(idp.jinrong.internal vs .dev) |
统一后旧 token 全 401 | AL-09 已统一:/api/auth/login → issue_dev_token;gateway/jwt_service issuer/audience 读 settings;旧 dev token 需重签 |
| main 无 STAFF-90001(风控演示账号) | 演示账号变 401 | 模块账号由模块 seed 维护(02-seed-base.sql 融合时保留),不走宿主 DEFAULT_ROLES_BY_ACTOR |
main infer_roles 未知 actor 默认 ["analyst"](fail-open,含 sql:execute:readonly) |
越权风险 | 模块鉴权不采用 infer_roles;若宿主侧修复前上线,须在 AL-09 记录为待办 P1——模块自身为 fail-closed(缺失 X-Agent-Type 即 401) |
6. AL-09 执行步骤(按本方案修订)
git merge main(预期 20 冲突,按 §4 表逐个裁决,禁止git checkout --ours/theirs批量解决);- 跑
pytest tests/test_module_boundary.py,红了先修边界再继续; - 接 S2 适配器:
app/api/auth_adapter.py接线到模块入口; - 接 S1 挂载点:
main.py补 router + 两层中间件,实测中间件顺序(trace 在内层); - 处理 S4:确认模块无
config.database导入(防呆测试已覆盖); - 融合
02-seed-base.sql(保留 STAFF-90001 / 补 STAFF-31001、31002); - SOP §2 重灌演示库 → 全量
pytest须 530 passed 0 skipped + 边界用例全绿; - uvicorn 冒烟:三端点 + 模块 4 API 逐个验证(注意 JWT 与 debug 头两条通道都要走一遍)。
7. 待办与遗留
- AL-09 实现
setup_risk_module(app)与中间件注册(2026-09-08) - 中间件执行顺序实测;trace 对 ApiError 等 re-raise(2026-09-08)
- 宿主
infer_rolesfail-open 缺陷:建议宿主侧修复(P1,非模块阻塞项) - 模块代码若未来要彻底隔离,可迁至
app/modules/risk/——本次不做(属重构,收益 < 风险)