Files

130 lines
10 KiB
Markdown
Raw Permalink Normal View History

# 评审待办 · 风控主架构 + 基金转换架构
> ⚠️ **历史参考文档(2026-09-10 产物)**:文中「基金转换架构 v1.0 评审」对应的是 **v0.x 模型**,已被 **T+1 受理/确认分离模型(架构 v2.1 / PRD v1.1 / 开发计划 v2.0)取代**;当前评审结论见各文档「独立审查」节,**当前以 v1.1/v2.1/v2.0 为准**。
>
> **用途**:两份架构评审意见**合并处理**的执行清单。
> **用户指示(2026-09-10)**:「等基金架构的评审也出来了一起改」。
> **当前状态**:**基金转换架构 v1.0 评审 13 条已全部落地**(落点索引见架构 §13.2,0 条悬空,2026-09-10);
> **P0-1 账号方案已拍板**(§三 → 采纳独立账号方案,落 D20 / T-0b);其余风控主架构 **P0/P1 待处置**(见 §一)。
> **关联文档**:`架构设计-基金转换交易.md`(v1.0)· `架构设计-风控模块.md` · `基金转换-审查意见处置表.md`
---
## 一、风控主架构评审(2026-09-10 收到 · 已抽查核实)
### P0 · 安全与正确性底线
| # | 风险点 | 核实结论 | 建议修复 |
| --- | --- | --- | --- |
| 1 | Core 只读靠约定、**无 DB 级强制** | **✅ 属实**(`settings.py:17` 单 root 账号;`db.py:23` 仅按库名缓存 → `core_ro:55` 与 `gateway_repository:24` 共用同一连接池) | **✅ 已拍板并落地设计**:**D20 双账号分离**(见 §三 + 架构 §11.1)—— `xh_core_ro`(SELECT) / `xh_core_rw`(4 表写) / `xh_agent_rw`(audit 仅 INSERT);`get_engine(db, role)`;DDL 走管理员。**T-0b 阻断前置** |
| 2 | `agent_message(seq_no)` 并发重号 | **✅ 属实**:`表设计/01-mysql-共用底座.sql:47` 是普通 `KEY idx_session_seq`,非 UNIQUE | 加 `UNIQUE(session_id, seq_no)` + `insert_turn` 捕获 `IntegrityError` 重试 |
| 3 | dev debug 通道双闸门 | **⚠️ 已实现,且外审行号引错**:双闸门实际在 `app/api/deps.py:258`(非 237-238) | 属**加固**:非 development 或配了公钥时加显式启动断言 |
| 4 | `scoring.py` 仅预留桩,L3 `risk_score` 恒 NULL | **✅ 属实,但是设计意图**:`scoring.py:18` 抛 `NotImplementedError`,docstring 明写「FR-7 本期静态映射、评分模型后置」,签名已冻结 | 列入下一里程碑实现(PRD R-05) |
| 5 | 跨库最终一致无自动补偿 | 未实证(按架构判断基本属实) | `rebuild_alerts.py` 纳入 cron + 监控告警 |
### P1 · 可维护性债务
| # | 问题 | 核实结论 | 建议 |
| --- | --- | --- | --- |
| 1 | C5 红线权限逻辑分散三处(`deps.py` 矩阵 / `chat.py:122` / `tool_service.py:138`) | 未核 | 抽 `PermissionPolicy` 类集中,或至少加文档交叉引用清单 |
| 2 | 意图识别仅关键词 | 属实(架构已声明为有意取舍) | 先做同义词扩展表(低成本),再评估 LLM 路由(会损失可复现性) |
| 3 | `entities.py` 31 个 ORM 类闲置 | **✅ 属实**(实测 `^class ` 计数 = 31),查询走 text SQL + dict | 渐进引入 TypedDict,先从高频 Tool 入手 |
| 4 | 注入词表硬编码 45 条 | 未核 | 配置化(表或 `.env` 多行)+ 热加载 |
| 5 | 日志基建缺失 | **✅ 属实**:`app/utils/logger.py` 全文仅一行 `"""日志模块。"""` | 接入 structlog + `logging.config.dictConfig`,统一 JSON 落盘 |
### 无需反悔(外审已确认的既有架构决策)
四 Agent 差异表达(一张图 + 数据驱动)· Tool 与 LLM 流式(Tool 同步跑完再流式)· 意图识别(关键词,可复现可单测)· 适当性矩阵(查表数据驱动)· 降级策略(限流 fail-open、Embedding fail-closed)· 阻断逻辑(网关专用分支,物理隔离防误扩权,**仅 R-02 可阻断**)。
---
## 二、基金转换架构 v1.0 独立评审(2026-09-10 已出 · 已逐条判定 · **已全部落地**)
**评审结论:✅ 通过(M-7 满足),唯一硬前置 = R1。**
**判定结果:接受 10 · 修正性接受 3 · 驳回 0。**
**落地状态:✅ 13 条全部闭合**(R2~R5 / S1~S5 / Q4·Q6·Q10 落架构正文各节 + 架构 §13.2 索引表;
R1 升级为 T-0 阻断前置;S2 另回填 PRD v0.9 枚举;R2 回填 PRD v0.9 响应字段)。
### 硬风险(R1~R5)
| # | 意见 | 判定 | 处置与证据 |
| --- | --- | --- | --- |
| **R1** | sqlite/MySQL `core_holding` 列名不同名 | **接受(升级为 T-0 阻断前置)** | **已核实属实**:`tests/_ddl.py:50-54` 只有 `market_value`/`quantity`,且**无 PK/唯一约束**(额外发现)。处置:**T-0 先于 T-1** —— 以 MySQL 为准统一 + `conftest.py` 启动期列名断言 |
| R2 | T+1 自然日近似 vs 真实工作日 | **接受** | 响应与审计加 `confirm_basis: "natural_day_approx"`;真实 Core 接入后补交易日历 |
| R3 | 阶段 1.5 异常不阻断、不重试 | **接受(一期备注)** | 一期人工 SLA 24h;评审建议的「补偿队列 / Redis 事件 + cleanup 顺带巡检补跑」记**二期** |
| R4 | `convert_batch_max_lots=200` 超限 400 | **接受** | 保留 400 `TOO_MANY_LOTS` + 注释「自动分拆多笔留二期」 |
| R5 | `to_fund_ratio=1.0` 不参与计算 | **接受** | 已在 §0.4 / D16 注明「本期不参与计算」;二期启用时同步改 `fee.py` |
### 设计建议(S1~S5)
| # | 建议 | 判定 | 处置与证据 |
| --- | --- | --- | --- |
| S1 | `lot_id` 改 BIGINT + UNIQUE | **修正性接受** | **理由部分不成立**:FIFO 排序依据是 `confirmed_at`(架构 §7 + PRD §4.1 已建 `KEY(customer_id,product_id,confirmed_at)`),**不是 `lot_id`**;且 `lot_id` 已是 PK(唯一)。处置:`ORDER BY confirmed_at, lot_id` 作确定性 tiebreaker,**主键类型不动**(真实 TA 注册登记批次号是字符串) |
| S2 | cleanup 改 `status='expired'`,不硬删 | **接受** | 建表即含 `'expired'`(新表零 ALTER);同步改 PRD §4.1 ENUM |
| S3 | `PlanResult` 加 dataclass/TypedDict | **接受** | 防 dict 键拼写错误;呼应主架构 `entities.py` 闲置问题 |
| S4 | `client_request_id` 加正则 | **接受** | **有现成参照**:`app/main.py:44` `_TRACE_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,64}$")`,直接复用 |
| S5 | nav stale 按产品类型区分 | **修正性接受** | 模拟库 14 产品均日频、无频次差异;真实 Core 接入后再按产品类型分档 |
### 自检十问追问(Q4/Q6/Q10)
| # | 追问 | 判定 | 处置与证据 |
| --- | --- | --- | --- |
| Q4 | `rebuild_alerts.py` 是否幂等 | **修正性接受** | **已幂等**:`scripts/demo/rebuild_alerts.py:8-10` docstring + `:54-66` 的 `find_alerts_by_trade(trade_id)` 命中即 `skipped`,**不需要「再加 uk_idem」**。但 convert 补偿须以**转出端 `out_trade_id` 为幂等锚点**(一次转换两条流水) |
| Q6 | 并发重试间隔是否够 | **接受** | §10 增「50 并发压力用例」验证重试成功率 |
| Q10 | `rounding_diff` 谁承担 | **接受** | 审计落 `rounding_diff` 正负向与金额,供二期对账 |
### 任务优先级
**接受**:插入 **T-0(列名统一 + 启动断言)** 于 T-1 之前。
---
## 三、交叉点(✅ 已拍板 · 2026-09-10)
**P0-1 / C1 · Core 读写账号分离** —— 用户拍板结论:**「按真实项目走,贴近真实项目做法」= 采纳独立账号方案**。
**历史脉络(重要 · 勿重复设计)**:本项在主架构线**已评审过** ——
`改进方案评审-问题清单与对比.md` **§C1**(`core_ro` 无 DB 级只读账号,红线相关,**推荐方案 A:独立只读用户 GRANT SELECT ONLY**,
否决 B「代码层 SQL 白名单」:易被注释/子查询绕过),并已挂进 `开发计划-架构改进.md **§5.3**`(标注「需运维配合」,**未实施**)。
本次由基金转换线**接手落地**。
**落地清单** → `架构设计-基金转换交易.md` **§11.1**(账号矩阵 / 配置 / `db.py` 改造 / 3 个权限断言)+ **D20** + **T-0b**:
| 账号 | 授权 | 使用者 |
| --- | --- | --- |
| `xh_core_ro` | `SELECT` 全库 | `core_ro` / `core_tools` / `deps` / risk / **gateway 的校验读** |
| `xh_core_rw` | 4 表 `SELECT/INSERT/UPDATE`,**无 DELETE / DDL** | **仅 gateway 写路径** |
| `xh_agent_rw` | 业务表读写 + **`audit_log` 只授 INSERT** | Agent 侧全部 |
| `root` | 全权 | **仅** `00-grant.sql` / `01-ddl.sql` / `reset.ps1` |
> **对基金转换的意义**:`app/gateway/convert_core_repository.py` 是**唯一写 `jinrong_core` 的层** →
> 必须持有 `rw` 账号。若按「只读连接池」一刀切,T-1/T-6 会直接返工 —— **该风险已由 D20 消除**。
> 附带收益:把项目红线「**审计表只 INSERT**」从代码约定升级为 **DB 级强制**(`audit_log` 回收 UPDATE/DELETE)。
---
## 四、处理时机
| 阶段 | 动作 | 状态 |
| --- | --- | --- |
| 基金转换评审出来 | 两份**合并**,逐条判定 → 更新各自架构文档 | ✅ 已完成(13 条落地,架构 §13.2 索引) |
| 基金转换开工前(T-1 之前) | **必须定 P0-1 账号方案**(见 §三) | ✅ **已拍板**:按真实项目走 = 独立账号方案(D20 / T-0b) |
| 基金转换 T-0 | sqlite/MySQL 列名统一 + `conftest.py` 启动断言(R1) | ⏳ 待开发 |
| 基金转换 T-0b | DB 账号分离:`00-grant.sql` + `db.py(db, role)` + 3 权限断言(D20) | ⏳ 待开发 |
| 下一迭代 | P0-4 `scoring.py` 实现 · P1-5 日志基建 · P1-2 同义词表 · §一其余 P0/P1 | ⏳ 排期 |
> **收口结论**:基金转换线**文档阶段已闭环**(PRD v0.9 + 架构 v1.0 + 评审 0 悬空),
> 门控 M-7 满足、可进第 4 步开发计划;**唯一开工前置**是 §三 的 gateway 可写账号方案(用户拍板)+ T-0(开发首个任务)。
### 补充:执行期风险 5 条(用户 2026-09-10 提供 · 已并入架构)
| # | 风险 | 架构落点 |
| --- | --- | --- |
| 1 | T-0 未完成就跑 T-1~ | 架构 §12 #1(CI 门禁 `tests/test_db.py::test_core_holding_columns`)+ §15 T-0 依赖 |
| 2 | 批次补建规则写两处漂移 | 架构 **D18**(抽 `lot_bootstrap.bootstrap_lots()` 纯函数)+ §12 #2 + §15 T-2/T-10 |
| 3 | `Decimal` 量化未传 `ROUND_HALF_UP` | 架构 §1 原则 10 + §12 #3(`.5` 边界断言) |
| 4 | 阶段 1.5 异常不丢预警 | 架构 **D19**(`on_error_hook` 可插拔)+ §6.2 + §12 #4 |
| 5 | `convert_batch_max_lots` 超限 | 架构 §1 原则 12 + §8.2 `batch_count`/§8.3 响应体 + §12 #5(前端文案) |
> 另:用户提供的任务拓扑已落 **架构 §15.1**(并行组 A/B、关键路径、高风险任务 T-10、门禁与基线)。