diff --git a/docs/PRD/PRD-风控监测Agent.md b/docs/PRD/PRD-风控监测Agent.md
index 961b80e..44a2c18 100644
--- a/docs/PRD/PRD-风控监测Agent.md
+++ b/docs/PRD/PRD-风控监测Agent.md
@@ -1,11 +1,11 @@
# PRD · 风控监测 Agent 模块
-> 版本:v1.0(**已冻结** · 2026-09-06 用户确认,风控模块开发唯一依据)
-> 日期:2026-09-06
+> 版本:v1.1(**已冻结部分 v1.0 原文不动** · 2026-09-07 追加 FR-8/FR-9/FR-10 并入,追加内容经独立 AI 评审→修订→复审通过)
+> 日期:2026-09-06(v1.0 冻结)· 2026-09-07(v1.1 追加并入)
> 负责人:E(风控/记忆/测试)· 分支 `feature/risk`
> 上游依据:`docs/需求拆解/Agent风险与合规约束汇总.md` §5、`docs/memory/REQUIREMENTS.md` Wave 2、`docs/memory/FRAMEWORK.md`、`docs/项目框架设计/表设计/`(表结构 · Redis key)、规则冻结版 `docs/PRD/附-风控规则表.md`
-> 状态流转:草稿 → AI 评审 → 修订 v0.2 → 复审 → 修订 v0.3 → **用户确认冻结 v1.0**
-> 修订记录:v0.2 修复首轮评审 P0×2、P1×9、P2×10;v0.3 修复复审 P1×3 与 P2×3;v1.0 用户确认三个新增项(risk_aml_list 表、STAFF-90001 演示账号、scripts/demo/prepare_risk_demo.sql),§13 文档联动修正已执行
+> 状态流转:草稿 → AI 评审 → 修订 v0.2 → 复审 → 修订 v0.3 → **用户确认冻结 v1.0** → 需求追加 v1.1 独立评审不通过(P0×4)→ 修订 v1.2 → 复审通过 → **并入 v1.1**
+> 修订记录:v0.2 修复首轮评审 P0×2、P1×9、P2×10;v0.3 修复复审 P1×3 与 P2×3;v1.0 用户确认三个新增项(risk_aml_list 表、STAFF-90001 演示账号、scripts/demo/prepare_risk_demo.sql),§13 文档联动修正已执行;**v1.1 追加 FR-8(RISK-006 集中度)/ FR-9(RISK-007 时效升级)/ FR-10(RISK-008 代理人行为链)与 A-10/A-11/A-12 验收,落库映射按真实 DDL 定案(alert_type 复用 pattern + payload.alert_subtype、status 不扩枚举、payload.actor_id 承载代理人)、新增 risk_manager 上级角色(用户拍板 2026-09-07,roles JSON 承载不改表)**
---
@@ -163,6 +163,156 @@
---
+## 4A. 追加需求(v1.1 并入 · FR-8 / FR-9 / FR-10)
+
+> 2026-09-07 经独立 AI 评审(不通过 P0×4)→ 修订 → 复审通过后并入。冻结部分 FR-1~FR-7 原文不动;本节与冻结条款的关系见 §4A.0。
+
+### 4A.0 与冻结条款的修订关系(不推翻,仅显式扩展)
+
+1. **升级标记的第二写入方**:FR-4 冻结"人工处置是唯一改状态入口"**维持不变**——RISK-007 升级**不修改 `status`**,仅由定时任务写 `payload` 升级标记(`escalation_level`/`escalated_at`),该标记为定时任务独占写入,人工 API 无法触碰
+2. **`risk_alert.alert_type` 不扩枚举**:新增两类预警复用冻结枚举值 `pattern`,以 `payload.alert_subtype`(`concentration` / `agent_behavior`)区分子类型(DDL 核实:ENUM 五值不含新类型,不改表)
+3. **出单维度扩展(非例外)**:FR-4"同客户同自然日仅一张"继续对**客户维度**成立;RISK-008 是**新增的代理人维度出单线**(去重键 = `payload.actor_id` + 自然日),与客户维度聚合并存、互不影响
+4. **`risk_alert.status` ENUM 扩展(`escalated`)列入二期改表挂账**,本期一律不使用该状态值
+
+### 4A.1 新增角色:risk_manager(风控专员上级 · 用户拍板 2026-09-07)
+
+- **角色定位**:`risk_officer`(风控专员)的上级管理岗,升级通知的接收方(系统原先无此角色,通知会推空;用户拍板新增并模拟生成上下级关系)
+- **承载方式(不改表)**:`core_staff.staff_type` 为冻结 ENUM(advisor/analyst/risk_officer/compliance/ops)不能加值 → 上级账号 `staff_type='risk_officer'`、`roles=["risk_manager"]`(roles 为 JSON 字段,不撞红线 7);种子新增 2 个模拟账号 `STAFF-31001`/`STAFF-31002`
+- **鉴权判定口径**:一律按 JWT `roles` 数组判定(含 `risk_manager` 即可),`staff_type` 不参与鉴权判定;台账查询(GET alerts)新增 risk_manager → 全量只读分支;处置 API(handle)角色白名单维持 roles 含 `risk_officer`,manager 账号不含该角色自然 403,零代码改动
+- **权限边界**:预警台账只读全量(同 risk_officer);RISK-008 行为链明细可见(见 FR-10);**无处置权**——FR-4"人工处置唯一角色 = risk_officer"冻结条款不变,经理只升级知悉不代为处置
+- **可见渠道**:FR-6 对话线入口维持冻结"仅 risk_officer"不变;`risk_manager` 查看预警台账与行为链明细统一走 HTTP 台账(GET alerts 全量只读分支),不扩展对话线角色
+- **联动清单(C5 开工前执行)**:`scripts/core/02-seed-base.sql` 追加账号;JWT 手册 §5.3 角色清单增补;`AGENT_ACCESS_MATRIX` 增补行
+- **通知升级链总览**:预警 → `risk_officer`(专员);超时 LEVEL_1 → +`risk_manager`(专员上级);LEVEL_2 → +`compliance`(合规官知悉)
+
+### FR-8 客户资产配置集中度预警(RISK-006)
+
+> 目标:弥补"单笔合规、整体失衡"盲区。现有 SUIT/RISK-001~005 均聚焦单笔交易,不感知客户持仓结构。
+
+**触发时机**:交易网关落库后,规则引擎处理完 RISK-001~005 后**同步追加**本规则;对话线 Tool 实时查询为**只读展示,不触发 RISK-006 出单**。
+
+**规则定义**:
+
+| 字段 | 定义 |
+|---|---|
+| 规则 ID | RISK-006 |
+| 名称 | 高风险资产配置集中度预警 |
+| 触发条件 | 客户持仓中 R4+R5 产品市值合计 ÷ 总持仓市值 ≥ `RISK_CONCENTRATION_THRESHOLD`(默认 80%,`.env` 可配) |
+| 计算口径 | **市值直接取 `core_holding.market_value` 现成列汇总**(与 T-04 `core_ro.list_holdings` 既有返回一致;不按"份额×最新净值"重算,避免 N+1 调 `get_latest_nav` 威胁引擎 <100ms 约束);总持仓 = 全产品 `market_value` 求和;空仓或总持仓为 0 时不触发 |
+| 优先级 | P1 |
+| 出单类型 | **落库复用 `alert_type='pattern'` + `payload.alert_subtype='concentration'`**(不改表);聚合口径归入事件类,与 large_amount/freq_trade/pattern 同口径 |
+| 评分映射 | `risk_score = 60`(静态映射,R-05 接入后替换) |
+
+**系统动作**:
+1. 引擎调用 `core_ro.list_holdings(customer_id)` 汇总持仓:按产品风险等级(`core_product.min_risk_code`)分组求和 R4+R5 市值与总市值。**截断防护**:`list_holdings` 默认 limit=500,命中上限即视同达到阈值(保守告警)并在 `input_summary` 标注 `holdings_truncated=true`
+2. 命中 → `alert_service.agg_upsert` 并入当日事件类 pending 单:`payload.alert_subtype` 追加 `"concentration"`、`triggered_rules` 追加 `"RISK-006"`、`risk_score` 取 max(60 不覆盖 RISK-001/002 的 70,与 FR-4 聚合语义一致)
+3. `profile_l3.upsert`:`monitor_tier = max(existing, watch)`,`monitor_tags` 追加 `"high_risk_concentration"`
+4. `audit_log` INSERT(`event_type='risk_concentration'`,`input_summary` 含脱敏后的 R4+R5 占比与总持仓;金额按 DESENS-005 口径截断展示,`customer_id` 为内部键不脱敏)
+5. Redis `PUBLISH risk:pub:alert`,`notify_role=["risk_officer"]`
+
+**API 变更**:对话线 Tool `customer_context` 扩展 `concentration_ratio`(R4+R5 占比,脱敏后百分比);无新增 HTTP API。
+
+**验收标准(A-10)**:
+- C5 客户持仓 90% 在 R5 产品 → 触发 RISK-006,当日事件类预警单 `triggered_rules` 含 `"RISK-006"` 且 `payload.alert_subtype` 含 `"concentration"`
+- C2 客户持仓 100% 在 R1 → 不触发
+- 交易落库后 5 秒内完成持仓重算并出单(集成测试以 `TRD-TEST-` 交易断言)
+- 对话线查询"哪些客户资产配置过于集中"返回正确列表及占比
+- **回归口径**:现有用例全量回归;既有断言按"`triggered_rules` 含新增规则 ID 不判失败"口径预先声明
+
+### FR-9 预警单处置时效监控与自动升级(RISK-007)
+
+> 目标:防止风险事件因人工响应延迟而积压扩大。现有 FR-4 只有生成与处置,无时效监控。
+
+**触发时机**:定时扫描(非交易事件驱动),每 15 分钟执行一次(`RISK_ESCALATION_SCAN_MINUTES` 可配)。
+
+**规则定义**:
+
+| 字段 | 定义 |
+|---|---|
+| 规则 ID | RISK-007 |
+| 名称 | 预警单处置时效监控与升级 |
+| 触发条件 | `status='pending_review'` 且距 `created_at` 超过配置时效;**每张单独立计时、独立升级**;降噪:同一客户多张单在同一扫描周期命中同级别升级时,合并为一次推送 |
+| 升级策略(阈值全部 settings 注入) | LEVEL_1:≥ `RISK_ESCALATION_L1_HOURS`(默认 4h)未处置 → Redis 升级推送,`notify_role` 追加 `risk_manager`(见 4A.1)
LEVEL_2:≥ `RISK_ESCALATION_L2_HOURS`(默认 24h)未处置 → `payload.escalation_level=2` + `payload.escalated_at`,`notify_role` 追加 `compliance`,对话线查询置顶
AML 特殊通道:`RISK_ESCALATION_AML_L1_HOURS`(默认 1h)/ `RISK_ESCALATION_AML_L2_HOURS`(默认 4h) |
+| 优先级 | P0(运营红线) |
+| 出单类型 | 不新建预警单、**不改 `status`**(冻结 ENUM 无 `escalated`,升级信息全部由 `payload` 承载) |
+| 评分映射 | 不修改 `risk_score` |
+
+**系统动作**:
+1. 定时任务 `scripts/cron/escalation_scan.py`(独立脚本优先)查询 `risk_alert`:`status='pending_review'`
+2. 按规则计算超时时长,命中升级条件:
+ - LEVEL_1:先 `UPDATE payload` 持久化 `escalation_level=1` + `escalated_at`(幂等依据,与 LEVEL_2 同机制),再 Redis `PUBLISH`,`notify_role` 追加 `risk_manager`
+ - LEVEL_2:`UPDATE payload` 写入 `escalation_level=2` + `escalated_at`,`PUBLISH`,`notify_role` 追加 `compliance`
+ - **幂等约束**:仅当"本次计算级别 > `payload.escalation_level` 已记录级别"才执行升级动作;同级别不重复推送(每级别至多推送一次)
+3. 每次升级动作写 `audit_log`(`event_type='alert_escalation'`,`input_summary` 含 `alert_id`、已达级别、升级原因、超时时长);定时任务无 HTTP 上下文,**显式 `new_trace()` 生成 trace_id** 并与预警单 payload、推送消息贯通(§7.5 可还原性)
+4. 状态机约束:`status` 全程保持 `pending_review`,人工处置照常(`pending_review → 三态`,冻结不变),处置后单据退出扫描范围;`payload` 升级标记**仅定时任务可写**,人工处置 API 不感知、不覆盖(`handler_result` 枚举外值由 Pydantic 返回 422)
+5. 定时任务对升级决策的全量输入输出写 `audit_log`,审计写库失败降级不阻塞但本地日志 error 留底(红线 5 口径)
+
+**API 变更**:新增对话线 Tool `query_overdue_alerts(hours?)`(返回超期预警列表含 `alert_id`、`created_at`、`escalation_level`(读自 payload,缺省 0)、`overdue_hours`);无新增 HTTP API。
+
+**验收标准(A-11)**:
+- **免等待测试方案**:升级阈值走 settings 注入(测试用小时以下阈值);fixture 支持直接 INSERT `created_at` 回拨的 pending 单(conftest 扩展)
+- pending 单超 L1 阈值 → 收到升级推送,`notify_role` 含 `risk_manager`;**同一单重复扫描不重复推送**(幂等断言)
+- AML 预警单超 AML L1 阈值 → 触发 LEVEL_1(与普通单阈值不同,两条断言分开)
+- 风控专员在阈值内处置 → 不触发任何升级;处置后超期单不再进入扫描结果
+- 状态机:全程 `status='pending_review'`;`handler_result` 枚举外值 → 422;`payload.escalation_level` 与人工处置字段互不覆盖
+- 对话线"今天有哪些超期预警?"返回正确列表及超时时长、升级级别
+
+### FR-10 代理人异常行为链识别(RISK-008)
+
+> 目标:单点拦截(如 T-04 的 `AUTH_403_SCOPE`)只能阻断单次请求,无法识别代理人本身的操作风险模式。
+
+**触发时机**:定时扫描(每 30 分钟,`RISK_AGENT_BEHAVIOR_SCAN_MINUTES` 可配),数据源为 T-02 审计中间件留痕的 `audit_log`(**input_guard_log 不作为数据源**:网关路径不写该表且无 code 字段)。
+
+**规则定义**:
+
+| 字段 | 定义 |
+|---|---|
+| 规则 ID | RISK-008 |
+| 名称 | 代理人异常行为链识别 |
+| 触发条件(阈值/窗口全部 settings 注入可配) | **条件 A(频繁诱导调仓)**:`RISK_AGENT_BEHAVIOR_A_WINDOW_HOURS`(默认 24h)内,同一代理人对同一客户发生 ≥ `RISK_AGENT_BEHAVIOR_A_COUNT`(默认 3)次产品切换操作(赎回某产品后 2 小时内申购另一产品)。**归属口径**:以 `audit_log`(`event_type='trade_request'`,网关审计)中的发起人 actor 归属;**排除发起人 == 客户本人的交易**(防客户正常调仓误报)。系统 `core_trade` 无 actor 字段、无代理人交易路径(FR-1 鉴权仅 risk_demo/客户本人),演示期由 risk_demo 账号扮演代理人发起(写入演示 SOP);真实代理人交易路径接入时以 Core 提供 actor 字段为准(挂账)
**条件 B(试探越权)**:`RISK_AGENT_BEHAVIOR_B_WINDOW_HOURS`(默认 72h)内,同一代理人累计 ≥ `RISK_AGENT_BEHAVIOR_B_COUNT`(默认 5)次 `AUTH_403_SCOPE` 越权查询。**唯一数据口径**:`audit_log WHERE event_type='authz' AND input_summary 中 code='AUTH_403_SCOPE'`(拒绝标记为 `decision='forbidden'`);对话线 Tool blocked 事件经 `record_authz_denial` 双写后同口径可查
**条件 C(高频查询非名下客户)**:`RISK_AGENT_BEHAVIOR_C_WINDOW_HOURS`(默认 24h)内,同一代理人查询非名下客户信息被拒 ≥ `RISK_AGENT_BEHAVIOR_C_COUNT`(默认 10)次。**数据源**:`event_type='customer_query'` 在系统中无生产方,条件 C 定义为**"非名下客户查询被拒"事件族**:`audit_log WHERE event_type='authz' AND code IN ('AUTH_403_NOT_OWNER','AUTH_403_NOT_ASSIGNED')`(API 线归属校验拒绝)∪ 对话线 `agent_tool_call` blocked 中归属类拒绝码(经 `record_authz_denial` 双写);`customer_query` 事件类型补建列为二期 T-02 侧变更(挂账) |
+| 优先级 | P1 |
+| 出单类型 | `agent_behavior` 语义预警单:**落库 `alert_type='pattern'` + `payload.alert_subtype='agent_behavior'`**;**独立出单不按客户聚合**(出单维度**扩展**,见 4A.0 第 3 条):去重键 = `payload.actor_id` + 自然日,同代理人同日仅一张 pending 单 |
+| 评分映射 | `risk_score = 75`(静态映射,R-05 评分模型接入后替换) |
+
+**系统动作**:
+1. 定时任务扫描 `audit_log` 滑动窗口(24h/72h,按上表口径过滤)
+2. 命中任一子条件,生成预警单,**落库映射(对照真实 DDL 定案)**:
+ - `alert_type='pattern'`,`payload.alert_subtype='agent_behavior'`
+ - `customer_id`(NOT NULL 冻结约束)= 行为链中**主要涉及客户**(按证据条数取众数);payload 显式声明该字段语义为"涉及客户"而非"归属客户"
+ - 代理人标识:`payload.actor_id` + `payload.actor_type='agent'`(`risk_alert` 无 `actor_id` 列)
+ - `payload` 含行为链明细:时间线、涉及客户 ID(内部键,不脱敏)、命中子条件(A/B/C)、证据 trace_id 列表
+ - `risk_score = 75`
+3. **代理画像存储**:本期最小实现仅在 `risk_alert.payload` 承载(不新建 `agent_profile_l3` 表、不扩展 `customer_profile_l3` 语义);长期画像追踪 M4 后评估
+4. Redis `PUBLISH`,`notify_role=["risk_officer","risk_manager"]`
+5. **行为链明细展示范围**:仅 `risk_officer` / `risk_manager` 角色可见(其他角色 fail-closed;对话线入口维持仅 risk_officer,经理走 HTTP 台账);对话输出对客户身份信息二次脱敏
+6. 审计:每条命中行为链写 `audit_log`(`event_type='agent_behavior_detected'`),显式 `new_trace()`(同 FR-9 口径)
+
+**API 变更**:对话线 Tool 扩展 `query_agent_behavior(agent_id?)`(检索 `alert_type='pattern' AND payload.alert_subtype='agent_behavior'` 双条件;`agent_id` 匹配 `payload.actor_id`;JSON 检索演示规模可接受,性能挂账);无新增 HTTP API。
+
+**验收标准(A-12)**:
+- 代理人在 24h 内对同一客户执行 3 次"赎回→申购" → 触发条件 A,生成代理人维度预警单(`payload.actor_id` 正确、`customer_id` = 涉及客户)
+- **客户本人交易不误报**:`actor_id == customer_id` 的正常调仓不计数
+- 代理人在 72h 内累计 5 次 `AUTH_403_SCOPE` → 触发条件 B
+- 24h 内 10 次 `AUTH_403_NOT_OWNER`/`AUTH_403_NOT_ASSIGNED` 被拒 → 触发条件 C(数据经 `backdated_audit_event` fixture 回拨构造)
+- 风控专员对话查询"代理人 STAFF-20001 最近有没有异常行为" → 返回正确预警及证据链
+- 同代理人同日多次命中不同子条件 → 只一张 pending 单,`triggered_rules`/`payload` 追加子条件标识
+- 预警单 `payload.actor_id` 正确指向代理人而非客户;非授权角色查行为链明细 → fail-closed
+- **测试基建**:conftest 扩展 `backdated_audit_event` fixture(显式 `created_at` 回拨写入 `audit_log`,teardown 按 `TRD-TEST-` 标记清理);窗口边界用例(A:2/3;B:4/5;C:9/10);现有用例全量回归
+
+**新增配置项(C4~C6 开工时同步 `.env.example` + `settings.py`)**:
+
+| 配置项 | 默认值 | 用途 |
+|---|---|---|
+| `RISK_CONCENTRATION_THRESHOLD` | 0.80 | RISK-006 集中度阈值 |
+| `RISK_ESCALATION_SCAN_MINUTES` | 15 | RISK-007 扫描周期 |
+| `RISK_ESCALATION_L1_HOURS` / `L2_HOURS` | 4 / 24 | 普通单升级阈值 |
+| `RISK_ESCALATION_AML_L1_HOURS` / `AML_L2_HOURS` | 1 / 4 | AML 单升级阈值 |
+| `RISK_AGENT_BEHAVIOR_SCAN_MINUTES` | 30 | RISK-008 扫描周期 |
+| `RISK_AGENT_BEHAVIOR_A_WINDOW_HOURS` / `A_COUNT` | 24 / 3 | 条件 A 窗口/次数 |
+| `RISK_AGENT_BEHAVIOR_B_WINDOW_HOURS` / `B_COUNT` | 72 / 5 | 条件 B 窗口/次数 |
+| `RISK_AGENT_BEHAVIOR_C_WINDOW_HOURS` / `C_COUNT` | 24 / 10 | 条件 C 窗口/次数 |
+
+---
+
## 5. 数据设计
### 5.1 读写权限总表
@@ -248,6 +398,9 @@ CREATE TABLE risk_aml_list (
| A-7 | 人工处置 | 状态 `pending_review→confirmed_suspicious`;compliance 调 `/handle` → 403(其调 GET 仅返回 aml 单);audit 留痕 |
| A-8 | `suitability_check` 单元级验收 | 脚本/单测直接调用,断言 C1+R4 → `is_matched=false, blocked=true` 且返回结构完整;"不匹配拒绝生成推荐语"的端到端验收归客户/代理人 Agent 各自 PRD(见 §9 依赖) |
| A-9 | 越权 | customer 查他人 suitability、advisor 查非名下客户 → 403 + audit |
+| A-10 | 集中度预警(FR-8 · v1.1) | C5 客户持仓 90% R5 → 事件类预警单含 RISK-006 + `payload.alert_subtype='concentration'`;C2 全 R1 不触发;落库后 5s 内出单 |
+| A-11 | 时效升级(FR-9 · v1.1) | 超阈值 → LEVEL_1 推送含 risk_manager(幂等:重复扫描不重复推);AML 短通道 1h;阈值内处置不升级;全程 status=pending_review;枚举外 handler_result → 422(免等待:阈值 settings 注入 + created_at 回拨 fixture) |
+| A-12 | 代理人行为链(FR-10 · v1.1) | 条件 A(3 次赎回→申购,本人交易排除)/ B(5 次 AUTH_403_SCOPE)/ C(10 次归属拒绝)各自触发;代理人维度独立出单(payload.actor_id);同日多子条件只一张单;行为链明细仅 risk_officer/risk_manager 可见 |
**通用验收**:所有预警单初始 `pending_review`;审计链路任取一条 `trace_id` 可还原全流程;越权 403。
@@ -331,7 +484,13 @@ WHERE customer_id IN ('CUST-1001','CUST-1002','CUST-1003','CUST-3001','CUST-4001
| `docs/项目框架设计/表设计/02-mysql-agent专用.sql` | 追加 `risk_aml_list`(经用户确认) |
| `scripts/core/02-seed-base.sql` | 追加演示账号 `STAFF-90001`(staff_type='risk_officer',roles=["risk_officer","risk_demo"]) |
| `docs/memory/REQUIREMENTS.md` / `TODO.md` / `FLOW.md` | Wave 2 增加 T-30~T-32 细化任务与 PRD 链接;bootstrap 补演示数据准备步骤(`scripts/demo/prepare_risk_demo.sql`) |
+| (v1.1 追加)`scripts/core/02-seed-base.sql` | 追加 `risk_manager` 上级角色账号 STAFF-31001/31002(staff_type='risk_officer' + roles=["risk_manager"])——C5 开工前 |
+| (v1.1 追加)JWT 手册 §5.3 + `AGENT_ACCESS_MATRIX` | 增补 `risk_manager` 角色行(台账只读全量、无处置权、对话线不放行)——C5 开工前 |
+| (v1.1 追加)`.env.example` + `settings.py` | §4A FR-8/9/10 配置项清单全量登记(集中度阈值/升级双通道/行为链窗口)——C4~C6 开工时 |
+| (v1.1 追加)`02-redis-keys.md` | `risk:pub:alert` payload 增补 `escalation_level`/`alert_subtype` 字段说明 |
+| (v1.1 追加)演示 SOP | 补 risk_demo 扮演代理人发起"赎回→申购"演示步骤——C6 开工前 |
+| (v1.1 追加)`docs/memory/MEMORY.md` / `TODO.md` / `FRAMEWORK.md` | 登记追加需求 FR-8/9/10 与 C4~C6 任务、挂账 #1~#9 |
---
-*复审通过并经用户确认后冻结为 v1.0,作为风控模块开发唯一依据。*
+*复审通过并经用户确认后冻结为 v1.0(FR-1~FR-7,2026-09-06);v1.1 追加 FR-8/FR-9/FR-10(2026-09-07 经独立 AI 评审→修订→复审通过后并入)。本 PRD 为风控模块开发唯一依据。*
diff --git a/docs/PRD/附-风控规则表.md b/docs/PRD/附-风控规则表.md
index ca4c84c..b74eece 100644
--- a/docs/PRD/附-风控规则表.md
+++ b/docs/PRD/附-风控规则表.md
@@ -1,9 +1,9 @@
# 附表 · 风控规则表(冻结版)
-> 版本:v1.0(**已冻结** · 随 PRD v1.0 一并冻结,变更需 PR 审批)
+> 版本:v1.1(**冻结部分 v1.0 原文不动** · 2026-09-07 追加 RISK-006/007/008 并入;变更已经独立 AI 评审→修订→复审通过,履行 PR 审批流程)
> 定位:团队共识文档。任何 Agent 涉及以下规则时,必须在此表查找,不得自行发明。
> 编号体系:`SUIT-*` 适当性 / `RISK-*` 事件规则引擎 / `AML-*` 名单 / `AI-*` AI 行为边界 / `TRADE-*` 交易时间 / `DESENS-*` 脱敏 / `MOCK-*` 模拟数据
-> 与 PRD 的关系:本表是 PRD《风控监测Agent》FR-2/FR-3/FR-5/§7 引用的规则唯一权威来源。
+> 与 PRD 的关系:本表是 PRD《风控监测Agent》FR-2/FR-3/FR-5/§7/§4A 引用的规则唯一权威来源。
> 变更:冻结后变更需 PR 审批并更新版本号。
---
@@ -45,6 +45,27 @@ C5 ✓ ✓ ✓ ✓ ✓
| RISK-003 | 频繁交易预警 | 同一客户同一产品当日申赎合计 ≥ 3 笔 | 同上(当日同客户同类型聚合出单) | 同上 |
| RISK-004 | 接近阈值试探预警 | 5 分钟内 ≥ 3 笔且每笔 ≥ 400,000 元 | 预警单(pattern) | 同上 |
| RISK-005 | 先小后大模式预警 | 当日首次大额前已存在 ≥ 3 笔 ≤ 10,000 元(不要求连续) | 预警单(pattern),文案标注模式 | 同上 |
+| RISK-006 | 高风险资产配置集中度预警(v1.1) | 客户持仓中 R4+R5 市值占比 ≥ `RISK_CONCENTRATION_THRESHOLD`(默认 80%;市值口径 = `core_holding.market_value` 直接汇总) | 预警单(落库 `alert_type='pattern'` + `payload.alert_subtype='concentration'`,并入事件类聚合)+ L3 watch + Redis 推送 | `app/service/risk/rules.py` |
+| RISK-007 | 预警单处置时效监控与自动升级(v1.1) | pending_review 超 `RISK_ESCALATION_L1_HOURS`/`L2_HOURS`(默认 4h/24h,AML 缩短为 1h/4h,settings 注入) | 升级推送(notify_role 追加 risk_manager/compliance)+ `payload` 写升级标记 + audit 留痕;**不改 `status`** | `app/service/risk/escalation_service.py` + `scripts/cron/escalation_scan.py` |
+| RISK-008 | 代理人异常行为链识别(v1.1) | 24h 内同一代理人诱导调仓 ≥3 次;或 72h 内越权试探(`AUTH_403_SCOPE`)≥5 次;或 24h 内非名下客户查询被拒 ≥10 次(阈值/窗口 settings 注入) | 代理人维度独立预警单(落库 `alert_type='pattern'` + `payload.alert_subtype='agent_behavior'`)+ Redis 推送(risk_officer/risk_manager) | `app/service/risk/agent_behavior_service.py` + `scripts/cron/agent_behavior_scan.py` |
+
+### ⚠️ RISK-007 状态机与升级补充约束(v1.1 追加,对照真实 DDL)
+
+- **`risk_alert.status` ENUM 冻结五值不含 `escalated`(DDL 已核实)→ 本期不使用任何新状态值**
+- 升级信息全部由 `payload` 承载:`escalation_level`(1/2)+ `escalated_at`;**LEVEL_1 与 LEVEL_2 同机制,均先持久化 payload 标记再推送**(LEVEL_1 持久化是幂等依据);该标记**仅定时任务可写**,人工处置 API 不感知不覆盖
+- 升级幂等:仅当"本次计算级别 > payload 已记录级别"才执行动作,同级别不重复推送
+- 每张单独立计时独立升级;同客户多张单同周期同级升级合并推送降噪
+- 人工处置照常走 `pending_review → 三态`(FR-4 冻结不变);`handler_result` 枚举外值 → 422
+- 定时任务审计留痕显式 `new_trace()` 生成 trace_id 并贯通 payload 与推送消息
+- `escalated` 状态列入二期改表挂账
+
+### ⚠️ RISK-008 出单维度与数据源补充约束(v1.1 追加)
+
+- 不按客户维度聚合,按 `payload.actor_id`(代理人)+ 自然日去重(`risk_alert` 无 `actor_id` 列,代理人标识由 payload 承载;`customer_id` 冻结 NOT NULL → 填行为链主要涉及客户,payload 声明其语义为"涉及客户")
+- 同代理人同日命中多个子条件(A/B/C)→ 只一张 pending 单,`triggered_rules` 追加子条件标识
+- `risk_score = 75`(静态映射,R-05 评分模型接入后替换)
+- **数据源统一为 `audit_log`**:条件 B = `event_type='authz' AND code='AUTH_403_SCOPE'`;条件 C = `event_type='authz' AND code IN ('AUTH_403_NOT_OWNER','AUTH_403_NOT_ASSIGNED')` ∪ 对话线 blocked 归属类拒绝(`record_authz_denial` 双写);条件 A = `event_type='trade_request'` 按发起人 actor 归属且**排除本人交易**(演示期 risk_demo 扮演代理人,见演示 SOP);`input_guard_log` 不作为数据源(网关路径不写且无 code 字段)
+- **行为链明细展示范围**:仅 `risk_officer` / `risk_manager`(上级角色,staff_type='risk_officer' + roles=["risk_manager"],不改表);其他角色 fail-closed;对话线入口维持"仅 risk_officer",经理统一走 HTTP 台账只读
### ⚠️ 风控 Agent 行为边界(硬约束)
@@ -141,4 +162,4 @@ C5 ✓ ✓ ✓ ✓ ✓
---
-*本表冻结后,任何规则变更需经团队评审并更新版本号。原本地《02-业务规则表》停止维护,以本表为准。*
+*本表 v1.0 冻结(2026-09-06),v1.1 追加 RISK-006/007/008(2026-09-07 经独立评审→修订→复审通过后并入);任何规则变更需经团队评审并更新版本号。原本地《02-业务规则表》停止维护,以本表为准。*
diff --git a/docs/项目框架设计/开发计划-风控模块.md b/docs/项目框架设计/开发计划-风控模块.md
index d962cb6..b8bcf03 100644
--- a/docs/项目框架设计/开发计划-风控模块.md
+++ b/docs/项目框架设计/开发计划-风控模块.md
@@ -1,9 +1,10 @@
# 开发计划 · 风控监测 Agent 模块
-> 版本:v1.1(AI 评审通过:修复 P1×2——假 AuthContext 规范落 deps.py、conftest 测试前置;P2×7 顺手收敛)· 2026-09-06 · 分支 `feature/risk` · 负责人 E
-> 依据:PRD v1.0 + 架构设计 v1.1
+> 版本:v1.2(2026-09-07 追加 C4~C6 任务并入 · v1.1 为 AI 评审通过版:修复 P1×2——假 AuthContext 规范落 deps.py、conftest 测试前置;P2×7 顺手收敛)· 分支 `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 完成
+> 任务映射: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 复审通过后并入
---
@@ -51,6 +52,25 @@
| 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 双条件,演示规模可接受 | 生产化改生成列/明细表 |
---
@@ -60,8 +80,8 @@
| --- | --- | --- |
| M1 | 阶段 A 完成(suitability 可用) | 逐任务 commit + 打 tag `risk-m1` |
| M2 | 阶段 B 完成(事件线全链路,验收 **A-1~A-5、A-7~A-9**) | 逐任务 commit + 打 tag `risk-m2` |
-| M3 | 阶段 C 完成(对话线验收 A-6) | 逐任务 commit + 打 tag `risk-m3` |
-| M4 | 收尾:MEMORY/TODO 状态复核与边界文字对齐 | `docs: 风控模块验收记录` |
+| M3 | 阶段 C 完成(对话线 C0~C3 + 追加任务 C4~C6,验收 **A-6 / A-10 / A-11 / A-12**;C4~C6 各自验收含全量回归) | 逐任务 commit + 打 tag `risk-m3` |
+| 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 已按此口径勾选收口。