Commit Graph
141 Commits
Author SHA1 Message Date
WorkBuddy 2fbdbc32d0 fix(feature-flags): close Phase 22 KEEP-VERIFY — 2 real defects + doc overclaims
KEEP-VERIFY re-audit of the Feature Flag configuration extension (STATUS's
"Phase 22 部署运维配置扩展"): marked ✅ but contained real code defects and
overstated completion.

Real defects fixed:
- D-22-A (High): FeatureFlagController.updateFlag used reflection
  `getMethod("set"+cap, boolean.class)` and only supported boolean flags.
  7 non-boolean flags (livenessProvider, localEvidenceAllowedTypes,
  counselingAttachmentsBucket, localEvidenceMaxFiles, localEvidenceMaxFileSizeMB,
  localEvidenceRetentionDays) failed to update via the API. Now dispatches by
  field type (boolean/int/String).
- D-22-B (Medium): `body.getOrDefault("value", false)` silently set false when
  value was missing and crashed on non-boolean casts. Now returns 400 on missing
  value / type mismatch and leaves the original value unchanged.

Doc overclaims corrected (no silent scope creep):
- T01-T05 module-toggle flags (isCounselingEnabled etc.) have zero business
  call sites -> dead config (exposed but not gating). Left for each module's
  own phase to wire; documented as residual.
- T07 private-storage bucket flags have zero references; storage already uses
  object-storage.bucket-private. Documented as residual.
- STATUS claimed "application.yml defaults (15 flags)"; yml has no `features:`
  block, defaults are Java field defaults. Functionally fine, docs corrected.

T06 upload policy verified actually enforced in LocalEligibilityService +
PhotoService (kept ✅).

Guard tests: new FeatureFlagControllerTest (7 cases: 21 keys exposed, boolean/
string/int update reflected, unknown flag / missing value / wrong type -> 400).
Negative validation: reverting to boolean-only dispatch made exactly 2 update
cases fail red, then green after restore — proves guards are real.

Full regression: 657 tests / 0 failures / 0 errors / 3 skipped (baseline 650, +7).
2026-09-18 11:00:08 +08:00
WorkBuddy 0580da0c62 fix(account-lifecycle): close Phase 21 KEEP-VERIFY — 6 real defects + fake-green tests
KEEP-VERIFY re-audit of account lifecycle (EXTEND): STATUS marked ✅ but
contained hard Gate violations and dead code.

Real defects fixed:
- D-21-A: AuthService.checkAccountStatus did not reject CLOSED → closed
  account could still log in (Gate ❌→✅); added CLOSED branch before
  BANNED/SUSPENDED.
- D-21-B: notifyActiveMatches was a TODO stub → active Matches/conversations
  not closed on withdrawal (Gate ❌→✅); now finds active matches, sets
  UNMATCHED + closes conversation via chatService.closeConversationForMatch.
- D-21-C: cancelPendingActivityRegistrations was a TODO stub → future
  activity registrations not cancelled (Gate ❌→✅); now cancels pending/approved.
- D-21-D: anonymizeServiceHistory existed but was never wired into
  closeAccount → personal identity + counseling text leaked (Gate ❌→✅);
  wired in, anonymizes id_number/real_name with placeholder, plus
  counseling_followups content.
- D-21-E: closeAccount hard-threw on blockers → all cancelXxx unreachable
  (dead code contradiction); changed to informational log + continue cleanup.
- D-21-F: 7× assertTrue(true) fake-green tests → replaced with 11 real DB
  integration tests + 3 login-guard tests (Redis-decoupled via
  ReflectionTestUtils).

Cross-module defenses (RecommendationEngine/MatchEligibility/Capability
reject non-NORMAL) confirmed effective. Remaining (no silent scope creep):
matchmaker in-progress interception still hardcodes 0 (no table), data
retention delete flow is a product decision, privacy-settings endpoint stub.

Full regression: 650 tests / 0 failures / 0 errors / 3 skipped (baseline 648, +2).
2026-09-18 10:12:34 +08:00
WorkBuddy 22c778b111 fix(analytics): close Phase 20 KEEP-VERIFY — 20 defects (fake metric definitions, hardcoded zeros, systemic soft-delete leak, module-wide 403, fake-green tests); add MetricCatalog + unified calculator + V043 grants + 51 guard tests; full regression 648 green
口径造假:
- D1 实名人数用 user_profiles.profile_status != 'rejected' 冒充 → in_progress/need_edit 被算成已实名
- D3 Chat Started 直接数会话行数 → system/risk_notice 系统通知计入
- D4 Match 不分来源 → 活动 10 对互选被当 10 对成功引用(Gate)
- D6 Banner 曝光/点击硬编码 0 → 改返回 null + NOT_INSTRUMENTED 标记

死代码 / 占位:
- D5 活动漏斗全部硬编码 0 + 一行把"用户数"当"活动数"的死代码
- D20 getDailySummary(days) 无边界校验

系统性软删缺陷(D12):
- BaseEntity.deletedAt 无 @TableLogic 且全局无逻辑删除配置
  → selectCount 把软删数据全部计入统计且无任何报错
- 修复 45 处统计调用显式 deleted_at IS NULL(2 轮复验,首轮漏扫 5 处次级路径:
  服务用户数分域明细 / 服务站统计 / 性别与年龄分布)

结构类:
- D15 Dashboard/用户漏斗/专项页各实现一遍同名指标且互不相同 → 抽 AnalyticsMetricsCalculator 统一口径

权限(Gate ⑩):
- data_viewer 在 V001 定义却全仓从未授权(空壳角色)
- platform_admin 的 V015 扩展条件 id >= '20000000-…' 漏掉 V1.0 老权限段
  → 整个 Analytics 模块除 super_admin 外全员 403
- V043 授予两角色;刻意不授予 organization_admin(相关表无组织维度,无范围全量即越权)

假绿测试:
- 删除 AnalyticsServiceTest(5× assertTrue(true),零断言)

新增:
- 指标字典 MetricCatalog(36 指标 × 12 字段)+ MetricStatus/MetricDefinition + docs/ANALYTICS_METRICS.md
- 守卫 51 用例:MetricCatalogGuardTest(11) / AnalyticsMetricsPhase20IntegrationTest(21)
  / AnalyticsSoftDeleteGuardTest(4,结构性) / AnalyticsPermissionGrantTest(6)
  / LocalEligibilityStatusVocabularyGuardTest(9)

跨阶段修复(缺陷本体属账号生命周期模块,按"发现即修"处理):
- X1(Critical)注销写入 user_identities 状态域的 'failed' → 违反生产 V017 chk_elig_verify_status
  → 真实 PG 上 closeAccount 事务回滚、账号注销 100% 不可用(负向验证实测 DataIntegrityViolation)
- X2 checkCanWithdraw 资格拦截用 submitted/pending(该表永不可能出现)→ 死代码
- X3 测试库补 chk_elig_type/chk_elig_verify_status,暴露 ActivityPhase14Test 夹具违规

验证:
- 负向验证两轮共 7 处注入 → 7 个用例精准变红且无无关失败(一处被源码结构+运行时行为两层捕获)
- 还原后 md5sum 逐字节一致,NEG-VAL 残留 0
- 后端全量 648 tests / 0 failures / 0 errors / BUILD SUCCESS;admin-web vue-tsc EXIT=0
2026-09-17 23:25:31 +08:00
WorkBuddy ff22c0d06a docs(phase19): cite the recovery tag instead of a moving commit sha 2026-09-17 22:25:10 +08:00
WorkBuddy d74f0ea099 docs(phase19): record tag/bundle location + commit sha in the recovery report 2026-09-17 22:24:56 +08:00
WorkBuddy 72b4e9b846 fix(ops-backend/service-station): close Phase 19 KEEP-VERIFY — 25 defects (unreachable announcements + station write privilege escalation; banner 500 + soft-delete leak; content publish dead-end; program approval dead-end/draft leak/zero counts/missing withdraw; zero-coverage test schema) ; add V042 announcement chain + 34 guard tests; full regression 602 green
Backend
- Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed
  but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator
  endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint.
- Fix privilege escalation: create/update were guarded only by the class-level
  service_location.view, making V015's service_location.manage a never-used dead
  permission -> read-only roles could edit stations. Permissions moved to method level.
- Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest
  (it was NOT scanned, which is why the escalation stayed invisible).
- Add create/update validation (address NOT NULL + status CHECK only blew up on PG).
- Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR
  -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live).
- Content: operator list/detail split (admin «publish» was a dead end because it reused
  the published-only public API); category deactivate/reactivate; article body on edit.
- Service Program: operator member list now includes pending (approval reachable);
  active-only public visibility (draft leak); memberCount/pendingCount aggregation;
  withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash).
- MeController: add the missing GET /me/service-history route.

Schema / guards
- schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and
  content_categories (+ uk_content_category_code, chk_category_status); move both into
  REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42.
- V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029.
- New: ServiceLocationAnnouncementPhase19IntegrationTest (24),
  ServiceLocationControllerPermissionContractTest (5),
  ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <->
  backend whitelist <-> admin labels).

Frontend
- admin-web: ServiceStationView gains announcement management (statuses, create/edit,
  publish/archive with confirm, expiry); write buttons gated by service_location.manage.
- miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer
  talks to utils/api directly; announcement failure degrades to [] instead of erroring.
- ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to
  the service layer.

Verification
- Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema
  CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no
  collateral failures; restored, NEG-VAL residue = 0.
- Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420).
- admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0.
- ui-tests 14/14 PASS.
2026-09-17 22:21:31 +08:00
WorkBuddy 3f60611a74 fix(appeal): close Phase 18 KEEP-VERIFY — 8 defects (zero-coverage appeals table + 6 fake-green tests; operator-notification FK crash; appealType CHECK; escalated dead-end; processAppeal race; soft-delete filters; hardcoded resolved; evidence contract); add AppealPhase18IntegrationTest (15); full regression 494 green 2026-09-17 21:07:25 +08:00
WorkBuddy 307c9efb36 fix(risk/report): close Phase 17 KEEP-VERIFY/EXTEND — 6 defects + 1 hidden compile defect (jakarta) + test-coverage blind zone (3 missing tables); add sensitive-access audit + ReportAndRiskPhase17IntegrationTest (7); full regression 488 green 2026-09-17 20:42:09 +08:00
WorkBuddy 556f95a562 fix(activity-mutual): close Phase 16 KEEP-VERIFY/EXTEND — 7 defects + 1 hidden compile defect (reopenEpisode) + 1 test-fidelity; unify dual-impl to confirmActivityInterest; INV-006 fix; +7 tests (481 green) 2026-09-17 16:47:10 +08:00
WorkBuddy 29112eed6e fix(activity): close Phase 15 KEEP-VERIFY/EXTEND — 2 critical + 14 other defects, 3 test-fidelity fixes
Phase 15 (activity registration / review / checkin) verification closure.

Two CRITICAL defects, both "production always had it, test schema never did":
- D1: generateCheckinToken wrote the token to Redis with value "used", while
  scanCheckin rejects any token marked "used" -> every freshly issued check-in
  code was dead on arrival, staff scanning failed 100% of the time.
  Issuing now writes "issued"; "used" is set only after a successful scan.
- D9: UNIQUE(activity_id, user_id) exists in production, but re-registering
  after cancel/reject went through INSERT -> users who cancelled once could
  never register for that activity again (duplicate key 500). The H2 test
  schema lacked the UNIQUE, so the path had zero coverage. Now the original
  row is reused via an atomic conditional UPDATE.

Checkin (D2-D8):
- D2: duplicate check-in was read-then-write; now atomic checkinIfNotCheckedIn
  and repeat scans are idempotent (alreadyCheckedIn) without duplicate audit.
- D3: no data scope at all -> added activity staff assignment (V040) and
  assertCheckinScope; INV-020 (org relation grants nothing).
- D4: manualCheckin did not validate activity existence/cancellation.
- D5: getCheckinStats NPE on missing activity; remainingCount was semantically
  "free seats" -> replaced with notCheckedInCount.
- D6: malformed timestamp caused 500 -> 400. D7: constant-time signature compare.

Registration / review / cancel (D10-D13):
- D10: backend registration list only queried status='approved' -> the review
  screen never saw pending rows (review unusable).
- D11: registration_start_at / registration_end_at were never enforced.
- D12: cancel had no audit log (the only write without one).
- D13: reject/cancel were read-then-full-update -> rejectIfPending/cancelIfActive.

Capacity groups (D14, plan item 2): groups were stored and read back but never
participated in capacity -> incrementIfAvailable / decrementIfPositive now make
approved_count real; activities without groups are unaffected.

Frontend contract (F1-F2):
- F1: admin-web had no check-in surface at all -> ActivityCheckinView.vue
  (paste/scan token, manual check-in, stats) + /activities/checkin route & nav.
- F2: miniapp had no cancel-registration wrapper although the endpoint existed.

Test fidelity (T1-T3):
- T1: test schema gained UNIQUE(activity_id,user_id) + chk_reg_status +
  chk_checkin_status; two new guards parse the real V010 DDL and compare.
- T2: 3 assertTrue(true) placeholders in CheckinServiceTest (covering duplicate
  check-in, expiry, no-sensitive-plaintext) replaced with 5 real guards that
  parse the real source and real production DDL.
- T3: activity_registrations + activity_staff_assignments moved into
  REQUIRED_IN_TEST_SCHEMA; MigrationValidationTest baseline 39 -> 40.

Note: ActivityStaffAssignment must NOT extend BaseEntity - the table is a
composite PK (activity_id, operator_id); BaseEntity would add id/updated_at/
deleted_at to every INSERT and break on PostgreSQL (caught by
EntityMigrationParityTest immediately).

Tests: ActivityPhase15Test (21), CheckinServiceTest (5), 2 new constraint
guards. Negative verification: 3 injected defects -> 8 assertions turned red,
all restored.

Regression: 474 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS
(baseline 450). admin-web vue-tsc 0 error, vite build OK.
2026-09-17 15:07:52 +08:00
WorkBuddy 755a570849 fix(activity): close Phase 14 KEEP-VERIFY/EXTEND — 11 backend + 3 frontend-contract + 2 test-fidelity defects
Phase 14 (multi-type public-welfare activities) verification closure.

Backend (D1-D11):
- D1 CRITICAL: ActivityMapper.countRemainingCapacity had no @Select and no XML
  -> activity detail / register / approve all threw Invalid bound statement.
  Removed the shell; unified on ActivityRegistrationMapper.
- D2 CRITICAL: register & approve were read-then-write -> oversold under
  concurrency. Replaced with single atomic SQL (insertIfCapacityAvailable /
  approveIfCapacityAvailable, affectedRows==0 => full).
- D3 HIGH: activityType / orgRole never validated against production
  chk_activity_type / chk_org_role -> 500 only on PostgreSQL.
- D4 HIGH: ActivityListItemDTO had no activityType -> miniapp category tabs
  always empty (Gate failure).
- D5: male/female counts were fabricated as approvedCount/2; now real
  aggregation over user_profiles.gender.
- D6: minAge/maxAge was a TODO no-op; added assertAgeInRange.
- D7: registration_policy write-only; per-type eligibility now enforced
  (identity / local eligibility / gender quota).
- D8: POST /operator/activities dropped type, multi-org and capacity groups
  and set organizer_org_id to the operator user id; now delegates to
  createActivityFromAdmin (single implementation).
- D9: three operator write endpoints were mounted on the user-side
  ActivityController (dual implementation, zero callers) -> moved.
- D10: NPE on soft-deleted activity in getMyActivities; NPE/500 on missing
  admin form fields (Instant.parse(null)); tolerant time parsing added.
- D11: DTO lacked registeredCount / status / coverFileId / description
  -> card always showed 0 registered, "view detail", no cover, no summary.

Frontend contract (F1-F3):
- F1: miniapp category code "classroom" is not in the production whitelist
  (should be relationship_education); three types had no tab at all.
- F2: ui-activity-card read 7 fields that do not exist on the backend;
  now reads only real DTO fields, plus per-type badge and eligibility hint.
- F3: admin-web had no activity management page at all; added
  ActivityManagementView.vue (type-driven dynamic form, multi-org,
  capacity groups, publish/cancel) + /activities route and nav entry.

Test fidelity (T1-T2):
- T1: deleted two assertTrue(true) placeholder tests (testNoOverbooking,
  testNoDuplicateRegistration) that covered the exact broken invariants.
- T2: test schema lacked V023 columns/constraints and three tables; the
  activity tables were only in the OPTIONAL set (zero coverage) -> schema
  completed and moved to REQUIRED_IN_TEST_SCHEMA.

Tests: ActivityPhase14Test (23 cases), ActivityConstraintGuardTest (9 cases,
parses real db/migration/V*.sql plus miniapp source). Two negative-verification
rounds proved both guard families actually turn red.

Regression: 450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS
(baseline 420). admin-web vue-tsc 0 error, vite build OK.
2026-09-17 14:02:00 +08:00
WorkBuddy 7a2693b239 fix(counseling): close Phase 13A KEEP-VERIFY/EXTEND — 9 real defects + 4 test-infra defects
Phase 13A(婚恋咨询与婚前辅导)复验关闭。历史 ✅ 不可信:counseling 模块存在双实现,
线上权威 CounselingService(167 行)零测试,唯一被测的 CounselingServiceApp(280 行)
是孤儿类(无注入点、无 HTTP 端点可达,生产永不执行)→ 已合并并删除。

生产缺陷(D1~D9):
- D1 Critical 生产故障 · INV-013 泄露:咨询正文 userRequestSummary 原文被写入 WARN 日志
  (同 Phase 10 D2 一族)→ 改为只记 serviceId + riskLevel,绝不记正文。
- D2 High 越权:申请 userId 取自请求体而非认证主体 → Controller 强制 principal.id()。
- D3 High 并发:分配用「先读后写」→ 两管理员并发分配静默覆盖;
  改 assignIfPending 条件更新(WHERE status='pending' AND assigned_operator_id IS NULL),
  affectedRows=0 明确报错。
- D4 High 越权读:counseling_manager 经 getDetail 可读 followups 正文 → INV-013 绕过通道;
  manager 只加回 userRequestSummary,不装配 followups。
- D5 Medium:无状态机 → 「已完成」可被再取消;新增 ALLOWED_TRANSITIONS + assertTransition。
- D6 Medium 生产故障:GET /counseling/services/intro 后端不存在(且被 /services/{id} 吞掉)
  → 小程序 intro 页 100% 无数据;补端点并声明在变量路径之前。
