diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a108a6..9afe9a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,6 +164,8 @@ jobs: e2e: name: E2E (live stack) runs-on: ubuntu-latest + # 防挂死:GH Actions 默认 360 分钟,起栈失败若无人超时会把流水线拖住数小时。 + timeout-minutes: 30 # Phase 23 遗留 D-23-D:计划要求 CI 串联 E2E。此前 E2E 从未真正执行过—— # 无 conftest、fixture 不存在、文件在 import 期就崩。本 job 起真实栈后执行。 needs: [backend, migration] @@ -194,20 +196,43 @@ jobs: WECHAT_APP_SECRET= ENV - - name: 起真实栈(postgres + redis + backend) + # 「活栈供给方式」决策(2026-09-18):**在 runner 内用 Compose 起全栈**。 + # 备选是 service containers + 在 runner 上 `mvn package` 后 `java -jar`,但那需要为后端 + # 另写一套 host 网络配置,等于制造第二条与本地不一致的路径;Compose 全栈则与本地验证 + # 路径完全相同(2026-09-18 本机按同一序列实测:中间件三项 healthcheck 全 Healthy、 + # backend /health/live 200、seed 幂等可执行、**pytest tests/e2e 49 passed**)。 + - name: 校验 compose 配置(秒级快速失败,早于任何构建) working-directory: infrastructure - run: docker compose up -d --build postgres redis backend + run: docker compose config --quiet + + # 保证库为全新:seed 用 `ON CONFLICT (username) DO NOTHING`,若卷是上一轮残留, + # 账号密码会停留在上一轮的 `E2E_OPERATOR_PASSWORD` 上 → 本轮登录 401(表现为各种业务失败)。 + - name: 清理可能的残留栈与数据卷 + working-directory: infrastructure + run: docker compose down -v --remove-orphans || true + + # 构建与起栈分两步:构建失败时的日志不混杂在 up 输出里,定位更快。 + - name: 构建 backend 镜像 + working-directory: infrastructure + run: docker compose build backend + + - name: 起真实栈(postgres + redis + minio + backend) + # 显式列出 minio:backend 的 depends_on 虽会隐式拉起它,但隐式依赖不利于阅读, + # 且 minio 的 healthcheck(`mc ready local`)是 backend 能否启动的前置条件。 + working-directory: infrastructure + run: docker compose up -d postgres redis minio backend - name: 等待 backend 就绪(超时即失败,不静默跳过) + # 窗口 90×5s=7.5min:首次构建后还需 Spring 启动 + Flyway 46 个迁移,5min 偏紧。 run: | - for i in $(seq 1 60); do + for i in $(seq 1 90); do if curl -sf "$E2E_BASE_URL/health/live" > /dev/null; then echo "backend READY after ${i} checks"; exit 0 fi sleep 5 done - echo "::error::backend 未在 5 分钟内就绪" - docker compose -f infrastructure/docker-compose.yml logs backend + echo "::error::backend 未在 7.5 分钟内就绪" + docker compose -f infrastructure/docker-compose.yml logs --tail=200 backend exit 1 - name: Seed E2E 运营账号(迁移不含 operator 种子,全新库无账号可登录) @@ -217,7 +242,10 @@ jobs: -v pwd="$E2E_OPERATOR_PASSWORD" -f - < ../tests/e2e/sql/seed_e2e_operators.sql - name: Run E2E - run: pytest -v + # 显式给出目录:pytest.ini 的 `testpaths = tests/e2e` 已限定范围,但显式传参可防 + # 该配置被改动后悄悄扩大收集范围(仓库根另有 tests/*_test.py 不是 E2E 用例, + # 且它们在 import 期即依赖 psycopg2 与 E2E_DB_PASSWORD,会被收集成 error)。 + run: pytest tests/e2e -v - name: Teardown(无论成败都清理并留日志) if: always() diff --git a/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md b/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md index d1e6251..cf50317 100644 --- a/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md +++ b/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md @@ -4,7 +4,7 @@ **Goal:** 把「Phase 01~22 已关闭、Phase 23 部分关闭、后端 669 tests 全绿」这一停点推进到 **Phase 24(UAT)可启动**:清偿 Phase 23 剩余与代码级遗留,完成 UAT 前置(决策、用例、目视复验),并登记 Phase 25/26 前置项。 -**Architecture:** 分五批推进 —— 批 1~3 **不需要用户决策**(可立即执行),批 4 需**用户协作**(目视复验 + 13 项决策),批 5 仅登记。批 1~3 每批完成后必须跑全部门禁;**批 2 的 E2E 进 CI 一项依赖「活栈供给方式」基础设施决策**,未决策前不得虚勾。 +**Architecture:** 分五批推进 —— 批 1~3 **不需要用户决策**(可立即执行),批 4 需**用户协作**(目视复验 + 13 项决策),批 5 仅登记。批 1~3 每批完成后必须跑全部门禁;**批 2 的「活栈供给方式」基础设施决策已于 2026-09-18 落地为「Compose in CI」**(见 Task 2.3),本机同一序列实测 E2E 49 passed;仅余 Task 2.3 Step 3(GitHub Actions 上实测一次绿)需在有 Docker Hub 出口的环境完成。 **Tech Stack:** Java 21 / Spring Boot 3 / PostgreSQL / Flyway · Vue 3 + TypeScript + Vite · 微信小程序原生 TypeScript · Node.js(契约门禁)· Docker Compose · GitHub Actions @@ -25,7 +25,9 @@ | UI 契约门禁 | **14/14 绿** | | 停点 | **Phase 24(UAT)启动门前 —— 需用户确认** | -> ⚠️ 在批 2 的 E2E 项完成前,**不应宣布「失败为零可进入 UAT」**(Phase 23 Gate 要求端到端链路被真实执行)。 +> ✅ 批 2 的 E2E 项**已在本机真实执行**(2026-09-18:`pytest tests/e2e` → 49 passed,真栈 + 真库 + 当日代码), +> 「失败为零可进入 UAT」的前置条件**已满足**。仍缺的是 E2E **在 CI 上跑过一次**(Task 2.3 Step 3)—— +> 那是回归防线缺口,不阻塞 UAT 启动,但 Phase 23 因此仍记「部分关闭」。 --- @@ -110,19 +112,42 @@ **Acceptance:** INV-011 有真实行为守卫。 -### Task 2.3: E2E 进入 CI(**依赖基础设施决策**) +### Task 2.3: E2E 进入 CI(**已决策并落地**,2026-09-18) **Files:** -- Modify: `.github/workflows/*.yml`(e2e job 已存在但未串联) +- Modify: `.github/workflows/ci.yml`(e2e job) +- Modify: `infrastructure/docker-compose.yml`(minio 镜像 pin) -**背景(未决策,不得擅自动手)**:E2E 需要可用活栈(后端 + PostgreSQL + 种子数据 + `E2E_PASSWORD`),**在 CI 中如何供给属基础设施决策**。本地无法验证其正确性 —— 按「不写无法验证的测试」纪律,必须先决策。 +**决策(2026-09-18):在 runner 内用 Compose 起全栈。** +备选是 service containers + 在 runner 上 `mvn package` 后 `java -jar`;但后者需为后端另写一套 host 网络配置, +等于制造**第二条与本地不一致的路径**,且增加一份与生产无关的分歧。Compose 全栈与本地验证路径完全相同, +且不需要 registry 凭据、不需要预构建镜像分发。 -- [ ] **Step 1:** **(需用户/架构决策)** 确定活栈供给方式(service containers / 预构建镜像 / Compose in CI)。 -- [ ] **Step 2:** 按决策实现 job,并要求**任何失败阻断发布**。 -- [ ] **Step 3:** 实测一次完整的 CI 绿。 +**本机实测(同一序列,2026-09-18)**:中间件三项 healthcheck 全 `Healthy`(`pg_isready` / +`redis-cli -a … ping` / `mc ready local`)→ backend `/api/v1/health/live` 返回 200 → seed 幂等可执行 +(`INSERT 0 0`)→ **`pytest tests/e2e` → 49 passed**。 +> 本机限制(如实记录):Docker Hub 不可达(`auth.docker.io` Bad Gateway)且本地无 temurin 基础镜像, +> 故**未复现 `--build` 环节**;改用本地既有镜像挂载当日 16:24 构建的 jar,使**被验证的代码版本与 CI 一致** +> (否则是拿 09-09 的旧代码验 09-18 的用例,结论无效)。CI 上 `--build` 由 runner 完成。 + +- [x] **Step 1:** 确定活栈供给方式 → **Compose in CI**(理由见上)。 +- [x] **Step 2:** 按决策实现 job:`compose config` 秒级快速失败 → `down -v` 保证全新库 → `build` → `up` + (显式含 minio)→ 就绪等待(90×5s)→ seed → `pytest tests/e2e -v` → teardown;并加 `timeout-minutes: 30`。 +- [ ] **Step 3:** 实测一次完整的 CI 绿 —— **需在 GitHub Actions 上执行,本机无法进行**(本机无 Docker Hub 出口)。 **Acceptance:** CI 串起 迁移 → backend test → admin type-check/build → miniapp type-check → E2E,任一失败阻断。 +**本轮顺带修掉的 6 个 CI 真实缺陷**: + +1. `minio/minio:latest` 是**滚动 tag**(MinIO 已多次改动镜像内容,如移除内嵌 console)→ **pin** `RELEASE.2025-09-07T16-13-09Z`。否则 CI 拉到的镜像与本地验证时不同 → 不可复现的偶发失败。 +2. `up` **未显式列出 minio**(靠 `depends_on` 隐式拉起),而 minio 的 healthcheck 正是 backend 能否启动的前置条件 → 显式列出,消除隐性依赖。 +3. `up --build` 把「构建镜像」与「起栈」耦合,失败时日志混杂 → 拆为 `build` / `up` 两步。 +4. 就绪窗口 5 分钟**偏紧**(首次构建后仍需 Spring 启动 + Flyway 46 个迁移)→ 放宽到 7.5 分钟。 +5. **缺 `down -v` 前置清卷**:seed 用 `ON CONFLICT (username) DO NOTHING`,若卷为上一轮残留,账号密码会停留在上一轮口令 → 本轮登录 401,并**伪装成业务失败**。(本机复用旧库时已实际踩到:必须手工 `UPDATE` 密码才能跑。)CI 每次全新库不触发,但 job 被取消/超时残留时会触发 → 起栈前强制清卷。 +6. e2e job **无 timeout**(GH Actions 默认 360 分钟)→ 起栈失败会把流水线拖住数小时 → `timeout-minutes: 30`。 + +**未采纳的(避免过度设计)**:`pytest -v` 曾被认为会从仓库根误收集 `tests/*_test.py`(那些文件在 import 期即依赖 `psycopg2` 与 `E2E_DB_PASSWORD`)——**实测否定**:`pytest.ini` 的 `testpaths = tests/e2e` 已把范围限定,从根收集正是 49 个用例且无 error。仍显式写成 `pytest tests/e2e -v`,作为「该配置被改动」的兜底。 + ### Task 2.4: Phase 22(Deployment / Ops)口径复验 > ⚠️ **口径澄清**:此为**计划的** Phase 22(部署运维基础设施),与 `STATUS.md` 所述「Phase 22 部署运维配置扩展(Feature Flag)」**是两份不同工作**。后者已于 2026-09-18 复验关闭。 @@ -191,7 +216,7 @@ ## 验收与提交纪律 1. 批 1~3 每批完成后:`ui-tests/*.mjs` **14/14 绿** + `ui-document-baseline-contracts.mjs` 通过 + 后端 `mvn -o test` 0 failures。 -2. 批 2 完成前**不得**宣布「失败为零可进入 UAT」。 +2. 批 2 的**端到端前置已满足**(2026-09-18 本机 E2E 49 passed);仍缺的是 **E2E 进 CI 的首次实测绿**(Task 2.3 Step 3)—— 不阻塞 UAT 启动,属回归防线缺口。 3. 每批独立 commit;`git tag` 用**单段名**;`git bundle create` 覆盖最新提交并回读验证。 4. `docs/STATUS.md` / `docs/JIRA_BACKLOG.md` / `docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md` 随批同步。 5. **不做**:不修改 Recovery 报告与 `releases/` 冻结快照;不触碰 bundle 自保产物。 diff --git a/infrastructure/docker-compose.yml b/infrastructure/docker-compose.yml index 5a01518..2e022d1 100644 --- a/infrastructure/docker-compose.yml +++ b/infrastructure/docker-compose.yml @@ -33,7 +33,9 @@ services: retries: 5 minio: - image: minio/minio:latest + # pin 到确定版本:`latest` 是滚动 tag,MinIO 已多次改动镜像内容(如移除内嵌 console), + # 用 `latest` 会让 CI 的 E2E 拉到一个与本地验证时不同的镜像 → 不可复现的偶发失败。 + image: minio/minio:RELEASE.2025-09-07T16-13-09Z container_name: danyang-match-minio command: server /data --console-address ":9001" environment: