Files
group_xinghuo_jinrong/docs/项目框架设计/开发计划-风控模块.md

114 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 开发计划 · 风控监测 Agent 模块
> 版本:v1.2(2026-09-07 追加 C4~C6 任务并入 · v1.1 为 AI 评审通过版:修复 P1×2——假 AuthContext 规范落 deps.py、conftest 测试前置;P2×7 顺手收敛)· 分支 `risk-control-agent`(旧称 feature/risk 已过时)· 负责人 E
> 依据:PRD v1.1(含追加 FR-8/9/10)+ 架构设计 v1.1
> 铁律:**每步完成即测,测试通过才进下一步**;每个任务至少一个 commit,里程碑打 tag;涉及表结构/接口协议的步骤已获 PRD 批准
> 任务映射:T-31 = 阶段 A(**交付=suitability 服务函数+单测**;check API 随 B6)· T-30 = 阶段 B · T-32 = 阶段 C(**交付=对话线**;台账/处置 API 随 B6)——此边界已按 M4 复核(2026-09-07)同步修订 TODO 完成
> v1.2 追加说明:C4~C6 对应 PRD v1.1 追加需求 FR-8/9/10(RISK-006/007/008),与 C1~C3 并行开发;追加产物 v1.1 曾独立评审不通过(P0×4),修订 v1.2 复审通过后并入
---
## 阶段 A · 基建与公共校验(不依赖 T-01/T-07,可立即开工)
| # | 任务 | 产出 | 验证方式 | 依赖 |
| --- | --- | --- | --- | --- |
| A1 | `utils/trace.py` + `utils/desensitize.py` + `settings.py` 扩展(risk_* 阈值)+ `.env.example` 同步 | 3 个工具模块 + 配置 | 单测:脱敏 **DESENS-001~004 四规则**(005 资产金额归前端组件,后端不做)、trace set/get | 无 |
| A2 | `core_ro.py` 扩展 `sum_trades_on_date`(其余复用现有方法) | Repository 方法 | 单测/手工 SQL 对照 | **前置:本机已执行 `reset.ps1` 灌库(FLOW §0 ③,一次性本机操作,非等待 T-05 任务)** |
| A3 | `repository/risk_repository.py`(risk_alert / risk_suitability_log / l3 / aml_list 读写) | Repository | 手工 SQL 对照 | agent 库建表(含 risk_aml_list) |
| A4 | `service/suitability.py`:SUIT-001~008 纯函数 + check 服务(落日志)+ **`AuthContext` Pydantic 模型与 `get_auth_context` 工厂签名占位**(实现归 B6) | 公共校验服务 + AuthContext 接口冻结 | **单测:矩阵 25 组合 + 69/70 岁 + NULL 年龄 + 364/365 天(验收 A-8)** | A1、A2、A3 |
## 阶段 B · 事件驱动线(核心,不依赖 T-07)
| # | 任务 | 产出 | 验证方式 | 依赖 |
| --- | --- | --- | --- | --- |
| B1 | `service/risk/rules.py`:RISK-001~005 纯函数 | 规则函数 | **单测:各规则命中/不命中 + RISK-004 窗口 + RISK-005 非连续** | A1 |
| B2 | `alert_service.py`:聚合去重(进程内锁 + 锁内 check-insert)+ 审计落库 + `risk:pub:alert` PUBLISH(同步 Redis 单例)。**备注:merge 原语(append_alert_event)已下沉 repo(A3),本任务只做聚合决策与编排(评审 P2-5 口径)** | 预警服务 | 单测:聚合合并、risk_score 取 max、去重追加;**并发冒烟:两线程同客户同日首单 → 预警单数=1 且 events[] 含两笔** | A3 |
| B3 | `profile_l3.py`:get_l3 → 最高档合并 → insert_l3/update_l3。**备注:非原子,并发首单需 catch IntegrityError 转更新(或复用 B2 锁)**(评审 P2-7③);**B3 评审后口径(P3-4 · 用户拍板 2026-09-06):L3 `risk_score` 一期不写(保持 NULL,归 R-05 评分模型首写),tier/tags/last_alert_id/computed_at 照 FR-7** | L3 写入 | 单测:normal→high 不降级、AML 后大额不回落 | A3 |
| B4 | `aml_service.py`(归一化+相似度匹配、scan_all)+ `engine.py`(process_trade_event 组装 + 预留客户事件钩子)+ **`scoring.py` 占位签名(FR-7 预留)** | 引擎完整 | 单测:AML 阈值边界;引擎集成冒烟 | B1、B2、B3。**B4 评审修复(2026-09-06):FR-4 payload 客户上下文(L0+近 30 天统计)补组装;core_ro 增 list_trades_range(升序)/list_active_customers;种子名单 3 条改名维持唯一演示命中;core_cash_flow 上下文一期未接(PRD「如有」),B8 前评估;真实名单数据接入时 payload/审计出口接 utils/desensitize(B9b 前核查项);复审观察项:30 天窗口口径(现 31 自然日)/申赎混合求和方向,R-05 接入前统一** |
| B5 | `app/gateway/`(trade_gateway + gateway_repository 仅 INSERT core_trade)+ `api/simulate.py` 薄路由 | 网关 | 集成:convert 400、阻断不落 trade | A4、B4 |
| B6 | **`app/api/deps.py`:`AuthContext`(actor_id/roles/customer_id,字段按 JWT 手册冻结)+ `get_auth_context()` 工厂**——dev 模式从 `X-Debug-Role`/`X-Debug-Actor` 请求头构造、`app_env != development` 启动时检测 debug 头直接拒绝;T-01 就绪后仅替换工厂内部为 JWT 解析,签名不变。另:`api/risk.py` 4 个 API(GET alerts / POST handle / POST suitability/check / POST aml/scan)+ 归属校验(含 compliance 强制 aml 过滤)。**备注:依赖层须校验 handler_result 枚举(repo 不校验);本阶段顺手统一 `NotFoundError` 异常(utils/exceptions.py 现为占位)**(评审 P2-7①②) | 鉴权依赖 + 4 个 API | Swagger 手测 + **权限矩阵(按 debug 头切换角色/身份执行 A-7/A-9 用例)** | A4、B2、**B4**(aml/scan 依赖 scan_all) |
| B7 | `main.py` 集成:路由挂载 + lifespan(双 Engine 单例注入 + Redis 单例 + trace 中间件)。**备注:顺手提取 `utils/db.py` 引擎工厂收敛 core_ro/risk_repository 双份 _default_engine**(评审 P2-6);**B3 挂账(B3 评审 P2-5):① `_run_locked` 锁原语公共化(alert_service/profile_l3 现复用私有实现)② L3 写侧 Redis 缓存 DEL 钩子(PRD §5.1 `profile:l3:{customer_id}` 更新时 DEL,`profile_l3.upsert_profile_l3` 已留痕)③ 删除 core_ro.list_trades 死代码(B4 改用 list_trades_range 后无调用方,复审 N3)④ 统一响应外壳落地(utils/response.py 现占位,simulate/risk 路由届时一并包裹,B5 评审 P2-2;错误体对齐手册 §10 error_code/message/trace_id,B6 评审 P3-2)**;**B6 挂账(B6 复审):⑤ lifespan 启动期检测 `app_env != development` + debug 头依赖直接拒绝启动(现为请求时 RuntimeError,B6 评审遗漏①)⑥ 引擎工厂须覆盖 deps/simulate/risk 三处每请求 `RiskRepository()`/`CoreReadOnlyRepository()` 实例化点并 dispose(现复用 _default_engine 不释放,B6 复审 P3)⑦ `handle_alert` 的 update_alert_status 与 insert_audit_log 两事务非原子——统一事务或补偿记录(B6 复审 P3)⑧ input_guard_log 双写缺口(手册 P-05 要求 audit_log+input_guard_log,表归 T-03 底座,B7 接 T-02 审计中间件时统一补)** | 可运行应用 | `uvicorn` 启动 + `/health` + 全路由可达 | B5、B6 |
**B7 复审(独立 AI 评审 · 2026-09-06):有条件通过 → 已闭环。** 挂账①②③④⑤⑦证实落地、⑧确认随 T-02 无烂尾、红线全守住。P1-1 `dispose_engines` 仅 cache_clear 未真 dispose——已修(`utils/db.py` 手写单例字典 + 显式 `Engine.dispose()`,新增 `tests/test_db.py` 3 例,183 绿)。挂账:P2-1 L3 DEL 钩子无行为断言随 **B8**;P2-2 未捕获异常 500 无 trace 头回写 + 422/404/405 错误码补齐归 **T-02**;P3-1 `update_alert_status` 建议标注 deprecated(防绕过同事务审计)、P3-2 locks 降级文案中性化(可随 B8 顺手);P3-3 生产误配 `app_env=development` 检查归 **B9b SOP**;P3-4 独立 request_id(现沿用 trace_id)归 **T-02**。偏差留痕:成功响应不包裹外壳 = B5 P2-2「返回体不变」演进口径,M4 复核时对齐开发计划文本。
| B8 | **`tests/conftest.py`**:a) session fixture 启动校验演示数据就位(CUST-4001 测评 <365 天、risk_aml_list ≥8),缺失则中止并提示先跑 FLOW §0 ③④;b) fixture 幂等代跑 `prepare_risk_demo.sql`;c) teardown 按 `TRD-TEST-` 清 core_trade + 关联 risk_alert/risk_suitability_log/audit_log + 还原 L3 行。**顺手集中 sqlite 测试 DDL 为单一事实源(B4 评审 P3-12,各测试文件手写 DDL 收敛;CURRENT_TIMESTAMP 改 localtime 或 fixture 固定时间,防 UTC/本地日界错位——B5 评审 P3-4)**;集成测试交易统一走 `trade_id_factory` 注入 `TRD-TEST-` 前缀(trade_gateway 已留参数,B5 评审 P3-2)。集成测试:A-1~A-5、A-7(状态机/compliance 403/GET 强制 aml)、A-9 越权、**trace 一致性断言**;补 platform 审计 input_summary 的 JSON 解析断言(含引擎输出/阻断 reasons,B5 复审 L1);**B7 复审 P2-1:补 L3 DEL 钩子行为断言(fake 记录 deletes,断言 key=`profile:l3:{customer_id}` 与降级路径);P3-2 locks 降级文案中性化顺手改** | 测试套件 + fixture | `pytest` 全绿 | B7 |
| B9a | 演示/运维脚本开发:`scripts/demo/subscribe_alerts.py`(订阅演示)+ `scripts/demo/rebuild_alerts.py`(按 trade_id 幂等重放补偿) | 2 个脚本 | 手工执行验证 | B2、B4(可与 B5~B8 并行) |
**B9a 完成(2026-09-07):** 两脚本 + repo 只读扩展(core_ro.get_trade_by_id / risk_repo.find_alerts_by_trade:payload LIKE 查已入单,含 aml/已处置单——aml 出单路径本身无去重,幂等检查是其防重放双单的唯一闸门)+ tests/test_demo_scripts.py 5 例,201 绿。真库手工验证:rebuilt(RISK-001/002 出单)→ 再跑 skipped → missing exit 1;订阅端到端收假消息 + 真交易推送(trace 贯通、notify_role 正确),验证现场已清理。本机补装 redis 包(requirements 有而环境漏装,见 FLOW §0 尾注)。
**B9a 复审(独立 AI 评审 · 2026-09-07):有条件通过 → 已闭环。** 幂等选型(payload LIKE)经穷举核实无误匹配/漏匹配面、三条出单路径全覆盖、已处置单覆盖、红线全守、风格与文档注记准确。闭环:P2-1 部分失败中间态补偿盲区——skipped 分支新增 has_engine_error_audit 检测(audit_log 无 trade_id 列,input_summary LIKE 同口径),命中输出 warning 提示人工核对 aml/审计/L3 缺口 + docstring 局限声明(聚合锚点按执行日/编排非原子);P2-2 补「同日第二笔 append 进同单后 rebuild 非首笔 trade 必 skip 且不重复 append」用例。P3 顺手落:连接自检(subscribe 的 ImportError 分支 + rebuild 双库探针)、非 dict payload 防护(raw 兜底)、get_trade_by_id 加 confirmed 过滤(对齐引擎统计口径)、docstring 勿并行声明。P3 留痕:find_alerts_by_trade 全表扫 + 跨进程并发重放无防护(演示规模可接受,生产化改 JSON_CONTAINS/events 明细表)。204 绿;warning 路径真库复验后现场清理。
**B8 复审(独立 AI 评审 · 2026-09-06):有条件通过 → 已闭环。** 开发计划 B8 行 a/b/c + DDL 单一事实源/localtime + TRD-TEST- 注入 + A-1~A-5/A-7/A-9/trace/审计 JSON 全部达成,196 绿实跑,B7 复审 P2-1/P3-2 落地。**P1-1 `conftest.py` `except pytest.SkipRequested` 引用不存在属性,演示数据缺失时 skip 路径反噬整个 pytest 收集(未灌库机器单测全跑不了)——已修(Skipped 继承 BaseException 直传,故障注入验证:错密码收集期 skip 不 error)**;P3 顺手项已落:teardown `l3_snapshot` 哨兵(防 setup 失败掩盖原始异常)、A-1 补 trace 环①响应头与环②suitability_log.trace_id。挂账:**P2-1 集成测试对演示库当日状态隐式耦合(A-4 依赖 CUST-9527 当日无历史交易、A-5 aml 单查询未按本测试 trade_id 定位、A-1 同日已有 pending 单会并入旧单)→ B9b 核查单⑥,演示与 pytest 同日交叉前必须处理**;P3-3 `_state` 模块级顺序耦合(单跑 A-7 等 KeyError,文件头已声明保序,可接受留痕);P3-4 阈值冒烟用例在 .env 覆盖阈值时会红(注释已声明前提);P3-5 teardown 时间窗隐含 MySQL 时钟==本机时钟(docstring 已声明单机约定)。
| B9b | 演示链路走查:`reset.ps1` → `prepare_risk_demo.sql` → agent 库建表 → `seed-aml-list.sql` → Swagger 逐条过 **A-1~A-5、A-7~A-9(A-6 归 M3)**。**B6 挂账核查单:① 预警类 API 响应体含固定 disclaimer「本预警由系统自动生成,最终判定需经风控专员人工审核」(PRD §6/规则表 §5,B6 复审 P3-7)② aml/scan 幂等防护(重复扫描同命中客户重复出单,演示点击即复现,B6 评审 P3-6)③ B6 时代码以 TestClient 独立挂 router 等价验证,本次补一次真 Swagger 手测(B6 复审遗漏⑤)④ analyst 台账只读权限扩展待 Wave 3 分析 Agent 接入时定权限矩阵(手册 §5.3 有 risk:alert:read,现 fail-closed 拒绝,B6 复审观察③)⑤ 生产/演示机 `app_env=development` 误配检查(B7 复审 P3-3:误配时 debug 头可达且启动校验放行,列入演示 SOP)⑥ B8 复审 P2-1:集成测试对演示库当日状态的隐式耦合前置断言(A-4 前断言 CUST-9527 当日事件计数==0、A-5 aml 单按本测试 trade_id 定位、A-1 同日 pending 单并入防护),演示与 pytest 同日交叉执行前必须处理** | 演示 SOP | 按 PRD §8 验收表逐条打勾 | B8、B9a |
**B9b 完成(2026-09-07):** 走查前置代码三件:①预警类 API(GET alerts/POST handle/POST aml/scan)响应体加固定 `disclaimer`(核查单①)②`scan_all` 幂等防护——新增 `find_recent_aml_alert`,同客户当日已有 aml 单(含交易触发/已处置)不再出单,summary 增 `skipped_existing`(核查单②)③集成测试前置断言(核查单⑥):A-1/A-3/A-4 清洁检查(交叉时 fail+重灌提示)、A-5 aml 单按本笔 trade_id 定位。208 测试绿(+4:scan 幂等×2/disclaimer/交易触发后 scan skip)。演示 SOP 落地 `docs/项目框架设计/演示SOP-风控模块.md`(含脚本化重灌 MYSQL_PWD 方式)。真服务走查(uvicorn 8123 + 真库):**A-1~A-5/A-7/A-9 + 核查单①②④⑤ 全 PASS(11/11)**,核查单⑥防护价值现场复现(走查残留数据致 A-1 定位失效 → 重灌后过,正是 P2-1 预警的场景)。核查单③=本次真服务手测(区别于 B6 TestClient 等价验证);⑤本机 APP_ENV=development 确认。走查演示数据按 SOP 保留,pytest 集成测试前需重灌。**里程碑 M2 达成(阶段 B 全链路 + A-1~A-5/A-7~A-9 验收),commit 打 tag risk-m2(M1 时点未打 tag,留痕)**。
**B9b 复审(独立 AI 评审 · 2026-09-07):有条件通过 → 已闭环。** 核查单②幂等口径(不限 status/按日/覆盖交易触发单)、①disclaimer 落点三处与文案逐字、⑥前置断言与聚合锚点口径一致(LIKE 为保守超集 fail-closed)、红线全守、SOP 与 PRD §8 逐项一致,均在审查中证实。闭环三项(全为文档/账务):P2-1 SOP 交互方式重灌补 `DROP DATABASE jinrong_agent`(reset.ps1 只重建 core + 裸 CREATE TABLE 会 1050 中止);P2-2 T-31 名下阶段 A 挂账 P2-8 遗漏——已补做收口(4 组演示组合真库手工 SQL 推导与函数判定逐项一致,登记 SOP 核查单附行);P2-3 SOP 走查范围与 11/11 口径统一(A-6 归 M3、A-8 由单测矩阵 + P2-8 对照覆盖,§4 补映射交代)。P3 落地:A-4/A-9 COUNT 断言加当日窗(跨日残留不再裸失败)、scan→handle→再 scan 幂等用例(防 status 过滤回归)、suitability/check 阻断响应补 G-08 两要素 advice/notice(PRD §6 阻断类口径)、MEMORY tests 行/FLOW 日期戳同步。**留痕(P3-2):scan_all 幂等为读后写(无锁无约束),手动单发演示场景可接受;接定时批量/多操作员时以 run_locked 包住 check+insert**。209 测试绿。
## 阶段 C · 对话线(依赖 Wave 0 的 T-01 JWT / T-03 输入防护 / T-07 LangGraph)
| # | 任务 | 产出 | 验证方式 | 依赖 |
| --- | --- | --- | --- | --- |
| C0 | 阶段 C 开工前置:requirements 增补 `pytest-asyncio`(异步 Tool 单测需要) | 依赖落地 | `pytest --version` 正常 | T-07 临近 |
| C1 | `service/risk/chat_tools.py`:alert_query / customer_context / suitability_check / aml_lookup 四个只读 Tool | Tool 集 | 单测:各 Tool 输出结构 + alert_id 溯源 | B6、T-07 |
| C2 | `agent_service.py` 注册 risk 分支 StateGraph(intent → tool → respond)+ DeepSeek + 输出规范(alert_id、仅供参考标注) | 对话闭环 | A-6:"今天有多少待审预警" | C1、T-01、T-03 |
| C3 | A-6 验收 + 边界测试(诱导处置 → 拒绝并引导 API) | 验收记录 | 手测 | C2 |
| C4 | **RISK-006 资产配置集中度**(PRD v1.1 FR-8):① `core_ro` 持仓汇总核实与补齐(T-04 `list_holdings` 已有 `market_value` 返回,评估是否需包聚合方法;limit=500 截断防护)② `rules.py` 新增 `RISK-006` 纯函数 ③ 引擎入口追加持仓规则分支 ④ 对话线 `customer_context` Tool 扩展 `concentration_ratio` | 规则 + Tool 扩展 | 单测:各风险等级客户持仓组合(C1 全 R1 / C5 全 R5 / C3 混合)命中/不命中 + limit 截断保守告警用例;集成:A-10;**现有 345 用例全量回归(断言口径预先声明:`triggered_rules` 含 RISK-006 不判失败)** | B4(引擎框架)、T-04(core_ro 持仓查询) |
| C5 | **RISK-007 处置时效升级**(PRD v1.1 FR-9):① `escalation_service.py`(扫描 + 升级决策 + payload 幂等:LEVEL_1/2 均先持久化标记再推送,仅升不降、同级别不重复推送)② 定时任务脚本 `scripts/cron/escalation_scan.py`(15min 周期,初期可手工触发演示;审计留痕显式 `new_trace()`)③ **`payload` 升级标记写入(`escalation_level`/`escalated_at`,不改 `status`——DDL 已核实 ENUM 无 `escalated`)** ④ 对话线 Tool `query_overdue_alerts` ⑤ deps 台账查询新增 `risk_manager` 全量只读分支(鉴权按 roles 数组判定;handle 白名单不变,manager 自然 403) | 时效监控服务 | 单测:各超时边界(3h59m 不升 / 4h 升 / AML 1h 升,阈值 settings 注入 + fixture `created_at` 回拨,免真实等待)+ 幂等断言(重复扫描不重复推送)+ manager 台账可读/handle 403;集成:A-11;**现有 345 用例全量回归** | B2(预警服务)、B4(引擎)、B6(状态机 API)、**seed/JWT 手册/访问矩阵补 `risk_manager` 角色(用户拍板 2026-09-07,开工前联动)** |
| C6 | **RISK-008 代理人行为链**(PRD v1.1 FR-10):① `agent_behavior_service.py`(滑动窗口聚合 `audit_log`——数据源口径:authz 事件族(AUTH_403_* code)+ trade_request 归属排除本人)② 定时任务脚本 `scripts/cron/agent_behavior_scan.py`(30min 周期,`new_trace()` 同 C5)③ 预警单落库按 `alert_type='pattern'` + `payload.alert_subtype='agent_behavior'` + `payload.actor_id` 去重 ④ 对话线 Tool `query_agent_behavior`(双条件检索 + 角色 fail-closed) | 行为链识别服务 | 单测:条件 A/B/C 各边界(2 次调仓不触发 / 3 次触发;4 次越权不触发 / 5 次触发;9/10 次归属拒绝)+ 本人交易不误报;滑窗数据用 `backdated_audit_event` fixture(`created_at` 回拨 + teardown 清理);集成:A-12;**现有 345 用例全量回归** | T-02(审计中间件留痕)、B2(预警聚合)、B4(引擎)、**演示 SOP 补代理人扮演步骤(开工前)** |
---
## 阶段 C 追加任务挂账(v1.2 新增 · C4~C6)
| # | 挂账项 | 说明 | 预计收口 |
| --- | --- | --- | --- |
| 1 | C4 的持仓汇总能力 | T-04 `list_holdings` 已返回 `market_value`(含 limit=500),是否需包聚合方法 C4 开工前核实;市值口径定案为 market_value 直接汇总 | C4 开工前核实 |
| 2 | C5/C6 定时任务基建 | 初期用独立脚本 + 系统 cron/APScheduler;若后续要内嵌到主进程,需在 `main.py` lifespan 中注册 | M4 收尾时评估 |
| 3 | C6 的 `agent_profile_l3` 存储 | `risk_alert` 无 `actor_id` 列(DDL 核实)——定案:本期代理画像仅由 `risk_alert.payload`(actor_id/actor_type)承载,不新建表;长期画像追踪 M4 后评估建表 | M4 收尾时评估 |
| 4 | `escalated` 状态 | DDL 已核实 `risk_alert.status` ENUM 五值无 `escalated`——定案:升级信息由 `payload.escalation_level`/`escalated_at` 承载,status 不动 | 二期改表(M4 评估) |
| 5 | `alert_type` ENUM 扩展 | `concentration`/`agent_behavior` 本期复用 `pattern` + `payload.alert_subtype` 承载;查询/去重按双条件 | 二期改表(M4 评估) |
| 6 | `risk_manager` 角色联动落地 | seed 追加 STAFF-31001/31002(staff_type='risk_officer' + roles=["risk_manager"],roles 为 JSON 不改表)+ JWT 手册 §5.3 + `AGENT_ACCESS_MATRIX` 增补;鉴权按 roles 判定、handle 白名单不变自然拒绝、对话线入口维持仅 risk_officer(经理走 HTTP 台账) | C5 开工前执行 |
| 7 | `customer_query` 事件类型缺失 | 系统实际事件类型仅 6 种,条件 C 过渡用 authz 事件族(AUTH_403_NOT_OWNER/NOT_ASSIGNED);客户查询路径补专用事件类型属 T-02 侧二期变更 | 二期 T-02 统一事件口径 |
| 8 | 代理人交易身份留痕 | `core_trade` 无 actor 字段,条件 A 演示期以 risk_demo 扮演 + 排除本人交易(写入演示 SOP) | 真实交易系统接入时定 |
| 9 | 预警单按 payload JSON 检索性能 | `query_agent_behavior`/超期查询走 JSON 双条件,演示规模可接受 | 生产化改生成列/明细表 |
---
## 里程碑与提交策略
| 里程碑 | 内容 | 提交 |
| --- | --- | --- |
| M1 | 阶段 A 完成(suitability 可用) | 逐任务 commit + 打 tag `risk-m1`(**已补打 2026-09-07**,指向阶段 A 完成提交 3c07de6,历史归档) |
| M2 | 阶段 B 完成(事件线全链路,验收 **A-1~A-5、A-7~A-9**) | 逐任务 commit + 打 tag `risk-m2`(**已打**,中文名:阶段B·事件线全链路) |
| M3 | 阶段 C 完成(对话线 C0~C3 + 追加任务 C4~C6,验收 **A-6 / A-10 / A-11 / A-12**;C4~C6 各自验收含全量回归) | C0~C3 部分已于 2026-09-07 打 tag `risk-m3`(中文名:阶段C前半·对话线闭环);**C4~C6 完成后补打 `risk-m4`(中文名拟:追加需求·集中度/时效/行为链;tag 名待用户拍板,避免与已有 tag 冲突)** |
| M4 | 收尾:MEMORY/TODO 状态复核、挂账收口(含追加任务挂账 #1~#9)、边界文字对齐 | `docs: 风控模块验收记录` |
**M4 完成(2026-09-07 · 复核记录):**
1. **任务映射边界确认**:T-31 = 阶段 A(交付=suitability 服务函数+单测,check API 随 B6)、T-30 = 阶段 B、T-32 = 台账/处置 API 随 B6 + 对话线归阶段 C——TODO.md 已按此口径勾选收口。
2. **B7 复审留痕对齐**:「成功响应不包裹外壳」(B5 P2-2「返回体不变」演进口径)复核后**维持**——错误响应走统一错误体(utils/response),成功体平铺;此取舍为演示口径的长期约定,登记于本节(B7 复审段的「M4 复核时对齐」到此收口)。
3. **agent_message.trace_id 口径**:实现为「当次请求 trace_id」(手册 §9「同会话同一 trace_id」按请求贯通更合理,会话维度由 session_id 关联)——登记为实现决策,手册表述修订归 Wave 1 文档清理。
4. **会话存在性枚举**:404(不存在)/ 403(他人会话)可区分属已知取舍——session_id 为 uuid4 高熵随机、仅 dev 演示开放,风险可接受(评审 P3-4 登记)。
5. **风控阶段 B 正式完结**;阶段 C(C0~C3)依赖的 Wave 0(T-01/T-02/T-06/T-07)已于同日完成并经独立 AI 评审闭环(273 测试绿),C0 的 pytest-asyncio 增补与 C1 chat_tools 开工即可。
**Commit 规范**(自《03-团队分工》内联,该文档当前在仓库外):
```text
格式: <type>: <描述>
type: feat 新功能 / fix 修复 / docs 文档 / refactor 重构 / test 测试 / chore 辅助
粒度: 每个任务至少一个 commit(A1、A2…各自独立可回溯);里程碑完成时打 tag
示例: feat: suitability 公共校验服务(SUIT-001~008)
```
- **每任务完成 = 代码 + 测试通过 + 验证方式打勾 + 即时更新 `docs/memory/TODO.md` 勾选**,方可进下一任务;M4 仅做最终复核
- 阶段 B 期间若 T-01/T-02 就绪,B7 的 lifespan 立即接审计中间件(T-02)
## 已知依赖风险
1. **T-05 未执行**:A2/B8 前需本机灌库(`reset.ps1` + 共用底座 SQL,FLOW §0 ③④,一次性本机操作)
2. **T-01 未做 → 假 AuthContext 过渡规范**(B6 落地,零返工):
- `AuthContext` Pydantic 模型(actor_id / roles / customer_id)与 `get_auth_context()` 工厂签名在 A4 冻结
- dev 模式(`app_env=development`)从 `X-Debug-Role` / `X-Debug-Actor` 请求头构造 AuthContext;**生产环境(`app_env != development`)启动时检测到 debug 依赖注册直接拒绝启动**
- T-01 就绪后仅替换工厂内部为 JWT 解析,接口签名与调用方零改动,无双份维护
- B8 权限类用例(A-7/A-9)通过切换 debug 头执行,可完整验证权限矩阵
3. **Ollama 未启动**:不影响风控模块(无 Embedding 依赖)