相对第一版 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(含失败关闭反证)。
492 lines
28 KiB
Markdown
492 lines
28 KiB
Markdown
# 底座代码测试报告
|
||
|
||
> 首版版本: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` | 开箱可用性验证(生产工厂 → 能力绑定 → 工具调用) |
|