Files
group_fqcd_jr/docs/06-底座代码测试报告.md
T
lzf_0626 6516ccb385 feat: 第二版——接口契约对齐 docs/05,修复静默故障与数据库基线
相对第一版 46fc976 的完整变更。组员迁移对照表见 docs/20。

一、对外契约对齐 docs/05(破坏性,共 4 处,组员需按 docs/20 调整)
1) 配置发布端点改为文档规定的复数资源名:submit→validations、
   approve→reviews(需 body decision)、activate→activations、
   rollback→rollbacks;第一版这 4 个动词式路径 docs/05 从未定义过。
2) 错误码由 8 个笼统码改为 15 个具体语义码(FORBIDDEN→AGENT_PERMISSION_DENIED、
   UNAUTHORIZED→AUTHENTICATION_REQUIRED、CONFLICT→RESOURCE_VERSION_CONFLICT、
   RESOURCE_NOT_FOUND→RUN_NOT_FOUND/SESSION_NOT_FOUND 等),
   输入类错误状态码 400→422。
3) POST /api/v1/agent-runs 与 GET /api/v1/agent-runs/{run_id} 统一为
   {data, meta} 信封(data 内字段名与语义未变)。
4) 错误响应体统一为 {error:{code,message,retryable,field_errors}, meta:{trace_id}},
   不再返回 FastAPI 默认的 {"detail": ...}。

二、数据库基线与约束
新增 39 张表的基线迁移(链根)与联合唯一键纠偏(4 张表、删 8 增 4,幂等收敛);
撤下 config_release 的双人复核 CHECK(应用层已允许自审,审核节点保留,
自审如实写入 reviewer_id);记忆 active key 生成列与唯一键;
activate 开始记录 supersedes_release_id 使版本链可追溯。
docs/00 基线未修改,未重命名或删除任何表与字段。

三、修复会静默出错或无报错的缺陷
- 跑完集成测试后平台会静默失去生效配置:清理只删自己创建的版本,却没有恢复被它
  顶成 superseded 的原生效版本,且审计一并删除因而完全无痕,表现为所有工具被拒
  但没有任何报错。已修清理逻辑并加恢复。
- Worker 单轮异常导致进程退出;记忆抽取调用方的“事务已开始”异常;
  召回缓存丢失 degraded 标记;连接时区未生效导致 created_at/updated_at 差 8 小时;
  .env 与 os.getenv 密钥来源分裂导致“没有可用的已批准模型端点”。
- 记忆信号识别漏判与跨键误命中;SSE 未带 Accept 的协商行为。

四、功能补齐
记忆链路 P1/P2/P3(抽取、受控词表、召回与缓存、生命周期级联及投影事件)、
fin_* 场内交易只读 ORM 层、agent_intent_config 状态流转并在运行期真正生效、
限流(Redis 固定窗口、故障一律放行)、游标校验、trace_id 中间件、
示例业务 Agent fund_query_demo 与一键端到端验证脚本,以及审计/指纹/迁移状态工具。

五、文档与验证
新增 docs/19(业务 Agent 接入实操)、docs/20(第一版迁移指南)与 docs/evidence 证据;
docs/01/02/06/08/09/17 同步实现现状。

验证结果:ruff 通过、mypy 103 文件无错、unit+contract 447 passed、
integration 29 passed、acceptance_check --production 7 PASS、
demo_agent_e2e 9/9 PASS(含失败关闭反证)。
2026-09-10 15:55:54 +08:00

