b834fa86a06030aa310dcd95439b7ce32be40779
71
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b834fa86a0 |
test(invariants): Phase 23 Task 2.2 —— INV-011 显式行为守卫(9 用例 + 两次负向注入验证)
产出 Inv011EvidenceAccessGuardTest:非领取人 / 跨用户佐证 / 非 local_eligibility 任务一律拒绝, 且 successful 与 denied 两条路径都断言 sensitive_access_logs 真实落库(不用 mock 断言)。 另含 3 个结构性守卫(用户侧端点无用户可控 userId 入参、佐证读取入口的双重权限约束、 佐证读取不暴露可伪造 operatorId)与 1 个守卫自检(参数名反射可读,防断言静默永真)。 前置修复:schema-h2.sql 补 sensitive_access_logs 表 —— 该表此前在测试库完全不存在, 而它是「访问留痕」命题的唯一落点,缺失导致该命题根本无法断言(表漂移盲区,与 Phase 12/15/19 同族)。 负向注入两次,均精确命中: ① 停用 requireClaimOwner → 恰 1 红(非领取人用例),其余 8 绿 ② 停用 catch 内 denied 审计 → 恰 2 红(非领取人 + 跨用户),第 3 条走 try 外的另一路径故正确未红 两次注入均已还原并复跑全绿(9/9);V11InvariantsTest 12/12 绿;UI 契约门禁 18/18 绿。 文档:Task 2.2 闭环记录 + 附 A 扩至 7 处校正(含对本文档初稿「机构创建 500 归因」的自我更正, 实测为 chk_org_status 违约而非 id 为空)+ 附 B 扩至 11 项新发现 (5 个 Mapper 方法声明了却无实现、隐私设置三方契约冲突 + 表/实体主键错配、 测试镜像 mvn -o 不可用、本机全量回归会卡住)。 |
||
|
|
e666ee6f8a |
feat(e2e): Phase 23 Task 2.1 —— 活动互选 8 场景 + 服务预约 8 场景真实实现(16 个真实用例)
背景:`tests/e2e/activity_mutual_consent_test.py` 与 `service_appointment_test.py` 此前是 **诚实占位**(仅 docstring、0 用例,指针指向 run_v11_tests.py),计划 Task 2.1 要求把 文件头保留的场景清单逐条实现为真实 HTTP 用例。本提交完成该任务。 ═══ 新增真实用例 16 个(本机活栈 pytest tests/e2e 49 → 65 passed)═══ activity_mutual_consent_test.py(8,覆盖 INV-014/015/006 + AGENTS.md #19/#20) 1 建立互选记录不建 Match(INV-014) 2 单方确认不建 Match(INV-015) 3 双方确认建 Match Episode 且双方可见 4 已有 active Match 时再次双确认必须复用(#19) 5 曾 Unmatch 后重新双确认开启新 Episode:episode +1(#20) 6 一方拒绝不建 Match,对方随后确认也不建 7 超时:已过期记录不得被推动成双确认(含「未过期必须生效」对照,防假绿) 8 单向意向不泄露(INV-006,含「B 确认后必须看到 accepted」反证) service_appointment_test.py(8) 1 提交预约;2 幂等键重放不得产生第二条;3 运营确认 submitted→confirmed(用户侧同态可见) 4 并发「用户取消 × 运营确认」不得 5xx、最终状态必须收敛;5 我的列表只含本人 6 待确认可取消;7 归属隔离(他人不可读详情/不可取消) 8 运营侧状态闸门:已完成预约不得再被取消 ═══ 两个幂等 seed(E2E 前置数据,本机与 CI 通用)═══ - seed_e2e_activity.sql:organization + 两个 activity。 必须建的原因是硬约束而非选择:`activity_mutual_connection_consents.activity_id` 有 **FK 到 activities(id)**(实测约束名 …_activity_id_fkey),而 activities.organizer_org_id 又 FK 到 organizations(id) 且 NOT NULL —— 全新库里三张表全空,互选无活动可挂。 - seed_e2e_service_station.sql:service_location + **站点-运营关联**。 关联必需:运营确认受数据范围约束(`assertOperatorScope` → `canAccessByOperator`), super_admin **不豁免**,实测 e2e_admin 确认他站预约返回 403。 E2E 因此改用 seed 中该站点关联的 e2e_station。 两个 seed 由测试首用时幂等执行(docker exec psql),无需改 CI 步骤。 ═══ 负向注入(Task 2.1 Step 3)═══ ③ 撤销 service_station_operator 的 service_appointment.confirm → 恰 2 红(test_operator_can_confirm / test_operator_cannot_cancel_completed,403),其余 6 绿; ④ 触发器强制新建互选记录的对方侧 accepted → 3 红(both_confirm_creates_match_episode / timeout_handled / single_interest_not_leaked),其余 5 绿。 两处均已还原并复跑全绿(65 passed),确认无残留(无 e2e* 触发器、权限计数回到 1)。 **两次失败的注入(记录以免后人重复踩)**: - 删除站点→运营关联:**无效** —— 测试的 seed fixture 在用例前幂等重放 seed 会自愈该前置条件; - 改 user_b_status 列默认值:**无效** —— `createMutualConsent` 在 Java 侧显式写 pending, DB 默认值根本不参与(侧面证明写入路径的权威性)。 ═══ 计划文档校正(审阅产物)═══ 按实测推翻 4 处失实断言,登记 7 项新发现(详见计划文档「附 A / 附 B」): - 推翻:matchmaker_services「无表/需扩权」(实为 V009 已建表且 mapper 齐备); 门禁 14/14(实为 18/18);「tests/e2e 现为占位」(实为仅 2 个文件); E2E 基线 49(实为 65)。 - 新发现:① `POST /operator/organizations` 必然 500(createOrganization 不赋 id + IdType.INPUT + 无 DB 默认值;且未校验 chk_org_type)→ 连带「无法新建活动」; ② `createMutualConsent` 从不写 expires_at → 互选永不过期(即批 3 P16-T13,配置与 V041 部分索引空转);③ 用户侧取消预约不校验状态(已确认/已完成也能自取消), 与运营侧明确拒绝 completed 口径不一致 → 需产品裁定; ④ E2E 环境 organizations/activities/service_locations 均为空表(无迁移种子); ⑤ 运营数据范围 super_admin 不豁免(预期行为,勿误判); ⑥ 本机复跑会遇到 Redis 登录锁定键(auth:operator_login_lock:*); ⑦ e2e_* 账号因 ON CONFLICT DO NOTHING 重跑 seed 不覆盖旧口令。 ═══ 验证 ═══ - `pytest tests/e2e` → **65 passed**(含新增 16);`node scripts/run-ui-contracts.mjs` → **18/18 PASS**,退出 0; - 本机环境处置(不属代码改动):把 6 个 e2e_* 账号口令对齐到 CI 口令、清除 Redis 登录锁定键。 - **未验证**:微信开发者工具、CI 上的首次 E2E 实测绿(Task 2.3 Step 3,本机无 Docker Hub 出口)。 |
||
|
|
2ae72aabe7 |
docs(governance): JIRA Epic 总览表补全状态列(P01~P07 补 ✅ / P23~P26 如实标注);基线计划 Phase 22 合并重复口径澄清块
- Epic 表由 4 列改 5 列:P01~P07(此前漏标)补 ✅;P23 部分关闭、P24 待用户确认、P25 待运营、P26 Smoke 待手动
- UAT/RC/Production 当前执行项行更新:/me/* 已收敛,停在 Phase 24 门前
- 基线计划 Phase 22(Deployment/Ops):合并两次编辑产生的重复口径澄清 blockquote,补回 Files 行
|
||
|
|
b67a56479e |
fix(miniapp): 修 P0 请求层崩溃(一个下划线致所有请求卡死)+ 组件 WXSS 违规选择器,再补 3 项门禁
═══ 缺陷 1(P0,运行期):utils/api.js 成功回调调用不存在的方法 ═══ 控制台: TypeError: _this._releaseIdempotencyKey is not a function 根因:类中定义的是 `releaseIdempotencyKey()`,成功回调写成了 `this._releaseIdempotencyKey(...)`(**多一个下划线**)。 **为什么一个字符的笔误是 P0**:该调用位于 `wx.request` 的 success 回调内、 且在 `resolve()` **之前**。抛错 → `resolve` 永不执行 → 该 Promise **永不 settle** → 所有页面永远停在 loading,同时 App.onError 刷屏。 即「一次成功请求」本应推进流程,实际把前端整成不可用。 修复不只是改名,而是纠正结构: - **先 `resolve(body.data)`,再执行收尾动作** —— 收尾逻辑不允许挡在响应路径上; - 释放动作自带 try/catch + console.warn:释放失败最多是"下次操作没拿到新键", 不得升级为业务失败。 - 语义未变(勿改):成功→释放(下次同域操作是新的一次);失败→刻意不释放 (重试复用同一把键,后端才能识别重复投递)。 ═══ 缺陷 2(编译期警告):组件 wxss 用了标签选择器 ═══ Some selectors are not allowed in component wxss ... (components/recommend-card/index.wxss:1:1092) 实测违规 1 处(23 个组件样式文件全扫):`.compact-like image` —— 组件 wxss 只允许类选择器。 改为 `.compact-like__icon` 并**同步在 WXML 补上类名** (只改 wxss 会让样式静默落空,是另一种"假成功")。 ═══ 门禁:15 → 18(既有的全是源码内容断言,抓不到上述两类)═══ - 第15 `wxml-structure-contracts`(上一提交新增,本轮 MIN_GUARDS 基线顺带对齐) - 第16 `miniapp-self-method-contracts`:扫描 `this._x(` / `self._x(` 等私有调用, 要求同文件有定义(方法简写 / 类字段 / `this._x = 值` / prototype 四种形态)。 **判据先自证假阳性**:首版正则不允许 `.` 前缀,把 `this._unsub = store.bind(this)` 形式误报为未定义(3 个页面);修正并加反向自检样例固化了该坑。 - 第17 `wxss-component-selector-contracts`:组件 wxss 禁标签/ID/属性选择器。 **作用域刻意只限 miniapp/components/**(page 的 wxss 允许标签选择器,套上去必有误报)。 - 第18 `miniapp-api-client-behavior`:**唯一的行为断言** —— 桩掉 `wx.request` 真跑 api.js, 验证成功 resolve / 失败 retain 键 / 成功 release 键 / 收尾抛错不影响响应路径 4 组。 含 1s settle 超时,直接编码"永不 settle"这一失败模式。 - 新增 `scripts/lib/wxss-selectors.mjs` + `scripts/check-wxss-component-selectors.mjs`(CLI)。 - `MIN_GUARDS` 15 → 18;执行器措辞改为「源码结构与关键行为断言」。 ═══ 验证 ═══ - `node scripts/run-ui-contracts.mjs` → **PASS 18 / 18**,退出 0; - 负向注入:未定义私有方法 / 标签+ID+属性选择器 / 多余闭合标签 → 均精准变红, 且**合法样例(`this._x = 值`、纯类选择器、属性内含 `>`)不被误报**; 行为守卫的负向自证见下一提交/记录;`UI_GUARDS_MIN=99` 拒绝报绿。 - 文档口径同步:AGENTS.md、STATUS.md(4 处当前口径 + 新增 1.1.0-rc.18 行)、ci.yml 注释。 - **未验证**:微信开发者工具真实编译与真机渲染(本机无该环境)。 |
||
|
|
d036c9b986 |
fix(miniapp): 修复 2 个页面 WXML 结构缺陷,并补第 15 项门禁(WXML 结构守卫)
现象:微信开发者工具报
[ WXML 文件编译错误] ./pages/service-history/service-history.wxml
get tag end without start, near `</`
真实缺陷(实测 2 处,非 1 处):
- pages/service-history/service-history.wxml 多 1 个 </view>
- pages/service-program/service-program.wxml 多 1 个 </view>
(均为单行压缩写法,随更早提交入库;全仓另外 84 个 WXML 结构自洽)
根因不是这两个文件写错,而是**门禁覆盖盲区**:
既有 14 项 UI 契约全是「源码内容 / 清单断言」,没有任何一条会解析 WXML,
所以「14/14 全绿」与「小程序能不能编译」之间没有因果关系。
修复:
1. 删掉两处多余闭合标签 → 86 个 WXML 结构全部自洽。
2. 新增 scripts/lib/wxml-structure.mjs:按**引号状态机**逐字符扫描标签。
不能用懒惰正则 —— 属性值里可以合法出现 `>`(`subtitle="{{unreadTotal > 0 ? ...}}"`),
正则会在引号内的 `>` 处提前收尾,把自闭合标签的 `/` 读掉,报出假阳性。
(首版实现即踩此坑,曾把合法的 messages.wxml 误判为坏文件;已复核排除。)
3. 新增 scripts/check-wxml-structure.mjs(CLI,退出码 0/1/2)。
4. 新增 ui-tests/wxml-structure-contracts.mjs:全量扫描 + **3 组内置自检样例**
(正向样例故意在属性值内放 `>`,把「假阳性」这个坑钉进门禁)。
5. scripts/run-ui-contracts.mjs:MIN_GUARDS 14 → 15(守卫被删/改名仍会拒绝报绿)。
6. 文档口径同步:AGENTS.md、STATUS.md(4 处当前口径 + 新增 rc.17 版本行);
ci.yml ui-contracts job 注释 14 → 15。历史时点行保留原值。
负向验证(三项,全部通过):
- 注入「多余闭合 / 未闭合」→ 退出 1,且只报坏文件、合法的 `>` 属性文件未被误报;
- UI_GUARDS_MIN=99 → 拒绝报绿(数量下限分支可自检);
- CLI 直扫坏目录 → 退出 1。
验证:node scripts/run-ui-contracts.mjs → PASS 15 / 15,0 失败,退出码 0。
未跑:微信开发者工具真实编译(本机无该环境),结论仅由结构判据支撑。
|
||
|
|
6b2ff0f73e |
docs: 二次修正 features 断言失实(5 处)—— 原判据「*.yml 无 features 块」被证伪
Phase 22 复验报告 D-22-D 的判据「backend/src/main/resources/*.yml 无 features: 块」是错的,
且被上一轮收尾照抄进 STATUS.md(P26-T06 行),属二次传播。
实测事实:
- application.yml(基础) → 无 features 块
- application-dev.yml(默认激活 profile,自 63cbbff)→ 有,21 键
- application-docker.yml(自
|
||
|
|
f63e579a8d |
ci(e2e): 落地「活栈供给方式」决策 — Compose in CI,并修掉 6 处真实缺陷
决策:E2E 活栈在 runner 内用 docker compose 起全栈(postgres + redis + minio + backend), 与本地验证路径完全一致;未采纳「service containers + runner 上 mvn package 后 java -jar」—— 那需要为后端另写一套 host 网络配置,等于制造第二条与本地不一致的路径。 本机实测(与 CI 同一序列):中间件三项 healthcheck 全 Healthy(pg_isready / redis-cli ping / mc ready local)→ backend /api/v1/health/live 返回 200 → seed 幂等可执行(INSERT 0 0) → pytest tests/e2e → 49 passed。 (本机限制:Docker Hub 不可达且无 temurin 基础镜像,未复现 --build;改用本地既有镜像为底座、 挂载当日 16:24 构建的 jar,使被验证的代码版本与 CI 一致 —— 否则是拿 09-09 的旧代码验 09-18 的用例。) 修掉的 6 处缺陷: 1. minio/minio:latest 是滚动 tag → pin RELEASE.2025-09-07T16-13-09Z(否则 CI 拉到的镜像 与本地验证时不同,表现为不可复现的偶发失败) 2. up 未显式列出 minio(靠 depends_on 隐式拉起),而 minio 的 healthcheck 正是 backend 能否启动的前置条件 3. up --build 把构建与起栈耦合,失败日志混杂 → 拆为 build / up 两步 4. 就绪窗口 5min 偏紧(首次构建后仍需 Spring 启动 + Flyway 46 个迁移)→ 7.5min 5. 缺 down -v 前置清卷:seed 用 ON CONFLICT (username) DO NOTHING,残留卷会让账号口令 停留在上一轮 → 登录 401,并伪装成业务失败 6. job 无 timeout(GH Actions 默认 360 分钟)→ timeout-minutes: 30 另:pytest -v → pytest tests/e2e -v。原以为从仓库根收集会误收 tests/*_test.py (其在 import 期依赖 psycopg2 与 E2E_DB_PASSWORD),实测否定:pytest.ini 的 testpaths = tests/e2e 已限定范围,从根收集正是 49 个用例且无 error。显式传参作为兜底保留。 计划 Task 2.3 由「依赖基础设施决策」改为「已决策并落地」,仅余 Step 3(CI 上实测一次绿)。 |
||
|
|
70134dd62e |
docs: STATUS 历史区收敛 951→783 行,修正两处残留假声明,同步治理报告与收尾计划
治理报告「八、遗留」第 2 项(STATUS 后半历史区过长)至此关闭。 STATUS.md(951 → 783 行,净减 168): - 折叠 2026-09-09 期快照:Phase 01~11 逐项任务表 → 索引表(指向权威结论位置);旧 Phase 13 首轮表 → 指针;「V1.0 基线状态 + 历史状态记录」两块 → 一段带历史边界声明的治理痕迹。 - 收敛作废/重复块:两块已作废的 Phase 22 历史声明、已作废的 Phase 23「已完成」声明、 「整体进度总结」里重复的 Phase 24~26 摘要。 - 修正两处残留假声明(本轮新发现): ① `P26-T06 初始化Feature Flags | ✅ (application.yml配置)` → 标「不成立」(依据 Phase 22 复验: 4 份 application*.yml 无 features 块,15 个 flag 实为 Java 字段默认且业务不读取); ② `Phase 23(V1.1专项测试)— 已完成 ✅` → 标「已被 2026-09-18 复验推翻,实为部分关闭」。 - 口径更新:UI 契约测试行 13/13 → 14/14(统一执行器 + 已接入 CI),并注明阶段 4 的历史时点值。 - 三个门禁锚点全部保留并复验:UI REDESIGN ACCEPTED BY USER、Phase 07…已通过用户验收、 管理后台: http://localhost:13520/。 同步文档: - 治理报告:4.4 漏网文档改为「已处置」;悬空引用表最后一行为已关闭;第七节补 UI 门禁入 CI、 执行器负向验证、漏网文档处置三行,并改写 STATUS 收敛行;第八节改为「3 项遗留全部关闭」。 - 收尾计划:批 1 Task 1.1 标完成(Step 1~4 证据齐),并更正「15 个契约文件」为实测 14 个; 验收口径由逐文件执行改为 `node scripts/run-ui-contracts.mjs`。 验证:`node scripts/run-ui-contracts.mjs` → PASS 14/14、退出码 0(含文档基线契约门禁)。 |
||
|
|
b2ef60c04c |
docs: 废止漏网报告 Phase-V11-Deployment-Completion-Report(原地重写)+ 修订两处引用
治理报告 4.4 登记的「保留待决」项至此关闭(用户确认执行)。 - docs/Phase-V11-Deployment-Completion-Report.md:102 行 → 废止声明,含四要素 (①原文是什么 ②为何失效:16/16 E2E 与 Feature Flags 15 项均被 Phase 22/23 复验推翻 ③取代者:STATUS.md / OPS.md / V1_1_CHANGELOG.md ④为何保留而不删除)。 **未删除** —— 本机有「删文件会级联清空父目录」红线,且删除会制造悬空引用。 - docs/Phase-01-V11-Recovery-Completion-Report-2026-09-09.md:引用行标注「已于 2026-09-18 废止」, 并指明结论以 STATUS/OPS 为准,使引用有落点而非悬空。 - docs/Phase-13A-V11-Completion-Report.md:废止声明中「决定保留待决」改为「已同步原地重写为 废止声明」,与实况一致。 原文可从 Git 历史取回:git log --all -- docs/Phase-V11-Deployment-Completion-Report.md |
||
|
|
c061a55151 |
docs: 治理报告去掉 bundle 的写死 hash,改为现场可复核判据
上一提交在 D7 行与第七节写上「list-heads → main = 4e18272」,但该 ref 指向会随每次
提交变化 —— 报告一旦入库就已经过期,读者复核必然看到不一致。改为只给判据不给值:
- D7 行 / 第七节:删除固定 hash,改为「`git bundle list-heads <file>` 里的
`refs/heads/main` 必须等于 `git rev-parse HEAD`;引用时不要写死 hash」。
- 保留
|
||
|
|
75272a5c21 |
docs: 修正治理报告 D7(bundle 先虚后实)+ 补 9 项缺口补记与可复核证据
D7 是本轮唯一一处「先报完成、后被证伪」的项,如实修正并留证:
- 原写「✅ 本轮重跑 git bundle create 覆盖至最新提交 | 根目录 *.bundle」,
实测磁盘上并不存在该文件;且当时唯一的 .workbuddy/tmp/restore-checkpoint-2026-09-18.bundle
其 main 停在 f96dcb8,早于 21:15 的三个治理提交 —— 等于治理成果当时无 bundle 保护。
- 已补建 .workbuddy/backup/docs-governance-2026-09-18.bundle(23.3MB,--all),
git bundle verify = complete history,list-heads → refs/heads/main = 4e18272。
- 新增补记:安全网类交付(bundle / tag / 门禁)必须用可验证命令复核
(list-heads 看 ref 指向的提交号、git tag -l、门禁退出码),不能以「命令已执行」为准;
与本项目此前「假成功」同族(生产 /admin/ 返回 200 欢迎页、E2E 只断言常量字符串)——
共性为「验证信号与被验证对象解耦」。
- 第七节补 bundle 可复核证据行;第六节「安全网」行注明其覆盖面不足;
第八节执行纪律补 bundle 路径约定 .workbuddy/backup/<phase>-recovery-<date>.bundle 与 list-heads 校验步骤。
|
||
|
|
4e1827295d |
docs: 新增 Phase 23 收尾与 UAT 就绪计划 + 2026-09-18 文档治理报告(方案任务四、五)
- 新建 docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md(superpowers 计划格式), 五批:批 1 快速收尾(ui-tests 15 个契约接入 CI —— 目前**只在本地手工跑**,CI 无把关)/ 批 2 Phase 23 剩余(E2E 场景真实实现 + INV-011 显式守卫 + CI 串联 E2E【需基础设施决策】+ Phase 22 Deployment/Ops 口径复验)/ 批 3 代码级遗留清偿 / 批 4 UAT 就绪【需用户协作】/ 批 5 Phase 25-26 前置登记。STATUS.md 与客户需求基线计划均已引用(方案明确要求)。 - 新建 docs/文档治理报告-2026-09-18.md:五任务执行核对;本轮 9 项缺口(D1~D9)逐项处置; 删除/归档/头注/保留四张清单;悬空引用二分结论(naive 扫描 29 处,其中 **24 处是「已删除对象清单」 里的合法治理证据**,真实悬空 5 处);**docs/ 目录被 safe-delete 钩子整体移除的事故记录与恢复过程**; 验证证据;4 项遗留。 验证:UI 契约门禁 **14/14 PASS**;ui-document-baseline-contracts 通过(锚点齐全); database 文档 59 表 / 46 迁移与迁移文件实测一致。 |
||
|
|
ae2405f645 |
docs: 状态文件同步到 2026-09-18 实况(方案任务三)
- STATUS.md:顶部权威行由 "PHASE 20 COMPLETE"(09-17)更新为 09-18 口径(Phase 01~22 关闭 / Phase 23 部分关闭 / 三轮横向加固 / 669 tests / 停在 Phase 24 门前);版本演进表补 rc.11~rc.16; **合并三处重复的「V1.1 本地部署完成 (2026-09-09)」区块为一处**(996 行 → 946 行)并加 「历史记录边界」声明;清「Phase 04~05 ⏳ 待开发」等与实测矛盾项。契约锚点全部保住 (UI REDESIGN ACCEPTED BY USER / Phase 07 已通过用户验收 / 管理后台: http://localhost:13520/)。 - AGENTS.md + STATUS.md:UI 契约门禁口径 13/13 → **14/14**(历史时点行保留原值并注明, 不篡改史实)。 - JIRA_BACKLOG.md:Epic 总览表补 P14/P15/P19~P23 的 ✅(P23 标「部分关闭」)。 - 客户需求基线计划:Phase 07 标题去 "UI REOPENED";Phase 10~15/19 补 ✅;Phase 20/21 勾选 checkbox 并注日期;**Phase 22 如实标「未复验」**(口径澄清:计划的 Deployment/Ops 与 STATUS 的 「部署运维配置扩展(Feature Flag)」是两份不同工作,后者复验结论不构成本节证据); Phase 23 标「部分关闭」,第 3/5 项(E2E 场景实现、CI 串联)**不虚勾**。 - PENDING_DECISIONS.md:历史部署声明 10 项按各 Phase Recovery 报告证据回写处置结果 (9 项已关闭 / 1 项部分);补登 HTTP 与幂等检修遗留的第三方开放待决策 7 项。 - REMAINING_WORK.md:整体重写 —— 原文称 CHANGELOG 3.3MB(实为 4234 字节)、P1-3 指向从未存在的 Phase-23-Completion-Report.md;改为「已关闭 / 仍未完成 / 原编号索引 / 验收标准」四段,保留原编号 以便对照历史引用。 - database 两份:TABLE_INVENTORY 40 表 → **59 表**(带「来源迁移」列);MIGRATION_GUIDE V001~V011 → **V001~V046(46 个)**;两份均标注「以 backend/src/main/resources/db/migration/ 为准」。 |
||
|
|
4f37df3284 |
docs: 废止被取代的 Phase-13A 首轮报告与 V1.0 部署方案(原地重写,不用 git rm)
- docs/Phase-13A-V11-Completion-Report.md:2026-09-08 首轮报告自报「✅ 核心链路已完成」,
已被 Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md(CLOSED,Gate 全项通过)取代。
该文件因命名差异(`Phase-13A-` 带 13A、缺数字段)不匹配清理清单的 `Phase-XX-` 模式而漏删。
**原地重写为废止声明**而非 git rm —— 本机 safe-delete 钩子会把「删文件」放大为「删父目录」
(2026-09-18 实测:一次 git rm 导致整个 docs/ 被移除,已从 Git 完整恢复)。
- 部署架构、运维监控、备份恢复与升级方案.md:补 V1.1 取代头注(方案任务二点名要求,此前遗漏)。
另登记一份**不删**的:docs/Phase-V11-Deployment-Completion-Report.md(2026-09-09,含已被推翻的
「16/16 E2E 通过」)—— 它不在方案删除目标模式内,且被保留文档
Phase-01-V11-Recovery-Completion-Report-2026-09-09.md 援引,删除会制造悬空引用,保留待决。
|
||
|
|
16538d7af5 |
chore(docs): V1.0 前端设计文档归档至 docs/archive/design-v1.0;为仍被援引的 V1.0 总纲文档加取代头注
- git mv 4 份 UI 类文档(UIUX 视觉规范/组件库与 Design Token/高保真原型/页面级功能清单)入档, 附 README 说明取代关系:UI 域唯一基线为 docs/superpowers/specs/2026-09-13-* 规格 - 根目录两份 V1.0 WorkBuddy 提示词/总指令:加「已被 V1.1 整合版取代」头注,保留原位 (AGENTS_SUMMARY 与 Recovery 报告仍援引其行号) - REST API 规范:加「部分取代」头注(V1.1 增量以数据库与API增量设计为准;WebSocket 章节仍为协议出处) - 根 CHANGELOG.md:加指针指向 docs/V1_1_CHANGELOG.md - V1.0 业务设计 12 份与部署运维方案按用户决定保留原位不动 |
||
|
|
c77e6fcd63 |
chore(docs): 删除已被复验推翻或取代的失效历史文档(65 份)
- V1.0 自报式 Phase-XX-Completion-Report 26 份(README 已声明其不可信) - V1.1 首轮 Phase-XX-V11-Completion-Report 21 份(已被对应 Recovery 版复验推翻/覆盖) - V1.0 时代审查/发布报告 13 份(CODE_REVIEW/FIX_PLAN/ISSUE_TRACKER/发布与 Go-NoGo/UAT/渗透测试/P0 指令/REAUDIT/两次评估) - 自declared被取代的旧计划 4 份(RECOVERY_PLAN_2026-09-09、当前进度校正、09-14 两份 superpowers 计划) - IDEA.md(一行备忘) 引用核查:代码/脚本/CI/配置零引用;STATUS.md 与 REMAINING_WORK.md 中两处现行引用随后续文档同步修正; Phase-10 Recovery 报告与 2026-09-13 UI spec 中的历史叙述引用按原文保留。 治理详情见 docs/文档治理报告-2026-09-18.md。 |
||
|
|
3ff0c0641a |
chore(admin): 生产管理端端口 3000 → 13520;守卫扩至三处并容错 CI
生产 admin 容器发布端口原为 127.0.0.1:3000 —— 3000 是本机最易被其他项目
(videoos / ai-callcenter)抢用的端口,与新定的管理端端口 13520 不一致。
已改为 127.0.0.1:13520:80(仍绑定 loopback,对外访问走 nginx 80/443 + /admin/)。
⚠️ 该实体改动无法进入本次提交:infrastructure/docker-compose.prod.yml 位于
.gitignore 的「Secrets and certificates」段,刻意不进版本控制(含按主机定制的
部署参数)。因此本次提交承载的是「决策记录 + 守卫」,端口改动只在部署机生效。
守卫 ui-tests/ui-document-baseline-contracts.mjs:
- 由「两处一致」扩为「三处一致」:admin-web/vite.config.ts(权威声明)、
docs/STATUS.md(给人照做的)、docker-compose.prod.yml(生产发布端口)。
- 生产 compose 采取「存在才断言」:CI 全新克隆上该文件不存在,硬 read() 会抛
ENOENT 让门控假红。已实测——移走文件 → 打印 skip 且 EXIT=0;放回 → 断言恢复。
- 明确排除另外两处管理端入口并写清理由:本地整套栈的管理端经 nginx 6688 暴露
(6688 是 nginx 入口,不是纯管理端端口);staging 的 3001 是为与生产同机共存
而按「生产 + 1」错开(5433/6380/8081/3001),且被 tests/uat/run-uat.sh 的
UAT_ADMIN_URL 默认值依赖 —— 改动它们要同步消费方,不该由本守卫断言等于 vite 端口。
docs/STATUS.md:新增「管理端端口口径」表,记录四种管理端入口的端口与取舍理由,
并显式标注生产 compose 不在版本控制内(否则后人会以为改了没生效)。
验证:ui-tests 门控 14/14;负向验证 prod compose 改回 3000 → 精准报
「docker-compose.prod.yml=3000,vite.config.ts=13520」;文件缺失 → skip 且不假红;
还原后 md5 逐字节一致(71a30653…);docker compose config 解析出
published=13520 且 host_ip=127.0.0.1(loopback 绑定未丢失)。
|
||
|
|
9c26d4b7b2 |
chore(admin-web): 开发端口 3000 → 13520,并禁止端口静默漂移
本机长期并行运行三个项目(videoos / ai-callcenter / 本项目),3000 属最易被 抢用的端口之一。按用户决定改为 13520。 - admin-web/vite.config.ts:server.port 3000 → 13520 - 同处新增 strictPort: true。vite 默认在端口被占用时静默 +1 退到下一个可用端口, 结果是「文档写 13520、实际跑在 13521」且全程不报错 —— 与本项目反复处理的 「声明与兑现失联」同族。加上后端口被占用会直接失败。 - docs/STATUS.md「当前运行状态」同步为 13520。 - ui-tests/ui-document-baseline-contracts.mjs:新增端口一致性守卫。 端口写在两处(vite.config.ts 是权威,STATUS.md 给人照做),只改一处不会让任何 测试变红,却会让照文档操作的人打不开页面。守卫断言的是「两处一致」而非写死 13520 —— 允许改端口,允许的是「一起改」,不是「只改一处」。 两处解析均用「解析失败即失败」写法,避免正则匹配不到时断言退化成与 undefined 比较而失去意义(本项目踩过的守卫静默失效坑)。 用 assert.equal 而非 assert.match:match 失败会把整份 STATUS.md(3000+ 行) 打进报错,噪声会盖掉真正的不一致信息 —— 负向验证时实测确认。 - .gitignore:忽略 admin-web/vite.config.ts.timestamp-*.mjs。 vite 在 dev server 被强杀(未优雅退出)时遗留该配置临时文件,本机已多次出现。 未改动(有意): - CORS 不受影响。CorsConfig 默认拒绝所有跨域是刻意设计,且注释已写明「管理端走 Vite 代理(同源)」—— dev 端口变化不产生跨域。 - infrastructure/docker-compose.prod.yml 的 127.0.0.1:3000:80 是生产 admin 容器 的发布端口,与开发服务器无关,未动。 - releases/v1.0.0-RC.* 内的 3000 为历史发布快照,属冻结记录,不改写。 验证 - UI 契约门控 14/14 全绿(含新增守卫);vue-tsc --noEmit EXIT=0。 - 负向验证:把 STATUS.md 改回 3000 → 守卫变红并精确报出 「管理后台端口不一致:STATUS.md=3000,vite.config.ts=13520」及行号;还原后复绿。 - 真实启停:dev server 实跑 http://localhost:13520/(HTTP 200),旧端口 3000 无监听。 - strictPort 负向验证:端口被占用时第二实例立即以 「Error: Port 13520 is already in use」退出,未漂移到 13521。 - vite build EXIT=0(19.35s)。 注:vite build 直接执行会失败,栈停在 emptyDir(dist) 且全部位于本机 node-safe-delete-shim(批量删除守卫)内,与本次改动无关;改用全新 outDir 复跑即 成功,据此排除「改动导致构建失败」。该现象此前无记录,已写入技能备查。 |
||
|
|
c561bf46af | docs(idempotency): 统一报告中的计数口径(25 处调用点 / 30 处声明)并补 CI E2E 缺陷到结论行 | ||
|
|
2766e53fe2 |
docs: 幂等键检修报告;勘误 API_INVENTORY 与 HTTP 报告;同步 STATUS/JIRA
新增 docs/幂等键检修报告-2026-09-18.md:记录双向断裂(后端零处读取该头、
前端 Date.now 每次生成新键)、五处审计发现、机制设计与五个取舍
(先占位后执行 / 重放 / 失败释放占位 / 孤儿占位接管 / 作用域含 actor_id)、
列类型不变量扫描、E2E 与守门证据、三处同形状问题的连带修复。
同轮勘误:
- docs/HTTP语义检修报告-2026-09-18.md:其中「Idempotency-Key 已支持」一句
在写下时不成立(后端当时零处读取),已显式标注勘误并保留原文,
以免「结论先于核实」被当作既成事实继续引用。
- docs/api/API_INVENTORY.md:原记载 POST /activities/{id}/interests
「提交活动互选」,后端从未实现(Phase 16 已并入 /feedback 的
interestTargetUserIds),按真实端点重列并附勘误说明。
docs/STATUS.md:迁移范围更新为 V001~V046;回归行更新为 669 / 0 / 0 / 3;
新增「幂等键检修」章节;补登 HTTP 语义检修章节在两份文档中的缺失条目。
docs/JIRA_BACKLOG.md:补登 HTTP 语义检修与幂等键检修两行
(此前均未登记)。
|
||
|
|
ed725daccd | docs(status): record identity scope convergence and the PathPattern ** trap | ||
|
|
cbb4f64fdc |
fix(security): stop operator identity from reaching user endpoints; guard path rules
Continues the HTTP-semantics work. Fixing 401/403 exposed a worse defect on the
same axis: who is allowed to reach which endpoint.
Root cause chain:
- PlatformPrincipal has two implementations (OperatorPrincipal / UserPrincipal),
chosen by JwtAuthFilter from the token type.
- `@AuthenticationPrincipal UserPrincipal` against an OperatorPrincipal is a type
mismatch, and Spring Security returns null (errorOnInvalidType defaults to
false) instead of failing.
- 244 uses of that annotation repo-wide; only 48 null-check. The rest NPE -> 500
when reached with an operator identity.
- The fallback rule was `.authenticated()`, which an operator token satisfies, so
such requests did reach user-facing controllers.
Adding ~196 null checks would be mechanical, easy to miss, and does nothing to
constrain future code. The mismatch is between identity type and endpoint, so it
is fixed once, at the authentication layer: the fallback becomes
`.hasRole("USER")` (operator -> ROLE_OPERATOR, user -> ROLE_USER, per JwtAuthFilter).
Operator requests to user endpoints now get 403 before any controller runs.
Trap worth recording: operator endpoints are not all under /operator/ —
/counseling/services/operator/queue, /content/operator/articles,
/activities/operator/checkin/scan, /service-appointments/operator/list and others
sit deeper inside business paths. The first attempt covered them with
`requestMatchers("/**/operator/**")`, which fails at runtime:
PatternParseException: No more pattern data allowed after {*...} or ** pattern element
Spring's PathPattern permits `**` only as the final element. Worse, the symptom is
a 500 from the whole filter chain (Tomcat HTML error page, not our JSON), and E2E
went from 37 passed to 33 failed — an error far removed from its cause. Now the
nine prefixes are listed explicitly, each ending in /**.
Because explicit enumeration can be forgotten as endpoints are added, and because
the failure mode is "operators locked out of their own admin module" (invisible to
user-side tests), SecurityPathCoverageTest parses the OPERATOR rule prefixes out of
SecurityConfig source, walks every *Controller.java, joins class-level and
method-level mapping paths, and asserts every path containing /operator/ is
covered (/auth/operator/** excluded by design). Two things that would silently
neuter it, both handled: it must parse only the .hasRole("OPERATOR") chain — pulling
in the permitAll or fallback rules would mark everything covered — and the test
working directory is the repo root, not the Maven module root.
Verification: negatively validated by removing /content/operator/** -> exactly one
test turns red listing the six affected ContentController endpoints; restored.
E2E 40 passed (37 + 3 identity-isolation cases). Backend regression 664 / 0
failures / 0 errors / 3 skipped.
|
||
|
|
3a4e03a9f9 | docs(status): record HTTP semantics remediation; note 3 test assertions that encoded old wrong behavior | ||
|
|
7682abd85c |
fix(api): give failures real HTTP semantics; fix 401/403 confusion and CORS default
Phase 23B ended with an E2E assertion changed to expect "HTTP 200 + body.code == BAD_REQUEST", documented at the time as "this project's convention". That description was accurate about the code and wrong about the intent: it described a defect, and I made the test accommodate it instead of fixing it. Auditing it found the convention was already broken, not merely risky: - ~100 sites (105, across 30 controllers) do `return ApiResponse.error(...)` / `forbidden()` / `notFound()` with no exception and no `@ResponseStatus`, so the HTTP status is always 200. Gateways and APM cannot separate 4xx from 5xx; callers cannot tell "fix your request" (400) from "don't retry" (403) from "check state first" (409) from "retry later" (429). - miniapp/utils/api.js already had 401/403/429 branches that could never be reached, since the backend never returned those codes. The concrete damage: an expired token returned 403, so `onSessionExpired` never fired and users were stuck with no way back to the login flow. - SecurityConfig had no `authenticationEntryPoint`, so Spring's default `Http403ForbiddenEntryPoint` returned 403 for unauthenticated calls, with a framework body that has no `code` field at all. - CorsConfig defaulted `allowed-origins` to `*` together with `allowCredentials(true)`, while its comment claimed "localhost only by default". Comment contradicting code is worse than no comment: it reads as safe. - Only 4 files / 15 assertions in the whole backend suite touched HTTP status, which is how this survived. Approach: one definition, two applications, zero controller changes. - ApiResponseCode gains an `httpStatus` field — the single source of truth. The constructor requires it, so a new code cannot be added without one. - GlobalExceptionHandler reads status from the enum instead of hardcoding `@ResponseStatus` per handler. - New ApiResponseStatusAdvice (ResponseBodyAdvice) fills in the status just before the body is written, covering all ~100 direct-return sites without touching them. Two constraints, both noted in code: only downgrade when the current status is still 2xx (the advice also sees exception-handler results, and must not override an already-correct 401/403/409), and never alter the body. - SecurityConfig: entry point returns 401 with a uniform ApiResponse body; access denied handler returns 403 likewise. - CorsConfig: default changed to empty (deny). Both clients are same-origin anyway — nginx proxies /api/ and Vite proxies /api — so nothing depends on it. - miniapp api.js: 401/403/429/409 branches now prefer the server's `body.message`, otherwise "账号已被封禁" would be flattened to "无权限访问". Implementation trap worth remembering: `ServerHttpResponse` has `setStatusCode` but no getter, so reading the current status requires casting to `ServletServerHttpResponse`. Verification: 37 E2E passed (33 + 4 new contract cases), including a live-stack check that a controller's direct `notFound()` now yields 404 — that path throws no exception and never reaches the exception handler, so if the advice were inactive it would silently return 200 while the enum unit tests stayed green. Backend regression 662 / 0 failures / 0 errors / 3 skipped (+6 new guards). The first regression run had 3 failures, all tests asserting the old wrong behavior (anonymous access expected 403; outsiders reading a conversation expected 200). Fixed the assertions, not the implementation. Where 404 is used instead of 403 for "invisible", that is deliberate — it prevents probing whether another user's conversation exists — and the substantive assertions (e.g. that an unauthorized response must not leak message bodies) were kept. Not done, needs a product decision: third-party credential issuance, whether rate limiting is actually active, publishing OpenAPI externally, per-caller audit and quotas, pagination/idempotency coverage, desensitization boundaries. |
||
|
|
730dddde30 |
test(e2e): Phase 23B — rebuild E2E infrastructure; fix 6 real prod defects it exposed
Phase 23 closed as PARTIAL: fake coverage was gone, but the E2E layer itself
was still empty — no conftest.py anywhere, ~15 fixtures that never existed,
6 files crashing at import on os.environ["E2E_PASSWORD"], and CI never ran E2E.
STATUS's "10 E2E files / ~80+ cases ✅" described files that had never executed.
Infrastructure delivered:
- tests/e2e/conftest.py: ApiClient (trust_env=False — a host http_proxy turns
localhost calls into 502), operator_token(username) / new_user / admin_token
fixtures, and a session probe that calls pytest.exit(4) when the stack is
unreachable. NOT skip: skip would launder "did not run" into "passed".
- pytest.ini, tests/e2e/requirements.txt, and tests/e2e/sql/
seed_e2e_operators.sql — migrations contain NO operator seed at all, so a
fresh database has zero logins; the seed creates 6 e2e accounts with role
bindings, idempotent, password injected via `psql -v pwd`.
- CI job `e2e`: compose up -> poll /health/live (timeout = fail) -> seed ->
pytest -> always teardown. Gate D-23-D is now closed.
- 8 E2E files migrated off import-time env reads; tautological assertions
(`x == t or not x`), a counselor token standing in for content_editor, and a
bare `status_code == 200` claiming to prove INV-020 were replaced with real
permission/behavior assertions.
Six real production defects found only by running against real PostgreSQL
(H2 has zero coverage for all of them):
- D-23B-A (Critical): stale V042__add_appointment_operator_register_permission.sql
left in target/classes; src had replaced it with V042__add_service_location_
announcements.sql. Packing without clean put two V042 in the jar -> Flyway
"Found more than one migration with version 042" -> app would not start.
Guard added: testBuildOutputHasNoDuplicateMigrationVersions scans build output
(src is clean, so only this catches it).
- D-23B-B (High): non-UUID path variable reached the DB -> 500 + leaked
`invalid input syntax for type uuid`. Now rejected at the controller.
- D-23B-C (High): 6 E2E files raised KeyError at import -> never collected.
- D-23B-E (Critical): permissions = 48, role_permissions for super_admin = 18,
matchmaker = 0. V015's "grant all to super_admin" was a point-in-time snapshot;
the 30 permissions added later were never granted. On a fresh stack an admin
cannot create a service station and matchmakers can do nothing. V044 restores
super_admin to its documented definition (not an expansion); the matchmaker
permission set needs a product decision and is left as a residual.
- D-23B-I (Medium): IllegalArgumentException (e.g. invalid activity type) was
unmapped -> 500. Now BAD_REQUEST.
- D-23B-J (High): `operator does not exist: uuid = character varying` — Java
ids are String, columns are uuid, PostgreSQL does not implicitly cast in
comparisons. Fixed at the source with JDBC `stringtype=unspecified` (docker
profile); production datasource comes from deploy env and needs the same.
One judgement retracted: an initial read suggested service_appointment.register
was missing. It is not — the endpoint uses service.assisted_registration, seeded
in V029, and the code deliberately avoids a duplicate permission. Nothing was
changed on that basis.
Verification: 33 E2E passed against a live stack; unreachable stack and a
missing operator password both exit 4 (never skip/pass); backend regression
656 / 0 failures / 0 errors / 3 skipped (baseline 655, +1 guard, negatively
validated by injecting a duplicate V042 -> exactly one test turned red).
|
||
|
|
ae1bd2b734 |
test(phase23): KEEP-VERIFY — remove 44 pass shells + fake INV tests, add shell guard
Phase 23 "可信系统测试重建 (REFACTOR)" re-audit. STATUS claimed "10 E2E
files / ~80+ cases" and "INV-011~020 ✅"; measurement showed the opposite.
D-23-A (Critical): V11InvariantsTest's 14 cases asserted only Constants
strings. INV-016 "Banner target types" actually asserted ARTICLE_STATUS_*;
INV-017 "home 8 entries" declared local arrays then asserted
firstRow.length == 4 (tautology, never called the home service);
INV-018/019/020 used appointment/article/account status constants as
stand-ins for permission and data-scope checks. Rewritten to 12 cases:
4 real service-behavior guards (INV-013 via real CounselingService.getDetail
— Matchmaker sees null userRequestSummary, with an assigned-Counselor
counter-proof so global sanitization cannot fake a pass; INV-016 reads the
production ALLOWED_TARGET_TYPES whitelist and asserts no external/http/url/
web entry; INV-017 calls real getHome and pins all 8 codes + order;
INV-019 asserts content_editor reads null), plus assertBehaviorTestExists
turning "行为验证见 XxxTest" pointers into executable Class.forName contracts.
D-23-B (High): 44 `pass` shells across 6 files in tests/e2e (4 files were
100% shells). Gate forbids skip/pass shells. Rewritten in place as honest
placeholders with the full scenario lists preserved.
D-23-C (High, residual): the pytest E2E suite is not executable at all —
no conftest.py anywhere, ~15 fixtures (api_client/verified_user/
banner_factory/...) do not exist. Only run_v11_tests.py really runs
(16 cases, needs a live stack + E2E_PASSWORD).
D-23-D (High, residual, NOT fixed): CI does not chain E2E as the plan
requires. Needs an infrastructure decision on how CI provisions a live
stack; not implemented rather than written unverified.
D-23-E (Medium): the CI migration job claimed to "验证 migration SQL 语法"
but only checked file non-emptiness, while the real syntax/baseline checks
live in MigrationValidationTest (already run by the backend job). Replaced
with a real sequence check (naming / non-empty / unique versions / no
gaps); verified by extracting the exact inline script and running it.
New gate: scripts/check-no-empty-tests.py (AST scan, blocks pass-only and
unconditionally skipped tests) + a CI test-shell-guard job. Verified both
ways: 0 findings on the real tree, 2 precise findings on injected shells.
Negative validation: 3 injected production defects (external_url in the
banner whitelist, swapped home entry order, isMatchmaker returning the full
record) turned exactly inv016/inv017/inv013 red, 9 others green, zero
unrelated failures; md5sum -c confirmed byte-identical restore.
Full regression: 655 tests / 652 passed / 0 failures / 0 errors / 3 skipped
(baseline 657; -2 because 14 vacuous cases were merged into 12). The 3
skips carry explicit reasons (2x @EnabledIfEnvironmentVariable(CI=true),
1x @Disabled "需要完整数据库连接") — none is a shell.
Phase 23 is PARTIALLY closed: fake coverage is gone, but the UAT gate
requires the end-to-end chain to actually execute, so "失败为零可进入 UAT"
is NOT claimed.
|
||
|
|
050d005c0a |
docs(status): correct two stale duplicate Phase 22 ✅ tables disproved by KEEP-VERIFY
STATUS.md contained two duplicate legacy Phase 22 blocks still claiming: - "application.yml 默认配置 ✅ (15个V1.1 flag)" / "application-dev.yml + application-docker.yml 配置 ✅" — verified false: all four yml files (application.yml / -dev / -docker / -production) contain neither a `features:` block nor any flag key; defaults are Java field defaults only. - "P22-T01..T05 ✅" and "P22-T07 ✅" — these are dead config: zero business call sites for the five module toggles, zero references for the private storage bucket flags. Both blocks now carry a "superseded by the 2026-09-18 authoritative section" notice with per-row measured verdicts (T06 remains ✅ as genuinely enforced). No code change; doc-only. |
||
|
|
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). |
||
|
|
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). |
||
|
|
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 |
||
|
|
ff22c0d06a | docs(phase19): cite the recovery tag instead of a moving commit sha | ||
|
|
d74f0ea099 | docs(phase19): record tag/bundle location + commit sha in the recovery report | ||
|
|
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. |
||
|
|
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 | ||
|
|
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 | ||
|
|
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) | ||
|
|
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. |
||
|
|
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. |
||
|
|
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
|
||
|
|
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 未自动进入,等待用户确认。
|
||
|
|
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) |
||
|
|
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 机制。 |
||
|
|
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 未启动,按计划每阶段出报告后停止等待确认。 |
||
|
|
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 未启动,按计划每阶段出报告后停止等待确认。
|
||
|
|
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/。
|
||
|
|
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 个新文件名与全部引用一致 |
||
|
|
6ae08b89fb |
docs(agents): 新增「版本控制约定」强制节 + 修正 UI 验收口径
## 背景:refs/heads 嵌套分支名被实时清理,导致 HEAD 悬空 提交 |
||
|
|
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/* 端点表
|
||
|
|
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 通过
|
||
|
|
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
|