docs: sync every document with the behavior at HEAD

Each document was verified claim by claim against the source. Corrections
that mattered: client API keys have no per-key model scoping; admin
requests never need to send x-request-id and the configurable header name
does not apply to admin authentication; GET /v1/models serves two distinct
response shapes depending on client_version; the crate dependency rules
are convention rather than machine-enforced across all crates; the HTTP
server and worker runtime live in gateway-host, not apps/gateway; the
OpenAI path forwards upstream bytes verbatim over both SSE and WebSocket
while xAI translates between the Grok and Responses wires. Document the
graceful-shutdown budget, the CORS contract, the migration freeze rule,
and the integration-test environment variables, and drop the stale login
throttling references.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
zyycn
2026-07-27 01:05:33 +08:00
co-authored by Claude
parent 34a6ccce43
commit 1bd40ffa67
6 changed files with 84 additions and 38 deletions
+3 -3
View File
@@ -26,7 +26,7 @@
| 协议 | OpenAI Responses JSON、SSE、WebSocket 与模型目录 |
| Provider | 固定 OpenAI、xAI 两个 Provider;各自管理 OAuth、账号、额度与模型目录 |
| 路由 | Client Key 绑定 Provider、全局模型映射、会话亲和与同 Provider 安全 fallback |
| 透明边界 | OpenAI 保持 Responses 语义透明;xAI 的协议适配只驻留在 `providers/xai` |
| 透明边界 | OpenAI SSE 与 WebSocket 均原样转发上游字节;xAI 在 `providers/xai` 内做 Grok 与 Responses 协议转换 |
| 延续 | OpenAI native/replay continuation;xAI 使用客户端提交的完整历史 |
| 管理 | Client Key、账号、模型目录、设置、观测、系统更新与 OAuth |
| 计量 | 模型请求 Token、费用、延迟、账号与 Provider 归因 |
@@ -91,7 +91,7 @@ curl -i http://127.0.0.1:8080/healthz
2. 在账号页选择对应 Provider:OpenAI 支持 OAuth、账号文件与 Agent Identity;xAI 支持 OAuth
和兼容账号文件导入。
3. 按需配置客户端模型到上游模型的全局映射;未命中映射的模型名原样透传。
4. 创建 `sk_...` Client Key,绑定 `openai` 或 `xai`,并设置模型范围、速率与并发限制。
4. 创建 `sk_...` Client Key,绑定 `openai` 或 `xai`,并设置速率与并发限制。
> [!IMPORTANT]
> xAI 使用 OAuth session,不支持把 xAI API Key 作为上游 credential。
@@ -158,7 +158,7 @@ docker compose -f deploy/compose.yaml up -d
```
> [!IMPORTANT]
> `.runtime/` 保存 PostgreSQL、Redis、日志、OpenAI 会话锚点和自更新状态。删除该目录会永久清除
> `.runtime/` 保存 PostgreSQL、Redis、日志、OpenAI 会话锚点密钥和自更新状态。删除该目录会永久清除
> 运行状态。
> [!WARNING]
+15 -2
View File
@@ -5,7 +5,7 @@
- `config.yaml`:应用行为与真实凭据,由 `config.example.yaml` 复制得到并被 Git 忽略。
- `compose.yaml`:镜像、容器网络、端口、目录映射、健康检查和资源限制。
项目不使用 `.env` 配置文件。Compose 中的少量环境变量只描述容器内部拓扑,不是用户配置入口。
项目不使用 `.env` 配置文件。Compose 中的少量环境变量只描述容器内部拓扑与发布链路装配,不是用户配置入口。
## 准备
@@ -70,6 +70,17 @@ curl -i http://127.0.0.1:8080/healthz
不要把未脱敏的 `docker compose config` 或 `docker inspect` 输出上传到工单;它们会包含
PostgreSQL/Redis 启动密码。日常校验使用 `config --quiet`。
## 优雅关停
收到停止信号后,应用先停止接收新连接并 drain 存量连接;整个 drain 共享一个从停止信号
起算的绝对截止点(`host.drain_timeout_seconds`,默认 30 秒),逾期放弃等待,存量连接随
进程退出终止。drain 结束后才关停后台 worker,预算为
`host.worker_shutdown_timeout_seconds`(默认 30 秒),两段预算按最坏情况串联。
Compose 的 `stop_grace_period` 为 30 秒,覆盖空闲与常规负载下的关停;若存量长连接把两段
预算全部用满(合计 60 秒),Docker 会在宽限期结束时 SIGKILL。要求最坏情况下也完整走完
drain 与 worker 收尾时,把 `stop_grace_period` 调到大于两个超时之和。
## 本地开发
本地 PostgreSQL 和 Redis 可继续由 Compose 启动:
@@ -81,7 +92,9 @@ cargo run -p codex-proxy-rs
```
后端会从当前目录向上查找 `deploy/config.yaml`。相对数据和日志目录以该文件所在目录解析;
Compose 只把监听地址和数据库、Redis 地址固定覆盖为容器内部服务名。
Compose 把监听地址和数据库、Redis 地址固定覆盖为容器内部服务名,并把前端静态目录指向
容器内构建产物。PG/Redis 集成测试所需的 `CPR_TEST_DATABASE_URL` / `CPR_TEST_REDIS_URL`
约定见 [迁移文档](../backend/migrations/README.md)。
## 持久化与备份
+22 -8
View File
@@ -1,6 +1,6 @@
# Codex Proxy RS 接口
本文列出 v3.0.0 当前公开 HTTP 接口。路由事实以
本文列出 v3 当前公开 HTTP 接口。路由事实以
`backend/crates/gateway-api/src` 中的 router 为准。
## 1. 鉴权与公共约定
@@ -22,8 +22,12 @@ Client Key 固定绑定一个 Provider:`openai` 或 `xai`。同一次请求不
- 浏览器登录后得到的 `cpr_admin_session` Cookie;
- `x-api-key: <admin-api-key>`。
已鉴权的管理请求还必须携带非空 `x-request-id`。管理端响应统一带
`Cache-Control: no-store`。
请求无需自带 `x-request-id`;缺失时服务端自动生成 UUID 并在响应头回传同一 request ID。
`api.request_id_header` 只改变注入与回传的 header 名,而管理端鉴权固定读取 `x-request-id`,
因此启用管理端时该配置必须保持默认值。管理端响应统一带 `Cache-Control: no-store`。
配置了 CORS 白名单 origin 时,跨域请求以凭据模式放行,仅允许 `GET`/`POST` 方法和
`authorization`、`content-type`、`x-api-key` 与 request ID 四个请求头,不使用通配符。
普通成功响应使用以下信封:
@@ -36,8 +40,8 @@ Client Key 固定绑定一个 Provider:`openai` 或 `xai`。同一次请求不
```
错误响应仍使用 `code`、`message`、`data`,其中 `data` 为 `null`。稳定业务码包括参数错误
`40001`、会话缺失 `40101`、资源不存在 `40401`、revision 冲突 `40901`、上游失败 `50201`
和依赖不可用 `50301`。
`40001`、会话缺失 `40101`、登录凭据错误 `40102`、管理 API Key 错误 `40103`、资源不存在
`40401`、revision 冲突 `40901`、内部错误 `50001`、上游失败 `50201` 和依赖不可用 `50301`。
### 配置 revision
@@ -60,12 +64,17 @@ Client Key 固定绑定一个 Provider:`openai` 或 `xai`。同一次请求不
| `POST` | `/v1/responses` | OpenAI Responses JSON;`stream=true` 返回 SSE,否则返回完整 JSON |
| `GET` | `/v1/responses` | 通过 HTTP Upgrade 建立 Responses WebSocket |
| `POST` | `/v1/responses/review` | 使用同一 Responses 合同发起 review 子代理请求 |
| `GET` | `/v1/models` | 返回当前 Client Key 所属 Provider 的可用公开模型 |
| `GET` | `/v1/models` | 返回当前 Client Key 所属 Provider 的可用公开模型;有两种响应形态,见下 |
| `GET` | `/v1/models/catalog` | 返回 Codex 客户端使用的模型目录 |
| `GET` | `/v1/models/{model_id}/info` | 返回 Codex 客户端使用的单模型信息 |
| `GET` | `/v1/models/{model_id}` | 返回 OpenAI 兼容的单模型详情 |
OpenAI 路径保留客户端 Responses wire 语义;xAI 所需的协议转换只在 xAI Provider 内完成。模型映射
`GET /v1/models` 默认返回 OpenAI 兼容列表 `{"object": "list", "data": [...]}`;请求携带非空
`client_version` query 参数(Codex 客户端)时改为返回 Codex 专用目录合同 `{"models": [...]}`。
OpenAI 路径保留客户端 Responses wire 语义,上游事件字节在 HTTP SSE 与 WebSocket 两条链路上都
原样转发;xAI 是 Grok wire 与 Responses wire 之间的协议转换层,转换只在 xAI Provider 内完成,
上游结构化错误的 message/code/type 会透传给客户端,其中内嵌的账号指纹 UUID 已脱敏。模型映射
是全局精确映射,未命中时模型名原样交给所属 Provider。
## 4. 管理员认证
@@ -111,6 +120,7 @@ OpenAI 路径保留客户端 Responses wire 语义;xAI 所需的协议转换
- OpenAI 接受 OAuth 账号文档、账号 bundle 和 Agent Identity;
- xAI 从单账号 object 或 `accounts` 数组中提取 OAuth token;包装中的代理、并发、优先级等字段不参与认证;
- xAI 批量导入逐条独立校验:失败条目跳过并记录日志,不中断其余条目,仅当没有任何条目成功时整个导入才报错;
- xAI API Key 不是受支持的账号 credential;
- 导入仍会通过 Provider 的身份或刷新链路校验,不能只凭文件外形写入账号。
@@ -220,6 +230,9 @@ auditRetentionDays
request/response/upstream ID、outcome 与搜索文本。诊断 `dimension` 可取 `model`、`account`、
`apiKey`、`provider`、`transport`、`failureClass`、`status`。
汇总与洞察中的请求数与 outcome 分布覆盖筛选范围内全部请求;token、缓存、延迟与成本聚合仅统计
已完整交付客户端的成功响应。
## 9. 版本、更新与重启
| 方法 | 路由 | 主要 query/body | 说明 |
@@ -232,5 +245,6 @@ request/response/upstream ID、outcome 与搜索文本。诊断 `dimension` 可
| `POST` | `/api/admin/system/rollback` | 无 | 回滚到保留的上一版本 |
| `POST` | `/api/admin/system/restart` | 无 | 请求进程重启 |
在线更新仅在当前部署模式、Release 资产和进程重启能力都满足要求时可用。仓库发版流程见
在线更新仅在当前部署模式、Release 资产和进程重启能力都满足要求时可用,且只在同一 major 版本内
提供:跨大版本目标会以 `40901` 冲突拒绝,需按发布说明手动迁移。仓库发版流程见
[部署文档](../deploy/README.md) 与根目录 [README](../README.md)。
+27 -16
View File
@@ -21,16 +21,16 @@ Gateway Engine 不识别具体 Provider 协议;Provider 不拥有客户端 adm
```text
backend/
├── apps/gateway/ composition root、HTTP server、后台任务
├── apps/gateway/ composition root(Bundle 装配)
├── crates/gateway-core/ operation、routing、engine、policy、accounting
├── crates/gateway-protocol/ 可共享 wire contract 与 canonical event
├── crates/gateway-admin/ 管理领域、用例与抽象端口
├── crates/gateway-store/ PostgreSQL、Redis adapter
├── crates/gateway-api/ OpenAI Responses 与 Admin HTTP adapter
├── crates/gateway-host/ host/update/system 能力
├── crates/gateway-host/ 配置与日志、HTTP serve/drain、worker 运行时、自更新
├── crates/providers/openai/ OpenAI credential、catalog、transport
├── crates/providers/xai/ xAI/Grok credential、catalog、transport
└── migrations/0001_initial.sql 唯一终态初始迁移
└── migrations/ 编号迁移与冻结清单 .frozen-sha256
```
依赖规则:
@@ -42,7 +42,7 @@ backend/
- `gateway-store` 实现 Admin/Core 端口,但不拥有业务策略。
- 只有 `apps/gateway` 组合具体实现。
各 crate 的 architecture 测试强制验证 crate 集合、依赖 DAG、生产源码边界与测试树。
依赖 DAG 主要由上述约定与评审维护,机器校验只覆盖两处:`gateway-api` 的 architecture 测试冻结自身源码树与测试树,并禁止 manifest 出现 store/Provider/基础设施依赖(禁止清单不含 `gateway-host`);`apps/gateway` 的 architecture 测试冻结应用源码树、禁止生产测试挂载并约束 bootstrap 只做装配。其余 crate 没有 architecture 测试。
## 3. 请求执行链
@@ -64,7 +64,7 @@ API decode/auth
1. 每个客户端请求只有一条 `model_requests`;attempt 明细保存在其 JSON 事实中,不另建 attempt 表。
2. 任何可能到达上游的调用都必须先有持久请求事实。
3. `not_sent`、`sent`、`ambiguous` 是不同的上游发送边界;`ambiguous` 不自动重放。
3. `not_sent`、`sent`、`ambiguous` 是不同的上游发送边界;发送状态是请求级单调水位(`sent` > `ambiguous` > `not_sent`),跨 attempt 只升不降,终态写回不得低于水位;`ambiguous` 不自动重放。
4. `downstream_committed_at` 表示网关已作出不可撤回的交付承诺,从该时刻禁止 retry/fallback;它在实际首字节写出前持久化,不宣称字节已经到达客户端。
5. Provider 每次 `execute` 只能选择一个 credential 并准备一个 cold stream,不得隐藏换号或业务 retry。
6. 下游 commit 前,Core 可按冻结策略处理同一 Provider 内的账号 fallback;禁止跨 Provider fallback。
@@ -78,7 +78,7 @@ Fallback 只允许在同一 Provider 内更换可用账号,不跨 Provider。
明确的上游认证、封禁、额度或 cooldown 错误会更新对应账号状态,使下一次选择排除该账号;满足重放安全条件且尚未 downstream commit 时,可以换号。传输结果不明确时不得假定请求未到达上游。
重试矩阵属于独立策略,本次终态迁移保持 OpenAI 原正式行为与 xAI 参考行为,不在 Provider transport 内增加额外重试。
重试矩阵属于独立策略,OpenAI 保持原正式行为、xAI 保持参考行为,不在 Provider transport 内增加额外重试。
## 5. Continuation
@@ -103,7 +103,12 @@ Provider 独占以下职责:
- HTTP/WebSocket transport 和错误分类;
- quota 投影与 Provider 私有观测字段。
credential 更新使用 `credential_revision` CAS。认证永久失败、封禁、额度耗尽和带截止时间的 cooldown 是账号运行时事实。cooldown 到期后,Core 的有效调度谓词会自然允许账号重新参与选择;Redis cooldown 只是热缓存。成功调用后才可把账号状态重新观测为 `ready`。
客户端可见正文的立场按 Provider 不同:
- OpenAI 是逐字节透明代理:上游 wire 是客户端可见的事实来源,HTTP SSE 帧以原始字节原样下发,WebSocket 上游 JSON 文本原样下发;canonical facts 只从同一帧旁路解析,用于观测、亲和与计费。仅网关内部帧(rate limits、metadata)被就地消费,不向客户端转发。
- xAI 是翻译层:把 Grok wire 解码后重新合成 Responses wire 事件,不承诺字节透明;上游错误按结构化 message/code/type 透出,message 中的 UUID 账号指纹替换为占位符。
credential 更新使用 `credential_revision` CAS。认证永久失败、封禁、额度耗尽和带截止时间的 cooldown 是账号运行时事实。cooldown 到期后,Core 的有效调度谓词会自然允许账号重新参与选择;Redis cooldown 只是热缓存。成功调用后才可把账号状态重新观测为 `ready`。自动 refresh 的瞬态失败(限流、5xx、超时、畸形响应)保留现有凭据并按指数退避推迟下一次刷新,不把账号标记为失效;只有上游明确的永久错误(`invalid_grant`、封禁)才写入终态失效。
`refresh_token_expires_at` 不是公共 SQL 列或 Core 权威状态。xAI 可在 `provider_credentials_json` 内保存它作为 Provider 私有提示;真正失效以 refresh endpoint 返回的永久错误为准。
@@ -141,14 +146,16 @@ credential 更新使用 `credential_revision` CAS。认证永久失败、封禁
- credential JSON 以 Provider 自己的 schema 明文保存在 `provider_credentials_json`;日志、Debug、API 和 audit
不输出其中的 secret,Provider 只在需要发往上游时把值包装为运行时 secret。
- stale recovery 只把超时 `running` 请求收敛为 `incomplete`,不重放业务请求。
- `ops_events` 当前逐条同步持久化,`occurrence_count=1`;OpsFlush 可禁用,不存在隐藏内存聚合权威。
- `ops_events` 逐条同步持久化,`occurrence_count=1`;OpsFlush worker 以 Disabled 声明贡献(没有需要刷写的内存缓冲),不存在隐藏内存聚合权威。
- retention 只删除已满足保留规则的历史事实,不改变运行中请求。
业务 schema 由唯一初始迁移 `0001_initial.sql` 定义。
业务 schema 由 `backend/migrations/` 下的编号迁移按序定义:`0001_initial.sql` 建表,后续迁移只做增量变更。已合入的迁移永久冻结——`.frozen-sha256` 是冻结清单,CI 校验清单相对 PR base 只增不改,启动时 sqlx 重校验已应用迁移的 checksum,不一致直接拒绝启动。规则见 `backend/migrations/README.md`。
## 9. Redis 终态
Redis 保存 admission、lease、circuit/cooldown 热状态、OAuth pending flow 与 runtime change 通知。Redis 丢失后必须从 PostgreSQL 或 Provider 事实恢复;恢复完成前需要保护的 acquire 路径 fail closed。
Redis 保存的协调状态恰好是:客户端 admission 热状态、credential lease/fencing 与调度信号、账号 cooldown 与 availability 热缓存、catalog 缓存、Provider circuit、会话亲和与会话级账号排除、continuation pin、OAuth pending flow、worker leader lease 以及 runtime change 通知。Redis 丢失后必须从 PostgreSQL 或 Provider 事实恢复;恢复完成前需要保护的 acquire 路径 fail closed。
多副本部署时,跨副本协调面只有上述 Redis 状态与 PostgreSQL 事实;WebSocket 连接池、请求画像与 RuntimeSnapshot 副本都是进程内状态,其中 RuntimeSnapshot 依靠周期对账收敛,请求画像依靠各自的官方版本检查收敛。
OAuth pending key:
@@ -167,13 +174,17 @@ create/take 使用 Lua 原子执行。owner mismatch 不删除记录;匹配 ow
## 10. 后台任务与恢复
后台任务由 Provider bundle 和 host bundle 显式贡献,bootstrap 统一启动:
HTTP serve/drain 与 worker 运行时都在 `gateway-host`:Host 负责注册校验、leader lease、失败退避、健康暴露与关闭;`apps/gateway` 的 bootstrap 只装配。WorkerContribution 由 Store、Core 与两个 Provider bundle 显式贡献,host bundle 自身不贡献任何 worker:
- RuntimeSnapshot revision 对账与 Redis change wake-up;
- credential refresh、catalog、quota 等 Provider worker;
- stale execution recovery;
- retention 与必要的 Redis 热状态重建;
- host update/system worker。
- Store:stale execution recovery 与 retention;OpsFlush 以 Disabled 声明贡献。
- Core:RuntimeSnapshot revision 周期对账(无 lease,逐副本运行)与 Redis change 长驻订阅(daemon,逐副本运行)。
- Provider:credential refresh、quota/catalog 健康,以及模型 etag 与官方版本 release 检查(OpenAI CLI、OpenAI Desktop、xAI CLI)。
带 lease 的周期任务在每个周期开始向 Redis 申请一次 leader lease,周期内续租,周期结束即释放。这只是单周期互斥,不是跨周期 leader 选举:各副本独立计时,N 副本部署下同一任务的实际执行频率最坏可达单副本的 N 倍。
自更新、回滚与进程重启不是 worker:它们是 Admin API 触发的 Host 系统操作,只替换并重启处理该请求的那个副本。
关闭时序:收到取消信号后,HTTP drain(axum 优雅关闭与游离连接等待)共享同一个从信号时刻起算的绝对截止点,逾期放弃等待存量连接;随后 worker 在独立的 shutdown 超时内 join。
worker 不得通过导入 Admin use case 绕过边界,也不得维护第二份业务状态。
+7 -5
View File
@@ -19,10 +19,12 @@ description: Codex Proxy RS 仓库开发指南。Use when changing or auditing i
- `gateway-api` 只做协议适配;`gateway-core` 编排;Provider 独占 credential、catalog 与 transport;`gateway-store` 实现持久化端口。
- Provider 每次调用只选择一个 credential,不隐藏换号、业务 retry 或跨 Provider fallback。
- PostgreSQL 是持久化权威;Redis 只保存可恢复协调状态和 OAuth pending flow。
- 已应用的数据库迁移按字节冻结(清单 `backend/migrations/.frozen-sha256`,CI 做 append-only 校验);
schema 变更一律新增编号迁移,规则见 `backend/migrations/README.md`。
- 当前只有 `openai` 与 `xai` 两个 Provider;账号归属 Provider,不存在 Provider Instance 层。
- OpenAI 的 OAuth 与 Agent Identity credential 由 `providers/openai` 独占解析;账号导出使用 CPR 文档,Agent Identity
只输出运行所需身份材料,不伪造 OAuth 字段。
- 测试放在各 package 的 `tests/`。禁止在生产 `src/` 写 test-only 代码。
- 测试放在各 package 的 `tests/`,统一挂在单一 `main` 集成测试目标下(`--test main`)。禁止在生产 `src/` 写 test-only 代码。
- Vue 使用 `<script setup lang="ts">`、现有基础组件和主题 token。
- README 面向使用者,保持简短;长期架构只写入 `docs/architecture.md`。
- 完整 HTTP/Admin 路由与敏感导入/导出格式写入 `docs/api.md`。
@@ -41,16 +43,16 @@ description: Codex Proxy RS 仓库开发指南。Use when changing or auditing i
从仓库根目录执行:
```bash
cargo +1.97.0 fmt --manifest-path backend/Cargo.toml -- --check
cargo +1.97.0 fmt --all --manifest-path backend/Cargo.toml -- --check
cargo +1.97.0 clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings
cargo +1.97.0 test --manifest-path backend/Cargo.toml --locked
cargo +1.97.0 test --manifest-path backend/Cargo.toml --test main --locked
pnpm --dir frontend format:check
pnpm --dir frontend build
docker compose -f deploy/compose.yaml config --quiet
```
后端完整测试需要 PostgreSQL、Redis,以及与 `deploy/config.yaml` 一致的
`CPR_TEST_DATABASE_URL`、`CPR_TEST_REDIS_URL`。
PostgreSQL/Redis 集成测试需要指向本地实例的 `CPR_TEST_DATABASE_URL`、
`CPR_TEST_REDIS_URL`;本地未设置时这些测试静默跳过,CI 缺失则直接失败。
## Git 与发布
+10 -4
View File
@@ -31,15 +31,17 @@
- `gateway-api` 不写具体 Provider 分支。
- Core 冻结 RuntimeSnapshot、RoutePlan、retry/fallback 与 downstream commit 边界。
- Provider 每次 `execute` 只选择一个 credential 并返回 cold canonical stream。
- OpenAI 数据面是透明代理:请求 body 逐字段(含未知字段与顺序)原样透传,HTTP SSE 与 WebSocket 的上游业务事件逐字节原样转发;xAI 是 Grok wire 与 Responses wire 的翻译层,向客户端转发结构化上游错误 message/code/type,message 先擦除 UUID 账号指纹。
- 换号只由 Core 在下游 commit 前按重放安全性决定。
- fallback 只允许同一 Provider kind 内的账号;不跨 Provider,也不存在 Provider Instance 层。
- OpenAI continuation 为 native → replay owner → replay any;xAI 使用客户端完整历史。
- continuation 升级链由 Core 冻结:native(原生 handle 绑定原账号)→ replay owner(原账号完整 transcript 重放)→ replay any(transcript 已可携带,允许换账号);两个 Provider 都实现全链,xAI 的 transcript 由不透明 session state 承载,跨账号重放前做脱敏投影。
- Provider wire profile 以配置为启动基线,并由共享运行时状态统一发布。OpenAI CLI 读取 `@openai/codex`、Desktop 读取官方 appcast、xAI CLI 读取 `@xai-official/grok`;发现新版本后自动更新对应版本字段,所有消费边界不得维护独立常量。
- `downstream_committed_at` 是不可撤回交付承诺,不是首字节已经写达的证明。
## 存储
- PostgreSQL 只有 `0001_initial.sql` 定义的七张终态业务表。
- PostgreSQL 业务表只有 `0001_initial.sql` 定义的七张;后续编号迁移只做增量(如 `0002` 的保留期删除索引)。
- 已应用迁移按字节冻结:`backend/migrations/.frozen-sha256` 是冻结清单,CI 相对 PR base 做 append-only 校验;schema 变更一律新增编号迁移,规则见 `backend/migrations/README.md`。
- `config_revision` 只用于会改变调度快照或安全配置的管理 mutation。
- quota、cooldown、catalog generation、自动 refresh 不推进全局 revision。
- refresh 只推进账号 `credential_revision`;Redis cooldown 是可丢失热缓存。
@@ -55,14 +57,18 @@
## 验证
```bash
cargo +1.97.0 fmt --manifest-path backend/Cargo.toml -- --check
cargo +1.97.0 fmt --all --manifest-path backend/Cargo.toml -- --check
cargo +1.97.0 clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings
cargo +1.97.0 test --manifest-path backend/Cargo.toml --workspace --all-targets --locked
cargo +1.97.0 test --manifest-path backend/Cargo.toml --test main --locked
pnpm --dir frontend format:check
pnpm --dir frontend build
docker compose -f deploy/compose.yaml config --quiet
```
每个 package 的集成测试统一挂在单一 `main` 测试目标下(`autotests = false`)。
`gateway-store` 的 PostgreSQL/Redis 集成测试需要 `CPR_TEST_DATABASE_URL`、
`CPR_TEST_REDIS_URL`;本地未设置时静默跳过,CI 缺失则直接失败。
真实账号测试只从仓库外路径读取,不打印或复制 credential。
真实数据面验证使用两个显式 ignored 测试: