docs: add maintainer guidance and API contract

This commit is contained in:
zhoujun0601
2026-08-13 10:36:00 -04:00
parent 0a17b6b1c9
commit b5b36674d3
17 changed files with 649 additions and 11 deletions
+29
View File
@@ -0,0 +1,29 @@
# Codex Helper 文档中心
代码和测试是当前行为的最终事实来源。本目录解释跨文件机制、工程约束和操作流程,不替代源码、根 `README.md` 或后端 API 契约。
## 按任务导航
| 任务 | 必读文档 |
| --- | --- |
| 修改启动、路由、中间件、健康检查或后台任务 | [`backend/runtime-and-api.md`](backend/runtime-and-api.md) |
| 修改初始化、登录、session、请求来源或秘密 | [`backend/authentication-and-security.md`](backend/authentication-and-security.md) |
| 修改 Codex 登录、账号进程、同步、套餐或用量解析 | [`backend/codex-integration-and-usage.md`](backend/codex-integration-and-usage.md) |
| 修改 SQLite、提醒、Telegram、SMTP、清理或备份 | [`backend/data-notifications-and-backup.md`](backend/data-notifications-and-backup.md) |
| 修改 React 路由、总览、设置、状态或 API 调用 | [`frontend/application.md`](frontend/application.md) |
| 开发、构建、测试或检查格式 | [`guides/development-and-validation.md`](guides/development-and-validation.md) |
| 修改镜像、Compose、运行用户或持久化 | [`guides/deployment.md`](guides/deployment.md) |
| 修改高风险行为或核对工程约束 | [`reference/engineering-invariants.md`](reference/engineering-invariants.md) |
| 代码审查 | [`reference/code-review-rules.md`](reference/code-review-rules.md) |
| 修改 API 路径、状态码、字段或兼容文案 | [`../backend/CONTRACT.md`](../backend/CONTRACT.md) |
## 目录职责
- `backend/`:后端运行、认证、Codex 集成、数据和通知的实现事实。
- `frontend/`:前端路由、状态、渲染和 API 集成。
- `guides/`:可执行的开发、验证和部署流程。
- `reference/`:工程不变量与代码审查等查表型规则。
## 维护规则
同一事实只保留一个权威位置,其他文档通过链接引用。用户功能和部署概览继续维护在根 [`README.md`](../README.md);端点级 API 契约维护在 [`backend/CONTRACT.md`](../backend/CONTRACT.md)。只有出现需要长期保留取舍背景的真实决策时,才新增 ADR,不创建空目录或占位文件。
@@ -0,0 +1,25 @@
# 认证与安全
## 初始化与管理员
新数据库始终创建账号表中的默认 Codex 账号,但只有 `settings.initialized` 存在才视为完成安装。`POST /api/v1/setup` 在事务中创建唯一 `admin(id=1)`、通用设置和安装标记;用户名至少 3 位、密码至少 10 位。首次初始化没有额外安装令牌,因此初始化完成前不得把实例直接暴露到不可信网络。
管理员密码使用 argon2id(3 次、64 MiB、2 lanes、32 字节结果和随机 salt)保存。当前没有改密或找回接口;不要通过新增旁路直接写入明文或弱摘要。
## Session 与请求来源
登录成功生成 32 字节随机 token,客户端得到七天 `HttpOnly`、`SameSite=Strict` cookie,SQLite 只保存 SHA-256 摘要。每次受保护请求回查未过期 session。登出删除当前摘要并清 cookie。
所有非 `GET`/`HEAD` 受保护请求还必须携带 `X-Requested-With: codex-helper`。这是当前同源部署下的额外 CSRF 门禁,不替代 session 校验,也不意味着可以放宽 CSP 或 cookie 策略。`Secure` 当前为 false,以支持 README 中直接 HTTP 部署;公网必须由 HTTPS 反向代理保护,调整此兼容行为时同步评估代理终止 TLS 的方式。
登录失败限流仅存在于单进程内,以 `RemoteAddr` 为 key,每 15 分钟最多 10 次。修改反向代理或客户端 IP 处理时,不能未经可信代理白名单就相信任意转发头。
## 密钥与外部凭据
`security.OpenVault` 首次启动创建权限 `0600` 的 `/data/secret.key`。SMTP 密码和 Telegram Bot Token 使用该 32 字节密钥经 AES-GCM 加密,密文写入 SQLite;GET 和保存响应不得返回秘密明文,空密码/Token 表示保留旧值。
Codex OAuth 凭据由 app-server 写入各账号隔离的 `CODEX_HOME`,不经过浏览器或 SQLite。`secret.key`、数据库、Codex 目录和日志都可能包含敏感运行信息,不得提交 Git、加入镜像层或复制到前端。
## HTTP 边界
JSON 解码限制为 1 MiB 并拒绝未知字段。统一安全头包括限制性 CSP、`nosniff`、禁止 iframe 和 same-origin referrer。前端路由、按钮禁用和邮箱掩码均不是服务端授权边界;所有新敏感端点必须在后端经过 `require`,改变 API 方法时还要核对来源头逻辑。
@@ -0,0 +1,35 @@
# Codex 集成与用量同步
## 账号隔离与进程生命周期
每个 `accounts` 记录对应一个 `accountRuntime` 和独立 `codex app-server` 子进程。账号 1 为升级兼容固定使用 `/data/codex`;其他账号使用 `/data/accounts/<id>/codex`。该目录通过子进程 `CODEX_HOME` 注入,保存并刷新 ChatGPT 登录凭据。
`ensureReady` 用 lifecycle mutex 串行冷启动:创建目录、启动子进程、在 20 秒内调用 `initialize`,成功后才标记 ready。仅子进程存活不代表协议已初始化;失败进程必须关闭后重试。`syncing` mutex 串行同账号同步并保护 Dashboard,`stateMu` 保护 ready/stopped。删除账号先从应用 map 移除并停止进程,再删除数据库和精确凭据目录。
## JSONL 协议与登录
客户端以 stdio 启动 `codex app-server`,每行一个 JSON 消息。递增 ID 将响应关联到 buffered channel;请求受调用 context 或 20 秒 timeout 控制。旧进程晚退出时不能把替代进程标记为断开。
设备码登录调用:
```text
account/login/start {type:"chatgptDeviceCode"}
```
前端展示 `verificationUrl` 与 `userCode`。收到 `account/login/completed`、`account/updated` 或 `account/rateLimits/updated` 后异步重拉数据;登录完成最多以 0、1、3 秒退避等待套餐分类可用。官方流程和字段见 [Codex App Server 文档](https://learn.chatgpt.com/docs/app-server)。Dockerfile 固定的 CLI 版本及项目测试仍是实际兼容基线。
## 同步数据流
一次同步依次读取 `account/read`、`account/rateLimits/read` 和 `account/usage/read`:
- `account/read` 决定连接、邮箱、认证模式和套餐;读取失败会使整次同步失败。
- 限额读取兼容 `rateLimitsByLimitId` 多 bucket 和旧 `rateLimits` 单 bucket,再把 primary/secondary 展平。失败时保留空限额而不令整次同步失败。
- 套餐未知时,可从所有可分类且一致的限额 bucket 回填;冲突或未知时必须保持 unknown。
- 用量读取保存 summary 和每日 Token bucket;接口失败时使用空摘要和空历史,不令整次同步失败。
- 每次限额同步写入快照、检测用量百分比显著回落,并更新账号元数据及内存 Dashboard。
`account/usage/read` 的 optional 指标和 daily buckets 可能暂未提供。仅 API Key 或 Bedrock 登录不能保证读取 ChatGPT 用量;不得在缺失数据时合成调用次数、输入/输出 Token、价格或账单日期。
## 套餐类型
用户期望类型仅为连接后的校验提示:`any`、`personal`、`team`。当前 personal 包括 free/go/plus/pro/prolite;team 包括 team/business 及两种 self-serve business 标识。未知新套餐必须显示 unknown,不能静默当作某一类型。相同邮箱和相同已知实际类型的多个账号标记 `possibleDuplicate`,但不会自动合并或删除。
@@ -0,0 +1,32 @@
# 数据、通知与备份
## SQLite 与迁移
数据库位于 `${DATA_DIR:-/data}/codex-helper.db`,启用 WAL、5 秒 busy timeout 和 foreign keys。`backend/internal/store/store.go` 是 schema 与启动迁移的事实来源:
- `settings` 保存通用、SMTP、Telegram、绑定码及安装标记;秘密单独以密文 key 保存。
- `admin` 与 `sessions` 保存唯一管理员和登录会话。
- `accounts` 保存 Codex 连接元数据与期望套餐类型。
- `daily_usage` 和 `limit_snapshots` 按 `account_id` 保存历史,删除账号时级联删除。
- `notifications` 保存稳定去重键、调度时间、结构化消息、状态、次数和错误。
- `telegram_updates` 保存 Bot API offset。
启动迁移必须幂等并兼容早期单账号库:创建默认账号 1,把旧用量和限额数据迁入该账号,补 `expected_kind` 和通知 body。schema 变化应增加覆盖旧结构且保留已有行的测试。
## 清理与备份
每分钟调度器根据 `retentionDays` 清理旧限额、通知和每日用量;允许范围为 30–365 天。`maintenance/backup` 使用 SQLite `VACUUM INTO` 创建独立一致性快照,包含已提交 WAL 数据且不中断写入。
数据库快照不包含 `/data/secret.key`、`/data/codex` 或 `/data/accounts/*/codex`,因此不能单独恢复通知凭据和 Codex 登录。完整灾难恢复必须停止容器并备份、恢复整个 `/data`,同时保持 UID `10001` 可读。不得把 `docker compose down -v` 写成普通升级步骤。
## 提醒生成与重试
限额快照按 `(account_id, limit_id, window_type)` 比较。计划提醒的 key 还包含 `resets_at` 和 `before|after`;异常提前重置以旧快照 ID 生成 `detected_after` key。百分比回落必须超过 `0.01`,旧快照不得超过六小时。
处理器每分钟为当前 Dashboard 生成到期提醒,并只发送 `scheduled_at` 后六小时内的未发送记录。Telegram 与 SMTP 中任何启用渠道失败都会把记录标记为 failed,后续周期在窗口内重试;全部启用渠道成功才标记 sent。稳定 key 和 `INSERT OR IGNORE` 是防重复边界。
## Telegram 与 SMTP
Telegram 保存加密 Token、Chat ID、启用开关和菜单开关。保存 Token 前调用 `getMe`;long polling timeout 为 25 秒,HTTP client timeout 为 35 秒。六位绑定码十分钟有效且一次成功后清除。只有绑定 Chat ID 且启用菜单时才处理查询命令。
SMTP 支持 `starttls`、隐式 `tls` 和 `none`,TLS 最低 1.2;支持可选 PLAIN AUTH,发送 multipart text/html。TCP 连接和后续 SMTP/TLS 读写共享 35 秒 deadline。修改外部调用时必须保留这一超时边界、TLS server name、HTML 转义和不记录秘密的错误处理。
+26
View File
@@ -0,0 +1,26 @@
# 后端运行与 API
## 启动与关闭
入口为 `backend/cmd/server/main.go`。普通启动调用 `app.New`:打开 `/data/codex-helper.db` 并执行兼容迁移、打开或创建 `/data/secret.key`、为数据库中的每个账号创建运行时对象,再组装 HTTP server。`Run` 启动 app-server 保活、每分钟调度器和 Telegram long polling;SIGINT/SIGTERM 触发五秒 HTTP 优雅关闭、停止所有账号进程并关闭数据库。
`codex-helper healthcheck` 将 `${LISTEN_ADDR:-:8080}` 的通配地址转换为 `127.0.0.1` 并请求 `/health/live`,供 Docker `HEALTHCHECK` 使用。
## 请求链路
- `internal/app/app.go`:应用生命周期、账号运行时、路由、静态前端和通用 HTTP 辅助函数。
- `internal/app/api.go`:`/api/v1/` 分派、初始化、会话、账号和用量同步。
- `internal/app/notify.go`:SMTP、Telegram、提醒生成和发送。
- `internal/store/store.go`:SQLite schema、兼容迁移和数据方法。
- `internal/codex/client.go`:与 `codex app-server` 的 JSONL 请求/响应关联。
所有路由由 `http.ServeMux` 承载。API 先处理三个匿名入口,再统一调用 `require`;前端资源从 Go `embed.FS` 提供,未知浏览器路径回退到 `index.html`。完整端点以 [`backend/CONTRACT.md`](../../backend/CONTRACT.md) 为准。
## 后台任务
- `keepCodex` 每秒检查未就绪的账号,串行完成进程启动与协议初始化,成功后立即同步。
- `scheduler` 每分钟按 `syncMinutes` 的 Unix 时间取模触发全账号同步,清理过期历史,并异步处理提醒。
- `telegramLoop` 使用 Bot API long polling;仅已配置 Token 才请求更新。
- app-server 的登录、账号和限额通知会触发带短退避的同步;多次失败将内存 Dashboard 标为 stale 并记录 `lastError`。
这些 goroutine 都以应用 context 为退出边界。修改调度时必须避免无限阻塞、重复启动、停止后重启和同一账号并发同步。
+25
View File
@@ -0,0 +1,25 @@
# 前端应用
前端位于 `frontend/`,使用 React 19、TypeScript、Vite、React Router、Lucide 和 Recharts。`frontend/src/main.tsx` 当前集中承载应用组件和 API 数据类型,`frontend/src/api.ts` 是 fetch 包装层。
## 状态与路由
应用启动先请求 `system/status`,已初始化时再请求 `auth/me`。未初始化渲染安装页;未登录渲染登录页;登录后由 `BrowserRouter` 提供 `/` 总览和 `/settings` 设置,未知路径回到 `/`。这些分支只负责交互,服务端 session 才是安全边界。
主题和当前账号 ID 保存到 `localStorage`。总览先加载账号列表,为当前账号加载 Dashboard,并每 30 秒刷新内存数据;切换账号必须清空旧 Dashboard,避免短暂展示另一账号信息。手动刷新调用账号级 sync 后重新读取 Dashboard。
## API 客户端
所有请求使用相对 `/api/v1/`、`credentials: same-origin`、JSON content type 和 `X-Requested-With: codex-helper`。非 2xx 响应优先显示 `{error}`,否则退化为 HTTP 状态。新增下载或非 JSON 响应不能直接套用当前 `api` helper。
后端的 optional、`null`、空数组及 unknown 套餐必须在 TypeScript 中准确表达。邮箱在账号选择器、总览和设置中统一经过 `maskEmail`;不得把未掩码邮箱添加到新的可见位置。
## 总览与设置
总览显示账户连接、套餐、每个限额窗口的剩余百分比和重置时间、四项摘要及每日 Token 图。`usedPercent` 展示前限制到 0–100,但原始数据语义不得在 API 类型层改写。缺失摘要显示“暂无”,缺失限额显示明确空状态。
设置页四个 tab 全部保持挂载,以保留未提交表单状态;非活动 panel 使用 `aria-hidden` 和 `inert` 隔离。tab 支持方向键、Home 和 End,程序化切换不得改变页面滚动和布局。账号删除必须保留不可撤销确认,并清理正在显示的设备码和本地账号选择。
## 验证重点
纯逻辑和组件测试使用 Vitest;现有浏览器测试使用 Playwright 覆盖设置布局、键盘操作、隐藏表单隔离、账号删除、窄屏溢出和邮箱掩码。修改 effect、轮询或异步加载时应覆盖卸载清理、错误状态及旧响应覆盖新状态;修改 CSS 时同时跑 desktop 和 mobile projects。
+32
View File
@@ -0,0 +1,32 @@
# 部署与运行
用户可见的安装和日常操作以根 [`README.md`](../../README.md) 为准;本文记录维护代码时必须理解的构建和数据边界。
## 镜像结构
`Dockerfile` 有四个阶段:Node 24.19.0 构建 React 静态资源;Go 1.26.0 以 `CGO_ENABLED=0` 构建后端;Node 阶段安装固定 `@openai/codex`;最终 Debian bookworm 镜像只包含 CA、时区、后端、Node runtime 和 Codex 包。
前端产物复制到 `backend/internal/web/dist` 后嵌入二进制。运行层使用 UID `10001` 的 system 用户 `helper`,默认 `DATA_DIR=/data`、`LISTEN_ADDR=:8080`,并暴露 `/data` volume 和 8080。健康检查调用二进制自身的 `healthcheck` 子命令。
## Compose 与持久化
`docker-compose.yml` 本地构建 `codex-helper:latest`,将宿主机 8180 映射到容器 8080,并把命名卷 `codex-helper-data` 挂载到 `/data`。全部数据库、密钥、Codex 配置和多账号凭据都依赖这个卷。
升级应使用:
```bash
git pull --ff-only
docker compose up -d --build
```
不要使用 `docker compose down -v`,也不要在未确认 volume 名和备份前重建、迁移或删除数据卷。需要改变运行 UID、volume 或 `DATA_DIR` 时,必须提供旧数据权限和路径的升级验证。
## 网络与安全
应用自身监听 HTTP。公网部署应放在 HTTPS 反向代理后,初始化前限制访问来源,并保证到 OpenAI 登录/Codex 服务、Telegram Bot API 和所选 SMTP 服务的出站连接。当前只有 `LISTEN_ADDR` 是 Compose 显式环境变量;运行配置由前端保存到数据库。
设备码登录不需要 OpenAI API Key。Codex app-server 为每个 `CODEX_HOME` 管理 ChatGPT token;不要把这些目录暴露为静态文件或外部共享目录。
## 备份与恢复
维护接口下载的 SQLite 快照适合查看或数据库级备份,但不包含解密密钥和 Codex 凭据。完整恢复步骤是:停止容器、备份或恢复整个 `/data`、确认 UID `10001` 可读写、再启动并检查 `/health/live`、`/health/ready`、管理员登录和各账号连接。恢复过程不得只替换数据库而遗失对应 `secret.key`。
+75
View File
@@ -0,0 +1,75 @@
# 开发与验证
## 环境政策
宿主机只保证 Docker 和 Docker Compose。所有 Go、Node、npm、格式化、构建、测试和安全扫描都在容器内执行。以下命令从仓库根目录运行;挂载源码时使用只读模式,只有构建输出确实需要写回工作区时才放宽。
## 后端
完整格式、构建、vet 和测试:
```bash
docker run --rm \
-v "$PWD/backend:/app:ro" \
-v codex-helper-go-mod:/go/pkg/mod \
-v codex-helper-go-cache:/root/.cache/go-build \
-w /app golang:1.26.0-bookworm \
sh -c 'test -z "$(gofmt -l .)" && go build ./... && go vet ./... && go test -count=1 ./...'
```
针对性测试可把最后一段替换为 `go test -count=1 ./internal/store` 或相应 package。测试通过 `t.TempDir()` 创建专用 SQLite,禁止把 `DATA_DIR` 指向运行实例。
## 前端
在临时副本中安装依赖、构建并运行 Vitest,避免容器把 root 所有权的 `node_modules` 写入工作区:
```bash
docker run --rm -v "$PWD/frontend:/src:ro" node:24.19.0-bookworm-slim \
sh -c 'cp -a /src /tmp/frontend && cd /tmp/frontend && npm ci --no-audit --no-fund && npm run build && npm test'
```
依赖或安全改动额外运行:
```bash
docker run --rm -v "$PWD/frontend:/src:ro" node:24.19.0-bookworm-slim \
sh -c 'cp -a /src /tmp/frontend && cd /tmp/frontend && npm ci --no-audit --no-fund && npm audit --omit=dev --audit-level=high'
```
## Playwright E2E
使用与 `frontend/package.json` 中 `@playwright/test` 匹配的官方镜像:
```bash
docker run --rm --ipc=host \
-v "$PWD/frontend:/src:ro" \
mcr.microsoft.com/playwright:v1.62.1-noble \
sh -c 'cp -a /src /tmp/frontend && cd /tmp/frontend && npm ci --no-audit --no-fund && npm run test:e2e'
```
若升级 Playwright,必须同步镜像 tag。E2E 的 API 由 route mock 提供,不要求启动 Go 后端。
## 镜像与 Compose
```bash
docker compose config --quiet
docker build --check .
docker build -t codex-helper:test .
```
涉及容器启动、静态资源或健康检查时,再使用隔离的临时 Compose project 和专用 volume 验证;不得连接或删除用户的运行卷。
## 最低验证矩阵
| 改动 | 最低验证 |
| --- | --- |
| 后端普通逻辑 | 相关 Go test、gofmt 检查、build、vet |
| SQLite schema、迁移、备份 | 新旧 schema 测试、WAL 快照测试、完整后端门禁 |
| app-server 生命周期或同步 | codex/app runtime 单测、并发和失败重试路径 |
| 鉴权、session 或秘密 | security 与 app 测试、成功和拒绝路径 |
| 通知 | reminder 渲染、去重、计划与异常重置测试 |
| 前端 | 相关 Vitest、生产 build |
| 布局、路由、设置或响应式 | Playwright desktop 和 mobile |
| Dockerfile、Compose、持久化 | 配置检查及相应镜像/运行验证 |
| 纯文档 | 链接、术语、事实来源和 `git diff --check` |
先运行针对性检查,再按风险扩大范围。修复缺陷时优先添加修复前会失败的回归测试。
+50
View File
@@ -0,0 +1,50 @@
# Codex Helper 代码审查规则
目标是发现可复现的正确性、安全性、兼容性和可靠性问题,而不是增加评论数量。审查必须结合完整 diff、调用链、测试和部署边界,不能孤立阅读修改行。
## 输出格式
findings 优先,按 `P0` 至 `P3` 排序。每条必须包含严重级别和简短标题、精确文件与起始行号、触发输入或执行顺序、实际影响和最小修复方向。同一根因只报告一次。
- `P0`:可利用的安全突破、不可恢复数据丢失或全系统中断。
- `P1`:高概率生产故障、认证绕过、账号凭据删除或核心同步失效。
- `P2`:条件性但真实的错误、兼容性破坏、状态漂移、重复通知或资源风险。
- `P3`:影响有限且不阻塞合并的真实维护问题。
不要报告纯格式偏好、没有现实失败路径的猜测、仓库已有且未被当前改动暴露的问题,或仅以“缺少测试”为问题本身。没有 finding 时明确说明,并列出未执行验证或剩余风险。
## 审查流程
1. 阅读完整 diff,识别 API、认证、SQLite、Codex runtime、通知、前端、部署和持久数据影响。
2. 搜索修改符号的调用方、被调用方、相似路径和测试,走完实际成功与失败路径。
3. 对照 [`engineering-invariants.md`](engineering-invariants.md) 和 [`backend/CONTRACT.md`](../../backend/CONTRACT.md)。
4. 在容器内运行与风险成比例的验证;复核 finding,删除重复根因和不影响用户的推测。
## 后端专项
- 匿名端点是否意外扩大;session、过期检查和非只读来源头是否在所有路径生效;cookie 改动是否适配 HTTPS 反代。
- setup 是否事务化且不能覆盖管理员;登录限流是否存在竞态、无限内存或错误信任代理头。
- SQLite migration 是否可从旧 schema 启动且保留数据;查询是否带正确 `account_id`;rows、transaction 和临时文件是否关闭。
- 删除账号是否精确停止 runtime、级联历史并删除正确目录,尤其是账号 1 的旧路径。
- app-server 生命周期是否可能双启动、停止后重启、死锁或由旧进程回调污染新进程;pending 请求在所有结束路径是否释放。
- app-server optional/null、多 bucket、未知套餐和部分接口失败是否降级,而非生成错误数据或清除有效凭据。
- 提醒是否稳定去重、限定六小时重试、正确区分 before/after/detected reset;多渠道部分失败是否按既定语义重试。
- SMTP、Telegram 错误是否有 timeout、TLS 和 HTML 转义;设置接口、日志和错误是否泄露秘密。
- 备份是否包含 committed WAL 且不阻塞写入;文案是否误称数据库快照为完整恢复包。
## 前端专项
- 初始化、未登录、登录态路由是否无闪烁或循环;401 后是否进入可恢复状态。
- 切换或删除账号时,旧 Dashboard、设备码和 localStorage 是否清理;异步旧响应是否覆盖新账号。
- API 类型是否准确表达 `null`、optional、unknown 和空列表;错误响应是否可能被当作成功。
- 邮箱是否在所有可见位置掩码;服务端文本和外部 URL 是否以安全方式渲染。
- effect、30 秒 polling、设备码 polling、timer 和 event handler 是否在卸载时清理。
- 设置 tab 是否保持键盘操作、`inert` 隔离、表单状态、滚动位置和窄屏无溢出。
## 部署与验证专项
- 固定 Go、Node、Codex CLI 和 Playwright 版本是否同步 Dockerfile、lockfile 与文档;架构和静态构建是否匹配运行层。
- 静态前端是否在 Go build 前正确复制;`.dockerignore` 是否会丢失必须资源或带入运行数据。
- 最终镜像是否继续非 root,`/data` 权限是否兼容 UID `10001`;Compose 升级是否复用原卷。
- 新环境变量是否同步代码、Compose、README 和部署文档;秘密是否可能进入 build arg、镜像层或日志。
- 最低验证遵循 [`development-and-validation.md`](../guides/development-and-validation.md) 的矩阵。纯文档至少检查链接、术语、事实来源和 `git diff --check`。
+40
View File
@@ -0,0 +1,40 @@
# 工程不变量
修改鉴权、SQLite、账号删除、Codex 进程、秘密、通知、备份或部署边界前核对本页。
## API 与认证
- [`backend/CONTRACT.md`](../../backend/CONTRACT.md) 是路径、状态码、响应字段和兼容文案的契约。匿名入口只能是当前 status、setup 和 login。
- 所有受保护端点必须回查 session;非只读请求还必须验证 `X-Requested-With`。前端路由与按钮不能代替后端门禁。
- session 原 token 只进入 cookie,SQLite 只保存摘要;密码保持 argon2id。错误和日志不得包含密码、cookie、Bot Token、SMTP 密码或 Codex token。
- 初始化是事务性单管理员创建。新增自动初始化能力前必须保留并发与首次公网暴露的安全边界。
## 数据与账号
- `internal/store/store.go` 是 SQLite schema 和兼容迁移的事实来源。启动迁移必须幂等、保留旧数据并启用 foreign keys。
- 旧单账号数据迁到账号 1;账号 1 的凭据路径永久为 `/data/codex`,不能统一搬到 `accounts/1`。
- 删除账号必须先停止并移除精确 runtime,再删除该账号数据库行和精确凭据目录;不得使用未校验路径、glob 或宽泛递归删除。
- `daily_usage` 与 `limit_snapshots` 以 `account_id` 隔离。任何查询、更新、提醒 key 或清理不得串账号。
- `expectedKind` 只是校验期望;未知套餐保持 unknown。相同邮箱提示重复不能成为自动合并依据。
## Codex 运行时
- 每账号一套 app-server 和 `CODEX_HOME`。进程存活与协议 ready 是不同状态,初始化失败必须关闭旧进程。
- lifecycle mutex 串行启动/停止,syncing mutex 串行同步和 Dashboard 访问,state mutex 保护 ready/stopped;不得以无锁读写替换。
- 旧子进程晚退出不能断开新子进程或失败新请求。请求取消和 20 秒 timeout 必须清除 pending channel。
- `account/read` 是同步的硬依赖;rate limits 与 usage 是可缺失数据。optional、`null`、多 bucket 和未知字段必须安全降级。
## 通知、外部输入与秘密
- SMTP 密码和 Telegram Token 使用 `/data/secret.key` 的 AES-GCM 密文保存;设置响应继续掩去秘密。密钥丢失不能通过返回密文或明文“修复”。
- 提醒 dedupe key 必须包含账号、limit、window、reset 或旧 snapshot 身份及 kind;重试窗口保持计划时间后六小时。
- Telegram/SMTP 用户文本进入 HTML 前必须转义。Telegram HTTP client 和 SMTP 连接/读写必须保留 timeout;SMTP TLS 最低 1.2 并验证 server name。
- Telegram 绑定码十分钟且成功即消费;其他 chat 在绑定后不得查询实例数据。
## 前端与部署
- 所有可见邮箱继续掩码。账号切换清空旧 Dashboard;删除账号清除设备码和 localStorage 选择。
- 非活动设置 panel 保持 `inert`,tab 键盘和布局稳定性是现有可访问性契约。
- 前端生产资源嵌入 Go 二进制;Docker build stage 的复制顺序变化必须验证实际嵌入的是新产物。
- `/data` 是唯一完整恢复单元。数据库快照不含 `secret.key` 和 Codex 凭据;任何文档不得暗示其可完整灾难恢复。
- 运行数据、数据库、密钥、凭据、`.env`、`node_modules` 和构建缓存不得进入 Git 或 Docker 构建上下文。