- D7 Medium 契约:Admin 调用 5 条不存在的 /services/operator/*(404)+ 缺四态
  → 后端补 8 个运营端端点(@PreAuthorize),前端整体重写 + 注册 /counseling 路由。
- D8 Medium 契约:小程序字段名错(userRequestSummary/preferredServiceMode)+ 无幂等
  + my-counseling 未检查 success(403 静默空列表)+ 跳转不存在的页 + WXML 内调用方法。
- D9 Medium 测试保真度:测试库 counseling 两表零 CHECK 约束,
  counseling_services 未列入 EntityMigrationParityTest 任何集合(= 零覆盖)。

测试基建缺陷(T1~T4,不修即恒真假绿):
- T1 守卫找不到 DDL:surefire workingDirectory = ${project.basedir}/.. 使 CWD 为仓库根,
  且 backend/ 是子目录非祖先 → 改为候选后缀 + 逐级向上定位,失败大声抛异常。
- T2 全限定 @com.baomidou...TableField(exist=false) 漏解析(假漂移警报)→ 正则放宽 + 回归守卫。
- T3 OperatorEndpointPermissionCoverageTest 扫到私有辅助方法(假阳性)→ 只扫描真实端点 + 自检。
- T4 两处测试自身逻辑缺陷:陈旧内存快照比较、Instant 纳秒 vs DB 微秒精度。

迁移:新增 V039(counseling 幂等键 + 三个 CHECK 白名单显式固化),基线 38 → 39。

负向验证两轮(均已还原,grep -c NEG-VAL = 0):
- ① DDL 常量守卫:从 V039 移除 'scheduled' → 常量守卫精准变红
  (证明解析的是真实 DDL 而非副本)。
- ② 行为守卫:注入「并发回退先读后写 / manager 读 followups / 状态机守卫失效」
  → 三个用例各自精准变红。

测试:新增 CounselingPhase13ATest(25)+ CounselingConstraintGuardTest(5,解析真实 DDL);
删除孤儿类及其 3 个 DTO、删除测死代码的 CounselingDataScopeTest。
全量回归:Tests run: 420, Failures: 0, Errors: 0, Skipped: 3 / BUILD SUCCESS(4:49)。
基线 391 → 420(净 +29)。admin-web vue-tsc EXIT=0;miniapp tsc EXIT=0。

Tag: phase13a-recovery-2026-09-17
Report: docs/Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md
2026-09-17 01:25:12 +08:00
WorkBuddy 5b3bad326e fix(matchmaker,referral): close Phase 13 KEEP-VERIFY/EXTEND — 7 real defects
KEEP-VERIFY/EXTEND 复验(历史 ✅ 不可信;测试覆盖函数 ≠ 覆盖运行时路径)。
逐条核对运行时路径与生产约束后修复 7 处缺陷,其中 3 处「只在真实 PostgreSQL 上炸」。

D1 (Critical, 生产故障)
  matchmaker_recommendations.status 代码写 liked/passed/confirmed_mutual,
  而 V009 chk_rec_status 只允许 pending_view/viewed/responded 且从未扩展
  → 真实 PG 上用户接受/跳过推荐必崩。测试库未复刻该 CHECK,故长期静默。

D2 (Critical, 生产故障)
  控制器缺省 serviceType="recommendation" 不在 chk_matchmaker_service_type
  白名单 → 提交红娘服务必崩。
  D1+D2 同修于新增 V038__align_matchmaker_status_constraints.sql
  (沿用 V018/V020/V025 的 DROP CONSTRAINT IF EXISTS + 重加白名单惯例)。

D3 (High, 越权写)
  MatchmakerServiceService.recommendPartner 无 assignedOperatorId 归属校验,
  且可把用户推荐给自己 → 补校验 + 禁止自我推荐。

D4 (High, 越权读)
  getServiceDetail 无归属校验且返回完整用户资料(userId/description/
  userProfile/followups)→ 签名加 requesterId,按调用者返回完整/脱敏视图。

D5 (High, 越权写)
  ReferralService 的 userId 取自请求体且零授权校验 → 新增
  assertMatchmakerServesUser,在 referToServiceStation/referToActivity 入口
  强制校验存在进行中的服务关系。

D6 (High, 生产故障)
  MatchmakerFollowupMapper.findByServiceId 只有声明无 @Select
  → Invalid bound statement (not found),getServiceDetail 必崩。
  该缺陷由本轮新增测试首次暴露(历史测试从未调用该路径)→ 补 @Select。

D7 (Medium, 契约缺口)
  admin-web 5 处 API 缺口(4 条 404 + C 端端点被运营端误用)+ 小程序缺转介服务层
  → 后端补 GET /operator/matchmaker/recommendations 与
  GET /referrals/operator/history 两个运营端端点;前端改路径、
  assign 改 POST + body operatorId;新增 miniapp/services/referralService.js。

测试保真度
  - 测试库补 matchmaker_services/recommendations/followups 三表(此前该模块零真实覆盖),
    约束与 V038 对齐;EntityMigrationParityTest 三表移入 REQUIRED_IN_TEST_SCHEMA;
    MigrationValidationTest LATEST_MIGRATION 37 → 38。
  - 新增 MatchmakerReferralPhase13Test(17 用例,@SpringBootTest 走真实方法路径 + 真实落库)。
  - 重写 MatchmakerServiceTest(6 用例):直接解析 db/migration/V*.sql,按文件名顺序应用
    DROP CONSTRAINT IF EXISTS + ADD CONSTRAINT 取"最终有效"白名单,并内置解析器自检。
    【关键】首版守卫硬编码白名单副本,注入 D1 后仍全绿(对漂移零反应)→ 必须解析真实 DDL。
  - 删除 3 处假测试占位(MatchmakerServiceTest.testMatchmakerCannotLike 的 assertTrue(true);
    CoreInvariantsTest INV-007 断言的是状态常量、INV-010 亦为 assertTrue(true)),
    已如实改名并注明行为验证位置。

负向验证(两轮)
  注入 D1(回退 chk_rec_status 白名单)→ 行为守卫 DataIntegrityViolation
  + 常量守卫 Multiple Failures (3 failures) → Tests run: 23, Failures: 1, Errors: 2
  / BUILD FAILURE,守卫有效性得证。缺陷已还原。

全量回归(离线容器,实测)
  [WARNING] Tests run: 391, Failures: 0, Errors: 0, Skipped: 3
  [INFO] BUILD SUCCESS / Total time: 04:59 min
  Phase 12 基线 371 → 391(净 +20)。

报告:docs/Phase-13-V11-Recovery-Completion-Report-2026-09-16.md
Phase 13A 未自动进入,等待用户确认。
2026-09-17 00:01:53 +08:00
WorkBuddy c607e87b45 fix(relationship): Phase 12 复验 —— 修复实体↔生产迁移系统性漂移(生产故障族)/ 双路径 episode 与 NPE / 测试库缺表致首页零覆盖 / 计划项 2 未实现
KEEP-VERIFY 复验发现并修复 4 处真实缺陷:
- D1(Critical,生产故障·同一族系统性缺陷)实体与 db/migration 系统性漂移:23 表 50 列在生产
  迁移中不存在,而测试库 schema-h2.sql 手写补齐 → 全量绿=假绿。其中 11 表属"INSERT 必崩"级
  (缺 id/created_at/updated_at,被 AutoFillHandler 强制填充),含 matches.episode、user_preferences
  权重列、permissions/roles、consent_records、risk_events 等;真实 PG 上 Match 创建、择偶偏好保存、
  权限/角色写入、同意记录、风险事件全部失败。修复:新增 V037(幂等 ADD COLUMN IF NOT EXISTS
  + 存量 episode 回填)。
- D2(High)RelationshipService.createMatch 不写 episode/lastActivityAt,与 MatchService 双路径
  填充不一致;且 episode 为 null 时 m.getEpisode()+1 对 Integer 自动拆箱抛 NPE。修复:统一按
  AGENTS.md #20 推导 + null 兜底。
- D3(High)测试库缺 user_identities 等 7 表 → GET /api/v1/home 在测试环境 BadSqlGrammar,
  首页接口零回归覆盖。修复:schema-h2.sql 补齐 7 表。
- D4(Medium)Phase 12 计划项 2 实际未实现:getting_to_know 分支只产出 counseling。修复:
  HomeApplicationService 补 premarital 引导(与 counseling 并列)。

测试保真度:
- 新增永久守卫 EntityMigrationParityTest(5 用例):把"实体字段 ⊆ 生产迁移累积列"与"测试库覆盖
  关键业务表"固化为自动不变量,区分"必崩级/潜伏级";经两轮负向验证证明会变红(第一轮分级过窄
  已修正:episode 由"潜伏级"纠正为"必崩级")。
- 新增 RelationshipProgressPhase12Test(14 用例,含 8 线程并发确认)与
  HomePremaritalGuidancePhase12Test(5 用例)。
- 删除 2 个假测试类中的 assertTrue(true) 占位(AGENTS.md 明令禁止),改走真实服务方法验证。

Gate:单方确认不改变最终关系 ✅ / 双确认后才 successful ✅ / 历史 Episode 可追踪 ✅ /
新 Episode 不恢复旧聊天权限(conversations.match_id UNIQUE)✅ / 非成员不可代确认 ✅

全量回归:371 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(71 类,5m07s)
2026-09-16 23:01:13 +08:00
WorkBuddy 52e11a6c65 fix(notification): Phase 11 复验 —— 修复通知类型违反生产 CHECK 约束(生产故障)/ 幂等无库兜底 / 婚前辅导通知未接线
复验模式:KEEP-VERIFY/EXTEND(不轻信历史 ✅,逐条核对运行时路径与生产约束)。

== D3(Critical · 生产故障)通知 type 全量违反生产 CHECK 约束 ==
NotificationService 的 10 个业务模板传入复合 type
(appointment_submitted / counseling_assigned / le_verified / referral_created 等),
而生产 V020__extend_notification_types.sql 的 chk_notification_type 白名单只允许 12 个短名。
真实 PostgreSQL 下这些通知全部落库失败;测试库 schema-h2.sql 未复刻该约束,故历史 334 测试全绿 → 假绿。
修复:
- type 归一为白名单短名,业务细分交由 businessType/title 承担:
  appointment_submitted|_confirmed|_changed -> appointment
  counseling_assigned|_scheduled|_completed  -> counseling
  premarital_scheduled                       -> premarital
  le_need_evidence|le_verified|le_rejected   -> local_eligibility
  referral_created                           -> matchmaker
- 重写 buildPrivacySafeCopy:原以复合 type 为 key 的 Map.ofEntries 归一后产生重复 key 会抛
  IllegalArgumentException(运行期崩溃),改为按短名 switch。
- schema-h2.sql 补齐生产 CHECK 约束(测试保真度下沉)。

== D1(High)通知幂等仅 SELECT-then-INSERT,无 DB 兜底 ==
NotificationMapper.findExisting 为查后插,并发下可重复落库。
修复:新增 V036__add_notification_idempotency_index.sql,建 partial unique index
uk_notification_business(user_id, type, business_type, business_id, content)
WHERE business_id IS NOT NULL(先按 created_at ASC 去重存量数据)。
schema-h2.sql 同步唯一索引(H2 无 partial index,标准 UNIQUE 对 NULL 多行共存语义等价)。

== D2(Medium)婚前辅导通知未接线 ==
premaritalScheduled 零调用;CounselingServiceApp.scheduleCounseling(P13A-T22 管理端路径)完全不发通知。
修复:CounselingService.schedule 与 CounselingServiceApp.scheduleCounseling 均按 service_type 分流,
premarital 走 premaritalScheduled,其余走 counselingScheduled;
管理端路径包 try/catch —— 通知失败不回滚排期事实。

== 测试(基线 334 → 346,+12) ==
- NotificationPhase11IntegrationTest(5):回滚不发通知(Gate 前半)、提交后可见、
  顺序重试幂等、绕过 findExisting 由唯一索引兜底抛异常且最终仅 1 条、business_id IS NULL 多行共存。
- NotificationPrivacyCopyTest(5):反射校验 4 类隐私文案,咨询类不含「咨询」且标题为「服务通知」,
  不泄露姓名/时间/证件类型;未知 type 安全回退;ALLOWED_TYPES 守卫。
- NotificationTemplateTypeGuardTest(2):关键 —— 调用模板方法本身(而非直接传字面量 send),
  断言 10 个模板全部以白名单短名落库。
- MigrationValidationTest:LATEST_MIGRATION 35 -> 36,DDL 校验补 CREATE UNIQUE INDEX 识别。

== 负向验证(两轮,含第一轮失效教训) ==
第一轮:注入复合 type 后 NotificationPhase11IntegrationTest 仍全绿 ——
  因该测试直接 send(..., "appointment", ...) 传字面量,不经过模板方法,属无效守卫。
第二轮:改为 NotificationTemplateTypeGuardTest 调用模板本身,注入后精准抛
  DataIntegrityViolation,守卫有效性得证。教训已写入报告:守卫必须经负向验证证明会变红。

== 证据 ==
全量回归 346 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS
(离线 Docker:danyang-match-backend-test:latest + 卷 danyang-m2-cache)
迁移校验 12/12;定向 19/19。

== Gate 判定 ==
回滚事务不发通知 ✅ | 通知失败不回滚核心事实 ✅ | 隐私安全文案 INV-010 ✅
已提交事务通知失败可重试且不产生重复业务记录 ✅(修复后成立)

== 遗留 gap(登记不自动实现) ==
sendExternal 在事务内同步执行外部副作用,当前为占位实现;接入真实微信推送前必须改为
@TransactionalEventListener(AFTER_COMMIT) / REQUIRES_NEW —— 全仓当前无任何 afterCommit 机制。
2026-09-16 21:50:56 +08:00
WorkBuddy fd7195ecb0 fix(safety): Phase 10 复验 —— 修复图片上传魔数校验死代码(生产故障)/ 高风险词写入日志
对 Phase 10(Image & Chat Safety)执行 KEEP-VERIFY 复验:不采信历史 ✅,逐条核对
图片私有访问、类型/大小/魔数校验、审核与水印、风险识别/举报/违规处理、日志脱敏。
Gate 判定通过,发现并修复 2 处缺陷,并推翻旧报告 2 项安全声明。

D1(Critical,生产故障)图片上传的类型与魔数校验整条是死代码
  - PhotoService.uploadAndSave 用 `header = new byte[Math.min(12, stream.available())]`
    仅分配零值缓冲,从不调用 stream.read(...) → validateMagicNumber 对任何文件都收到
    12 个 0x00 → 恒返回 false → 恒抛"无效的图片文件头"。
  - 影响链(已确认线上触发):小程序 local-eligibility-supplement.ts 以
    photoType='local_eligibility' 调 POST /photos/upload → 本地资格佐证上传 100% 失败。
  - 次生问题:isAllowedMimeType(image-only 白名单)在 local_eligibility 分支之后
    无条件执行,即使魔数修好,application/pdf 仍被挡,而 FeatureFlagConfig
    的 localEvidenceAllowedTypes 明确允许 pdf;另有未 mark 的 stream.reset() 无效调用。
  - 测试为何没发现:PhotoServiceTest 只对 validateMagicNumber 传手写字节做单测,
    从不经过 uploadAndSave,该路径缺陷完全无覆盖。
  - 修复:改用 try(InputStream){ header = s.readNBytes(12); } 真正读取;
    MIME 校验按 photoType 分流(local_eligibility 走自己的白名单含 pdf);
    魔数校验仅对图片执行、PDF 跳过;移除死 reset();上传用全新流与校验流隔离;
    generateFileKey 重载按 Content-Type 推断扩展名,PDF 不再拿到误导性 .jpg key。

D2(Low)高风险词写入普通日志
  - ChatSafetyService.scanContent 把命中的高风险词原样写入 log.warn,
    与同文件 CONTACT 分支"只记 pattern 不记内容"矛盾,且违反本阶段 Gate。
  - 修复:改为 log.warn("Chat content matched HIGH_RISK keyword (blocked)")。

推翻的旧声明(docs/Phase-10-Completion-Report.md)
  - 「伪装恶意图片被拒(Magic Number校验)」不成立:该校验运行时恒失败,
    连合法图片也一律被拒(fail-closed 到不可用),安全属性并非由它提供。
  - 「EXIF清理预留(PhotoService.stripExif占位)」不存在:全仓库无该符号。
    且 getUploadToken 客户端直传路径服务端无法介入清理 EXIF。
  - 已在旧报告顶部加状态注记,指向本次复验报告。

测试
  - 新增 PhotoUploadValidationTest(6 用例),用 MockMultipartFile 真正走一遍
    uploadAndSave:合法 JPEG/PNG 通过且确实调用 putObject、伪造 Content-Type 被魔数挡、
    PDF 佐证不被 image-only 挡且 key 以 .pdf 结尾、非法 MIME 被拒、超大被拒。
  - 负向验证:临时把魔数守卫改为 if(false && ...) → 恰好 spoofedContentTypeRejectedByMagic
    一个用例变红,其余 5 个仍绿;随后还原并 grep 确认无 NEGATIVE-CHECK-TEMP 残留。
  - 全量回归(离线容器 danyang-match-backend-test,mvn -o test):
    334 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(基线 328,+6)。

登记 gap(不自动实现,待产品确认)
  - 图片水印:全仓库无实现;主计划未列硬需求,仅"开发执行计划修订版"提"高级动态水印"。
  - EXIF 清理:旧声明不存在;客户端直传路径服务端无法介入,需改为接收侧重编码。
  - 直传凭证路径(getUploadToken)无服务端内容校验且不落 DB 记录 → 后续 getSignedUrl
    归属校验会查不到 UserPhoto 行,属契约层缺口,超出本阶段安全 Gate 范围。
  - 违规自动升级:ChatSafetyService 注释明确属 Phase 17 范围,当前 high 仅拦截不落库。

文档:新增 Phase-10-V11-Recovery-Completion-Report-2026-09-16.md;
旧报告加状态注记;主计划 Phase 10 复选框置 ✅;STATUS / JIRA_BACKLOG 同步(334 tests)。

Phase 11 未启动,按计划每阶段出报告后停止等待确认。
2026-09-16 21:00:52 +08:00
WorkBuddy 03a03c7c11 fix(chat): Phase 09 复验 —— 修复 WebSocket 下行死链路 / Unmatch 后可发消息 / Block 未落库 / 聊天 IDOR
对 Phase 09(Text Chat)执行 KEEP-VERIFY/EXTEND 复验:不采信历史 ✅,逐条核对
REST 发送 + WebSocket 接收、幂等、重连、未读计数、非 active Match 禁聊、Unmatch/
Block 即时禁聊、活动双确认会话复用。共发现并修复 4 处真实缺陷 + 1 处规范不符。

D1(Critical)WebSocket 下行整条链路是死代码
  - 无 HandshakeInterceptor,无任一环节把 userId 写入 session attributes,
    extractUserId 恒返回 null;sendToUser/sendToConversation 全仓库零调用点。
  - 新增 ChatHandshakeInterceptor:从 ?token= 或 Authorization: Bearer 解析 JWT,
    校验通过后写 session attr userId;失败 fail-closed 拒绝握手;仅接受 type=access。
  - 重写 ChatWebSocketHandler:按规范 {event,data} 信封推送;未认证连接直接
    close(NOT_ACCEPTABLE);同账号重连由新连接接管、旧连接 close(NORMAL);
    上行仅支持 ping->pong,禁止客户端自订阅。
  - ChatService.sendText 落库+递增未读后推送 message.new;RiskNotice 同推。
  - 移除 WebSocketConfig 的 .withSockJS()(SockJS 改写端点致原生 WS 无法直连)。

D2(Critical)Unmatch 后仍可发消息
  - unmatch 只改 Match,不联动会话;sendText 仅校验 conv.status==active。
  - 新增 ChatService.assertMatchAlive(conv):按 matchId 查 Match,状态非
    {active,getting_to_know} 抛 "匹配已解除,无法发送消息"。sendText 在 conv.status
    校验之后调用。
  - MatchController.unmatch 落库后调 chatService.syncFromMatch 推 conversation.closed。
  - syncFromMatch 重写:alive 恢复 active;非 alive 只推 conversation.closed,
    不改 status(会话仍以灰显态留在列表,属未授权产品决策,登记待决)。

D3(Critical)RelationService.block 调静态 MatchService.unmatch 只改内存态不落库
  - 结果:Block 后 matches.status 仍为 active。
  - 补 matchMapper.updateById(m) 持久化 + chatService.closeConversationForMatch(
    CONVERSATION_STATUS_BLOCKED) 通知对端。RelationshipService.block 同步补会话关闭通知。

D4(Critical 安全)聊天 4 个接口无成员校验 -> IDOR 越权读私聊
  - MessageController.load/markRead、ConversationController.detail/unread 补
    chatService.isMember(conversationId, userId) 校验;非成员返回
    ApiResponse.notFound()(body 级 success=false code=NOT_FOUND,HTTP 200,
    与 MatchController.authorizedMatch 同约定),不区分"不存在"与"无权"以防探测。

D5(Medium)上行订阅/信封不符规范
  - REST API 接口详细设计规范.md 第124-126章要求禁止客户端自订阅,事件名
    message.new/message.read/conversation.closed,信封 {event,data}。已对齐。

测试保真度 + 测试补齐
  - schema-h2.sql 新增 uk_message_client_id(sender_id, client_message_id),
    对齐生产 V004:71;H2 不支持过滤索引故省略 WHERE(NULL 语义等价)。
    否则 service 的 SELECT-then-INSERT 竞态在测试中无 DB 兜底,形成测试盲区。
  - 新增 ChatPhase09IntegrationTest(13 用例):幂等重试不重复投递、推送信封字段、
    未读增减清零、Match unmatched 拒发、Unmatch/Block 端点即时禁聊+收 closed 事件、
    活动双确认复用同一消息体系、未形成 Match 无法建会话、非成员读消息/详情/未读
    被拒且不泄露正文、DB 唯一索引兜底重复 clientMessageId、无幂等键系统消息可共存。
  - 新增 ChatWebSocketHandlerTest(7 用例):userId 路由、信封格式、未认证关连、
    断开不推、重连替换旧连接、上行仅 ping。

验证证据
  - 全量回归(离线容器 danyang-match-backend-test,mvn -o test):
    328 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(基线 308,+20)。
  - 负向验证:临时注释 assertMatchAlive + 两处 isMember,恰好 3 个用例变红,
    证明守卫有效;随后还原并 grep 确认无残留。

文档
  - 新增 Phase-09-V11-Recovery-Completion-Report-2026-09-16.md(完整报告)。
  - Phase-09-V11-Completion-Report.md 顶部加状态注记,推翻其 4 条 WebSocket 结论。
  - 计划/STATUS/JIRA_BACKLOG 同步 Phase 09 完成态与测试总数(328)。

Phase 10 未启动,按计划每阶段出报告后停止等待确认。
2026-09-16 16:42:48 +08:00
WorkBuddy 4e478ce6ba fix(invariants): Phase 08 复验 —— 修复 INV-004 搜索/详情绕过 Block + matches.source 取值越界
复验(KEEP-VERIFY)在既有 Phase 08 实现中发现 3 处真实缺陷并修复:

D1(严重)DiscoverService.searchUsers 不含查看者身份,查询条件既无排除自己也无任何 Block 条件,被拉黑者仍可被搜到并点开 —— INV-004「搜索不可见」整体失效。改为接收 viewerUserId,排除自己,并用新增的 UserRelationMapper.findBlockedUserIdsBothWays(UNION 双向)一次取回后 notIn 批量排除,避免 N+1。

D2 详情路径(DiscoverService / UserProfileService)均无 Block 判断,知道 userId 即可读到被拉黑者资料。两条路径统一加双向判断并返回 null,由 Controller 转 404(不用「已隐藏」以免被用来探测存在);UserProfileService 中该判断排在隐私设置之前,拉黑不能被对方「允许搜索可见」覆盖。

D3(潜伏,仅 PostgreSQL 暴露)RelationshipService.like() 把关系类型值 "liked" 当 match source 写入 matches.source,违反 V003:91 的 chk_match_source。测试库 schema-h2.sql 该列是裸 VARCHAR(32)、无约束,故长期静默通过。改传 MATCH_SOURCE_USER_LIKE。canonical 路径 RelationService.like 传值本就正确,线上未触发。

测试保真度对齐:schema-h2.sql 补 chk_match_source(与 V003 逐字对齐)与 user_profiles 表(此前测试库无此表,资料查询无法端到端测试)。

新增 DiscoverBlockInvisibilityIntegrationTest(9 用例,@SpringBootTest 真实 H2 + 真实 SQL),覆盖正向/反向拉黑、对称性、排除自己、详情 404 与查自己不受限。P0FixVerificationTest 补 source 回归守卫。DiscoverServiceTest 同步构造签名。

负向验证:临时还原 D3 修复后,4 个用例因 CHK_MATCH_SOURCE 约束违反报错,证明守卫有效。

证据:后端全量 308 tests / 0 failures / 0 errors / 3 skipped(基线 299);UI 契约 13/13 GREEN。报告 docs/Phase-08-V11-Recovery-Completion-Report-2026-09-16.md。

同步 STATUS.md / JIRA_BACKLOG.md(EPIC-P08-V11 置 ✅)/ 客户需求基线计划 Phase 08 复选框;首轮报告加状态注记(其「Known Issues 无」已被推翻)。.gitignore 忽略 .workbuddy/。
2026-09-16 15:11:38 +08:00
Codex e4af367609 docs(brand): 补提交遗漏文件 —— 部署架构运维文档的 Codex 署名
上一提交 60eeb8a 的 staging 通配符未覆盖根目录这份文件名以「部」开头的文档,
其 11 处 Codex 署名(生成者/执行者标注)已是 WorkBuddy,但未入库。本次补齐。
2026-09-16 14:38:05 +08:00
Codex 60eeb8aef5 docs(brand): 全仓库 Codex 署名统一为 WorkBuddy + 5 个文件重命名
## 背景
用户指令:「先将 codex 的任务一并改为 workbuddy 的任务」。
Codex 已不再运行,当前由 WorkBuddy 承担开发执行,文档署名需同步。

## 判定规则(用大小写精确区分)
| 类别 | 写法 | 处置 |
|---|---|---|
| 工具署名 | 大写 Codex | → WorkBuddy |
| 分支名 | codex/ui-v11-fidelity | → workbuddy-ui-v11-fidelity(去斜杠,规避本仓库 ref 清理) |
| 已删除文件的历史路径 | 小写 2026-09-11-codex-baseline-cleanup* | 保持原样(记录真实曾存在的文件名,改了即失真) |

结果:26 个文件、126 处署名替换。残留小写 codex 仅 8 处,全部是历史路径。

## 文件重命名(git mv,保留历史)
- Codex开发执行总指令、项目规则与任务拆解.md → WorkBuddy 同名
- Codex首轮启动提示词 + Phase 01~Phase 26 Jira级任务清单 + 每阶段验收条件.md → WorkBuddy 同名
- docs/善缘丹阳 V1.1 Codex 开发执行计划修订版.md → docs/善缘丹阳 V1.1 WorkBuddy 开发执行计划修订版.md
- docs/善缘丹阳 V1.1 Codex-Jira 最终开发任务增量清单.md → docs/善缘丹阳 V1.1 WorkBuddy-Jira 最终开发任务增量清单.md
- docs/善缘丹阳 V1.1 Codex最终主启动提示词(整合版).md → docs/善缘丹阳 V1.1 WorkBuddy最终主启动提示词(整合版).md

引用已同步:AGENTS.md 6 处、AGENTS_SUMMARY.md、以及 4 份计划/规格文档。

## AGENTS.md 两处特别处理
- 「git config user.name = Codex」一句改为不点名工具的表述
- 「feature/ fix/ codex/ ui/ 前缀」示例去掉 codex/

## 有意未改动
- ui-tests/ui-document-baseline-contracts.mjs 的废弃清单仍列 codex-baseline-cleanup*
  那是「必须不存在」的断言目标,改了会让哨兵失去意义
- 仓库 git config user.name 仍是 Codex —— 未擅自改动环境身份。
  如需彻底交接:git config user.name WorkBuddy && git config user.email workbuddy@local

验证:UI 契约门禁 13/13;AGENTS.md 零 Codex 残留;5 个新文件名与全部引用一致
2026-09-16 14:37:44 +08:00
Codex 6ae08b89fb docs(agents): 新增「版本控制约定」强制节 + 修正 UI 验收口径
## 背景:refs/heads 嵌套分支名被实时清理,导致 HEAD 悬空

提交 bc4531a 后,refs/heads/codex/ui-v11-fidelity 被某个常驻监听进程删除(连 codex/ 目录一起),
HEAD 悬空 → `git log` 报 does not have any commits yet,`git status` 把全仓库文件标成新增。
`git update-ref` 重建返回退出码 0,但 0.2 秒内文件又消失(假成功)。

定向实验(14:06~14:10)定位的真实规则,与最初判断完全不同:

| ref 位置                        | 结果                     |
|---------------------------------|--------------------------|
| refs/heads/<单段名>             | 存活(连续观测 30s 稳定)|
| refs/heads/<任意>/<子名>        | 0.2s 内删除(连父目录)  |
| refs/tags/<任意深度>            | 存活                     |
| .git 下非 refs 的普通子目录     | 存活                     |

→ 删除器只针对 refs/heads 的子目录,且是事件驱动(0.2s),不是定时轮询(schtasks 无匹配项)。
→ 推论:标准 Git Flow 的 feature/ fix/ codex/ ui/ 前缀在本仓库全部不可用。

## 更正一处此前的错误结论

refs/codex/turn-diffs/checkpoints/... 是 09-11 / 09-14 的存量 ref,不代表有活跃 Codex 进程;
且仓库 git config user.name 本来就是 Codex,故 reflog 中所有条目都署名 Codex
(包括非 Codex 工具产生的提交)→ **reflog 署名不能用来判断是否存在并发进程**。

## 仓库处置

- HEAD 由悬空的 refs/heads/codex/ui-v11-fidelity 改为有效的 refs/heads/main
  (symbolic-ref + reset -q,仅同步索引,未触碰任何工作区文件)
- main 由 b8c33b9(09-11)快进至 bc4531a —— 纯快进:main 是 bc4531a 的严格祖先,
  `git rev-list --left-right --count main...bc4531a` = 0 58,无分叉、零冲突、零文件变化
- 保留顶层分支 feat-ui-v11 与 tag ui-v11-accepted-and-me-routes 作为冗余锚点

## AGENTS.md 变更

- 新增「版本控制约定(本机环境强制,务必先读)」一节:
  分支命名约束(禁用斜杠)、提交后自保清单(tag + bundle + 回读验证)、悬空 HEAD 的恢复方式
- 修正 UI 口径:首批范围(小程序 11 页 + 管理端框架/工作台)已于 2026-09-16 通过用户验收
  (基本达标),关闭「视觉否决 / REOPENED」判定;管理端桌面端目视复验仍待用户执行
- 头部日期更新为 2026-09-16

## STATUS.md

- 分支引用由已不存在的 codex/ui-v11-fidelity 改为 main

验证:UI 契约门禁 13/13;工作区 vs bc4531a 差异 0 行
2026-09-16 14:31:26 +08:00
Codex bc4531a734 feat(api): 补齐 /me/* 命名空间,恢复 17 个 404 调用 + 修 /me/status 过期桩
背景:小程序 services/*.js 按 REST 规范(§16 / §22-§40 / §80 / §83-§84)统一调用
/me/*,但后端从未注册 /me 基路径,个人资料、照片、择偶条件、隐私设置、关系状态、
我的举报/申诉等约 17 个调用全部 404。

后端
- 新增 me/controller/MeController:把既有 service 能力暴露到规范路径(纯委托,不复制
  业务规则,AGENTS.md #26)。旧路径(/profile/*、/photos、/relationship/*、
  /account/privacy-settings、/risk/reports/my、/appeals/my)全部保留。
- 响应形状按小程序 JSDoc 适配(小程序被 86 页引用,是权威契约):
  · /me/relationship-status 返回 status(旧 /relationship/status 返回 relationshipStatus,
    字段名不同,前端读的是 result.status)
  · /me/profile/review-status 返回 {status, items, submittedAt, reviewedAt},时间统一 epoch 毫秒
  · /me/profile/submit-review 返回 {reviewStatus, submittedAt}
- 小程序三条历史写法一并兼容:/me/privacy-settings、/me/photos/{id}/main、/me/appeals

修复
- AuthService.getMeStatus:verified / profileStatus / reviewStatus 三项写死为
  false / not_started / not_submitted,注释标注"Phase 03/04/05 填充",但那三个 Phase
  早已交付,导致 /me/status 长期返回错误的实名与审核状态。改为 capabilityService
  + userProfileMapper + reviewTaskMapper 的真实来源(与首页聚合同一判定)。
- PhotoController.deletePhoto:原用 ((Map<?,?>) p).get("id") 强转 UserPhoto 实体,
  必然抛 ClassCastException,删除照片恒定 500。改用实体主键比对。
- PhotoService.setMainAvatar(新增):数据模型以 photo_type='avatar' 标识头像(见
  UserPhotoMapper#findAvatar),无 is_main 字段,故"设主图"= 换类型并保证至多一张。
  注意:插入该方法时曾把 deletePhoto 原有的 @Transactional 挤到新方法上,导致
  "Transactional is not a repeatable annotation" 并引发 Lombok 全面停摆(满屏
  找不到 getter/setter),已修正注解归属。

验证
- 后端全量:299 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(+2 为新增契约测试)
- 新增 MeRouteContractTest(2 例):同时断言 HTTP 方法,锁定 21 条 /me/* 与 13 条旧路径
- 小程序 tsc --noEmit 通过;UI 契约门禁 13/13

文档
- STATUS.md:/me/* 从"待裁决"改为"已完成";核心待清债务 1 项 → 0 项
- API_INVENTORY.md:顶部"待收敛"警告改为"已收敛",补全 /me/* 端点表
2026-09-16 12:54:39 +08:00
Codex ce172ffd4f feat(ui): activity 页迁 TS + 修复过期契约 + 新增 11 页静态预览器
## 背景
2026-09-13 UI 重设计方案的 13 个契约门禁中 3 个为红,逐一查明性质后分类处理。

## 1. activity 页迁移 TypeScript(11/11 页现已全 TS)
- activity.js → activity.ts(139 行),业务逻辑零改动,仅补类型
- 此前 STATUS.md 记「9/11 TS-only、3 页待迁」,但 messages/matchmaker
  已在 4f91be0 迁完,实际只剩 activity 一页,本提交后清零

## 2. miniapp-reference-contracts 契约自身过期(非代码问题)
- 契约引用 messages.js / matchmaker.js / activity.js,但页面已 TS 化,
  readFileSync 抛裸 ENOENT 栈,看不出「契约引用了陈旧文件名」这一真实原因
- 改为统一走 readPageLogic(page):断言 .ts 存在 + .js 不存在(锁单一运行源),
  找不到文件时给出可读断言而非崩溃
- 核心 11 页循环内统一加 TS-only 校验,防止 JS 回流

## 3. phase07-home-contracts 契约断言架构写反(非代码问题)
- 契约要求 HomeApplicationService 字面出现 userIdentityMapper.findByUserId,
  但实际是 capabilityService.isIdentityVerified(userId)(HomeApplicationService:116)
- 这符合 AGENTS.md #26「前端入口可见性由服务端 CapabilityService 返回决定」,
  让首页穿透 Capability 直接持有 mapper 反而是架构重复
- 且契约自身另一条已断言 CapabilityService 必须有 isIdentityVerified,
  两条要求互相矛盾
- 改为:查库断言落在 caps 侧;首页断言必须委托 capabilityService.isIdentityVerified;
  新增 doesNotMatch(home, /userIdentityMapper/) 禁止首页绕过 Capability
- 保留原有 doesNotMatch(home, /setVerified\(true\)/) 防硬编码断言

## 4. 新增 11 页静态预览器(本机无微信开发者工具)
- scripts/preview-miniapp.mjs:把 WXML + WXSS 转成可浏览器打开的静态 HTML
- 能力:wx:if/wx:elif/wx:else 链、wx:for 列表、嵌套三元、比较/取反运算、
  自定义组件按属性展开、slot 替换、rpx→px 换算、组件样式收集
- mock 数据严格对齐 types/index.d.ts 与各页 WXML 实际读取字段
  (如 regionName/heightCm/educationCode、status: registration_open),
  不用想象字段,避免误导验收判断
- 产出 artifacts/ui-redesign/miniapp/preview.html(11 页可切换,284K)
- 明确边界:非真机渲染、不执行页面 JS、不连后端,
  只用于视觉走查,不得作为功能验收证据

## 验证
- 契约门禁 12/13 通过(唯一红的 ui-document-baseline-contracts 涉及
  STATUS.md 验收口径,待用户看真机效果后裁决,本轮不动)
- 11 页渲染体量:116/70/47/56/94/68/39/39/41/37/38 个类名元素,
  零残留 {{}} 插值、零残留原始组件标签
- 小程序 tsc --noEmit 通过
2026-09-16 12:33:48 +08:00
Codex 3244e02e7e refactor(api): 关系/匹配域采用 2026-09-14 canonical 路径,消除三代并存
## 问题:同一能力存在三代路径,前端指向的是已废止的一代

按 mtime 排查后发现"最新"并非上一轮以为的 /relations/*,而是 2026-09-14 的 match 模块:

| 代际 | 位置 | 日期 | 路径 |
|---|---|---|---|
| canonical | platform.match: Like/Block/MatchController | 09-14 12:13~12:14 | /likes /blocks /matches |
| 二代 | platform.relationship: Relationship(Status)Controller | 09-10 10:59 | /relations/* /relationship/* |
| 一代(规范) | REST API 接口详细设计规范.md | 08-28 | /relationships/*(多一个 hips) |
| 前端 | miniapp/services/matchService.js | 08-29 | 混用二代 + 一代 |

后果:缘分三榜、黑名单页、成功牵手流程全部 404;另有两份重复 success 实现 + 一份桩实现。

## 裁决:采用 2026-09-14 最新路径,前端与二代让位(用户指令)

### 补齐 canonical 模块缺口(此前只有 javadoc 声明或服务方法,无控制器)
- RelationService.unlike() + LikeController `DELETE /likes/{targetUserId}`
- 新增 PassController `POST /passes`(RelationService.pass 在 09-14 模块内却无控制器,补齐与 /likes、/blocks 对称)
- BlockController `GET /blocks`(javadoc 早声明"我的 Block 列表"但未实现,mapper findBlockedByUser 已就绪)
- MatchController `POST /matches/{id}/success/request|confirm|decline`(javadoc 标"Phase 12 预留"),
  委派真实实现 RelationshipStatusService;authorizedMatch 对"不存在"与"无权"统一回 404,避免探测他人 Match
- LikeResponse 对齐前端 LikeResult:{ relationStatus, mutualMatch, matchedConversationId, matchId }
  Mutual 时调 ChatService.getOrCreateConversation 取会话 id → "双向喜欢→跳聊天"链路真正打通
- LikeController/PassController/BlockController 补 targetUserId 空值校验

### 移除被取代的路由与桩实现
- RelationshipController 移除 like/cancelLike/pass/block/unblock/matches/unmatch/success,仅保留 /fate/*
- RelationshipStatusController 移除 /success/*(已由 /matches/{id}/success/* 取代),保留关系状态生命周期
- RelationshipService 删除 requestSuccess/confirmSuccess 桩 ——
  前者自述 "TODO: 查询 relationship_success_requests 表" 却返回硬编码 status="pending",
  等于绕过 AGENTS 硬规则 6「成功牵手必须双确认」
- 注意:RelationshipService.like/block/pass 被 P0FixVerificationTest /
  RelationshipBlockPriorityPhase08Test 直接测试,方法体保留

### 列表信封统一
- CursorPage 从 chat.dto 提升到 shared.dto(消除 match 模块反向依赖 chat DTO)
- /relations/fate/mutual|liked|likers 与 GET /matches 改返回 { items, nextCursor }

### 前端对齐
- matchService.js 全部路径改到 canonical;补 declineSuccess
- matchStore 补 _decorateBlockedList,修复"模板渲染 blockedAtText 但全仓从未计算"的缺陷
  (与上一轮 lastMessageAtText 同源,同类字段一律在 store 装饰一次)

### 文档
- docs/api/API_INVENTORY.md 关系/缘分/匹配三节重写为 canonical 路径;
  顶部加 /me/* 路径族显式警告(小程序按规范写 /me/*,后端实为 /profile/*、/photos、/relationship/*,
  约 15 个端点会 404,收敛方向未裁决,防止后续照抄踩坑)
- docs/STATUS.md:Backend 回归债务由「待清(277 tests 4F/8E)」更新为「已清偿(297 tests 全绿)」

## 验证
- 新增 CanonicalRouteContractTest(3 例):用 RequestMappingHandlerMapping 断言
  canonical 路径已注册、被取代的旧路径不再注册、关系状态生命周期保留 —— 路由再漂移即红
- 后端 297 tests, 0 failures, 0 errors, 3 skipped, BUILD SUCCESS
- 小程序 tsc --noEmit 通过
2026-09-15 23:16:27 +08:00
Codex 687a156350 fix(chat): 会话/消息响应契约对齐小程序,修复聊天页字段断链
## 问题(三处真实断链)
后端 chat 模块字段名与小程序契约不一致,但自动化测试此前未覆盖,故一直未被发现:

1. 会话 DTO 用 otherUserId/otherDisplayName/otherAvatarUrl,
   而前端(types/index.d.ts + pages/chat/chat.ts + components/conversation-item)
   只读 targetUserId/targetDisplayName/targetAvatarUrl。
   → chat.ts 取不到对方 userId(无兜底,落到空串),
     导致"查看资料 / 拉黑 / 举报"点击后提示"暂无更多信息"并失效。
2. 消息响应只给 createdAt(ISO 字符串),前端读 Message.sentAt: number(epoch 毫秒)。
   → chat.ts 时间分隔 ts 恒为 0,消息时间分隔与时间文案不显示。
3. 会话对象缺 relationStatus,前端 conversation-item 依赖它做灰显与"已拉黑"提示。

## 裁决依据
按"以文件修改时间为准"的裁决统一到前端约定:
- 前端 types/index.d.ts 被 86 个页面引用,是声明式契约
- pages/chat/chat.ts(09-04)是最新的真实消费者
- targetUserId 已是项目内既有惯例(ActivityMatchItemDTO)
- admin-web 不消费 /conversations,无第二方受影响

## 变更
- ConversationDTO:字段改名 target*;新增 relationStatus;lastMessageAt 改 epoch 毫秒
- ChatService.toConversationDTO:同步映射;relationStatus 按 Block > unmatched > mutual_match > neutral 推导
- Constants:新增 RELATION_STATUS_NEUTRAL / MUTUAL_MATCH / UNMATCHED(不硬编码)
- MessageResponse:新增 sentAt(epoch 毫秒)
- miniapp/utils/datetime.js(新增):收敛时间格式化,
  补上模板依赖但全仓从未计算的 lastMessageAtText
- messageStore:会话进入 store 时统一装饰 lastMessageAtText
- chat.ts:_formatTime 改为复用共享工具;移除已失效的 otherDisplayName 兜底

## 验证
- 新增 ChatContractJsonTest(4 例)锁定字段名、sentAt 数值类型、
  并断言 other*/status 旧字段名不再出现
- 后端 294 tests, 0 failures, 0 errors(原 290 + 新增 4)
- 小程序 tsc --noEmit 通过

遗留(未在本轮处理):平台尚未实现头像 URL 解析(user_photos 只存 fileKey),
targetAvatarUrl 保持 null,前端 ui-avatar 回退昵称首字,不影响展示。
2026-09-15 22:45:46 +08:00
Codex eaf8fdf195 refactor(backend): P0 阻断修复 + 按最新修改时间收敛重复模块与契约
## P0 阻断修复
- 编译:修复实体类与服务类同名导致 Lombok 注解轮次中止的问题
  (MatchmakerService/CounselingService -> *Record),196 个编译错误归零
- 权限矩阵:platform_admin 按设计文档收紧为拒绝 5 项高危权限(不改测试阈值)
- 启动:修复 8 组 MyBatis-Plus 同名 bean 冲突(ConflictingBeanDefinition)
  与 URL 路由歧义(Ambiguous mapping),应用可正常启动

## 模块收敛(裁决规则:以文件修改时间为准)
- messaging 包整体删除,能力迁移至 chat 包
  * 会话列表/详情/未读端点、ConversationDTO、CursorPage
  * 消息游标分页统一为 { items, nextCursor }
- relationship 的 Match/UserRelation entity 与 mapper 合并进 match 包
- MatchmakerUserController 合并进 MatchmakerController,删除冗余 Controller
- 补齐 INV-004:chat sendText 增加双向 Block 校验,拉黑后禁止发送

## 契约对齐
- 红娘服务提交请求兼容前端 { requirement } 字段
- 消息与会话分页契约前后端统一

## 测试
- 迁移 6 个风控用例至 ChatSafetyServiceTest
- 清理 messaging 测试包遗留:6 个真实常量断言归位 chat 包,
  移除 2 个 assertTrue(true) 占位假测试(Block 真实行为已由
  P0FixVerificationTest / RelationshipBlockPriorityPhase08Test 覆盖)
- schema-h2.sql 对齐 PostgreSQL 真实表结构(matches 补列、
  conversation_members 改复合主键)

结果:290 tests, 0 failures, 0 errors, 3 skipped, BUILD SUCCESS
2026-09-15 22:29:26 +08:00
Codex 9d73087eb0 feat(admin-web): matchmaker management view — D3 debt cleared
New module admin-web/src/modules/matchmaker/MatchmakerManagementView.vue (2 tabs):
- Service request queue: assign operator / create recommendation
- Recommendations list: pending_view / viewed / liked / passed / confirmed_mutual

Route added: /matchmaker -> MatchmakerManagementView.vue (permissions: matchmaker.service.view)

D3 debt cleared: admin-web matchmaker recommendation now has a real view.
vue-tsc --noEmit -> 0 errors.
2026-09-14 15:02:51 +08:00
Codex 4f91be0cab feat(miniapp): final 3 pages migrated to TypeScript (matchmaker/messages/mine)
D1 debt cleared: all 11 miniapp core pages now TS-only.
tsc --noEmit -> 0 errors. JS sources deleted.
2026-09-14 14:59:28 +08:00
Codex 918368159c feat(phase-13A): CounselingService — INV-012/013 + 婚恋咨询 + 婚前辅导
Phase 13A Counseling — ADD new domain module per AGENTS.md rule #21/#22.

counseling module (5 files):
- Entity: CounselingService -> counseling_services
- Entity: CounselingFollowup -> counseling_followups
- Mapper: CounselingServiceMapper (findFull + findSanitized INV-013)
- Service: CounselingService
  INV-012: assertAssigned() guards schedule/complete
  INV-013: getDetail() returns sanitized for matchmaker/operator,
           full for user/assigned-counselor/manager
- Controller: 7 endpoints (user/apply/list/detail, counselor/assigned/schedule/complete, manager/assign)

Permission model (Service layer guarded):
  User(self) -> full detail
  Assigned Counselor -> full detail
  counseling_manager -> full detail
  Matchmaker/Operator -> sanitized only (no user_request_summary)

service_type: counseling(婚恋咨询) / premarital(婚前辅导)
2026-09-14 14:51:25 +08:00
Codex 5cbd931fa2 feat(phase-13): MatchmakerService — referral + manual recommendation + user confirmation
Phase 13 Matchmaker — EXTEND per JIRA. P13-V11-T01 through P13-V11-T05.

New matchmaker module (5 files):
- Entity: MatchmakerService (request), MatchmakerRecommendation (manual rec)
- Mapper: MatchmakerServiceMapper, MatchmakerRecommendationMapper
- Service: MatchmakerService
    T01 createReferral(type=counseling) — referral only, does NOT submit on behalf
    T02 createReferral(type=activity)
    T03 createReferral(type=service_station)
    T04 referral_source追溯: every referral carries operator_id + service_id + type
    T05 用户拥有最终决定权: acceptRecommendation() calls RelationService.like()
        as the USER, not the matchmaker. Matchmaker NEVER creates Match directly.
        If mutual, updates Match.source='matchmaker' for traceability.
