Files
group_xinghuo_jinrong/docs/项目框架设计/风控Agent模块边界与合并接缝标注.md
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

14 KiB
Raw Permalink Blame History

风控 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 等文件与模块私有实现允许并存——它们不是"重复实现",而是"宿主层"与"模块层"各自的实现,只要接缝不串、模块不被宿主覆盖即可。

前提(三条,缺一不可):

  1. 模块私有文件不被宿主同名文件覆盖(合并时以模块版本为准);
  2. 模块对宿主的依赖只走 §2 的 4 个接缝,不得直接 import 宿主私有实现;
  3. 接缝由 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 · 当前全量基线见 MEMORY §0

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 组断言,任何一条被破坏即测试失败:

  1. 模块私有文件存在性:A 类关键文件不得被误删。
  2. 禁止跨层 import:模块文件(A/B 类)中不得出现 from app.gateway.auth_deps、from app.config.database、from app.middleware.trace、from app.utils.input_guard(宿主版)的导入。
  3. AuthContext 契约:模块 AuthContext 必须保留 actor_id/roles/customer_id/token_type/permissions/tenant_id/jti 字段与 has_role(多参)/has_permission/is_customer 方法——防合并时被宿主类替换。
  4. 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 执行步骤(按本方案修订)

  1. git merge main(预期 20 冲突,按 §4 表逐个裁决,禁止 git checkout --ours/theirs 批量解决);
  2. 跑 pytest tests/test_module_boundary.py,红了先修边界再继续;
  3. 接 S2 适配器:app/api/auth_adapter.py 接线到模块入口;
  4. 接 S1 挂载点:main.py 补 router + 两层中间件,实测中间件顺序(trace 在内层);
  5. 处理 S4:确认模块无 config.database 导入(防呆测试已覆盖);
  6. 融合 02-seed-base.sql(保留 STAFF-90001 / 补 STAFF-31001、31002);
  7. SOP §2 重灌演示库 → 全量 pytest 须 530 passed 0 skipped + 边界用例全绿;
  8. uvicorn 冒烟:三端点 + 模块 4 API 逐个验证(注意 JWT 与 debug 头两条通道都要走一遍)。

7. 待办与遗留

  • AL-09 实现 setup_risk_module(app) 与中间件注册(2026-09-08)
  • 中间件执行顺序实测;trace 对 ApiError 等 re-raise(2026-09-08)
  • 宿主 infer_roles fail-open 缺陷:建议宿主侧修复(P1,非模块阻塞项)
  • 模块代码若未来要彻底隔离,可迁至 app/modules/risk/——本次不做(属重构,收益 < 风险)