docs: P2 CI+test baseline design + 5-task plan (e2e-stack hosts in server repo per private/public token asymmetry)

This commit is contained in:
2026-09-30 10:39:16 +08:00
parent 7e7d1ab142
commit 7a6262f5ce
2 changed files with 479 additions and 0 deletions
@@ -0,0 +1,76 @@
# P2 CI + 测试基线 — 设计规格
日期:2026-09-30 | 状态:已批准(用户指令:写计划、派子代理、端到端交付、不再提问)
涉及仓库:crearte-server(GitHub 私有)、crearte-deploy(GitHub 私有)、crearte(GitHub 公开)
前置事实:P0/P1/P4/P5 已交付;`crearte/.github/workflows/validate.yml` 已存在(pull_request 触发,node 24,check + e2e 两 job);后端/部署仓零 CI。
## 1. 问题定义(ROADMAP 原文拆解)
1. **后端/部署仓无 CI** → 两仓各建 `validate.yml`,风格对齐前端现有文件。
2. **Postgres 集成层静默 skip** → 后端 CI 必须有真实 Postgres service,且**当且仅当集成测试真的跑了才算绿**(skip 在 CI 上下文里视为失败)。
3. **e2e:stack 全链路** → 真栈 Playwright full-loop 首次进 CI(宿主仓因凭据限制定为 crearte-server,见 2.2 修订)。
## 2. 范围决策
### 2.1 crearte-server `validate.yml`(新文件)
- 触发:`pull_request`(paths: `src/**`、workflow 自身)+ `push: master`。与前端文件同构。
- Job `check`(无 DB):`gofmt -l` 空断言 → `go vet ./...` → `go build ./...` → `go test ./...`。pg-gated 在此 job 正常 skip(语义与本地开发一致)。
- Job `integration`:GitHub `services: postgres`(`postgres:17-alpine`,与 crearte-deploy 所用镜像一致,user/pass/db = crearte);`TEST_DATABASE_URL` 指向**空库**——集成测试自带 `repository.Migrate` 引导(`migrate_test.go:testDB` sync.Once + Migrate;service 层 gated 测试也各自 Migrate),所以**空库即全量真跑,同时白赚一条"从空库可完整迁移"的每 PR 回归**。
- `go test -v -count=1 -p 1 ./...`:**`-p 1` 必须**——各 gated 测试对共享表做 `TRUNCATE`,包间并行(默认并行度)必互相踩踏;`-p 1` 序列化包执行是既有测试写法(不改测试)下唯一的正确姿势。
- **skip 守卫**:`tee` 落日志后 `grep 'skipping postgres integration test'` 命中即 FAIL(pipefail 显式开)。没有守卫,这个 job 退化成 check 的复读机。
- Go 版本:`actions/setup-go@v5` + `go-version-file: src/go.mod`(1.24.1)+ `cache-dependency-path: src/go.sum`。
### 2.2 真栈 e2e 的宿主仓:放在 crearte-server,不放 crearte(计划阶段勘察后的范围修订)
原稿把 e2e-stack 挂进 crearte CI——**勘察后不可行**:`XingfenD/crearte-server` 是私有仓(GitHub API 404 验证),公开仓的 workflow 用默认 token 无法 checkout 它;本机无 GitHub API 凭据,不能自助签发 PAT。反向则天然可行:server 的 workflow checkout 公开的 crearte 无需任何 token。
修订后的布局:
- **e2e-stack job 落进 crearte-server 的 validate.yml**:双 checkout(server=默认 token,crearte=公开无 token,均带 path)+ setup-go(go-version-file 指向 server 仓内 go.mod)+ node 24 + playwright chromium;env `CREARTE_STACK_BACKEND_REF: ${{ github.sha }}`——直接测 **PR 那个后端 commit**,信号强于拉 master(原布局只能测 master 后端)。路径适配:两仓 path 必须使 `dirname(REPO_ROOT)/crearte-server` 命中兄弟仓(即 `path: crearte` 与 `path: crearte-server` 同级);脚本用 `$BACKEND/.git` 的 origin 去 fetch SHA(GitHub 允许 fetch 任意 commit SHA)。working-directory 全部 `crearte/src`。
- **crearte 仓的改动缩小为**:`e2e-stack.sh` 支持 `CREARTE_STACK_GO`(显式指定 go 二进制,解 runner 上 `/usr/local/go/bin/go` 版本地板假绿隐患;不设即旧逻辑,本地零变化)与 `CREARTE_STACK_REQUIRED`(置 1 时栈起不来直接 exit 1,封死“CI 里静默 skip=假绿”通道;不设即本地 skip 模式);`validate.yml` 补 `push: branches: [master]` 触发(既有两 job 不动);CHANGELOG 0.17.0。server CI 的 e2e-stack job 设这两个 env。
- full-loop 假绿风险由 REQUIRED 模式从根上封死:webServer 命令非零退出 → playwright job 红。无需 JSON 报告器断言,不赌 reporter 输出格式。
- 已接受的限制(如实记录):纯前端 PR(只动 crearte)不再有真栈 e2e 信号,改由 server 侧每次 PR/push 带最新 crearte master 补位(前端变更合入后即被覆盖);若 owner 日后在 crearte 仓配 `STACK_BACKEND_TOKEN` secret,可镜像同构 job 回 crearte——列为可选增强,不阻塞交付。
### 2.3 crearte-deploy `validate.yml`(新文件)
- 无编译产物,CI 价值 = compose 健全性:`docker compose --profile dev --profile prod --profile full config -q`(`:?` 守卫变量注入假值,另测**缺 `POSTGRES_PASSWORD` 必须非零退出**,证明守卫没被无声旁路)。
- `config` 不校验 bind 路径存在性,故 `../crearte` 挂载在单仓 checkout 下也能过——**如实记录**:单仓 config ≠ 可部署;跨仓挂载完整性仍由本 monorepo 演练承担(P1 已证)。deploy 仓 README「单仓 clone」小节加一行指向本 spec,防误读。
- deploy 的 AGENTS.md「no host-run workflows」指本机别跑 npm/go;GH Actions 跑 `compose config` 不落任何宿主机进程,不违反。
- CHANGELOG 新 patch 条目(双语)。
### 2.4 全局非目标
- **不做分支保护**:无 GitHub API 凭据(`gh` 缺失、token 库空),owner 在 Settings→Branches 手动开启(server/deploy 必开,crearte 建议开)。写进 spec 交付清单,不进计划任务。
- **不验证 Actions 已启用**:私有仓 Actions 可能因计划限制未开/额度耗尽——推送后无 API 可查证。**如实交付**:计划里把「owner 在 GitHub UI 看 Actions 出现绿勾」列为部署后人工尾巴(与 P5 浏览器验收同类 manual-deferred);本机能验的全验(见 3)。
- 不动 git.yoresee.cc 的 Gitea Actions(内层三仓不在 Gitea)。
- 不重构测试文件(除任务 4 实测证明假绿通道时的最小修复)。
- 不引入 lint(golangci-lint)、不引 codecov。
## 3. 验证策略(无 GitHub API,全部本地等价模拟)
任务 4 在本机以与 CI job **同构的容器化步骤**跑三套:
1. `check`:docker `golang:1.24-alpine`(GOPROXY 走 goproxy.cn——本机网络事实)四连。
2. `integration`:`postgres:17-alpine` 容器(端口 5434,全新空库,`--add-host=host.docker.internal:host-gateway` 让测试容器可达)+ `go test -v -count=1 -p 1 ./...` + skip 守卫。跑**两遍**:第二遍证明 Migrate 幂等下的共存。
3. `e2e:stack`:在 monorepo 真实布局(兄弟仓天然就位、宿主 go 1.26.8)跑 `npm run e2e:stack`,断言日志出现 `[stack] ready:`(真绿非假绿),另跑一次「无 docker 假绿反向验证」用 PATH 屏蔽 docker 令 `stack_ok=0`,确认 SKIP 模式在报告中显式可见。
4. deploy config 正反两路。
推送本身(git push 成功)是 Actions 接收 workflow 的充分条件;job 结果查证为人工尾巴。
## 4. 交付物清单
| 仓库 | 文件 | 动作 |
|---|---|---|
| crearte | `src/scripts/e2e-stack.sh` | `CREARTE_STACK_GO` + `CREARTE_STACK_REQUIRED` 两个 env 开关 |
| crearte | `.github/workflows/validate.yml`、`docs/CHANGELOG.md` | push:master 触发;0.16.1 |
| crearte-server | `.github/workflows/validate.yml`、`docs/CHANGELOG.md` | check + integration + **e2e-stack** 三 job;0.11.1 |
| crearte-deploy | `.github/workflows/validate.yml`、`docs/CHANGELOG.md`、`README.md` | compose config 正反两路;0.5.1 |
| wrapper | 本 spec + 计划、ROADMAP P2 行、CHANGELOG 0.2.4 | 收尾 |
| GitHub UI | Actions 启用确认 + 分支保护 | **owner 手动**(见 2.4) |
## 5. 验收标准(Definition of Done)
1. 三仓 `.github/workflows/validate.yml` 合入各自 master 并推送;分支删除。
2. 任务 4 四组模拟全绿且证据留档(`.superpowers/sdd/p2-task-4-report.md`):integration 首跑必须含 `RUN Test…Postgres`/真跑字样、零 "skipping postgres integration test"。
3. `e2e:stack` 真栈一跑 `[stack] ready:` 出现、full-loop 用例计数 >0 且全过;`CREARTE_STACK_REQUIRED=1` 反路在栈起不来时 exit 1(不静默 skip)。
4. ROADMAP P2 → 完成(三 hash);wrapper CHANGELOG 0.2.4;人工尾巴(Actions 查证、分支保护)在 ROADMAP 行或 spec 有明确记载。