- Controller: user endpoints (submit/list/pending-recommendations/accept/pass/viewed)

Core AGENTS.md #5 compliance:
- Matchmaker can only 'recommend', NEVER 'like on behalf of'
- User must actively accept recommendation (calls acceptRecommendation -> RelationService.like)
- createRecommendation creates pending_view record + notification, nothing else
- confirm mutual only when BOTH users independently Like (existing Like flow)

Status machines:
- matchmaker_services: submitted -> assigned -> processing -> completed
- matchmaker_recommendations: pending_view -> viewed -> liked/passed/confirmed_mutual
2026-09-14 14:48:23 +08:00
Codex 0418856ce3 feat(phase-12): RelationshipStageService — home service suggestions
Phase 12 Relationship — KEEP/EXTEND per JIRA. 不增加复杂状态.

New RelationshipStageService (match module):
  P12-V11-T01 Successful match -> premarital_suggestion
  P12-V11-T02 GettingToKnow match -> matchmaker + counseling
  P12-V11-T03 7-day silence in GettingToKnow -> counseling_recommendation

Rules are simple, based on existing Match.status + lastActivityAt:
- successful  -> 婚前辅导建议
- getting_to_know -> 红娘服务 + 婚恋咨询建议
- getting_to_know + 7d silence -> 婚恋咨询促活建议
- no Match    -> 默认红娘 + 婚恋课堂

