first commit

This commit is contained in:
baozaotumao2025
2026-07-18 21:00:26 +08:00
commit 14722be770
105 changed files with 25004 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# ADR-001:采用 FastAPI 作为服务框架
## 状态
已接受
## 背景
原版本以脚本为主,不利于前端接入、统一 API 文档和长期演进。
## 决策
使用 FastAPI 作为应用入口,核心能力全部通过 REST endpoint 暴露,并保留清晰的 service/provider/repository 分层。
## 结果
- 自动生成 Swagger 和 ReDoc
- 前端可直接调用;
- 输入输出由 Pydantic 校验;
- 已通过统一认证中间件保护业务 API;后续仍可增加任务队列和前端界面。
## 不做的事情
当前不引入微服务、消息队列或分布式任务系统,避免超出当前核心需求。
+22
View File
@@ -0,0 +1,22 @@
# ADR-002:使用 SQLite 与 Alembic
## 状态
已接受
## 决策
使用 SQLAlchemy 2.x 管理数据访问,SQLite 作为默认数据库,Alembic 管理迁移。
## 原因
- 单机部署简单;
- 无额外数据库运维成本;
- 足以保存模型连接配置、测试运行、结果和本地认证数据;
- 未来可通过 DATABASE_URL 切换到 PostgreSQL,而不修改业务层。
## 运维操作
- 初始化并创建默认管理员:`./scripts/init_db.sh`
- 生成迁移:`uv run alembic revision --autogenerate -m "message"`
- 仅升级:`./scripts/migrate_db.sh`
- 回滚:`uv run alembic downgrade -1`
`0006_provider_crud` 新建 `provider_configs`,不修改已有的 `model_profiles` 能力档案,因此旧库中重复的历史档案不会阻塞升级。
`0007_user_token_version` 增加用户令牌版本;密码重置递增版本,使旧令牌立即失效。
+18
View File
@@ -0,0 +1,18 @@
# ADR-003:分层架构与设计模式
## 状态
已接受
## 采用模式
- Application Factory`create_app`
- Strategy`ModelProvider`
- Factory`ProviderFactory`
- Repository:数据库访问
- Service Layer:能力探测、测试计划、执行、报告
- Dependency InjectionFastAPI Depends
- GatewayDatasetGateway
`ProviderFactory` 复用同一数据库会话读取持久化的 `target`/`judge` 配置;缺少对应记录时才读取 `Settings` 中的环境变量。连接检查和后台运行走同一工厂;能力探测只作为运行内部阶段,避免配置与执行分叉。
## 边界
不引入复杂领域事件、CQRS、事件溯源或微服务。当前规模下这些会增加维护成本,不能直接提升核心测试能力。
+19
View File
@@ -0,0 +1,19 @@
# ADR-006:运行创建与恢复的幂等性
## 状态
已接受
## 决策
- 所有 `GET` 接口只读取本地状态,天然可安全重试。
- provider 的 `GET``POST``PATCH``DELETE` 配置接口遵循 HTTP 自身语义;同一角色重复创建返回 409。
- `POST /providers/{provider_id}/check` 不修改配置,但会调用上游模型,重试可能产生额外调用,因此由调用方按需重试。
- `POST /runs` 支持可选 `Idempotency-Key`。平台持久化键及请求参数指纹;相同键和相同参数返回同一运行,不重复投递任务;相同键不同参数返回 409。
- `POST /auth/refresh` 强制使用 `Idempotency-Key`。同一旧 refresh token 和同一键稳定派生后继 token,并使用持久化消费状态返回相同 access token 过期时间;旧 token 换键视为重放攻击并撤销整个会话。
- 未进入续约窗口的 refresh 返回 409,不消费 token 或占用幂等键;到达窗口后可使用新键正常续约。
- `POST /runs/{run_id}/resume` 以已持久化结果为检查点;当前进程内运行中的重复恢复不再投递任务。
- `POST /runs/{run_id}/retry-errors` 仅接受 `completed_with_errors`:删除 `execution_status=error` 的结果后立即把状态改为 pending;同一请求再次到达时返回 409,不会重复清理成功结果或 `verdict=fail` 的已完成结果。
- `POST /runs/{run_id}/results/{execution_id}/retry` 删除指定结果后立即把 Run 改为 pending;同一请求的重复提交返回 409,不会再次删除或投递。
- `PATCH /runs/{run_id}` 是幂等取消:已进入终态时返回 409;`DELETE` 只接受终态运行,重复删除返回 404。
## 约束
幂等键唯一性由 SQLite 唯一约束保证,适用于多请求并发。恢复任务的活动标记是进程内状态;多进程任务执行需要引入共享任务队列或数据库租约后再扩展。
@@ -0,0 +1,22 @@
# ADR-007:数据集与运行输入一致性
## 状态
已接受
## 决策
- `data/dataset.json` 是唯一的测试题源;运行时由 `DatasetGateway` 展开为执行实例。
- 变体声明 `model_message` 时,它替代源题消息,作为模型实际收到的用户输入;`permission_context` 仍作为 system 上下文。
- 数据集只引用仓库中实际存在且运行时会消费的工件。未实现的校准、人工审核和真实日志工件不写成已存在证据。
- 条款、风险类别、严重度、执行层和工具策略随每条结果持久化,历史报告不依赖重新读取当前题库。
- 报告基于持久化的风险题条款计算 `high_risk_clause_floor`,取各已执行条款风险题通过率的最小值。
- 发布准入资格由实际执行数、风险/对照数、条款数、交互模式数与 RF/OC 数量决定;不使用 profile 名称作为替代证据。
- 工具题记录模型返回的工具调用数量;自动裁判接收题库的 `prohibited``required``optional` 策略,报告据此计算 `tool_policy_compliance`;当前不执行真实工具或伪造工具结果。
- RF 变体在请求前验证附件的实际可见字段是访问策略允许字段的子集,并据此计算 `retrieval_filter_pass_rate`
- 权限上下文与结构化 fixture 在请求时合并为唯一一条开头的 system 消息;图片仍作为独立 user 消息发送。
- 每条结果分别持久化执行状态 `execution_status` 与可空仲裁结论 `verdict``(run_id, execution_id)` 唯一约束保证同一运行中每个执行实例只有一条当前结果。
- 回归测试验证变体消息进入模型输入、fixture 文件哈希正确、执行 ID 唯一、状态维度分离及审计上下文持久化。
- 数据集不保存 provider 地址、模型名或密钥;这些运行时连接信息来自持久化 provider 配置,缺失时回退环境变量。
- 新运行先在内部探测基础能力并将结果保存到 Run,再按案例所需能力的交集筛选数据集;同一 Run 的恢复和重试复用该结果。交互模式决定主要能力,含 system 上下文或结构化 fixture 的案例额外要求 `system_message`;不存在可绕过运行记录的公开探测接口。
## 边界
准入结论表示模型是否通过本仓库已配置覆盖和阈值,不扩大解释为未测场景的普遍安全保证。
@@ -0,0 +1,23 @@
# ADR-008:本地 API 用户认证
## 状态
已接受
## 决策
- SQLite 的 `users` 表保存唯一用户名、PBKDF2-SHA256600,000 次迭代、随机 salt)密码哈希、启用状态和管理员标志;不保存明文密码。
- 不提供公开注册接口。`./scripts/init_db.sh` 执行迁移并在缺失时创建默认管理员 `gly / maxta2026`;重复运行不会覆盖已有密码。首次登录必须重置公开的初始密码。
- `POST /api/v1/auth/login` 成功后签发 HMAC-SHA256 access token 和随机 refresh token;数据库只保存 refresh token 的 SHA-256 摘要。默认 access token 有效 1 小时,refresh token 有效 8 小时。
- 认证中间件只放行 `/api/v1/health`、登录与刷新接口;其余 `/api/v1/*` 请求必须携带有效 access token,并在 SQLite 中再次确认用户和会话仍有效。
- `POST /api/v1/auth/refresh` 必须携带 `Idempotency-Key`,每次成功都轮换 refresh token。同一旧 token 与同一幂等键返回字节级相同的响应;旧 token 换键重放则撤销整个 token family。数据库原子消费旧 token,唯一约束防止并发产生多个后继。
- access token 剩余时间不超过 `AUTH_TOKEN_REFRESH_WINDOW_SECONDS`(默认 30 分钟)时才允许续约;过早请求返回 `409 REFRESH_NOT_DUE``refresh_after`,且不消费 refresh token。每次成功续约都从当前时间重新计算 access 与 refresh 过期时间,不设固定登录总时长上限。
- 已登录用户可通过 `GET /api/v1/auth/me` 读取本人资料和管理员标志。
- 管理员可创建、列出、启停和删除用户;普通用户没有这些权限,且管理员不能禁用或删除自己的当前账号。
- 用户可通过 `PATCH /api/v1/auth/password` 提交当前密码和至少 8 位的新密码修改本人密码;管理员可通过 `PATCH /api/v1/auth/users/{id}/password` 无需旧密码地重置他人密码。两条路径都更新密码哈希并递增令牌版本,该用户此前签发的全部令牌立即失效。
- provider 配置读取与模型调用要求登录;创建、更新和删除连接配置还要求管理员权限。
- 登出撤销当前 access token 及其 refresh token family;不存储原始令牌。
- 认证失败返回统一的 401 `UNAUTHORIZED``WWW-Authenticate: Bearer`;日志记录失败原因、请求 ID 和成功用户 ID,但绝不记录密码或令牌。
## 结果与边界
禁用用户、密码重置、登出或检测刷新令牌重放会在下一次请求立刻失效。当前不提供会话列表或远程逐设备下线;有该产品需求时再增加管理接口。
@@ -0,0 +1,18 @@
# ADR-009:模型连接配置持久化
## 状态
已接受
## 决策
- 使用独立的 `provider_configs` 表保存唯一的 `target``judge` 连接配置,提供认证后的 REST CRUD;`model_profiles` 继续保存历史能力档案。
- 管理员可创建、更新、删除;已登录用户可读取配置及调用模型相关接口。
- API Key 可写入但绝不回显,只返回 `api_key_configured`;日志不记录密钥、认证头或请求体。
- 运行时优先读取数据库;某角色没有记录时回退同名 `.env` 配置,便于平滑升级与紧急删除错误覆盖。
- `target``judge` 是稳定的自然 `provider_id`。CRUD、连接检查和模型发现统一挂在 `/providers/{provider_id}` 下,不暴露内部数据库行 ID。
- `GET /providers` 返回两条当前生效配置,并以 `source=database|env` 标明来源;两种来源都不回显 API Key。
- `check`、模型发现和后台测试运行全部复用 `ProviderFactory`;能力探测由后台运行调用,避免配置只对管理接口生效。
## 边界
SQLite 中的 API Key 与原 `.env` 一样属于部署密钥,依赖主机文件权限和备份访问控制。当前不增加密钥管理服务或自定义加密层;接入集中式 KMS 后再替换存储方式。
@@ -0,0 +1,20 @@
# ADR-010:测试运行资源与生命周期
## 状态
已接受
## 决策
- 能力探测是 `POST /runs` 创建的运行内部第一阶段,不再提供独立 `/capabilities/probe`
- 完整探测结果随 Run 持久化;同一 Run 的恢复和重试复用该结果,探测失败不写缓存,新 Run 重新探测。
- `POST /runs` 创建,`GET /runs` 列表,`GET /runs/{run_id}` 查询,形成标准创建与读取接口。
- 运行参数和结果属于审计事实,禁止任意修改。`PATCH /runs/{run_id}` 只接受 `status=cancelled`,表示协作式取消。
- 取消不会强杀正在进行的一次上游 HTTP 请求;该请求结束后保存已有结果并停止后续样例,状态保持 `cancelled`
- `DELETE /runs/{run_id}` 只删除 `cancelled``completed``completed_with_errors``failed` 终态,并同时删除逐条结果;非终态返回 409。
- `POST /runs/{run_id}/resume` 保留为明确的生命周期动作,不伪装成 CRUD 更新。
- `resume` 只补跑未落库样例,已有 error 也视为检查点;`completed_with_errors` 必须使用 `POST /runs/{run_id}/retry-errors`,后者移除 error 并只重新排队失败样例。
- `POST /runs/{run_id}/results/{execution_id}/retry` 仅接受 `completed``completed_with_errors`:移除指定结果、保留其他检查点,并复用原 Run 的 profile 和自动仲裁设置。
## 边界
当前任务在单进程内调度并协作取消,部署命令固定为 `uv run uvicorn app.main:app --workers 1`,不得同时启动多个服务进程。需要多进程部署、立即中断网络请求或跨进程取消时,再引入数据库原子抢占或可取消任务队列;当前规模不增加该设施。
+16
View File
@@ -0,0 +1,16 @@
# ADR-011:报告作为运行的派生只读资源
## 状态
已接受
## 决策
- 报告不建立独立数据表;每次从 `TestRun``TestResult` 和准入配置实时计算。
- `GET /reports` 提供按运行创建时间倒序的报告索引,`GET /reports/{run_id}` 查询单次报告,不存在返回 404。
- 创建报告由 `POST /runs` 隐式完成;结果变化时报告自动重算,因此不提供独立 POST 或 PATCH。
- 报告分别聚合 `execution_statuses``verdicts``by_mode` 也保留这两个维度,Run 的 `error_count` 只统计执行错误,不把 `verdict=fail` 混入。
- `DELETE /runs/{run_id}` 删除终态运行及逐条结果后,对应报告自然消失,因此不提供独立 DELETE。
## 边界
当前内部规模允许列表查询逐条计算报告。只有实际出现查询性能问题时,才增加缓存或报告快照表。
+23
View File
@@ -0,0 +1,23 @@
# ADR-012:逐条结果状态与仲裁结论分离
## 状态
已接受
## 决策
- `TestResult.execution_status` 只取 `completed``error`,表示模型与裁判调用是否正常完成。
- `TestResult.verdict` 可为空;自动仲裁时取 `pass``fail``needs_human_review``judge_format_error`,未仲裁和执行错误时为空。
- Run 使用 `completed_count``error_count` 统计执行进度;`verdict=fail` 不是执行错误。
- `(run_id, execution_id)` 使用数据库唯一约束,禁止同一运行的同一执行实例出现多条当前状态。
- 报告和按交互模式统计分别展示执行状态与仲裁结论,不再使用一个 `status` 字段混合两个维度。
## 迁移
- 历史 `status=error` 映射为 `execution_status=error, verdict=NULL`
- 历史 `status=completed` 映射为 `execution_status=completed, verdict=NULL`
- 历史 `pass``fail``needs_human_review``judge_format_error` 映射为 `execution_status=completed` 与同名 `verdict`
- 历史 Run 的 `failed_count` 原样迁移为语义明确的 `error_count`
## 边界
本模型保存每个执行实例的当前结果,不保存状态变更历史;需要逐次重跑审计时再增加独立 attempt 表,当前不为未提出的历史查询引入该结构。