背景:交接文档的定位是「给 AI 接手用的入口」,此前三份分散(项目根 交接文档.md
+ docs/交接文档-基金转换.md + docs/交接文档-架构改进.md),且 docs/ 两份停留在旧版
(v1.0 / v1.1,不含 T-0~T-2b、609 passed、D20 实施等进度),新会话极易被误导。
改动:
- 三份合并为项目根 交接文档.md v3.0(545 行 = §0 公共层 + §A 风控主线 + §B 基金转换线
+ §C 架构改进线);该文件在 .gitignore:47 内,按用户要求不入库(交接文档只留本地)
- 全仓指向统一到 交接文档.md §A/§B/§C:AGENTS.md(含顶部新增「接手先读交接文档.md」)、
docs/memory/{MEMORY,TODO,FRAMEWORK,ITERATION}、PRD-架构改进与稳定性加固、
开发计划-架构改进、TODO-架构改进、开发计划-基金转换交易
- 两份 docs/交接文档-*.md 加「已废弃(2026-09-10)· 勿读」横幅并指向新入口,
保留作历史留档(不删除)
- 记录事故:对 docs/ 下中文名文件使用 git rm 会静默抹除整个 docs/ 目录(复现 2 次、
退出码 0),已零损失恢复;纪律写入 交接文档.md §0.4 与工作区记忆
无代码改动;pytest 609 passed / 3 skipped。
10 KiB
PRD · 架构改进与稳定性加固
版本:v1.0(2026-09-09) 状态:待评审 → 通过后进入开发 代码基线:分支
risk-control-agent,HEAD2d0e2fa,pytest 503 绿 关联文档:
- 架构现状剖析:《docs/项目框架设计/架构设计说明书.md》
- 问题清单与方案对比:《docs/项目框架设计/改进方案评审-问题清单与对比.md》(已过第一轮审核)
- 开发计划(HOW):《docs/项目框架设计/开发计划-架构改进.md》(已过第二轮审核)
- 任务清单:《docs/项目框架设计/TODO-架构改进.md》
- 开工入口:项目根《交接文档.md》§C
本文定位:只定义做什么(WHAT)与为什么(WHY)及验收标准;怎么做(HOW)见开发计划。
1. 背景与目标
1.1 背景
2026-09-09 对 risk-control-agent 分支做了全量架构梳理,产出《架构设计说明书》,并在其中登记了 24 项待处理项(11 项改进空间 + 5 处文档口径不符 + 3 项非缺陷标注 + 5 项审核补充)。
经两轮独立 AI 审核(第一轮审问题清单、第二轮审开发计划),六项核心事实断言全部属实、无阻断级错误,设计方案核验成立。
用户随后拍板:补告警 + 换 Redis 分布式锁,理由是"马上要合并代码了"。
1.2 目标
| 目标 | 衡量方式 |
|---|---|
| G1 消除多实例部署下的并发保护失效 | Redis 锁生效;单进程行为不劣化 |
| G2 消除"静默失败"——出了事没人知道 | 无 key、审计降级两处均有明确告警 |
| G3 消除文档与代码事实不符 | 5 处口径修正完成,核对一致 |
| G4 关键设计决策可追溯、不被后人误改 | 3 项"非缺陷"设计写入架构文档 |
| G5 中间件顺序错误可被测试拦截 | 顺序被调换时测试变红 |
1.3 非目标(本期不做)
- 不实现 R-05 动态评分(
scoring.py桩保留) - 不支持
convert交易类型(接口协议变更,待产品确认) - 不做日志基建(格式化/落盘/全量接 trace_id)
- 不收拢权限逻辑、不启用
model/层、不做 Tool 注册表合并 - 不改变审计 fail-open 策略(保持现状 + 补告警)
2. 范围
2.1 本期范围(合并前实施,11 个原子项 → 6 个需求)
| 需求 ID | 名称 | 原子项 | 批次 |
|---|---|---|---|
| FR-01 | 文档口径修正 | A1~A5 | 第 1 批 |
| FR-02 | 非缺陷设计标注 | G1~G3 | 第 1 批 |
| FR-03 | 无 key 启动告警 | C4 | 第 1 批 |
| FR-04 | 审计降级告警强化 | C3 相关 | 第 1 批 |
| FR-05 | Redis 分布式锁 | B6 | 第 2 批 |
| FR-06 | 中间件顺序测试守卫 | B2 | 第 2 批 |
2.2 后续范围(合并后,本期不实施)
第 3 批(需拍板或 DDL):B1 唯一索引+重试、B5 限流 EXPIRE、C1 只读账号、F1 convert、F2 意图识别 第 4 批(重构类):D1 日志基建、E1~E4 可维护性、F3 动态评分、B3/B4 判据固化
详细设计见《开发计划-架构改进.md》§5、§6。
3. 需求清单
FR-01 文档口径修正
描述:修正 5 处"文档说法与代码实测不符",避免后续读者按错误数字核对。
动机:这些数字会被当作事实依据做决策(如"注入词表 42 条"影响安全评估、"Tool 4 个"影响能力盘点)。错了会引发误判。
明细与验收标准:
| 原子项 | 位置 | 现状 → 应为 | 验收 |
|---|---|---|---|
| A1 | docs/memory/MEMORY.md §0 |
42 条 → 45 条 | 与 input_guard.py:43-94 逐行计数一致 |
| A2 | docs/memory/MEMORY.md 仓库地图 |
4 个 → 5 个 | 与 chat_tools.py:328 注册表一致 |
| A3 | docs/项目框架设计/表设计/02-mysql-agent专用.sql 注释 |
5 张 → 6 张 | 与实建表一致(含 risk_aml_list) |
| A4 | 《架构设计说明书》§4.3.2 | 补明 scoring.py 为 NotImplementedError 桩 |
读者不会误以为评分已生效 |
| A5 | 《架构设计说明书》§4.3.4 | 补明 locks.py 为进程内锁 |
读者知道多实例会失效 |
FR-02 非缺陷设计标注
描述:把 3 项"看起来像问题、实际设计正确"的决策写入架构文档。
动机:这三项每次评审都会被重新提出,消耗沟通成本。写明后可直接引用。
| 原子项 | 要写清什么 |
|---|---|
G1 X-Trace-Id 可伪造 |
有白名单防响应头注入;身份只认 JWT,trace_id 绝不用于权限判定 |
G2 trace_id 概率唯一 |
64 bit 随机,请求级;约 2^32 次请求才 50% 碰撞,无需数据库唯一校验 |
G3 admin.py / knowledge.py 空壳 |
T-21 拍板一期只做脚本入库;未挂载路由;空 ≠ RAG 缺失(能力在 service/rag_service.py) |
验收:三项均在《架构设计说明书》中有独立条目,含"为什么不是问题"。
FR-03 无 DeepSeek key 启动告警
描述:启动时若 DEEPSEEK_API_KEY 未配置,打 WARNING 日志。
动机:当前无 key 时静默走 _degraded_reply,生产漏配会"看起来正常",用户和运维都察觉不到。
验收标准:
- 清空
.env的DEEPSEEK_API_KEY→ 启动日志出现降级告警,且不阻塞启动 - 恢复 key → 无该告警
- 告警位置与
main.py:61-65现有 dev secret 告警一致(沿用同模式) - 不产生循环导入(若引入,改用字面量常量)
FR-04 审计降级告警强化
描述:两处审计失败日志补充 trace_id 与关键上下文字段。
动机:当前 utils/authz.py:70-76 与 audit_middleware.py:78 已打日志,但不含 trace_id——审计失败时连是哪次请求都不知道,无法追溯。
验收标准:
utils/authz.py审计失败日志含code/agent/actor/trace_idaudit_middleware.py审计失败日志含status/path/request_id- 日志级别与降级语义不变(仍不阻塞业务)
- 失败路径仍返回原响应(行为零变化)
FR-05 Redis 分布式锁
描述:把 locks.py 的进程内锁改造为「Redis 为主、进程内为备」的双层结构。
动机:threading.Lock 跨进程不可见,多实例部署时同一客户并发请求会同时进入临界区。用户明确要求,因"马上合并代码"(合并后可能多实例部署)。
验收标准:
| # | 标准 |
|---|---|
| 1 | run_locked(key, fn) 签名与 fn(locked) 回调语义完全不变,三处调用点无需修改 |
| 2 | Redis 可用时跨进程互斥生效(Redis 侧 key 带 TTL) |
| 3 | Redis 不可用(含 ConnectionError、Fake 缺方法的 AttributeError)→ 退回进程内锁,不抛异常、业务照常完成 |
| 4 | Redis 可用但等待超时 → fn(locked=False) 降级执行,与原语义一致 |
| 5 | 释放只删自己持有的锁(错误 token 释放返回 False,锁仍在) |
| 6 | 三处调用点全覆盖:agg:event: / agg:suitability: / l3:(不得只改两处) |
| 7 | 全量 pytest 503 仍绿(新增锁测试计入总数) |
| 8 | 三处调用点的 key 均带 lock: 前缀,与既有 sess:/ratelimit:/auth: 命名空间隔离 |
关键约束:测试环境不依赖本机 Redis(redis_gateway 一律 fake 注入),新代码不得假设 Redis 可用。
FR-06 中间件顺序测试守卫
描述:新增测试,断言 audit_log 中 trace_id 非空。
动机:main.py 中 audit 先注册(81 行)、trace 后注册(87 行),依赖 Starlette 的 insert(0) + reversed 语义保证 trace 在外层先执行。调换两个装饰器会使 audit 先执行、current_trace() 返回空串,全站审计静默丢失 trace_id,且不报错。
验收标准:
- 新增测试在正常顺序下通过
- 临时调换装饰器顺序 → 测试变红(确认测试确实有效)
- 恢复顺序 → 测试转绿
- 不放
ensure_trace()兜底(会掩盖顺序错误,导致 trace_id 与响应头不一致)
4. 非功能需求
| 类别 | 要求 |
|---|---|
| 兼容性 | 不破坏任何现有 API 契约;不改路由;不新增配置项(若配置化 LOCK_TTL_SECONDS 需同步 .env.example) |
| 依赖 | 不新增第三方依赖(redis 库已在 requirements.txt) |
| 测试 | 全量 pytest 保持 503 绿,新增测试随改动交付 |
| 可回滚 | 每个需求独立可回滚;FR-05 退回进程内锁即等价原行为 |
5. 约束与红线
项目五条红线(任何改动不得触碰):
- Core 表只读
- 审计表只 INSERT
- 不自动冻结
- 不自动改风险等级
- 仅 R-02 可阻断交易
技术约束:
- Windows 原生部署,系统 Python 3.13.14
- 测试不依赖本机 Redis
- 改路由须同步
tests/test_main.py::test_all_routers_mounted路径清单 - 新增依赖须用户确认
6. 风险
| 风险 | 影响 | 应对 |
|---|---|---|
| FR-05 引入 TTL 新失败模式:临界区超 TTL 导致并发进入 | 中(最坏重复 pending 单,非数据损坏) | TTL 取 30 秒;L3 乐观锁 + 锁内重查兜底;取得 p99 后按 max(30, p99×5) 调整 |
| 测试期 Redis 不可用产生大量日志噪声 | 低 | 被捕获不导致失败;对比基线确认不漂移 |
合并冲突(locks.py/redis_gateway.py 有在途改动) |
中 | 合并前确认目标分支无在途改动 |
| FR-03 引入循环导入 | 低 | 若出现,改用字面量常量 |
7. 验收总纲
本次改进全部完成的判定:
- FR-01~FR-06 六项验收标准逐条通过
python -m pytest -q全绿且用例数 ≥ 503(新增测试计入)- uvicorn 启动冒烟:
/health正常,无 key 告警按预期出现 POST /api/simulate/trade阻断 + 放行路径各一次,预警聚合仍为"同客户同日仅一张 pending 单"- 停 Redis → 业务仍可完成;恢复 Redis → 行为一致
- 未新增配置项、未新增依赖、未改路由
交付纪律:commit 但不 push,等用户在浏览器目视确认后再推送。