492 lines
28 KiB
Markdown
Raw 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.
# 底座代码测试报告
> 首版版本:v1.0(当前最新为 v3.0,结论见附录 D / 附录 E)
> 测试日期:2026-09-09
> 测试对象:`jr-agent-platform` 底座代码(首版统计 86 个 Python 文件)
> 测试依据:`AGENTS.md`、`docs/00-新数据库基线设计.md`、`docs/01-通用Agent平台开发设计.md`、`docs/02-数据库建表设计.md`、`docs/03-平台端到端流程文档.md`、`docs/05-接口文档.md`、`TODO.md`
> 测试方式:自动化测试执行 + 静态与类型检查 + 真实 HTTP 接口冒烟 + 契约逐项核对
> **当前结论(v3.0,附录 E):综合 94/100(A)。**
>
> 本文档保留完整版本演进,**各节摘要与评分属于相应版本的口径,不要混读**:
>
> | 版本 | 综合分 | 口径说明 |
> |---|---|---|
> | v1.0 | 55(D+) | 首版,使用了未定义规则的归一计算 |
> | v1.1 | 48(D+) | 统一为"综合分 = 加权总分",契约完整度 25 → 45 |
> | v2.0(附录 D) | 90(A-) | 修复后独立复审:P0 三项 + P1 五项闭环 |
> | v3.0(附录 E) | **94(A)** | 能力接线复审:生产组装、意图分类、首个业务工具闭环 |
>
> 首版"不可用于任何真实流量"对应的 P0 缺陷已在附录 D 闭环。当前的**已知限制**已转移到
> `docs/evidence/20260909-environment-facts.md`(模型端点与 embedding 端点未配置、
> 语义召回通道待启用、Worker 需常驻等),不再由本文档承担。
---
## 1. 结论摘要
| 维度 | 得分 | 判定 |
|---|---|---|
| 工程基建(工具链 / 类型 / 测试骨架) | 85 | B+ |
| 安全与鉴权 | 20 | D |
| 契约实现完整度(按当前阶段口径) | 45 | C- |
| MVC+S 架构合规 | 60 | C |
| 端到端可运行性 | 35 | D |
| **综合** | **48** | **D+** |
**一句话结论**:骨架搭起来了,承重墙没浇。`ruff` + `mypy --strict` 全绿、集成测试真连 MySQL,这层质量在同类项目里属上乘;但**任意登录用户可调用任意 Agent**(越权实测成功并落库),且**没有 worker 入口导致运行永远排队**,这两条使底座当前无法承载任何真实流量。
**缺陷统计**:P0 三项、P1 五项、P2 七项。
---
## 2. 测试范围与方法
| 层次 | 方法 | 覆盖 |
|---|---|---|
| 单元测试 | 执行仓库自带 `tests/unit` | 16 个测试文件 |
| 集成测试 | 执行仓库自带 `tests/integration`(真连本地 MySQL) | 7 个测试文件 |
| 静态检查 | `ruff check app tests` | 全部源码 |
| 类型检查 | `mypy app`(strict 模式) | 55 个源文件 |
| 接口冒烟 | 自建 `tools/smoke_check.py`,起真实 uvicorn 服务打真实请求 | 认证、越权、幂等、SSE、异常输入 |
| 契约核对 | 逐项比对 `docs/05-接口文档.md` 第 19 节接口总目录与代码路由 | 50 个接口 |
| 架构核对 | 比对 `01 §3.1/§4` 分层约束与代码目录、依赖方向 | MVC+S 四层 |
**未覆盖**:Milvus / Neo4j 真实链路、模型供应商真实调用、压测与故障注入(`tools/performance_baseline.py` 未执行)。
---
## 3. 测试环境
| 项 | 值 |
|---|---|
| Python | 3.13.15(conda 环境 `jr_py313`,`D:\conda\envs\jr_py313`) |
| 测试框架 | pytest 8.4.2、pytest-asyncio 0.26.0 |
| Web 框架 | FastAPI 0.141.1、uvicorn 0.52.4 |
| 数据层 | SQLAlchemy 2.0.52、asyncmy 0.2.14、PyMySQL 1.2.0 |
| 静态检查 | ruff 0.16.6、mypy 1.20.2 |
| 数据库 | 本地 MySQL(`jr` 库,集成测试真实读写)、Redis、Milvus、Neo4j(配置就绪) |
| 服务地址 | `http://127.0.0.1:8099` |
> **环境提示**:若使用 base 环境运行测试,因缺少 `pytest-asyncio`,16 个 `async def` 测试会被 pytest **静默跳过**(`PytestUnhandledCoroutineWarning`),仅显示 `17 passed, 16 skipped`。必须使用 `jr_py313`。
---
## 4. 测试执行结果
### 4.1 自动化测试
| 命令 | 结果 | 耗时 |
|---|---|---|
| `pytest tests/unit -q` | ✅ **33 passed**,0 skipped | 1.92s |
| `pytest tests/integration -q` | ✅ **9 passed** | 2.25s |
集成测试**确认真连数据库**(非 mock):使用 `app.infrastructure.db.SessionFactory`、真实 ORM 模型、`commit()` 与 `finally` 清理,覆盖配置发布生命周期、幂等并发、Outbox 重放与 worker 租约。
**测试有效性评价**:测试本身质量良好,但**存在关键盲区**——`test_agent_factory.py` 只覆盖"已注册可创建 / 未注册报错",**没有任何角色或入口鉴权用例**,因此 P0-1 越权缺陷未被现有测试发现。
### 4.2 静态与类型检查
| 命令 | 结果 |
|---|---|
| `ruff check app tests` | ✅ All checks passed |
| `mypy app`(`strict = true`) | ✅ Success: no issues found in 55 source files |
这两项是本项目最扎实的部分,说明类型契约与代码规范执行到位。
### 4.3 接口冒烟测试(真实服务)
执行 `python tools/smoke_check.py`(服务已启动):
| # | 用例 | 期望 | 实际 | 判定 |
|---|---|---|---|---|
| 1 | 无 token 调用 | 401 | 401 | ✅ |
| 2 | 无效 token | 401 | 401 | ✅ |
| 3 | 合法用户创建 `customer_service` run | 202 | 202 | ✅ |
| 4 | 普通客户调用 `risk` Agent(越权) | 403 | **202** | ❌ |
| 5 | 越权 run 是否真的落库 | 未落库 | **已落库 `agent_type=risk`** | ❌ |
| 6 | 未注册 `agent_type` | 400 | **202** | ❌ |
| 7 | `sub` 非数字 | 4xx | **500** | ❌ |
| 8 | 用户 2 读取用户 1 的 run | 404 | 404 | ✅ |
| 9 | SSE `Content-Type` | `text/event-stream` | `text/event-stream` | ✅ |
| 10 | SSE 首个事件 | `start` | `start` | ✅ |
| 11 | 同幂等键不同请求体 | 409 | 409 | ✅ |
**合计 11 项,失败 4 项(对应 3 个真实缺陷)。**
---
## 5. 缺陷清单
### 5.1 P0:阻断上线
#### P0-1 越权漏洞:任意登录用户可调用任意 Agent
| 项 | 内容 |
|---|---|
| **现象** | 用户 `sub=1` 的合法 token 请求 `agent_type=risk`,返回 `202`;随后 `GET /api/v1/agent-runs/{run_id}` 返回 `agent_type=risk`,证明运行记录已真实落库 |
| **根因** | 四处叠加,形成完整漏洞链 |
| | ① `app/service/agent/factory.py:19-26` — `create()` 只校验 `agent_type` 是否注册,**无任何授权调用** |
| | ② `app/core/contracts.py:50-56` — `AgentDefinition` 无 `allowed_roles` / `allowed_portals` 字段,权限上限无处声明 |
| | ③ `app/service/agent/base.py:46-47` — `validate_access()` 为空实现(`del request, context`) |
| | ④ `app/core/security.py:57` — 认证只提取 `sub`,`RequestContext.roles` 恒为空元组 |
| **影响** | 角色隔离完全失效。客户可调用风控、投顾、运营 Agent;`01 §7.2` 权限映射表、`03 §4.2` 第 7 步、`AGENTS.md` 第 7 条全部落空 |
| **建议** | `AgentDefinition` 补 `allowed_roles` / `allowed_portals`;`AgentFactory.create()` 增加 `AgentAuthorizer.ensure_allowed(definition, context)`;JWT 认证后从 `sys_user_role` / `sys_role_permission` 加载有效角色写入 `RequestContext`(Redis 缓存 + 版本失效) |
#### P0-2 `agent_type` 无白名单校验
| 项 | 内容 |
|---|---|
| **现象** | `agent_type=no_such_agent` 返回 `202` |
| **根因** | `app/service/agent_run_application_service.py:29-91` — `accept()` 全程不校验 `agent_type` 合法性,直接写 `agent_run` 并发 `agent.run_requested` 事件 |
| **影响** | 脏数据入库;worker 无法执行导致任务堆积;审计与指标被污染 |
| **建议** | `accept()` 前置校验 `AgentFactory` 注册表;未注册返回 `400` |
#### P0-3 无 worker 入口,端到端不通
| 项 | 内容 |
|---|---|
| **现象** | 全项目 grep 无 `if __name__ == "__main__"`、`asyncio.run`、`def main`。run 创建后状态永久停留 `queued`,SSE 只推 `start` 后持续心跳直至超时 |
| **根因** | `app/worker/` 下有 `agent_run_worker.py`、`outbox_worker.py` 等类实现,但**没有任何可执行入口**;`app/main.py` 只注册三个路由,未启动 worker |
| **影响** | 底座核心链路(受理 → 执行 → 落库 → 推送)断裂,任何业务 Agent 都无法产生结果 |
| **建议** | 补 worker 启动入口(独立进程或 lifespan 内后台任务),并把 `agent.run_requested` Outbox 消费打通 |
### 5.2 P1:编码前必须修复
#### P1-1 `sub` 非数字导致 500
- **现象**:token 的 `sub` 为字符串(如 `abc`)时返回 `500`,而非 `401`。
- **根因**:`app/core/security.py:57` 将 `sub` 原样转为字符串存入 `user_id`;`app/service/agent_run_application_service.py:30` 执行 `int(context.user_id)` 未捕获 `ValueError`。
- **影响**:500 泄露内部异常;不符合 `05-接口文档.md` 的错误码约定。
- **建议**:认证阶段校验 `sub` 为数字,失败抛 `UnauthorizedAgentError`;`accept()` 内不再做隐式转换。
#### P1-2 BaseAgent 治理钩子全部为空实现
- **现象**:`resolve_config`、`recall_memory`、`check_compliance` 均为 `del ...` 空实现(`app/service/agent/base.py:49-57`)。
- **影响**:
- **`check_compliance` 直接返回原结果 → 输出合规扫描完全缺失**(禁止表达、脱敏、适当性、跨客户检查全部未执行);
- `recall_memory` 空 → 记忆召回未接线;
- `resolve_config` 空 → 配置中心未接线。
- **建议**:注入 `ComplianceService` / `MemoryService` / `ConfigService` 并实现钩子;在钩子为空期间禁止接入真实流量。
#### P1-3 SSE 缺失 `delta` 事件
- **现象**:`app/api/views/agent_run_sse.py:16-23` 仅生成 `start` / `tools` / `replace` / `done` / `error`,**从不生成 `delta`**。
- **契约**:`docs/05-接口文档.md:400` 明确 `start -> tools(可选) -> delta(一个或多个) -> done -> 关闭`。
- **影响**:按契约解析 `delta` 的客户端拿不到正文;流式体验退化为一次性替换。
- **建议**:按安全分块发送 `delta`,或与接口文档同步改为"仅 `replace`"并更新第 19 节目录。
#### P1-4 Controller 直接访问 ORM(MVC+S 违规)
- **证据**:`app/api/controllers/agent_runs.py:5,62-69`(`select(AgentRun)`);`app/api/controllers/conversations.py:37-45,68-73,112-119`(三处 `select`)。
- **契约**:`01:131`"任何业务条件都不能写在 Controller 或 View 中";`02 §3`"Controller 不访问 ORM"。
- **影响**:数据访问逻辑散落 Controller,权限过滤与事务边界难以统一;新增接口时容易绕过 Repository 约定。
- **建议**:查询下沉到 `app/repository/`,Controller 只做路由、参数校验与 Service 调用。
#### P1-5 接口实现覆盖率:全量 20% / 当前阶段 55%
- **契约**:`docs/05-接口文档.md:895-946` 定义 **50 个**接口(R001-R004 共 4、C001-C007 共 7、M001-M002 共 2、K001 共 1、A001-A033 共 33、O001-O003 共 3)。
- **重要口径**:该 50 个接口**全部属于公共底座**(对应 `TODO.md` 阶段 3-7)。业务域接口在 `05-接口文档.md:948` 单独登记、不在本表内,由各业务模块负责。因此本项反映的是**底座自身进度**,不含业务接口。
- **实现**:代码共 10 个路由。
| 控制器 | 已实现 | 对应编号 | 归属阶段 |
|---|---|---|---|
| `agent_runs.py` | 3 | R001、R002、R003 | 阶段 3、5 |
| `conversations.py` | 3 | C003、C005、C007 | 阶段 5 |
| `config_releases.py` | 4 | 提交/审核/激活/回滚(路径与 A004-A007 不完全一致) | 阶段 6(提前实现) |
**双口径结论**
| 口径 | 应交付 | 已实现 | 完成度 |
|---|---|---|---|
| 全量底座接口 | 50 | 10 | **20%** |
| 当前阶段(T3.1-T5.3,即 R001-R004 + C001-C007) | 11 | 6 | **55%** |
- **说明**:阶段 6(平台管理面 A001-A033)与阶段 7(O001-O003)在 `TODO.md` 中尚未开始,不计入"当前阶段应交付"。按当前阶段口径,底座接口完成度为 55%。
- **当前阶段缺失**:R004(取消运行)、C001(创建会话)、C002(会话详情)、C004(关闭会话)、C006(转人工详情)。
- **建议**:按 `TODO.md` 阶段推进,并在每个接口完成时同步第 19 节目录与 OpenAPI。
### 5.3 P2:建议改进
| # | 问题 | 证据 | 建议 |
|---|---|---|---|
| 1 | 无 `svc_conversation_session` 表,澄清轮次无处存储 | `app/model/platform.py` 无该模型;`RequestContext` 无 `clarification_round` | 按 `02 §8.1` 补表;`RequestContext` 补字段 |
| 2 | `JwtAuthenticator` 每请求实例化并读公钥文件 | `app/api/dependencies/auth.py:18`、`app/core/security.py:27,29-36` | 单例化 + 公钥内存缓存 |
| 3 | 会话归属靠历史消息推断,新会话可被抢注 | `agent_run_application_service.py:35-42` | 改用会话表归属字段校验 |
| 4 | `docs/` 下两份 `05` 文档并存 | `05-公共Agent平台接口规范.md` 与 `05-接口文档.md` | 按 `TODO.md` T0.2 删除或归档废弃稿,并加检查脚本 |
| 5 | 目录结构偏离文档约定 | 代码 `app/api/controllers`、`app/api/views`、`app/repository`;文档 `01 §4` 为 `app/controller`、`app/view`、`app/model/repositories` | 统一其一并更新文档 |
| 6 | `TODO.md` 与设计文档数据不一致 | `TODO.md:34` 称 49 张表;`02` 称 47 张 | 以基线为准修正 TODO |
| 7 | 增量表模型未声明外键 | `app/model/platform.py` 各表无 `ForeignKey` | 与 `02 §8` 的 DDL 对齐或明确由应用保证 |
---
## 6. 契约一致性核对
| 契约项 | 权威源 | 实现状态 |
|---|---|---|
| HTTP 前缀 `/api/v1` | `05-接口文档.md` | ✅ 一致 |
| Bearer JWT 认证 | `05-接口文档.md` §3 | ⚠️ 仅解析 `sub`,未加载角色 |
| 幂等范围 `user_id + agent_type + idempotency_key` | `01:984` | ✅ 一致(唯一约束 + 409 实测通过) |
| 运行受理原子写消息/幂等/run/Outbox | `05-接口文档.md:326` | ✅ 一致(`accept()` 单事务) |
| SSE 事件序列含 `delta` | `05-接口文档.md:400` | ❌ 缺 `delta` |
| 结果级恢复(不实现事件级续传) | `05-接口文档.md:423` | ✅ 一致(轮询 + 重发全量) |
| 配置发布需双人审核 | `02 §8.4` | ✅ 一致(集成测试覆盖 creator≠reviewer) |
| 七步执行骨架 | `01 §6.3` | ❌ 记忆召回、意图分类、合规校验未实现 |
| 工厂按角色/入口授权 | `01 §7.2` | ❌ 完全缺失 |
| 审计写入范围 | `01 §1.10`(接口规范) | ⚠️ 部分(配置生命周期已写审计) |
### 6.2 TODO 阶段完成度校准
`TODO.md` 将阶段 0-7 归为底座、阶段 8 归为业务 Agent。下表按阶段核对"标记为已完成"的任务是否达到其**自己写明的验收标准**。
| 任务 | TODO 状态 | 验收标准(原文摘要) | 实测结果 | 判定 |
|---|---|---|---|---|
| T3.1 运行受理 | `[x]` | 返回 `202`、`run_id`、`trace_id`;同键同文返回原 `run_id`;同键异文 `409` | 全部通过 | ✅ 达标 |
| T3.2 AgentFactory 和 BaseAgent | `[x]` | "未注册 Agent、**非法角色、非法入口**和未授权工具均被拒绝";"实现 `BaseAgent.execute()` **七步流程**" | 非法角色/入口未被拒绝(越权返回 202);骨架仅 5 步,记忆召回、意图分类、合规校验为空实现 | ❌ 不达标 |
| T3.3 Worker 和运行租约 | `[x]` | "进程重启、租约过期和重复消费不会生成第二份最终消息" | 全项目无 worker 入口,链路无法启动,验收无法执行 | ❌ 不达标 |
| T4.1 `complete_run()` | `[x]` | 最终消息、审计、幂等完成、运行终态、记忆 Outbox 同事务 | 集成测试覆盖同事务提交 | ✅ 达标 |
| T4.2 Outbox Worker | `[-]` | — | 如实标注为进行中 | 状态一致 |
| T5.1 运行查询 | `[x]` | 越权资源统一返回 `404` | 实测 404 | ✅ 达标 |
| T5.2 结果级恢复 SSE | `[x]` | "输出 `start`、`tools`、`delta`、`replace`、`done`、`error`" | 从不生成 `delta` | ❌ 不达标 |
| T5.3 会话和消息 | `[-]` | — | 如实标注"会话关闭与转人工查询待补" | 状态一致 |
**结论**:4 个标记为 `[x] 已完成并通过验收` 的任务中,**T3.2、T3.3、T5.2 三项未达到其自身验收标准**。这不是覆盖率问题,而是**验收流程问题**——若 T3.2 的"非法角色被拒绝"验收项真实执行过,越权缺陷不可能通过。
**建议**:将"重跑 T3.2 / T3.3 / T5.2 验收并修正 TODO 标记"列为 P0(见 §9),在修复 P0-1 至 P0-3 后重新走验收。
---
## 7. MVC+S 架构合规核对
| # | 检查项 | 结论 |
|---|---|---|
| 1 | 四层目录存在 | ⚠️ 存在但命名与 `01 §4` 不一致 |
| 2 | Controller 薄层、无业务判断 | ❌ 直接查询 ORM(P1-4) |
| 3 | Controller 不访问 Model / ORM | ❌ 违反 |
| 4 | Service 编排业务 | ✅ `agent_run_application_service.py` 事务边界正确 |
| 5 | Model 仅做映射 | ✅ 基本符合 |
| 6 | View 仅做格式转换 | ✅ `agent_run_sse.py` 无业务逻辑 |
| 7 | 依赖方向 `Controller → Service → Repository → Model` | ⚠️ Controller 直接依赖 Model(越层) |
| 8 | Agent 属于 Service 层 | ✅ `app/service/agent/` |
| 9 | Agent 不得直连存储 | ✅ 未发现直连 |
| 10 | Agent 必须经 `AgentFactory` 创建 | ⚠️ `AgentExecutor` 走工厂,但 `accept()` 阶段不校验 `agent_type` |
| 11 | 治理能力不可被子类绕过 | ✅ `__init_subclass__` 硬拦截,优于文档的 `@final` |
| 12 | 横切关注点统一在底座 | ❌ 合规/记忆/配置钩子为空 |
**架构判定**:分层骨架与依赖注入设计正确,`__init_subclass__` 禁止覆盖治理方法的做法甚至比文档约定更硬。扣分集中在"Controller 越层访问 ORM"与"治理钩子未接线"两处。
---
## 8. 评分明细
| 维度 | 权重 | 得分 | 加权 |
|---|---|---|---|
| 工程基建(工具链 / 类型 / 测试骨架) | 20 | 85 | 17.0 |
| 安全与鉴权 | 25 | 20 | 5.0 |
| 契约实现完整度(当前阶段 55% 折算) | 20 | 45 | 9.0 |
| MVC+S 架构合规 | 20 | 60 | 12.0 |
| 端到端可运行性 | 15 | 35 | 5.25 |
| **合计** | **100** | — | **48.25 → 综合 48** |
**计算口径修正(v1.1)**:v1.0 的"44.25 → 归一 55"使用了一个未定义规则的归一计算,v1.1 统一为"综合分 = 加权总分"。同时"契约实现完整度"按当前阶段口径从 25 上调至 45(见 §5.2 P1-5)。两项修正叠加后综合分为 **48**。
> 判定说明:工程基建单项优秀拉高了加权分,但安全(20)与端到端(35)为门槛项,任一不达标即不可上线,故综合评级为 **D+**。
---
## 9. 修复优先级
| 优先级 | 事项 | 预估 |
|---|---|---|
| P0 | `AgentDefinition` 补权限字段 + `AgentFactory` 接入授权校验 | 0.5 天 |
| P0 | JWT 加载有效角色 / 数据范围写入 `RequestContext` | 0.5 天 |
| P0 | `accept()` 校验 `agent_type` 已注册 | 0.2 天 |
| P0 | 补 worker 入口,打通 run 执行闭环 | 1 天 |
| P0 | 重跑 T3.2 / T3.3 / T5.2 验收,修正 TODO 的"已完成"标记 | 0.5 天 |
| P1 | `sub` 非数字返回 401;`JwtAuthenticator` 单例化与公钥缓存 | 0.3 天 |
| P1 | 接线 `check_compliance` / `recall_memory` / `resolve_config` | 1 天 |
| P1 | SSE 补 `delta`;Controller ORM 查询下沉 Repository | 0.5 天 |
| P2 | 会话表、废弃稿清理、目录对齐、TODO 修正 | 1 天 |
**放行建议**:P0 全部闭环前,不得接入任何真实用户流量或对外暴露接口。
---
## 10. 附录:复现步骤
```powershell
# 1. 使用正确环境(必须 jr_py313,否则异步测试被静默跳过)
$py = 'D:\conda\envs\jr_py313\python.exe'
# 2. 自动化测试
& $py -m pytest tests/unit -q
& $py -m pytest tests/integration -q
# 3. 静态与类型检查
& $py -m ruff check app tests
& $py -m mypy app
# 4. 启动服务
& $py -m uvicorn app.main:app --host 127.0.0.1 --port 8099
# 5. 接口冒烟(另一个终端)
$env:PYTHONIOENCODING='utf-8'
& $py tools\smoke_check.py
```
**脚本说明**:`tools/smoke_check.py` 使用 `httpx.Client(trust_env=False)`,绕开 Windows 注册表代理设置——否则请求会被系统代理拦截并返回假性 `502`(本报告首次执行即遇到该问题)。
---
## 11. 变更记录
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0 | 2026-09-09 | 首版:5 类测试执行结果、15 项缺陷清单、契约与架构核对、评分与修复优先级 |
| v1.1 | 2026-09-09 | 修正接口总数(46 → 50)并区分全量/当前阶段双口径;新增 §6.2 TODO 阶段完成度校准;修正评分计算口径(综合 55 → 48,契约完整度 25 → 45) |
| v2.0 | 2026-09-09 | 新增附录 D:gpt 修复后的独立复审结果(综合 48 → 90) |
| v3.0 | 2026-09-09 | 新增附录 E:能力接线复审(综合 90 → 94);生产组装、意图分类接入、首个业务工具闭环 |
---
## 附录 D:v2.0 修复复审
> 复审对象:gpt 修复后的代码(82 个源文件,78 个测试用例)
> 复审依据:本报告 §9 修复清单 + `docs/07-测试问题修复记录.md`
> **复审结论:P0 三项 + P1 五项全部闭环,综合 48 → 90(A-)。**
### D.1 复审方式
不复用开发方结论,独立执行四类验证:
1. 全量回归(单元 + 集成);
2. 静态与类型检查(`ruff`、`mypy --strict`);
3. **独立验收脚本** `tools/acceptance_check.py`:真实 JWT + 真实 MySQL + 真实 HTTP 链路 + 显式注册的测试 Agent;
4. 真实 RBAC 数据加载验证 `tools/seed_test_rbac.py`。
### D.2 逐项验证结果
| 原缺陷 | 验证方式 | 结果 |
|---|---|---|
| P0-1 越权 | 真实 JWT(`sub=9001`,customer 角色)调用 `risk` Agent | ✅ **403**(修复前 202 + 落库) |
| P0-1 鉴权链路 | `IdentityService.resolve()` 真实读库 | ✅ `roles=('customer',)` / `('risk_operator',)` |
| P0-2 白名单 | 未注册 `agent_type` | ✅ **404** `AGENT_TYPE_NOT_FOUND`(依 05 权威规范,非 400) |
| P0-3 端到端 | HTTP 受理 → Outbox 消费 → worker 执行 → 结果查询 | ✅ `queued` → **`succeeded`**,`content` 正确 |
| P1-1 `sub` 非法 | 非数字 `sub` | ✅ **401**(修复前 500) |
| P1-2 治理钩子 | `AgentGovernance` 注入 + `resolve/recall/review` | ✅ 已接线(范围见 D.4) |
| P1-3 SSE `delta` | 运行中订阅 SSE | ✅ 含 `delta` 分块;终态重连走 `replace`(符合 05 §SSE 三场景) |
| P1-4 分层 | `tests/unit/api/test_architecture.py`(AST 扫描) | ✅ Controller 禁止 import `app.model` / `app.repository` / 直接操作 session |
| P1-5 接口覆盖 | `app.openapi()` 统计 | ✅ **41 paths / 50 operations,50/50 全覆盖** |
| 回归 | `pytest tests/unit tests/integration -q` | ✅ **77 passed**(修复前 42) |
| 静态 | `ruff check app tests` | ✅ 0 错误 |
| 类型 | `mypy app`(strict) | ✅ 82 文件 0 错误(修复前 55) |
独立验收脚本输出(7/7 通过):
```text
[PASS] 客户调用已授权 Agent 期望 202 / 实际 202
[PASS] 客户越权调用 risk Agent 期望 403 / 实际 403
[PASS] 未注册 agent_type 期望 404 / 实际 404
[PASS] SSE 含 delta 事件 期望 yes / 实际 yes
[PASS] SSE 终止事件 期望 done / 实际 done
[PASS] 运行终态 期望 succeeded / 实际 succeeded
[PASS] 运行结果内容 期望 独立验收通过 / 实际 独立验收通过
```
### D.3 评分更新
| 维度 | v1.1 | v2.0 | 依据 |
|---|---|---|---|
| 工程基建 | 85 | **90** | 测试 42→78,mypy 55→82 文件,新增 AST 架构测试 |
| 安全与鉴权 | 20 | **88** | 越权实测 403;RBAC 真实读库;拒绝写独立审计 |
| 契约实现完整度 | 45 | **95** | 50/50 接口全覆盖 |
| MVC+S 架构合规 | 60 | **92** | AST 测试机器强制 Controller 分层 |
| 端到端可运行性 | 35 | **85** | worker 闭环实测通过;扣分因生产零注册(见 D.4) |
| **综合** | **48** | **90** | A- |
### D.4 遗留局限(不构成缺陷,但影响验收边界)
| # | 局限 | 说明 |
|---|---|---|
| 1 | **生产代码零 Agent 注册** | `bootstrap.get_agent_factory()` 返回空工厂,正式注册入口与测试注册分离,业务 Agent 属 TODO 阶段 8。底座因此**无法独立端到端运行**,必须等组员注册后才能承接真实流量。 |
| 2 | 治理钩子部分覆盖 | `07` 文档自述:固定禁止表达、contains/exact、号码脱敏、记忆引用校验已接入;**适当性产品比较、真实模型意图分类、公共工具执行器未完成**;正则规则因无执行超时暂时失败关闭。 |
| 3 | RBAC 无初始化数据 | `sys_user` 等表为空,新环境需先造数据;仓库未提供 seed 脚本(本报告新增 `tools/seed_test_rbac.py` 可作参考)。 |
| 4 | 单元测试绕过真实认证 | `tests/unit/api/*` 用 `dependency_overrides` 注入 `RequestContext`,不覆盖 JWT→DB 身份链路;该链路仅由集成测试与本次独立验收覆盖。 |
### D.5 复审交付物
| 文件 | 用途 |
|---|---|
| `tools/acceptance_check.py` | 独立端到端验收(越权、闭环、SSE) |
| `tools/seed_test_rbac.py` | RBAC 测试数据种子 + 身份加载验证 |
| `tools/smoke_check.py` | HTTP 冒烟(认证、幂等、异常输入) |
---
## 附录 E:v3.0 能力接线复审
> 复审对象:新增能力接线与首个业务工具(89 个源文件,116 个测试用例)
> 复审依据:本报告附录 D 遗留 + `docs/10-业务域接入评估.md` 缺口清单
> **复审结论:能力层与组装层全部闭环,综合 90 → 94(A)。**
### E.1 上轮遗留的闭环情况
| 附录 D 遗留 | 本轮 | 证据 |
|---|---|---|
| 生产零注册,能力无法调用 | ✅ 组装入口补齐 | `bootstrap.get_agent_factory()` 注册 2 个内置工具、构造 `DatabaseModelGateway`、注入 `ModelGenerationService` / `ToolExecutor` / `IntentClassifier` |
| 治理钩子部分覆盖 | ✅ 补齐 | 模型调用、工具执行器、适当性校验、意图分类全部接线 |
| `IntentClassifier` 未接入骨架 | ✅ 已接入 | `base.py:104` 在 `execute()` 调用 `classify_intent()`;禁覆盖方法增至 13 个 |
| 健康检查 redis 硬编码 | ✅ 改真实 ping | `health_service.py:18,23-43` |
| 缺骨架契约测试 | ✅ 已补 | `tests/contract/test_agent_factory_contract.py`(遍历注册表) |
| `knowledge_service` 空壳 | ⚠️ 未处理 | 仍无条件 404(见 E.4) |
### E.2 本轮新增能力
| 能力 | 实现 | 质量要点 |
|---|---|---|
| 模型网关 adapter | `OpenAICompatibleGateway` + `DatabaseModelGateway` | `secret_ref` 强制 `env:` 前缀,密钥不入库不入日志 |
| 工具执行器 | `ToolExecutor` + `ToolRegistry` | 强制只读、参数全 `[redacted]`、超时控制、审计 |
| 适当性校验 | `SuitabilityService` | 时区校验、C1-C5/R1-R5、通过与拒绝均留痕 |
| 意图分类 | `IntentClassifier` | 严格 JSON、意图白名单校验、低置信 `needs_clarification` |
| **首个业务工具** | `fund_quote_service.py`(209 行) | 交易时段判断、盘中/收盘分档缓存、降级标记、只读约束、延迟建连 |
### E.3 独立验证
```text
pytest tests → 116 passed(附录 D 时 77)
ruff check → 0
mypy app → 89 文件 0 错误(附录 D 时 82)
开箱验证 tools/onboarding_check.py(不经替身):
生产工厂 : ModelGenerationService + ToolExecutor + PlatformGovernance
create() 后 : 能力自动绑定
配置解析 : config_version 正常,tools_by_intent={'general': ()}
工具调用 : ForbiddenAgentError 工具不在当前意图白名单(失败关闭,链路正确)
```
### E.4 遗留
| # | 项 | 影响 | 建议 |
|---|---|---|---|
| 1 | `knowledge_service.py` 仍无条件 404 | 客服 RAG 引用链路消费端为空 | 接 Milvus 检索后替换占位实现 |
| 2 | `milvus` 健康检查仍为 `True` | 有注释说明"optional projection",但探针仍会误判 | 改为 `not_probed` 或接入真实探测 |
| 3 | 开箱状态下工具不可用 | active release 的 `tools_by_intent` 为空 | 属正常安全设计,组员需先发布配置 |
### E.5 评分更新
| 维度 | v2.0 | v3.0 | 依据 |
|---|---|---|---|
| 工程基建 | 90 | **92** | 测试 77→116,mypy 82→89 文件,新增契约测试 |
| 安全与鉴权 | 88 | 88 | 无变化 |
| 契约实现完整度 | 95 | 95 | 无变化 |
| MVC+S 架构合规 | 92 | 92 | 无变化 |
| 端到端可运行性 | 85 | **92** | 生产组装完整,开箱验证通过 |
| 能力实现质量 | — | **94** | 四个服务 + 首个业务工具样板 |
| **综合** | **90** | **94** | A |
### E.6 复审交付物
| 文件 | 用途 |
|---|---|
| `tools/onboarding_check.py` | 开箱可用性验证(生产工厂 → 能力绑定 → 工具调用) |