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

12 KiB
Raw Blame History

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:

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 表:

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 条件是:

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:

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 无此两行):

.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 等)一次性提交:

# 本机 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 分支跑一遍全量绿。