Integration point: HomeApplicationService.buildRecommendations()
just inject RelationshipStageService and append suggestions to home DTO.
2026-09-14 14:40:13 +08:00
Codex e2a8a8c85f feat(phase-11): NotificationService — 10 business templates + privacy-safe external copy
Phase 11 Notification — EXTEND per JIRA. P11-V11-T01 through P11-V11-T11.

New notification module (4 files):
- Entity: Notification -> notifications (existing DB table)
- Mapper: findByUser/countUnread/markRead/markAllRead + findExisting(idempotent)
- Service: NotificationService with 10 business templates
  T01 appointmentSubmitted  T02 appointmentConfirmed  T03 appointmentChanged
  T04 counselingAssigned    T05 counselingScheduled   T06 counselingCompleted
  T07 premaritalScheduled   T08 leNeedEvidence        T09 leVerified
  T10 leRejected
  T11 buildPrivacySafeCopy -> external lock-screen copy NEVER reveals
  sensitive counseling type names (INV-010 compliance)
- Controller: GET list, GET unread, PUT markRead/{id}, PUT readAll

Design invariants:
- sendExternal() wrapped in try/catch -> failure does NOT rollback core service
- Idempotency via (user, type, business_type, business_id, content) dedup
- External copy uses neutral words '服务/预约' for counseling events, not '咨询'
2026-09-14 14:33:37 +08:00
Codex cf3a84e725 feat(phase-10): Chat Safety — content scan + rate limit + RiskNotice
Phase 10 Chat Safety — KEEP per JIRA. P10-V11-T01/P10-V11-T02.

New ChatSafetyService (chat module):
- scanContent(): HIGH_RISK keyword + CONTACT regex (phone/wechat/QQ/scan)
- checkRateLimit(): Redis counter + fail-open fallback, stricter for new Match (first 5 min)
- buildRiskNoticeBody(): system message JSON with anti-fraud targetPage

ChatService.sendText() integrated Phase 10:
  1. Redis rate limit check (newMatch-aware)
  2. Content scan -> risk_level on Message
  3. high/critical -> throw, block send
  4. warning -> peer gets RiskNotice system message (P10-V11-T01: anti-fraud entry)
                                        (P10-V11-T02: RiskNotice -> anti-fraud content page)
  5. Log enriched with risk_level

Constants already had RISK_LEVEL_* and MESSAGE_TYPE_SYSTEM — no new constants needed.
2026-09-14 14:16:47 +08:00
Codex eda2ce19a8 feat(phase-09): Text Chat full backend + WebSocket
Phase 09 Chat — KEEP per JIRA. P09-V11-T01 Activity Match source compatibility.

New chat module (11 files):
- Entity: Conversation, ConversationMember (复合主键), Message
- Mapper: ConversationMapper, ConversationMemberMapper, MessageMapper
- Service: ChatService (getOrCreateConversation/sendText/loadMessages/markAsRead/syncFromMatch)
- Controller: ConversationController (POST /from-match, GET /{id}, GET list)
              MessageController (POST send, GET load history, PUT mark read)
- WebSocket: WebSocketConfig + ChatWebSocketHandler (/ws/chat, JSON protocol)

Core invariants:
- INV-005: ChatService requires Match status active/getting_to_know
- Conversation.status sync with Match.status (syncFromMatch)
- Member.canSend + Conversation.status + sender membership all validated
- Client-side idempotency via client_message_id
- P09-V11-T01: Conversation binds only match_id — works identically for
  user_like / activity_mutual / matchmaker Match sources

WebSocket note: Memory-based session map; multi-node will need Redis Pub/Sub.
2026-09-14 14:00:04 +08:00
Codex 8b62b5b5df feat(phase-08): Like / Match / Block full backend implementation
Phase 08 Like/Match/Block — KEEP per JIRA, incremental P08-V11-T01.

New entities (match module):
- UserRelation -> user_relations (liked/passed/blocked)
- Match -> matches (source: user_like/activity_mutual/matchmaker, episode-aware)

New mappers:
- UserRelationMapper: findOne/countBlockedEitherWay/countReverseLike
- MatchMapper: findActiveBetween/findLatestBetween/findActiveByUser/findHistoryByUser

New services:
- RelationService: like/pass/block/unblock + mutual detection + capability guard
  - INV-004: Block priority highest (auto-unmatch, mutual block check)
  - INV-005: Like only creates Match on Mutual
- MatchService (static): createOrReuseMatch factory
  - AGENTS.md #19: existing active Match -> reuse
  - AGENTS.md #20: prior unmatched -> new Match with episode+1
  - source-aware: user_like / activity_mutual / matchmaker

New controllers:
- POST /api/v1/likes
- POST /api/v1/blocks, DELETE /api/v1/blocks/{target}
- GET /api/v1/matches, GET /api/v1/matches/{id}, POST /api/v1/matches/{id}/unmatch

Integrates with CapabilityService for base eligibility checks on Like.
2026-09-14 12:15:27 +08:00
Codex 0da82c9914 docs(gate): add phase 4 gate output artifacts 2026-09-14 11:44:02 +08:00
Codex 984827f415 docs: phase 4 acceptance report + STATUS sync
- STATUS.md updated: 阶段0-4全部完成, Ready for User Acceptance
- New acceptance report: 三端构建/13契约/源码健康度/债务清单
- CapabilityService independentized (AGENTS.md #26 compliance)
- discover 筛选完整重设计 (5 chip + 4 section + bottom sheet)
- miniapp C组 content/anti-fraud/service-station TS-only
- 三端构建全绿: miniapp tsc=0, admin-web vue-tsc=0, vite build OK
- 13/13 contracts all green
2026-09-14 11:43:46 +08:00
Codex dd58ab6eae feat(capability): extract CapabilityService — AGENTS.md rule #26 compliance
New module backend/capability/:
- CapabilityService: getCapabilities(entry point for frontend),
  plus public helpers (isAccountNormal/isIdentityVerified/isProfileApproved/
  isSearching/isBaseEligible/hasLocalEligibility/entry builder)
- CapabilityController: GET /api/v1/capabilities endpoint

HomeApplicationService refactored:
- Injects CapabilityService, delegates buildCapabilities()
  and QuickEntry construction + recommendation eligibility checks
- Removes duplicated private methods (isSearching/isIdentityVerified/
  isProfileApproved/entry) — single source of truth now in CapabilityService

Contracts updated to check CapabilityService.java for capability-related
assertions (setDisabledReason/isIdentityVerified/ACCOUNT_STATUS_NORMAL)
2026-09-14 11:38:30 +08:00
Codex 22325ec2b0 feat(discover): chip-targeted filter panel — scroll to + highlight target section
- Each filter chip (年龄/学历/婚姻) carries data-target pointing to its section
- openFilterPanel receives TouchEvent, maps target to section id, sets panelScrollTo + panelHighlight
- Panel uses scroll-view scroll-into-view with scroll-with-animation for smooth scroll
- Targeted section gets pink outline highlight via .hl class
- Non-targeted sections remain normally visible for context
2026-09-14 11:27:52 +08:00
Codex adfa4ff31e fix(discover): filter panel as bottom sheet with mask, add debug log
- Changed filter panel from in-flow to fixed bottom drawer with semi-transparent mask
- Mask click closes panel (closeFilterPanel)
- Added console.log in openFilterPanel for debugging event firing
- Panel now z-index:11 above mask z-index:10, guaranteeing visibility
2026-09-14 11:19:25 +08:00
Codex 3e5d2314aa fix(discover): restore discover-filter-chips class for contract 2026-09-14 11:09:53 +08:00
Codex 146ebab61d feat(discover): rewrite filter UI to align with backend DiscoverController 5 params
- Top chips: 推荐(重置) / 丹阳本地(regionCode=321181) / 年龄 / 学历 / 婚姻
- Each chip has real active state bound to applied filter value
- Panel expanded to 4 sections: age slider + region + education + maritalStatus
- Param names fixed: minAge/maxAge/regionCode/educationCode/maritalStatus
- Data flow: panel temp -> onApplyFilter -> applied -> _buildParams -> API
- Panel opens with current applied values, closes with openFilterPanel/closeFilterPanel
2026-09-14 11:09:23 +08:00
Codex 0f06d82a92 fix(discover): filter chips use applied state and apply immediately on top-chip tap
- Top chip '推荐' now active only when no active filter (was hardcoded)
- Top chip '丹阳本地' uses applied.region (was tempRegion)
- Top chip '年龄' uses applied age range
- New onTopRegionTap applies region filter immediately and refreshes results
- Panel onRegionTap preserved for filter-panel multi-select flow
2026-09-14 10:59:43 +08:00
Codex 7009ec079c fix(miniapp): delete redundant api.ts to avoid build conflict with CommonJS api.js
WeChat DevTools TypeScript compilation overwrites the CommonJS api.js
output with the ES-module api.ts compiled output, breaking require()
consumers (authStore.js getToken, authService.js post). The remaining
api.js has the identical ApiClient class plus extra patch() method.
2026-09-14 10:29:15 +08:00
Codex 751f14d5ee chore: remove obsolete .js sources for migrated C-group pages 2026-09-14 10:14:27 +08:00
Codex 93fc9b0d88 feat(miniapp): migrate C-group pages to TypeScript, update contracts 2026-09-14 10:14:05 +08:00
Codex e59038790c docs(status): sync with phase 0 gate results, V035 migration and clean git tree 2026-09-14 10:04:08 +08:00
Codex fd5f4d6e80 chore: add artifacts/backups to gitignore 2026-09-14 10:02:58 +08:00
Codex ea2bb2439d chore: complete infrastructure config, contracts, reference images and ui redesign artifacts 2026-09-14 10:02:34 +08:00
Codex 63cbbff2de feat: implement miniapp C-group pages with real API calls and design tokens 2026-09-14 10:02:22 +08:00