Files
danyang-match/docs/codex/V1.2-修复任务提示词.md
T
WorkBuddy 377429995c
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
release: freeze V1.2 demo acceptance 2026-10-25
2026-10-25 14:29:29 +08:00

165 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 分支跑一遍全量绿。