release: freeze V1.2 demo acceptance 2026-10-25
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Waiting to run
CI/CD Pipeline / Backend Build & Test (push) Waiting to run
CI/CD Pipeline / Admin Web Build (push) Waiting to run
CI/CD Pipeline / Mini Program Compile Check (push) Waiting to run
CI/CD Pipeline / Migration Validation (push) Blocked by required conditions
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Waiting to run
CI/CD Pipeline / E2E (live stack) (push) Blocked by required conditions
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Waiting to run
CI/CD Pipeline / Backend Build & Test (push) Waiting to run
CI/CD Pipeline / Admin Web Build (push) Waiting to run
CI/CD Pipeline / Mini Program Compile Check (push) Waiting to run
CI/CD Pipeline / Migration Validation (push) Blocked by required conditions
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Waiting to run
CI/CD Pipeline / E2E (live stack) (push) Blocked by required conditions
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
# V1.2 在途代码修复任务提示词(交给 codex 执行)
|
||||
|
||||
> 前置评审依据:`docs/RELEASE_REVIEW_2026-10-24.md`(若存在)与本轮审计证据。
|
||||
> 本提示词**自包含**:codex 无需访问其它会话即可执行。
|
||||
|
||||
---
|
||||
|
||||
## 背景(必读)
|
||||
|
||||
本仓库处于 V1.1 冻结基线(HEAD `d51c4c6`,tag `demo-v1.1.0-20261019`)之上,工作区有 **107–110 项未提交改动**,全部是 V1.2 在途开发。当前状态:
|
||||
|
||||
- 后端 `mvn -B -o test` 实跑 **935 用例,6 失败 / 0 错误 / 17 跳过**。
|
||||
- E2E `pytest tests/e2e -q` 实跑 **72 用例,27 失败 / 45 通过**。
|
||||
- UI 契约门禁 `node scripts/run-ui-contracts.mjs` 24 项中 **23 通过 / 1 失败**(`ui-contracts.mjs`)。
|
||||
- 活栈容器 `danyang-match-backend`(image `infrastructure-backend`,2026-10-24 23:07 构建)编译自当前工作区代码,`/health/ready` = READY,`/health/live` = 200。
|
||||
- 数据库 `danyang_match` 实测 1010 用户全部 `service_eligibility_status = 'pending'`,0 个 `eligible`。
|
||||
|
||||
**本机 Git 注意**:分支名必须用**顶层单段名**(如 `fix-v12-gates`),禁止带 `/` 的嵌套分支名(本机监听进程会 0.2s 内删除 `refs/heads/<a>/<b>`)。
|
||||
|
||||
---
|
||||
|
||||
## 任务 P0-1:解除 service_eligibility 死锁(**最高优先级**)
|
||||
|
||||
### 根因(已验证)
|
||||
|
||||
`backend/src/main/java/com/danyang/match/platform/account/service/ServiceConsentService.java:75`:
|
||||
```java
|
||||
if (!"eligible".equals(user.getServiceEligibilityStatus())) throw blocked("当前账号不具备服务资格");
|
||||
```
|
||||
该门禁被 13 个控制器约 50 处调用(`RecommendationController`、`MatchController`、`LikeController`、`ChatController`/`MessageController`/`ConversationController`、`ActivityInterestController`、`CounselingController`、`AppointmentController`/`ServiceAppointmentController`、`ServiceProgramController`、`DiscoverController`、`MatchmakerController`、`PassController`、`CapabilityService`)。
|
||||
|
||||
全仓库扫描证实:**没有任何生产代码路径把 `users.service_eligibility_status` 置为 `"eligible"`**——只有 `UserRegistrationService.java:39` 和 `UserOnboardingService.java:36` 置 `"pending"`;`ReviewService.approveLocalEligibility`(`backend/src/main/java/com/danyang/match/platform/review/service/ReviewService.java:380-419`)只更新 `local_eligibility` 表的 `verification_status`,**不写 `users` 表**。
|
||||
|
||||
结果:任何真实用户完成全部注册/认证/授权流程后,仍被该门禁永久挡住,match/chat/counseling/appointment 等全部核心 API 返回 403。
|
||||
|
||||
### 修复要求(二选一,推荐方案 A)
|
||||
|
||||
**方案 A(推荐,最小改动)**:在 `ReviewService.approveLocalEligibility` 方法内,`localEligibilityMapper.updateById(el)` 之后**同步更新 `users` 表**:
|
||||
```java
|
||||
user.setServiceEligibilityStatus("eligible");
|
||||
userMapper.updateById(user);
|
||||
```
|
||||
- 需要注入 `UserMapper`(如尚未注入)。
|
||||
- 同步修改 `rejectLocalEligibility`(若存在):`user.setServiceEligibilityStatus("ineligible")`。
|
||||
- 补充或更新 `backend/src/test/java/com/danyang/match/platform/review/` 下的对应单测,断言 `users.service_eligibility_status` 确实被更新。
|
||||
- 更新 `backend/src/test/java/com/danyang/match/platform/account/ServiceConsentGateTest.java:39` 的注释,说明测试不再需要直接置值(或保留为 fixture 辅助但加注释标注"这是测试快捷方式,生产路径由 ReviewService 负责")。
|
||||
|
||||
**方案 B(备选,若方案 A 的产品语义不合适)**:从 `ServiceConsentService.requireActive` 中**临时摘掉**第 4 项校验(`:"eligible"` 断言),让前 3 项(账号状态/手机、资料 published revision、双授权)作为当前门禁,并把 eligibility 写入路径作为独立 P1 任务。
|
||||
|
||||
### 验证
|
||||
|
||||
- 在活栈 DB 手工把任意一个用户 `service_eligibility_status` 改为 `'eligible'`,重跑 E2E 中该用户相关的 27 个失败用例,确认 403 消失、链路可用。
|
||||
- 或:修改 `tests/e2e/conftest.py` 里 E2E 用户种子逻辑,使其通过 `ReviewService` 路径获得 eligible 状态,再全量跑 `pytest tests/e2e`。
|
||||
|
||||
---
|
||||
|
||||
## 任务 P0-2:修复活动互选 E2E 7 用例失败
|
||||
|
||||
### 根因(已验证)
|
||||
|
||||
`backend/src/main/java/com/danyang/match/platform/activity_mutual/service/ActivityMutualConsentService.java` 中:
|
||||
|
||||
`createMutualConsent`(:37-67)按 `userId.compareTo(partnerId)` 排序后统一写 `userAId`/`userBId`,**只生成一行记录**(A→B 和 B→A 都指向同一行)。
|
||||
`confirmConnection`(:70-104)调用 `consentMapper.confirmSideIfPending(consent.getId(), userId)`(`backend/src/main/java/com/danyang/match/platform/activity_mutual/mapper/ActivityMutualConsentMapper.java:57-69`),该 SQL 的 WHERE 条件是:
|
||||
```sql
|
||||
WHERE id = #{id} AND deleted_at IS NULL
|
||||
AND both_confirmed_at IS NULL
|
||||
AND (expires_at IS NULL OR expires_at > NOW())
|
||||
AND CASE WHEN user_a_id = #{userId} THEN user_a_status ELSE user_b_status END = 'pending'
|
||||
```
|
||||
SQL 本身是**对称的**(谁 confirm 就改谁那侧),看似没问题。但 E2E `tests/e2e/activity_mutual_consent_test.py` 中 A 先 confirm 后,B 再 confirm 时**拿到的状态仍停在 `pending`**,`bothConfirmed` 永远 `false`。
|
||||
|
||||
排查方向(codex 执行前先精读):
|
||||
- 确认 `ActivityInterestController` / `ActivityMutualConsentController`(在途新增)里 `createMutualConsent` 的调用时机与 `confirmConnection` 之间是否有**状态回滚**(比如 A confirm 后某处把 user_a_status 又写回 pending)。
|
||||
- 确认 `ActivityMutualConsent` 实体(`??` 未跟踪)的 `both_confirmed_at` 字段与 H2/PG 的 `NOW()` 行为。
|
||||
- 确认 `expires_at` 写入逻辑(`createMutualConsent` :60-63)是否在测试场景下导致 `(expires_at IS NULL OR expires_at > NOW())` 恒不成立。
|
||||
|
||||
### 验证
|
||||
|
||||
跑 `pytest tests/e2e/activity_mutual_consent_test.py`,7 个用例全部通过:
|
||||
- `test_mutual_interest_does_not_create_match`
|
||||
- `test_one_side_confirm_no_match`
|
||||
- `test_both_confirm_creates_match_episode`
|
||||
- `test_existing_active_match_not_duplicated`
|
||||
- `test_prior_unmatch_creates_new_episode`
|
||||
- `test_one_side_decline_no_match`
|
||||
- `test_timeout_handled`
|
||||
|
||||
---
|
||||
|
||||
## 任务 P0-3:修复 6 个后端测试失败
|
||||
|
||||
### P0-3a:`MigrationValidationTest.testMigrationNamingConvention`
|
||||
|
||||
`backend/src/test/java/com/danyang/match/platform/deployment/MigrationValidationTest.java:46`:
|
||||
```java
|
||||
private static final int LATEST_MIGRATION = 67;
|
||||
```
|
||||
但 `backend/src/main/resources/db/migration/` 目录下 V062–V069 共 8 个文件全部 `??` 未跟踪,实际最大版本号是 **69**。
|
||||
|
||||
修复:把 `LATEST_MIGRATION` 改为 `69`,并在注释里补一行说明 V068、V069 分别对应 `grant_matchmaker_workbench_read_permissions` 和 `grant_matchmaker_workbench_operation_permissions`。
|
||||
|
||||
### P0-3b:`DiscoverBlockInvisibilityIntegrationTest` × 5
|
||||
|
||||
`backend/src/main/java/com/danyang/match/platform/discover/service/DiscoverService.java:43-44`(V1.2 在途改动,HEAD 无此两行):
|
||||
```java
|
||||
.isNotNull(UserProfile::getPublishedProfileRevision)
|
||||
.apply("profile_revision = published_profile_revision")
|
||||
```
|
||||
导致测试 fixture(`backend/src/test/java/com/danyang/match/platform/discover/DiscoverBlockInvisibilityIntegrationTest.java:195`)只 set `profileStatus=APPROVED`,未 set `publishedProfileRevision`,搜索返回空。
|
||||
|
||||
修复(**二选一**):
|
||||
1. **推荐**:在该测试的 fixture 里补 `p.setPublishedProfileRevision(p.getProfileRevision());`(对每个被搜索的用户 profile 都做),让测试与新契约一致。
|
||||
2. 备选:若 `published_profile_revision` 语义尚未完全落地,在 `DiscoverService` 里把 `.isNotNull(...)` 改为 `.and(w -> w.isNotNull(UserProfile::getPublishedProfileRevision).or().apply("profile_revision = published_profile_revision"))`(放宽为"有 published revision 或 revision 等于 published 都可见")。**选此方案必须同步补注释说明这是过渡期放宽,并加 TODO 指向任务 P0-1 完成后收紧。**
|
||||
|
||||
跑 `mvn -B -o test -Dtest=DiscoverBlockInvisibilityIntegrationTest` 验证 5 个用例全绿。
|
||||
|
||||
---
|
||||
|
||||
## 任务 P0-4:修复 UI 契约门禁失败
|
||||
|
||||
`ui-tests/ui-contracts.mjs`(或未跟踪的 `scripts/run-ui-contracts.mjs` 中)对 `miniapp/app.json` 的 `pages` 数组长度断言为 `=== 67`,但 V1.2 新增了 `pages/register/register` 和 `pages/service-consent/service-consent` 两页,实际为 **69**。
|
||||
|
||||
修复:把断言目标值从 67 改为 69。同时检查是否有**其它** UI 契约断言(如 tabBar 数量、特定页面路径白名单)需要同步更新。
|
||||
|
||||
验证:`node scripts/run-ui-contracts.mjs` 应输出 24/24 通过。
|
||||
|
||||
---
|
||||
|
||||
## 任务 P0-5:提交 V1.2 在途改动并推送
|
||||
|
||||
完成 P0-1 ~ P0-4 后,把全部 107–110 项未提交改动(含 V062–V069 八个迁移、47 个后端未跟踪文件、`UserCreateView.vue`、`UserImportView.vue`、`miniapp/pages/register/`、`miniapp/pages/service-consent/`、`ServiceConsentController/Service/Controller` 等)一次性提交:
|
||||
|
||||
```bash
|
||||
# 本机 Git:分支名必须是顶层单段名,禁止 feat/xxx 形式
|
||||
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe checkout -b fix-v12-gates
|
||||
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe add -A
|
||||
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe commit -m "fix(v1.2): unblock service consent gate + mutual consent + test regressions + UI contract"
|
||||
# 本机分支带斜杠会被监听进程 0.2s 删除,单段名 fix-v12-gates 是安全的
|
||||
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe push origin fix-v12-gates
|
||||
```
|
||||
|
||||
推送前先验证 `C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe branch -vv` 显示 `main` 领先 `origin/main` 28 个提交的情况是否已在本次 `fix-v12-gates` 分支基础上解决(即新分支应从最新 main 切出并包含全部在途改动)。
|
||||
|
||||
---
|
||||
|
||||
## 任务 P1(可选,若时间允许)
|
||||
|
||||
- **部署链路**(详见 release-ops 审计):`infrastructure/docker-compose.prod.yml` 补 healthcheck;`deploy.sh` 回滚改为按 digest;`MINIO_PUBLIC_ENDPOINT` 注入真实可达地址;`backend/Dockerfile:7` 去掉 `-DskipTests`(或在 CI 单独跑全量测试);`nginx-production.conf:55-60` 恢复 80→443 跳转;`infrastructure/certs/` 目录补齐证书产物或写入 CI 生成步骤。
|
||||
- **密钥治理**:`application.yml:88-89` 的 `admin/admin` 兜底、`docker-compose.staging.yml:118` 的硬编码 `ENCRYPTION_KEY`、`application-dev.yml:23,28,32,34` 的默认值全部改为 `${VAR:?}` 强制注入。
|
||||
- **版本注入**:`backend/src/main/java/com/danyang/match/platform/.../VersionController.java:20` 的 `@Value("${app.version:0.0.1-SNAPSHOT}")` 改为 Maven resource filtering 注入,使 `/api/v1/version` 返回与 `VERSION=1.1.0`(或 V1.2 定版后的值)一致的版本号。
|
||||
|
||||
---
|
||||
|
||||
## 验收标准(全部满足才算完成)
|
||||
|
||||
1. `mvn -B -o test`:**935 用例,0 失败 / 0 错误**(17 跳过允许保留)。
|
||||
2. `pytest tests/e2e -q`(活栈重建后):**72 用例,0 失败**。
|
||||
3. `node scripts/run-ui-contracts.mjs`:**24/24 通过**。
|
||||
4. `node node_modules/typescript/bin/tsc --noEmit`(miniapp):**0 错**。
|
||||
5. `node node_modules/vue-tsc/bin/vue-tsc.js --noEmit`(admin-web):**0 错**。
|
||||
6. 活栈重建后 `GET /api/v1/health/ready` = `READY`,`GET /api/v1/version` 返回非 `0.0.1-SNAPSHOT` 的版本号(P1 完成时)。
|
||||
7. `git status --porcelain` 在 `fix-v12-gates` 分支上为**空**(全部已提交)。
|
||||
8. 推送后 CI(若有)对 `fix-v12-gates` 分支跑一遍全量绿。
|
||||
Reference in New Issue
Block a user