first commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# ADR-001:采用 FastAPI 作为服务框架
|
||||
|
||||
## 状态
|
||||
已接受
|
||||
|
||||
## 背景
|
||||
原版本以脚本为主,不利于前端接入、统一 API 文档和长期演进。
|
||||
|
||||
## 决策
|
||||
使用 FastAPI 作为应用入口,核心能力全部通过 REST endpoint 暴露,并保留清晰的 service/provider/repository 分层。
|
||||
|
||||
## 结果
|
||||
- 自动生成 Swagger 和 ReDoc;
|
||||
- 前端可直接调用;
|
||||
- 输入输出由 Pydantic 校验;
|
||||
- 已通过统一认证中间件保护业务 API;后续仍可增加任务队列和前端界面。
|
||||
|
||||
## 不做的事情
|
||||
当前不引入微服务、消息队列或分布式任务系统,避免超出当前核心需求。
|
||||
@@ -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` 增加用户令牌版本;密码重置递增版本,使旧令牌立即失效。
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-003:分层架构与设计模式
|
||||
|
||||
## 状态
|
||||
已接受
|
||||
|
||||
## 采用模式
|
||||
- Application Factory:`create_app`
|
||||
- Strategy:`ModelProvider`
|
||||
- Factory:`ProviderFactory`
|
||||
- Repository:数据库访问
|
||||
- Service Layer:能力探测、测试计划、执行、报告
|
||||
- Dependency Injection:FastAPI Depends
|
||||
- Gateway:DatasetGateway
|
||||
|
||||
`ProviderFactory` 复用同一数据库会话读取持久化的 `target`/`judge` 配置;缺少对应记录时才读取 `Settings` 中的环境变量。连接检查和后台运行走同一工厂;能力探测只作为运行内部阶段,避免配置与执行分叉。
|
||||
|
||||
## 边界
|
||||
不引入复杂领域事件、CQRS、事件溯源或微服务。当前规模下这些会增加维护成本,不能直接提升核心测试能力。
|
||||
@@ -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-SHA256(600,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`,不得同时启动多个服务进程。需要多进程部署、立即中断网络请求或跨进程取消时,再引入数据库原子抢占或可取消任务队列;当前规模不增加该设施。
|
||||
@@ -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。
|
||||
|
||||
## 边界
|
||||
|
||||
当前内部规模允许列表查询逐条计算报告。只有实际出现查询性能问题时,才增加缓存或报告快照表。
|
||||
@@ -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 表,当前不为未提出的历史查询引入该结构。
|
||||
Reference in New Issue
Block a user