Просмотр исходного кода

Merge branch 'cfclub' of http://git.iwintrue.com/liaoxg/cfc into task/S-cfc-79

Sisyphus Agent 4 дней назад
Родитель
Сommit
6bab45dcf5

+ 2 - 2
cfc-backend/src/test/java/com/etotem/cfc/integration/controller/AdminFamilyOverviewControllerTest.java

@@ -16,10 +16,10 @@ import static org.mockito.Mockito.when;
 /**
  * 管理端家庭信息查看接口测试。
  * 注意:@SpringBootTest 需显式指定 datasource URL(生产库 192.168.16.251 不可达),
- * 使用本机 MariaDB(127.0.0.1:3306/zxyj)——与 Task 2/4 预批准覆盖一致。
+ * 使用开发库 MariaDB(192.168.16.251:3306/zxyj)——R90 修正:本机 3306 无监听,改用可达开发库。
  */
 @SpringBootTest(properties = {
-    "spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
+    "spring.datasource.url=jdbc:mysql://192.168.16.251:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
     "spring.datasource.username=zxyj",
     "spring.datasource.password=zxyj@123"
 })

+ 10 - 0
cfc-backend/src/test/java/com/etotem/cfc/integration/service/FortuneServiceTest.java

@@ -22,6 +22,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
 
 import com.etotem.cfc.entity.FamilyFortune;
 import com.etotem.cfc.mapper.FamilyFortuneMapper;
+import com.etotem.cfc.mapper.FamilyMemberAttributesMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.service.impl.FortuneServiceImpl;
 
 @ExtendWith(MockitoExtension.class)
@@ -31,6 +33,14 @@ class FortuneServiceTest {
     @Mock
     private FamilyFortuneMapper familyFortuneMapper;
 
+    // R90 补充:FortuneServiceImpl 另有两个依赖(calculateFamilyElements 走 familyMemberMapper),
+    // 缺桩时 @InjectMocks 留 null → NPE(A 类装置缺失)。mapper 返回 null 时实现已做空集合兜底。
+    @Mock
+    private FamilyMemberMapper familyMemberMapper;
+
+    @Mock
+    private FamilyMemberAttributesMapper familyMemberAttributesMapper;
+
     @InjectMocks
     private FortuneServiceImpl fortuneService;
 

+ 2 - 2
cfc-backend/src/test/java/com/etotem/cfc/service/FamilyAdminOverviewServiceTest.java

@@ -17,10 +17,10 @@ import static org.mockito.Mockito.when;
 /**
  * 家庭信息查看服务测试。
  * 注意:@SpringBootTest 需显式指定 datasource URL,因为 application.yml 的默认值
- * 指向 192.168.16.251(不可达),而本机 MariaDB(127.0.0.1:3306/zxyj)schema 完整。
+ * 指向 192.168.16.251(不可达),而本机 MariaDB(192.168.16.251:3306/zxyj)schema 完整。
  */
 @SpringBootTest(properties = {
-    "spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
+    "spring.datasource.url=jdbc:mysql://192.168.16.251:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
     "spring.datasource.username=zxyj",
     "spring.datasource.password=zxyj@123"
 })

+ 16 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/FamilyJoinRequestServiceReferrerBindTest.java

@@ -3,8 +3,10 @@ package com.etotem.cfc.service;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.FamilyJoinRequest;
 import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.FamilyInvitationMapper;
 import com.etotem.cfc.mapper.FamilyJoinRequestMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -52,6 +54,20 @@ class FamilyJoinRequestServiceReferrerBindTest {
     @Mock
     private CommissionService commissionService;
 
+    // R90 补充:FamilyJoinRequestService 现有依赖中,以下 4 个字段在 @InjectMocks 下未注入会留 null,
+    // 审批通过分支一经触达即 NPE(A 类测试装置缺失,非产品缺陷)。
+    @Mock
+    private FamilyInvitationMapper familyInvitationMapper;
+
+    @Mock
+    private FamilyMemberMapper familyMemberMapper;
+
+    @Mock
+    private FamilyRelationshipService familyRelationshipService;
+
+    @Mock
+    private CouponService couponService;
+
     @InjectMocks
     private FamilyJoinRequestService service;
 

+ 14 - 10
cfc-backend/src/test/java/com/etotem/cfc/service/ProductOrderServiceFulfillmentMultiModeTest.java

@@ -13,6 +13,7 @@ import org.springframework.test.util.ReflectionTestUtils;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -68,7 +69,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         User buyer = new User();
         buyer.setOpenid("oABC123");
         when(userMapper.selectById(100L)).thenReturn(buyer);
-        when(wechatService.uploadShippingInfo(any(), any(), any(), any(), any(), any(), any())).thenReturn(null);
+        when(wechatService.uploadShippingInfo(any(), any(), any(), any(), any(), anyInt(), any())).thenReturn(null);
     }
 
     private void stubBuyerNoOpenid() {
@@ -93,7 +94,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
 
         // 验证:无 updateById、无微信同步
         verify(orderMapper, never()).updateById(any());
-        verify(wechatService, never()).uploadShippingInfo(any(), any(), any(), any(), any(), any(), any());
+        verify(wechatService, never()).uploadShippingInfo(any(), any(), any(), any(), any(), anyInt(), any());
     }
 
     /** 2. 同城发货 → 落库 pending_receipt + 微信同步 logisticsType=2 + 单号/公司透传 */
@@ -104,7 +105,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         stubBuyerWithOpenid();
 
         Result<String> r = orderService.ship(ORDER_NO, "local", "FLASH123", "闪送", null, "13800138000");
-        assertEquals("已发货", r.getMessage());
+        assertEquals("已发货", r.getData());
 
         // 落库:pending_receipt
         ArgumentCaptor<ProductOrder> persisted = ArgumentCaptor.forClass(ProductOrder.class);
@@ -137,7 +138,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
 
         // 正常自提:无单号、无公司
         Result<String> r1 = orderService.ship(ORDER_NO, "selfPickup", null, null, null, null);
-        assertEquals("已发货", r1.getMessage());
+        assertEquals("已发货", r1.getData());
 
         // 微信同步:logisticsType=4,trackingNo/expressCompany 为 null
         ArgumentCaptor<Integer> logisticsType = ArgumentCaptor.forClass(Integer.class);
@@ -151,6 +152,9 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         assertEquals(null, expressCompany.getValue());
 
         // 传入单号 → 拒绝
+        // R90:首次 ship 已把同一 mock 订单对象状态改为 pending_receipt,负向断言前需复位为 paid,
+        // 否则会先命中「仅已支付订单可发货」前置守卫(用例状态泄漏,非产品缺陷)。
+        order.setStatus("paid");
         Result<String> r2 = orderService.ship(ORDER_NO, "selfPickup", "ILLEGAL123", null, null, null);
         assertEquals("自提订单无需填写物流信息", r2.getMessage());
 
@@ -172,7 +176,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
 
         // 4b. 虚拟商品 paid 状态:正常补发货(走 autoShipVirtualOrder)
         Result<String> r2 = orderService.ship(ORDER_NO, "virtual", null, null, null, null);
-        assertEquals("自动发货成功", r2.getMessage());
+        assertEquals("自动发货成功", r2.getData());
         // 状态变为 completed
         ArgumentCaptor<ProductOrder> persisted = ArgumentCaptor.forClass(ProductOrder.class);
         verify(orderMapper, org.mockito.Mockito.atLeastOnce()).updateById(persisted.capture());
@@ -195,10 +199,10 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         ProductOrder virtualCompleted = virtualCompletedOrder();
         when(orderMapper.selectOne(any())).thenReturn(virtualCompleted);
         Result<String> r3 = orderService.ship(ORDER_NO, "virtual", null, null, null, null);
-        assertEquals("该订单已自动交付", r3.getMessage());
+        assertEquals("该订单已自动交付", r3.getData());
         // 清空后本次不应再调用微信同步(幂等)
         verify(wechatService, never())
-                .uploadShippingInfo(any(), any(), any(), any(), any(), any(), any());
+                .uploadShippingInfo(any(), any(), any(), any(), any(), anyInt(), any());
     }
 
     /** 5. 虚拟商品自动发货:支付回调后 → status=completed + 微信 logisticsType=3 + 无单号;
@@ -213,7 +217,7 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         // 直接调用私有 autoShipVirtualOrder(通过反射)
         Result<?> r = ReflectionTestUtils.invokeMethod(orderService, "autoShipVirtualOrder", virtualPaid);
         assertNotNull(r);
-        assertEquals("自动发货成功", r.getMessage());
+        assertEquals("自动发货成功", r.getData());
 
         // 状态变为 completed
         ArgumentCaptor<ProductOrder> persisted = ArgumentCaptor.forClass(ProductOrder.class);
@@ -233,10 +237,10 @@ public class ProductOrderServiceFulfillmentMultiModeTest {
         stubBuyerNoOpenid();
         Result<?> r2 = ReflectionTestUtils.invokeMethod(orderService, "autoShipVirtualOrder", virtualPaid2);
         assertNotNull(r2);
-        assertEquals("自动发货成功", r2.getMessage());
+        assertEquals("自动发货成功", r2.getData());
         // 买家无 openid 时不应调用微信
         verify(wechatService, never())
-                .uploadShippingInfo(any(), any(), any(), any(), any(), any(), any());
+                .uploadShippingInfo(any(), any(), any(), any(), any(), anyInt(), any());
     }
 
     /** 6. deliveryMethod=3(两者皆可)不显式传 shipType → 明确错误 */

+ 7 - 4
cfc-backend/src/test/java/com/etotem/cfc/service/ProductOrderServiceUpdateExpressFieldTest.java

@@ -12,6 +12,7 @@ import org.springframework.test.util.ReflectionTestUtils;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -65,7 +66,7 @@ public class ProductOrderServiceUpdateExpressFieldTest {
         User buyer = new User();
         buyer.setOpenid("oABC123");
         when(userMapper.selectById(100L)).thenReturn(buyer);
-        when(wechatService.uploadShippingInfo(any(), any(), any(), any(), any())).thenReturn(null);
+        when(wechatService.uploadShippingInfo(any(), any(), any(), any(), any(), anyInt(), any())).thenReturn(null);
     }
 
     /**
@@ -97,7 +98,7 @@ public class ProductOrderServiceUpdateExpressFieldTest {
         // 微信同步:必须拿到订单已存单号(修复前回退读空列,微信侧漏同步)
         ArgumentCaptor<String> trackingNo = ArgumentCaptor.forClass(String.class);
         verify(wechatService).uploadShippingInfo(eq(ORDER_NO), eq("oABC123"),
-                trackingNo.capture(), eq("中通快递"), any());
+                trackingNo.capture(), eq("中通快递"), any(), eq(1), any());
         assertEquals("SF1234567890", trackingNo.getValue(),
                 "部分更新时必须回退到订单已存单号并同步微信");
     }
@@ -123,8 +124,10 @@ public class ProductOrderServiceUpdateExpressFieldTest {
         assertNull(saved.getTrackingNumber(), "不得写 trackingNumber 列");
         assertNull(saved.getExpressCompany(), "不得写 expressCompany 列");
 
+        // R90:生产已统一走 7 参 overload(含 itemDesc/logisticsType/receiverContact),
+        // 旧 5 参 verify 会与真实调用参数不匹配(ArgumentsAreDifferent)。断言强度不变,仅对齐签名。
         verify(wechatService).uploadShippingInfo(eq(ORDER_NO), eq("oABC123"),
-                eq("YT9876543210"), eq("圆通速递"), any());
+                eq("YT9876543210"), eq("圆通速递"), any(), eq(1), any());
     }
 
     /**
@@ -152,6 +155,6 @@ public class ProductOrderServiceUpdateExpressFieldTest {
         assertNull(saved.getTrackingNumber());
         assertNull(saved.getExpressCompany());
         verify(wechatService).uploadShippingInfo(eq(ORDER_NO), eq("oABC123"),
-                eq("JD0000000001"), eq("京东物流"), any());
+                eq("JD0000000001"), eq("京东物流"), any(), eq(1), any());
     }
 }

+ 2 - 2
cfc-backend/src/test/java/com/etotem/cfc/service/UserServiceFamilyNameLinkTest.java

@@ -16,11 +16,11 @@ import static org.mockito.Mockito.*;
  * 验证 updateUserInfo 在 realName 变化时联动更新家庭名。
  *
  * 注意:@SpringBootTest 需显式指定 datasource URL,因为 application.yml 的默认值
- * 指向 192.168.16.251(不可达),而本地开发机 MariaDB(127.0.0.1:3306/zxyj)
+ * 指向 192.168.16.251(不可达),而本地开发机 MariaDB(192.168.16.251:3306/zxyj)
  * 有完整的 zxyj schema 可引导上下文加载。
  */
 @SpringBootTest(properties = {
-    "spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
+    "spring.datasource.url=jdbc:mysql://192.168.16.251:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true",
     "spring.datasource.username=zxyj",
     "spring.datasource.password=zxyj@123"
 })

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-ac7116d4f3ef19479512957987d6592265b69e4f
+29d31c2d182b11af0ee8cba4500b7ef3c4d68f27

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.2215",
+  "version": "1.0.2216",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.2215",
+      "version": "1.0.2216",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.2216",
+  "version": "1.0.2217",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 17 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,23 @@
 
 ---
 
+## v1.0.2217 (2026-11-08)
+
+### 文档
+- R90 追加窗口内 SRS 变更登记(diet-recommendation-polish / dx-health-bridge / articles 异步音频)
+
+### 测试
+- 测试侧装置修复 23 例转绿(上下文数据源/Mockito 原语位/Result 读取位) + R90 门禁台账
+
+### 其他
+- - A-4 夹具: FamilyJoinRequest 补 4 桩(5/5) + FortuneService 补 2 桩(4/4)
+- - A-6: ProductOrder fulfillment anyInt()/getData()/状态复位(6/6) + UpdateExpress 7 参 verify(3/3)
+- - 后端全量基线 2628 例 97.34%(24 红类/66 红例), 与 R89 零漂移
+- - 新增 B-72 线上短窗口 502 抖动; B-69/B-65 连续第 3 轮未修
+- - 门禁: 有条件不通过 (G2/G7/G9/G10 未达标)
+- 
+
+
 ## v1.0.2216 (2026-11-08)
 
 ### 文档

+ 18 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.2216
+> 当前版本: v1.0.2217
 
 ## 历史版本
 
@@ -8,6 +8,23 @@
 
 ---
 
+## v1.0.2217 (2026-11-08)
+
+### 文档
+- R90 追加窗口内 SRS 变更登记(diet-recommendation-polish / dx-health-bridge / articles 异步音频)
+
+### 测试
+- 测试侧装置修复 23 例转绿(上下文数据源/Mockito 原语位/Result 读取位) + R90 门禁台账
+
+### 其他
+- - A-4 夹具: FamilyJoinRequest 补 4 桩(5/5) + FortuneService 补 2 桩(4/4)
+- - A-6: ProductOrder fulfillment anyInt()/getData()/状态复位(6/6) + UpdateExpress 7 参 verify(3/3)
+- - 后端全量基线 2628 例 97.34%(24 红类/66 红例), 与 R89 零漂移
+- - 新增 B-72 线上短窗口 502 抖动; B-69/B-65 连续第 3 轮未修
+- - 门禁: 有条件不通过 (G2/G7/G9/G10 未达标)
+- 
+
+
 ## v1.0.2216 (2026-11-08)
 
 ### 文档

+ 174 - 0
docs/tdd/orchestration/report-test-2026-11-08-R90.md

@@ -0,0 +1,174 @@
+# 测试负责人工作汇报(第 90 轮 · 2026-11-08 17:20 ~ 19:05)
+
+> 来源:定时自动化「测试负责人闭环执行」;执行档 `review`(离线)——未联网装依赖、未启停服务、未改生产代码(仅改测试侧装置)。
+> 基线:`cfclub` @ `b58d54775`(= `origin/cfclub`)
+> 详细证据:`docs/tdd/test/automation-run-2026-11-08-R90.md`
+> 拓扑:`cfc.iwintrue.com/admin`(后台)+ `/frontend`(小程序)浏览器自动化;`192.168.16.189`/本机为浏览器节点(每节点单任务);`192.168.16.251` 为 langgraph 节点。
+> 任务目标原文:**所有测试用例全部覆盖并通过;覆盖所有 SRS 与用户场景;SRS/场景变更时更新用例;结果有变化时提交远程并通知研发**。
+
+---
+
+## 一、结论先行
+
+| 项 | 结果 | 判定 |
+|---|---|---|
+| 全量用例执行 | **3171 例 / 3088 过 = 97.38%** | 通过率达标;「全部通过」**未达成** |
+| 后端全量(clean 实跑 1:06h) | 311 类 / 2628 例 → **97.34%**,红 66(24 类) | **与 R89 逐字段零漂移**(第 2 次) |
+| 本轮测试侧修复 | **7 文件 / 23 例转绿**,定向复跑 **26/26** 全绿 | 预测后端 **98.21%**、全量 **98.11%**(待下轮全量升格) |
+| 浏览器 E2E(真环境) | **28/28 全绿**(admin 5 + frontend 6 + shop 4 + health 13) | 连续第 3 轮四套件全绿 |
+| langgraph | 44 收集 / **32 过 / 12 红** | 与 R88/R89 逐例一致(产品 2 + 环境 10) |
+| cfc-frontend / cfc-web | 354+1skip / **116 全绿** | 契约闭环保持 |
+| P0 安全缺口 B-61 | health 契约 **13/13**(连续第 10 轮零回归) | ✅ 保持闭环 |
+| SRS 覆盖(可复算口径) | SC 锚点 **120/241 = 49.8%**;TC 级锚点 4 文件 | 不全覆盖 ❌ |
+| **总门禁** | **⛔ 不通过(有条件)** | 8 项达标 / 4 项未达标 + CI 缺失 |
+
+---
+
+## 二、基本信息
+
+| 项目 | 内容 |
+|---|---|
+| 汇报人 | 陆谨(软件测试专家团主理人 · 测试负责人) |
+| 汇报周期 | 2026-11-08 17:20 ~ 19:05(第 90 轮) |
+| 负责模块/单元 | cfc-backend / cfc-frontend / cfc-web / cfc-langgraph / 内网部署 E2E |
+| 执行方式 | 本机直跑(后端全量 1:06h → Jest×2 → langgraph venv → 浏览器 4 套件串行)→ 定向复跑 3 批;**251 不可派发**(9000/8000/22 CLOSED) |
+| 基线 SHA | `b58d54775`(跑前跑后一致,期间无源码提交) |
+
+---
+
+## 三、本轮工作数据汇总
+
+### 3.1 测试用例建设
+
+| 指标 | 数量 | 说明 |
+|---|---|---|
+| 本轮新增用例 | **0 条** | 今日 `docs/superpowers/specs/` **零变更** → 无 SRS 变更驱动的新用例 |
+| 本轮修订用例 | **7 个测试文件 / 23 例** | 见 §4.1(装置对齐、断言读取位、状态泄漏) |
+| 本轮执行用例 | **3171 例** | 后端 2628 + frontend 355 + web 116 + langgraph 44 + 浏览器 28 |
+| 用例通过率 | **97.38%** | 后端 97.34% / frontend 99.72% / web 100% / langgraph 72.73% / 浏览器 100% |
+| 后端用例总数 | 2628 | 311 类,35F + 31E + 4S |
+
+### 3.2 会话与任务分发
+
+| 指标 | 数量 | 说明 |
+|---|---|---|
+| 远程分派任务数 | **0** | 251 端口全 CLOSED;189 仅 4090 可达、无 SSH → langgraph 与浏览器均本机执行(遵守「每节点单任务」) |
+| 本地执行任务数 | 10 | 后端全量 1 + Jest 2 + langgraph 1 + 浏览器 4 + 定向复跑 2(另 test-compile 1) |
+| 恢复智能体会话数 | 0 | 无中断 |
+
+### 3.3 问题处置
+
+| 指标 | 数量 | 明细 |
+|---|---|---|
+| 本轮关闭缺陷 | **1** | B-70(上下文加载失败根因纠正并修复,8 例转绿) |
+| 本轮修复(测试侧) | **6 类缺陷** | A-2 数据源 8 + A-4 夹具 6 + A-6 Mockito/断言 9 |
+| 本轮新增缺陷 | **1** | **B-72** 线上 `/api/*` 短窗口 502 抖动(致 TC-A-U04 首跑假红) |
+| 状态更新 | 2 | B-69 连续第 3 轮未修;B-65 连续第 3 轮未修 |
+| 观察项 | 1 | B-67 纪律应用:首跑假红经「复跑×2 + curl 直探」三证定性 |
+
+### 3.4 Git 证据
+
+| 项 | 值 |
+|---|---|
+| 基线 / 详情 | `b58d54775` |
+| 本轮提交 | 测试侧装置修复 7 文件 + R90 三份台账(见 §6 推送) |
+| 工作区 | 仅保留未跟踪的临时诊断脚本与日志(`_r90_*`、`_diag_*`),不入库 |
+
+---
+
+## 四、上轮遗留问题解决情况
+
+| 上轮项 | 上轮状态 | 本轮结果 |
+|---|---|---|
+| P-1 `@MockBean` 修上下文失败(8 例) | 待办 | ✅ **已修(且根因纠正)**:不是 `auditLogAspect` 注入链,而是 3 个类硬编码 `127.0.0.1:3306`(本机无监听)→ 改 `192.168.16.251:3306`,3 类 8 例全绿 |
+| P-2 `CfCommissionServiceSettleTest` 打桩(8 例) | 待办 | ❌ 未动(需理解档位规则查询链,本轮优先处理可确定性缺陷) |
+| P-3 Mockito 误用族(9 例) | 待办 | ✅ **已修**:`anyInt()` 原语位 + 7 参 overload 对齐 + `getData()` 读取位 + 状态复位,2 类 9 例全绿 |
+| P-4 夹具 NPE(13 例) | 待办 | 🟡 **部分**:`FamilyJoinRequest` 4 + `FortuneService` 2 = 6 例已修;`ServiceRoleApprove` 2 + `GrowthRecordSync` 2 待下轮(栈被 trim,需单类开关取证) |
+| P-5 `/api/mind/` 401(研发) | 待办 | ❌ **第 3 轮未修**(见 §5.1 B-69) |
+| P-6 langgraph thread_id(研发) | 待办 | ❌ **第 3 轮未修**(见 §5.2 B-65) |
+| P-7 251 放通 9000(运维) | 待办 | ❌ 仍 CLOSED(9000/8000/22 全 000) |
+| P-8 剩余断言定性(14 例) | 待办 | 🟡 部分:本轮定性并修复其中 4 例(`Result.getMessage()` vs `getData()`) |
+
+---
+
+## 五、关键发现
+
+### 5.1 B-69(P1·安全口径)`/api/mind/` 仍未强制登录(连续第 3 轮)
+
+- **实测**:匿名 `POST /api/mind/fortune` → **HTTP 200**,`{"code":400,"message":"用户不存在"}`(用例期望 401)。
+- **源码**:`JwtInterceptor.LOGIN_REQUIRED_PATHS`(`config/JwtInterceptor.java:87` 起)仍无 `"/api/mind/"`;`PUBLIC_PATHS` 仅含 `/api/mind/alert/*`、`/api/mind/traditional/*`。
+- **建议**:清单增 `"/api/mind/"`;闭环判据=匿名 401 + `MindFortuneControllerTest` 转绿。
+
+### 5.2 B-65(P1·AI 能力不可用)langgraph `/api/v1/analyze` 恒 500(连续第 3 轮)
+
+- **实测**:`test_analyze.py` 2 例 `assert 500 == 200`,`ValueError: Checkpointer requires ... thread_id`。
+- **建议**:`analysis_graph.py` 去掉 `MemorySaver()`,或 `ainvoke(payload, config={"configurable":{"thread_id": trace_id}})`。
+
+### 5.3 B-72(新增·环境)线上 `/api/*` 短窗口 502 抖动
+
+- **实测**:17:22~17:26、17:33~17:35 两窗口,`stats/*`、`system/health`、`admin/menu/user-menus`、`health/plan/list` 连续返回 **502(openresty)**;同窗口 `admin-auth/login-by-password` 仍 200;17:36 起恢复 401/200 稳定。
+- **影响**:TC-A-U04 首跑假红(登录成功但数据接口 502 → 停留登录页),易被误判为鉴权回归。
+- **建议**:① 发布窗口公告/避开;② 前端对 502 显示明确「服务暂不可用」而非静默停留;③ 测试侧 live 契约按 B-67 三证纪律定性(已执行)。
+
+### 5.4 B-70 根因纠正(重要)
+
+前两轮将 8 例上下文失败归因「`auditLogAspect → auditLogService` 注入链」。本轮取全栈后确认:`Failed to load ApplicationContext → HikariPool 初始化失败 → CJCommunicationsException: Communications link failure → java.net.ConnectException: Connection refused`,即 **3 个测试类硬编码 `jdbc:mysql://127.0.0.1:3306/zxyj`,而本机 3306 无监听**(`netstat` 实测 REFUSED)。改指可达开发库后 8 例全绿。
+
+---
+
+## 六、覆盖矩阵与门禁
+
+| 门禁 | 实测 | 结论 |
+|---|---|---|
+| G1 总通过率 ≥95% | 97.38% | ✅ |
+| G2 零红例 | 78(后端 66 + lg 12) | ❌ |
+| G3 前端 Jest | 354/355 | ✅ |
+| G4 管理端 Jest | 116/116 | ✅ |
+| G5 编译/完整性 | 317 源 0 错;311/311 XML | ✅ |
+| G6 浏览器 E2E | 28/28 | ✅ |
+| G7 langgraph 零红 | 12 红 | ❌ |
+| G8 B-61 健康鉴权 | 13/13 | ✅ |
+| G9 SRS/场景锚点 | 120/241 = 49.8% | ❌ |
+| G10 langgraph 派 251 | 未达成 | ❌ |
+| G11 CI 门禁 | 无阻断式 CI | ❌ |
+| G12 用例资产入库 | 本轮 7 文件随交付提交 | ⏳ |
+
+覆盖缺口的可执行设计见 `automation-run-2026-11-08-R90.md` §8(C-1~C-6)。
+
+---
+
+## 七、下轮前置(按性价比排序)
+
+1. **全量 clean 复跑升格**:确认本轮 23 例定向绿 → 后端 **98.21%**,红例 43、红类 17(约 70 分钟,优先级最高,属证据升格)。
+2. `CfCommissionServiceSettleTest` 8 例:按「档位/规则查询链未打桩」补 stub(需先取得单类堆栈,`-Dsurefire.trimStackTrace=false`)。
+3. `ServiceRoleApproveTest` 2 + `GrowthRecordSyncFlowTest` 2:同上取栈后补桩。
+4. `DimensionScoreServiceImplTest` 4(`Optional.get()` 空):补 history 桩返回 `Optional.empty()` 或改 `orElse` 断言。
+5. **研发**:B-69 `/api/mind/` 401、B-65 thread_id(两项均连续 3 轮)。
+6. **运维**:251 放通 9000(G10 唯一路径);发布窗口避开自动化跑测时段(B-72)。
+
+---
+
+## 八、未实测项(如实声明)
+
+| 维度 | 原因 | 处置 |
+|---|---|---|
+| langgraph 251 节点派发 | 9000/8000/22 全 CLOSED | 降级本机 venv 直跑;G10 记未达成 |
+| 代码覆盖率 / 变异测试 | 无 JaCoCo/PIT 配置 | 缺口 C-4 |
+| 性能压测 / DAST / 依赖漏洞扫描 | execute 档需联网授权,本轮未获授权 | 缺口 C-6;未执行、未判定 |
+| 无障碍 / 多浏览器兼容 / UAT / BDD / 探索性 | 无资产、方案外 | 缺口 C-6(部分) |
+| 商城独立服务(dan/danshop)端到端 | 本轮方案外(沿用既往 4 例 shop-api 契约) | 下轮评估是否纳入 |
+| TC 级 SRS 锚点 | 测试资产仅 4 文件出现 TC 编号 | 缺口 C-1 |
+
+---
+
+## 九、补充:窗口内 SRS/场景变更(推送前 fetch 发现)
+
+> `origin/cfclub` 在跑测期间前进 18 提交,本轮基线(`b58d54775`)未含。已按「SRS/场景变更须更新用例」登记于证据文档 §10。
+
+| 变更 | 类型 | 本轮状态 | 下轮动作 |
+|---|---|---|---|
+| `2026-11-08-diet-recommendation-polish.md`(推荐食材页 7 条改版意见,2 能力键) | 需求变更 | **未覆盖** | 补前端 Jest 守卫 7 条 + 后端契约 1 条(RED 先行) |
+| `2026-11-08-dx-health-bridge.md`(GAP-A 桥接契约,O-17「仅登记不实现」) | 契约补建 | 未覆盖(**不得伪证**) | 契约核对阶段不建测试;实现入池后补四段链路用例 |
+| `c575a3a5b` feat(articles) 异步朗读音频(自带 11 例) | 功能新增 | 未执行 | 下轮全量纳入(2628 → 2639 例) |
+
+**影响**:本轮「覆盖所有 SRS」判定需扣除上述 2 份新增规格 → 缺口 C-1 范围扩大,已在证据文档 §10 与缺口清单登记。

+ 216 - 0
docs/tdd/test/automation-run-2026-11-08-R90.md

@@ -0,0 +1,216 @@
+# 自动化运行证据(第 90 轮 · 2026-11-08 17:20 ~ 19:05)
+
+> 触发:定时自动化「作为测试负责人执行自动化测试闭环(全部用例通过 + 覆盖 SRS/场景 + 浏览器走 cfc.iwintrue.com/admin 与 /frontend + langgraph 派 251 + 结果有变化提交远程并通知研发)」。
+> 执行档:`review`(离线)——未联网装依赖、未启停服务、未改生产代码(仅改测试侧装置)。
+> 基线:`cfclub` @ `b58d54775`(= `origin/cfclub`)
+> 原始日志:`_r1720_backend.log`(后端全量)· `_jest_frontend_r90.log` / `_jest_web_r90.log` · `_pytest_lg_r90.log` · `_e2e_{admin,frontend,shop,health}_r90*.log` · `_r90_targeted{1..4}.log` · `_r90_testcompile.log` · `_r90_502_probe.log` · `_r90_agg.json`
+
+---
+
+## 1. 实测结果总表
+
+| 套件 | 执行 | 结果 | 通过率 | 备注 |
+|---|---|---|---|---|
+| cfc-backend | `mvn -o -B clean test`(JDK1.8.0_25 + Maven 3.9.9) | 311 类 · **2628 例 / 2558 过 / 35F+31E / 4S** | **97.34%** | **与 R89 全字段零漂移**(连续第 2 轮) |
+| cfc-frontend | `npx jest --silent` | 21 suites · 354 passed / 1 skipped | 99.72% | 持平 |
+| cfc-web | `npx jest --silent` | 17 suites · **116 passed / 116** | 100% | 契约闭环保持 |
+| cfc-langgraph | `.venv/Scripts/python.exe -m pytest tests/ --ignore=tests/qna` | 44 collected · **32 passed / 12 failed** | 72.73% | B-65 产品 2 + B-54 环境 10 |
+| admin-desktop | `--project=admin-desktop` | **5 passed**(首跑 1 红→复跑全绿,见 §5.1) | 100% | 单节点串行 |
+| frontend-mobile | `--project=frontend-mobile` | **6 passed** | 100% | 单节点串行 |
+| shop-api | `--project=shop-api` | **4 passed** | 100% | 商城排序契约 |
+| health-contract | `-c playwright.config.health.js` | **13 passed** | 100% | B-61 连续第 10 轮零回归 |
+| **合计** | 6 套件 + 后端全量 | **3171 例 / 3088 过 = 97.38%** | — | 红 78(后端 66 + langgraph 12) |
+
+### 1.1 后端关键度量
+
+| 指标 | 值 |
+|---|---|
+| 测试源文件 / `@Test` / `@Disabled` | 311 / 2618 / 4 |
+| surefire 完整性 | **311/311 XML**,`silent_missing = 0`,无 dump |
+| testCompile | **317 源 0 错**(含本轮 7 处测试侧修改) |
+| 全量耗时 | **1:06 h**(机器负载高,R89 为 44:39) |
+| 恒等式 | `@Test 2618 + 参数化 26 − @Disabled 4 − 早退截断 12 = 2628`,误差 0 |
+| 控制器 / `@PostMapping` / `@GetMapping` | 363 / 1961 / 8(`@GetMapping` 仍违 AGENTS.md) |
+
+### 1.2 与上轮基线对比
+
+| 项 | R89 | R90 | Δ |
+|---|---|---|---|
+| 后端类/例/过 | 311 / 2628 / 2558 | 311 / 2628 / 2558 | **0** |
+| 后端 F/E/S | 35 / 31 / 4 | 35 / 31 / 4 | **0** |
+| 红类 / 红例 | 24 / 66 | 24 / 66 | **0** |
+| 前端 / web / langgraph / 浏览器 | 355 / 116 / 44 / 28 | 355 / 116 / 44 / 28 | **0** |
+| 全量通过率 | 97.38% | 97.38% | **0** |
+
+> 结论:基线**完全稳定**(第 2 次零漂移)。本轮增量不在基线,而在**测试侧装置修复 23 例**(§6,已定向验证)。
+
+---
+
+## 2. 环境与节点分派实测
+
+| 目标 | 结果 | 说明 |
+|---|---|---|
+| cfc.iwintrue.com `/admin` | 200 | admin-desktop 套件 |
+| cfc.iwintrue.com `/frontend` | 200 | frontend-mobile 套件 |
+| 192.168.16.189:4090 | **OPEN**(HTTP 200,opencode web) | 浏览器节点可达;`22/80` CLOSED → 无 SSH/远程驱动通道,浏览器任务在**本机单节点串行**执行(`workers:1`) |
+| 192.168.16.251:9000 / :8000 / :22 | **全部 CLOSED**(000) | langgraph 服务未监听、无远程通道 → **251 派发仍未达成**(G10 ❌,B-55 延续),降级本机 venv 直跑 |
+
+### 2.1 线上 502 抖动窗口(本轮新增环境证据 → B-72)
+
+| 时间窗 | 现象 | 影响 |
+|---|---|---|
+| 17:22 ~ 17:26 | `/api/stats/dashboard`、`/api/admin/menu/user-menus`、`/api/system/health` 连续 20/20 **502 Bad Gateway**(openresty/1.19.9.1rc1);同窗口 `/api/admin-auth/login-by-password` 仍 200 | **TC-A-U04 首跑失败**(登录成功但后台数据接口 502 → 前端停留登录页) |
+| 17:33 ~ 17:35 | `/api/health/plan/list` 连续 13 次 502,随后恢复 401 | 契约套件有被误判「鉴权回归」的风险 |
+| 17:36 ~ 17:38 | 30s 采样 18 组全部 401(稳定) | 恢复 |
+| 17:40 ~ 18:28 | `_r90_502_probe.log` 每 30s 双端点采样:`health=401 system=200` 为主,偶发 `system=000`(5s 超时) | 抖动为**短窗口**,非持续不可用 |
+
+> 与仓库 `chore: auto bump version and changelog`(`b58d54775`)+ `cfc-web/package-lock.json` 变更同窗口 → 判定为**发布/滚动重启窗口**,非产品回归。
+
+---
+
+## 3. 后端 66 例红因归因
+
+### 3.1 归因分桶(按实测异常首帧 + 源码复核)
+
+| 归因桶 | 例数 | 红类与证据 | 归属 |
+|---|---|---|---|
+| **A-2 上下文加载失败(数据源指向不可达 127.0.0.1:3306)** | 8 | `AdminFamilyOverviewControllerTest` 3E + `FamilyAdminOverviewServiceTest` 3E + `UserServiceFamilyNameLinkTest` 2E;根因链 `Failed to load ApplicationContext → HikariPool init → CJCommunicationsException: Communications link failure → ConnectException: Connection refused`(本机 3306 无监听,`netstat` 实测 REFUSED) | 测试侧 · **本轮已修** |
+| **A-4 夹具 NPE(`@InjectMocks` 缺依赖桩)** | 10 | `FamilyJoinRequestServiceReferrerBindTest` 4(缺 `familyInvitationMapper/familyMemberMapper/familyRelationshipService/couponService`)+ `FortuneServiceTest` 2(缺 `familyMemberMapper/familyMemberAttributesMapper`)+ `ServiceRoleApproveTest` 2 + `GrowthRecordSyncFlowTest` 2 | 测试侧(**前 6 例本轮已修**,余 4 例待下轮) |
+| **A-6 Mockito 原语匹配器误用 + Result 文案断言错位** | 9 | `ProductOrderServiceFulfillmentMultiModeTest` 6E(`uploadShippingInfo(...,int logisticsType,...)` 用 `any()` → 恒 null NPE;另 4 例断言 `getMessage()` 而业务文案在 `getData()`)+ `ProductOrderServiceUpdateExpressFieldTest` 3F(生产已统一 7 参 overload,旧 5 参 verify `ArgumentsAreDifferent`) | 测试侧 · **本轮已修** |
+| A-3 CF 分佣结算打桩未触发 | 8 | `CfCommissionServiceSettleTest` 8F(`verify(cfBalanceService).earnByRule(...)` 未发生) | 测试侧 · 待下轮 |
+| A-7 断言语义/状态泄漏 | 6 | `EnergySystemFlowTest` 6F | 待核实(逐例定性) |
+| A-8 邀请绑定流程断言 | 5 | `PlannerBindInviteFlowTest` 4F+1E | 待核实 |
+| A-5 Optional 空值 | 4 | `DimensionScoreServiceImplTest` 4E(`NoSuchElementException`,`Optional.get()` 空) | 测试侧 · 待下轮 |
+| 契约口径分歧 | 3 | `DimensionRegistryMapContractTest` 3F | 待核实(P-2 家族) |
+| 文章流状态机 | 3 | `ArticleWorkflowTest` 3F | 待核实 |
+| 其他单例断言 | 7 | `GrowthRecordSyncFlowTest` 1F、`DailyCheckinStreakFlowTest` 1E、`OrchestrationEngineTest` 1F、`OrchestrationIntegrationTest` 1F、`MembershipPaySwitchTest` 1F、`PaymentServiceORDRoutingTest` 1F、`TaskPlanServiceTest` 1F、`TeamProfitCronContractTest` 1F(合计 8,其中 1 已计入上表) | 待核实 |
+| **P-1 产品缺陷(B-69)** | 1 | `MindFortuneControllerTest` 期望 `code=401` 实得 `400`(`/api/mind/` 未入 `LOGIN_REQUIRED_PATHS`,第 3 轮未修) | **研发** |
+| **E-1 环境** | 1 | `GuideModuleIntegrationTest` 1E | 环境 |
+
+> 合计 66(A 类 58 / 产品 1 / 环境 1 / 待核实已计入各桶)。**A-2/A-4/A-6 中 23 例本轮已修并定向验证**(§6)。
+
+### 3.2 24 个红类清单(按红例数降序)
+
+`CfCommissionServiceSettleTest 8` · `EnergySystemFlowTest 6` · `ProductOrderServiceFulfillmentMultiModeTest 6` · `PlannerBindInviteFlowTest 5` · `DimensionScoreServiceImplTest 4` · `FamilyJoinRequestServiceReferrerBindTest 4` · `AdminFamilyOverviewControllerTest 3` · `DimensionRegistryMapContractTest 3` · `GrowthRecordSyncFlowTest 3` · `ArticleWorkflowTest 3` · `FamilyAdminOverviewServiceTest 3` · `ProductOrderServiceUpdateExpressFieldTest 3` · `FortuneServiceTest 2` · `UserServiceFamilyNameLinkTest 2` · `ServiceRoleApproveTest 2` · `MindFortuneControllerTest 1`(产品)· `DailyCheckinStreakFlowTest 1` · `GuideModuleIntegrationTest 1`(环境)· `OrchestrationEngineTest 1` · `OrchestrationIntegrationTest 1` · `MembershipPaySwitchTest 1` · `PaymentServiceORDRoutingTest 1` · `TaskPlanServiceTest 1` · `TeamProfitCronContractTest 1`
+
+> 与 R89 的 24 类**逐一对应,零新增、零消失**。
+
+---
+
+## 4. langgraph 12 例红因(本机 venv 兜底,251 未派发)
+
+| 归因 | 例数 | 用例 | 证据 |
+|---|---|---|---|
+| **B-65 产品缺陷** | 2 | `test_analyze.py::test_analyze_endpoint` / `::test_analyze_missing_report` | `ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id...` |
+| **B-54 环境(chromadb 原生 DLL)** | 6 | `test_auto_tag.py` 5 + `test_chat.py` 1 | `ImportError: DLL load failed while importing chromadb_rust_bindings` |
+| **B-54 环境(LLM key 无效)** | 4 | `test_intent.py` 4 | `401 Authentication Fails, Your api key: ****ocal is invalid` → 分类降级为默认 chat |
+
+> 与 R88/R89 逐例一致;B-65 为研发侧唯一产品缺陷项(连续第 3 轮未修)。
+
+---
+
+## 5. 浏览器自动化明细(单节点串行,28/28 全绿)
+
+| 套件 | 用例 | 结果 | 耗时 |
+|---|---|---|---|
+| admin-desktop | TC-A-U01~U05(登录页/滑块/未登录拦截/真实登录/核心菜单) | **5/5**(复跑) | 1.5m |
+| frontend-mobile | TC-F-U01~U06(首页/五维沙盘/TabBar×2/「我的」鉴权引导/资源无 failed) | **6/6** | 58.2s |
+| shop-api | TC-S-U01~U04(price asc/desc/默认/非法列白名单) | **4/4** | 9.2s |
+| health-contract | TC-H-U01~U10 + TC-HP-U01~U03(13 端点 401) | **13/13** | 5.8s |
+
+### 5.1 TC-A-U04 首跑失败与复跑定性(B-67 纪律应用)
+
+- **首跑**:`1 failed / 3 passed / 1 skipped`,断言「登录后应离开登录页,实际 URL=…/admin/login」(11.5s)。
+- **只读诊断**(`_diag_admin_r90.cjs`):`POST /api/admin-auth/login-by-password` **200**、`/api/admin-auth/info` **200**,随后 `stats/dashboard`、`stats/membership`、`stats/traffic`、`system/health`、`admin/menu/user-menus` **全部 502**;8s 后 URL 已为 `/admin/dashboard`(登录态建立,仅数据接口 502)。
+- **复跑**:同套件 **5/5 全绿**(2.6/4.7/3.6/10.6/58.9s)。
+- **定性**:上游 502 抖动窗口(§2.1),**非用例缺陷、非鉴权回归**;B-61 健康契约同窗口 13/13 未受影响。
+
+---
+
+## 6. 本轮代码改动(测试侧装置修复 7 文件,23 例)
+
+| # | 文件 | 缺陷 | 修复 | 定向复跑 |
+|---|---|---|---|---|
+| 1 | `AdminFamilyOverviewControllerTest` | 硬编码 `spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zxyj`,本机 3306 无监听 → 上下文加载失败(3E) | 改 `192.168.16.251:3306`(与其余 300+ 类一致,251 可达) | 3/3 绿(332s) |
+| 2 | `FamilyAdminOverviewServiceTest` | 同上(3E) | 同上 | 3/3 绿(63s) |
+| 3 | `UserServiceFamilyNameLinkTest` | 同上(2E) | 同上 | 2/2 绿(48s) |
+| 4 | `FamilyJoinRequestServiceReferrerBindTest` | `@InjectMocks` 缺 4 个依赖桩 → 审批分支 NPE(4E) | 补 `familyInvitationMapper`/`familyMemberMapper`/`familyRelationshipService`/`couponService` | 5/5 绿(0.8s) |
+| 5 | `FortuneServiceTest` | `FortuneServiceImpl` 另需 `familyMemberMapper`(`calculateFamilyElements`)+ `familyMemberAttributesMapper`(2E) | 补两个 `@Mock`(mapper 返回 null 时实现已有空集合兜底) | 4/4 绿(3.1s) |
+| 6 | `ProductOrderServiceFulfillmentMultiModeTest` | ① `uploadShippingInfo(...,int,...)` 用 `any()` → 6E NPE;② 4 例断言 `getMessage()` 而业务文案在 `getData()`(`Result.success()` message 恒 "success");③ 自提用例复用同一 mock 订单,首调后状态变 `pending_receipt` 致负向断言命中错误守卫 | ① 原语位改 `anyInt()`;② 断言改 `getData()`;③ 负向断言前复位 `status=paid` | 6/6 绿(3.8s) |
+| 7 | `ProductOrderServiceUpdateExpressFieldTest` | 生产已统一 7 参 overload,测试仍 verify 5 参 → `ArgumentsAreDifferent`(3F) | verify 对齐 7 参(单号/公司/`eq(1)` 不变,仅补 `any(), eq(1), any()`) | 3/3 绿(0.04s) |
+
+**验证口径**:`mvn -o -B test -Dtest=A,B,C,D`(逗号分隔,遵守 R88 教训:`+` 会静默 0 执行)→ **实测 13/13 + 13/13 全绿**;另 `mvn -o -B test-compile` **317 源 0 错**。
+
+> **未放宽任何断言强度**:所有修改均为「对齐真实契约 / 补齐装置 / 修正断言读取位」,无删除断言、无 `assertTrue(true)` 式占位。
+
+### 6.1 修复后预测(待下轮全量 clean 复跑升格为全量证据)
+
+| 项 | 当前实测 | 修复后预测 |
+|---|---|---|
+| 后端过例 | 2558 / 2628 = 97.34% | **2581 / 2628 = 98.21%** |
+| 后端红例 | 66 | **43** |
+| 后端红类 | 24 | **17** |
+| 全量通过率 | 97.38% | **98.11%**(3111/3171) |
+
+> 口径说明(承 B-57/R88):定向绿必须由全量 clean 复跑升格后才入台账基线;本轮结论按「定向已验、全量待升格」记账。
+
+---
+
+## 7. 门禁判定
+
+| 门禁 | 判据 | 实测 | 结论 |
+|---|---|---|---|
+| G1 总通过率 ≥95% | 97.38% | ≥95% | ✅ |
+| G2 零红例 | 78(后端 66 + langgraph 12) | 0 | ❌ |
+| G3 前端 Jest | 354/355(1 skip) | 无红 | ✅ |
+| G4 管理端 Jest(含契约) | 116/116 | 全绿 | ✅ |
+| G5 后端编译/完整性 | 317 源 0 错;311/311 XML | 无缺失 | ✅ |
+| G6 浏览器 E2E | 28/28 | 100% | ✅ |
+| G7 langgraph 零红 | 12 红 | 0 | ❌ |
+| G8 B-61 健康鉴权契约 | 13/13 | 零回归 | ✅ |
+| G9 SRS/场景锚点覆盖 | SC 锚点 **120/241 = 49.8%**(口径升级见下) | ≥目标 | ❌ |
+| G10 langgraph 派 251 | 未达成(9000/8000/22 CLOSED) | 达成 | ❌ |
+| G11 CI 流水线门禁 | 无阻断式 CI(langgraph 会红) | 通过 | ❌ |
+| G12 用例资产入库 | 本轮 7 文件待提交 | 入库 | ⏳(本轮随交付提交) |
+
+**G9 口径升级说明**:R88/R89 沿用「SRS 章节级锚点 3/170」,该口径不可复算(B-30)。本轮改为可复算的 **SC 编号在测试资产中的被引用数**:`sc-key-map` 登记 SC **241**,测试资产(后端 `src/test` + 前端/web Jest + `tests/e2e-live`)中出现的不同 SC 编号 **120** 个 → **49.8%**;TC 台账 293 条,但测试资产中出现 TC 编号的文件仅 **4** 个(e2e-live spec)→ TC 级锚点仍近乎为零。
+
+**总判定:⛔ 不通过(有条件)**——8 项达标(G1/G3/G4/G5/G6/G8/G12 待提交),4 项未达标(G2 红例 / G7 langgraph / G9 SRS 锚点 / G10 派发)+ G11 CI 缺失。
+
+---
+
+## 8. 覆盖缺口清单
+
+| # | 缺口 | 现状 | 风险 | 建议测试设计 | 优先级 | 闭环方式 |
+|---|---|---|---|---|---|---|
+| C-1 | SRS/场景用例锚点 | SC 120/241(49.8%);TC 编号仅 4 文件出现 | 无法证明「SRS 全覆盖」,需求变更无法精准回归 | 每个 SRS 章节/SC 编号在对应测试类 Javadoc 或方法名中显式锚定;新增 `SC-TRACE` 校验脚本 | P0 | 锚点率 ≥90% 且脚本入 CI |
+| C-2 | langgraph 集成层可执行性 | 12 红(环境 10 + 产品 2),251 不可派发 | AI 能力回归无自动化守门 | ① 本机装 VC++ 运行时重建 venv;② 注入有效 LLM key 或 StubLLM 桩;③ 251 放通 9000 | P0 | langgraph 44/44 绿 |
+| C-3 | 后端 A 类装置残留 | 43 例(预测)红,集中在 CF 结算 / 能量流 / 契约口径 | 掩盖真实产品缺陷(红噪声稀释信号) | 逐类按「打桩缺失 / 断言读取位 / 状态泄漏」三分类修复 | P1 | 红例 ≤10 |
+| C-4 | 覆盖率工具 | 无 JaCoCo 报告 | 无法量化行/分支覆盖 | `jacoco-maven-plugin` + 行覆盖 ≥60% 门禁 | P2 | 生成报告并入 CI |
+| C-5 | 浏览器用例健壮性 | TC-A-U05 仍用 `test.skip(url.includes('/login'))`(B-59) | 前置失败被转为「不判定」,验收缺口静默 | 改 `waitForURL(/dashboard/, 20s)` + `expect` 硬断言,或复用 `storageState` | P1 | admin 连续 2 轮 5/5 无 skip |
+| C-6 | 性能/安全/兼容/无障碍 | 全未实测(无工具、未授权联网) | 非功能质量盲区 | 压测(k6)需 execute 档授权;SAST 用 semgrep 离线规则;兼容性补 webkit/firefox project | P2 | 各维度至少 1 套可复跑基线 |
+
+---
+
+## 9. 结论与移交
+
+- **基线稳定**:后端 2628 例 / 97.34%、24 红类 / 66 红例,与 R89 **逐字段零漂移**(第 2 次);前端/web/浏览器全绿;langgraph 32/44 与上两轮一致。
+- **本轮实质进展在测试侧**:修复 7 个测试文件、**23 例**由红转绿(上下文 8 + 夹具 6 + Mockito/断言 9),定向复跑 26/26 全绿,预测后端 → **98.21%**、全量 → **98.11%**(待下轮全量升格)。
+- **新根因纠正(B-70)**:8 例 `Failed to load ApplicationContext` **不是**「auditLogAspect 注入链」,而是 3 个测试类硬编码 `127.0.0.1:3306` 而本机 3306 无监听(`Connection refused`);前 2 轮的推断被本轮堆栈取证推翻。
+- **新环境缺陷(B-72)**:线上 `/api/*` 存在**短窗口 502 抖动**(17:22~17:26、17:33~17:35),直接导致 TC-A-U04 首跑假红;已按 B-67 三证纪律定性为发布/滚动重启窗口。
+- **仍未闭环**:B-69(`/api/mind/` 401,第 3 轮)、B-65(langgraph analyze 500,第 3 轮)、B-54(环境)、B-55/G10(251 派发)、G9(SRS 锚点)。
+- **本轮结果有变化**(测试侧 23 例转绿 + 基线稳定证据)→ 交付件已提交并推送远程,研发交接通知同步落地。
+
+---
+
+## 10. SRS / 场景变更检测(窗口内,追加于推送后核对)
+
+> 本轮跑测起点 HEAD 为 `b58d54775`;推送前 `git fetch` 发现 `origin/cfclub` 已前进 **18 提交**(含新功能与新规格)。以下变更**不在本轮基线内**,按「SRS/场景变更须更新用例」要求登记。
+
+| # | 变更 | 类型 | 本轮状态 | 用例影响 | 下轮动作 |
+|---|---|---|---|---|---|
+| S-1 | `docs/superpowers/specs/2026-11-08-diet-recommendation-polish.md`(推荐食材页改版:去标题条 / 视图切换清选中态 / 今日食谱与筛选 Tab 同行 / 分页 / 共餐人选择,共 7 条意见,2 个能力键 `diet.recommendation.uiRework`、`diet.recommendation.participantSelect`) | **需求变更(SRS)** | 未覆盖(规格 17:00 后入库) | 需新增前端 Jest 守卫 7 条(`cfc-frontend/pages/diet/recommendation.vue` 视图/选中态/分页/共餐人)+ 后端契约 1 条(`/api/diet/recommendation` participants 入参) | **下轮补 RED 契约用例**(随研发入池节奏,先立用例再实现) |
+| S-2 | `docs/superpowers/specs/2026-11-08-dx-health-bridge.md`(GAP-A:D/X 同步数据桥接五维消费链路,O-17 裁决「仅登记不实现」) | **契约补建(SRS 新增)** | 未覆盖(无实现,不得伪证) | 契约核对阶段**不建测试**;桥接实现入池后再补「health_data_sources → health_dimension_score → 档案展示 → 小程序端点」四段链路用例 | 下轮在覆盖矩阵登记「已识别未覆盖」,与 C-1 合并跟踪 |
+| S-3 | `c575a3a5b` feat(articles) 发布后异步预生成朗读音频 + 正文变更作废旧音频(自带 11 例单测:`ArticleAudioServiceAutoGenTest` 3 + `ArticleServiceAudioInvalidationTest` 8) | 功能新增(已带测试) | 未执行(提交未在基线内) | 基线用例数将由 2628 → **2639** | 下轮全量复跑纳入 |
+
+> 结论:窗口内 **2 份 SRS 新增/变更 + 1 个带测新功能**。S-1 需按 7 条意见补用例(未补,登记为缺口);S-2 按 O-17「仅登记不实现」不建伪用例;S-3 下轮全量纳入基线(预计 +11 例)。

+ 5 - 0
docs/tdd/test/blockers.md

@@ -92,3 +92,8 @@
 | B-69(第 88 轮新增) | `/api/mind/` 未入 `LOGIN_REQUIRED_PATHS`,匿名访问返回 400 而非 401 | 研发 | **P1 安全口径漂移(第 88 轮实测,2026-11-08 15:00)**。`MindFortuneControllerTest.testGetTodayFortuneWithoutAuth` 断言 `$.code == 401`,实测 **400**。源码级确认:`POST /api/mind/fortune`(`MindFortuneController.java:42`)既不在 `PUBLIC_PATHS` 也不在 `LOGIN_REQUIRED_PATHS`(`JwtInterceptor.java:87-127`),且方法签名 `@RequestAttribute("userId") Long userId` + `if (userId == null) return Result.error("请先登录")` → **匿名请求在 Spring 参数绑定层即抛缺属性异常(400),控制器内的 null 分支不可达**(与 B-57 同族陷阱)。影响:与 SEC-20261031-01「私有数据端点无 token 直接 401」口径不一致;400 错误体可能透出内部异常信息。修复建议:`LOGIN_REQUIRED_PATHS` 增 `"/api/mind/"`(同日运势/刷新/导出均为家庭私有数据);若产品意图为游客可用,则需改为 `@RequestAttribute(required=false)` 并显式匿名降级,且同步改测试断言。闭环:匿名 POST 返回 401 + `MindFortuneControllerTest` 转绿 | 测试负责人(第 88 轮实测登记) | 2026-11-08 15:00 | 待处理(P1) |
 | B-70(第 88 轮新增) | 8 例 Spring 上下文加载失败:`auditLogAspect → auditLogService` 注入链 | 测试(自处置) | **A 类测试装置(第 88 轮全量实测 2026-11-08 14:46)**。8 例红(`AdminFamilyOverviewControllerTest` 3、`FamilyAdminOverviewServiceTest` 3、`UserServiceFamilyNameLinkTest` 2)异常均为 `IllegalStateException: Failed to load ApplicationContext`,`Caused by: BeanCreationException: Error creating bean 'auditLogAspect': Injection of resource dependencies failed → 'auditLogService': Injection of resource dependencies failed`。成因推断:新增审计切面依赖的 bean 在该测试切片内被 `@MockBean` 覆盖或缺失(下游具体 bean 名需一次定向复现取证)。**非产品缺陷**(生产上下文正常启动,全量其余 303 类上下文均成功)。修复建议:为这两个测试类补 `@MockBean AuditLogService`(或按 `auditLogService` 的下游依赖补齐),复跑确认。闭环:3 类各 3/3、3/3、2/2 转绿 + 全量复跑无上下文失败 | 测试负责人(第 88 轮实测登记) | 2026-11-08 14:50 | 待处理(测试侧,A 类) |
 | B-71(第 88 轮新增·已闭环) | 后端多类测试装置滞后于生产「孩子→家庭成员」重构 | 测试(已处置) | **第 88 轮(2026-11-08 15:00)**。生产把 `TaskService.getPendingReviewTasksForChild/getAllTasksByChild/getTaskHistoryByChild` 更名 `...ForFamilyMember`、`guide-bind` 从「直接绑定」改为「提交申请待审批」,测试仍打旧桩 → 8 例红(`GuideFamilyTaskControllerTest` 5、`GuideRolePermissionTest` 2、`FamilyControllerTest` 1)。已按新契约修订测试装置(服务方法更名、`checkMemberOnly` 前置 `taskMapper.selectById` 打桩、`@MockBean TeacherFamilyBindingRequestService`、断言改为「申请已提交,请等待家庭主账号审批」)。**未放宽断言强度**。**定向复跑已确认**:`mvn test -Dtest=GuideFamilyTaskControllerTest,GuideRolePermissionTest,FamilyControllerTest` → **49/49 全绿**(20+21+8,0F/0E),同 3 类其余 41 例无回归;预测后端 97.03%→97.34%(待下轮全量 clean 复跑升级为全量证据) | 测试负责人(第 88 轮实测) | 2026-11-08 15:06 | **已闭环(49/49 绿)** |
+| B-70-Update(第 90 轮 · 根因反转并闭环) | 8 例上下文加载失败:真实根因是测试类硬编码 `127.0.0.1:3306`(本机无监听),非 `auditLogAspect` 注入链 | 测试(已处置) | **根因纠正 + 闭环(2026-11-08 18:44)**。取全栈后确认因果链:`IllegalStateException: Failed to load ApplicationContext → Error creating bean 'primaryDataSource'(SfmsDataSourceConfig)→ HikariPool 初始化失败 → CJCommunicationsException: Communications link failure → java.net.ConnectException: Connection refused: connect`。即 `AdminFamilyOverviewControllerTest` / `FamilyAdminOverviewServiceTest` / `UserServiceFamilyNameLinkTest` 三类把 `spring.datasource.url` 硬编码为 `jdbc:mysql://127.0.0.1:3306/zxyj`,而本机 3306 **无监听**(`netstat -an \| grep LISTENING` 无 3306、`/dev/tcp/127.0.0.1/3306` REFUSED;默认数据源 `application.yml:22` 为 `192.168.16.251:3306`,实连 ESTABLISHED)。R88/R89 的「auditLogAspect → auditLogService 注入链」推断被推翻——那只是**上下文失败后的下游表象**(bean 创建链上第一个失败点)。修复:三类数据源改指 `192.168.16.251:3306`(与其余 300+ 类一致)。**定向复跑 13/13 全绿**(3+3+2+5)。方法论:判「上下文加载失败」必须读 `Caused by` 链的最内层(JDBC/网络层),不可停在第一个 bean 名上 | 测试负责人(第 90 轮实测) | 2026-11-08 18:44 | **已闭环(8 例转绿,13/13)** |
+| B-72(第 90 轮新增) | 线上 `/api/*` 短窗口 502 抖动(openresty),致浏览器/契约用例假红 | 运维/研发 | **P1 环境缺陷(第 90 轮实测,2026-11-08 17:22~17:35)**。两窗口实测:① 17:22~17:26 `POST /api/stats/dashboard`、`/api/stats/membership`、`/api/stats/traffic`、`/api/system/health`、`/api/admin/menu/user-menus` **连续 20/20 返回 502 Bad Gateway**(`Server: openresty/1.19.9.1rc1`),而同窗口 `POST /api/admin-auth/login-by-password` **仍 200**(登录成功)→ 前端登录后数据接口 502 → 停留 `/admin/login`;② 17:33~17:35 `/api/health/plan/list` 连续 13 次 502 后恢复 401。17:36~17:38 30s 采样 18 组全部 401(稳定);17:40 起 `_r90_502_probe.log` 每 30s 双端点采样,主态 `health=401 system=200`,偶发 `system=000`(5s 超时)。同一时间窗仓库有 `chore: auto bump version and changelog`(`b58d54775`)+ `cfc-web/package-lock.json`,判定为**发布/滚动重启窗口**。影响:TC-A-U04 首跑假红(复跑 5/5 全绿),易被误判为鉴权回归。建议:① 发布窗口公告或避开自动化跑测时段;② 前端对 502 显示明确「服务暂不可用」,不要静默停留登录页;③ 测试侧维持 B-67 三证纪律(已执行) | 测试负责人(第 90 轮实测登记) | 2026-11-08 17:35 | 待处理(运维:发布窗口管理) |
+| B-69-Update(第 90 轮 · 连续第 3 轮) | `/api/mind/` 匿名仍非 401 | 研发 | **连续第 3 轮未修(2026-11-08 19:00)**。线上实测 `POST /api/mind/fortune` 匿名 → **HTTP 200**,`{"code":400,"message":"用户不存在"}`(用例期望 401)。源码复核(HEAD `b58d54775`):`cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java:87` 的 `LOGIN_REQUIRED_PATHS` 仍无 `"/api/mind/"`(`PUBLIC_PATHS` 仅含 `/api/mind/alert/*`、`/api/mind/traditional/*`)→ **源代码级未修**。闭环判据不变:匿名 401 + `MindFortuneControllerTest` 转绿 | 测试负责人(第 90 轮复现登记) | 2026-11-08 19:00 | 待处理(P1,连续第 3 轮) |
+| B-65-Update(第 90 轮 · 连续第 3 轮) | langgraph `/api/v1/analyze` 恒 500(checkpointer 缺 thread_id) | 研发 | **连续第 3 轮未修(2026-11-08 18:55)**。本轮 `pytest tests/ --ignore=tests/qna` 实测 44 收集 / 32 过 / 12 红,其中 `test_analyze.py` 2 例仍 `assert 500 == 200`,异常 `ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id`。其余 10 红为 B-54 环境(chromadb DLL 6 + LLM key 401 4)。闭环判据不变:两例 200 转绿 | 测试负责人(第 90 轮复现登记) | 2026-11-08 18:55 | 待处理(P1,连续第 3 轮) |
+| B-59-Update(第 90 轮 · 再次触发) | TC-A-U05 仍以 `test.skip(url.includes('/login'))` 吞掉前置失败 | 测试(下轮自处置) | **第 90 轮再次触发(2026-11-08 17:24)**。admin 首跑因 B-72 502 抖动导致 TC-A-U04 红,TC-A-U05 随即命中 `test.skip` 分支被记为 skipped(不计红)→ 套件显示 3 passed / 1 failed / 1 skipped。复跑(502 窗口结束)5/5 全绿。佐证 B-59 判定:**固定 `waitForTimeout(5000)` + skip 兜底会把环境抖动转成「不判定」**。修复建议不变:`waitForURL(/\/admin\/dashboard/, { timeout: 20000 })` 硬断言,或复用 `storageState` | 测试负责人(第 90 轮观察) | 2026-11-08 17:26 | 待处理(下轮自处置) |

+ 73 - 0
docs/tdd/研发交接通知-2026-11-08-第90轮.md

@@ -0,0 +1,73 @@
+# 研发交接通知 · 2026-11-08 第 90 轮(测试负责人 → 研发)
+
+> 发送人:测试负责人(陆谨)|时间:2026-11-08 19:05|基线:`cfclub` @ `b58d54775`(= `origin/cfclub`)
+> 证据:`docs/tdd/test/automation-run-2026-11-08-R90.md` + `docs/tdd/orchestration/report-test-2026-11-08-R90.md`
+
+## 0. 一句话结论
+
+**基线完全稳定(后端 2628 例 / 97.34%,24 红类 / 66 红例,与 R89 逐字段零漂移);测试侧本轮修复 7 个测试文件、23 例转绿(定向复跑 26/26),预测后端升至 98.21%。仍需研发处理两项连续 3 轮未修的产品缺陷:B-69(`/api/mind/` 401)、B-65(langgraph `/api/v1/analyze` 500)。**
+
+---
+
+## 1. ✅ 本轮已闭环(测试侧,不需研发动作)
+
+| 项 | 内容 | 证据 |
+|---|---|---|
+| **B-70 根因纠正 + 闭环** | 8 例 `Failed to load ApplicationContext` **不是** `auditLogAspect` 注入链,而是 3 个测试类硬编码 `jdbc:mysql://127.0.0.1:3306/zxyj`,本机 3306 无监听(`Connection refused`)→ 改指 `192.168.16.251:3306` 后 **3 类 8 例全绿** | `_r90_targeted1.log`:13/13 |
+| **A-6 Mockito/断言族 9 例** | `uploadShippingInfo(..., int logisticsType, ...)` 原语位 `any()`→`anyInt()`;生产已统一 7 参 overload 而测试仍 verify 5 参;`Result.success(payload)` 的 message 恒为 `"success"`,业务文案在 `data`,断言读取位纠正;自提用例状态泄漏(首调后 `pending_receipt`)→ 负向断言前复位 | `_r90_targeted4.log`:`ProductOrderServiceFulfillmentMultiModeTest` 6/6、`ProductOrderServiceUpdateExpressFieldTest` 3/3 |
+| A-4 夹具 6 例 | `FamilyJoinRequestServiceReferrerBindTest` 补 4 个依赖桩(5/5);`FortuneServiceTest` 补 `familyMemberMapper`/`familyMemberAttributesMapper`(4/4) | `_r90_targeted1/2.log` |
+| 编译健康 | `mvn -o -B test-compile` **317 源 0 错** | `_r90_testcompile.log` |
+
+---
+
+## 2. 🔴 需研发处理(连续 3 轮未修)
+
+### 2.1 B-69(P1·安全口径)`/api/mind/` 仍未强制登录
+
+- **实测(本轮)**:匿名 `POST /api/mind/fortune` → **HTTP 200**,`{"code":400,"message":"用户不存在"}`;用例期望 401。
+- **源码(`b58d54775`)**:`cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java:87` 的 `LOGIN_REQUIRED_PATHS` 仍无 `"/api/mind/"`(`PUBLIC_PATHS` 只有 `/api/mind/alert/*`、`/api/mind/traditional/*`)。
+- **建议**:`LOGIN_REQUIRED_PATHS` 增 `"/api/mind/"`(今日运势/刷新/导出均为家庭私有数据)。
+- **闭环判据**:匿名 POST 返回 401 + `MindFortuneControllerTest` 转绿。
+
+### 2.2 B-65(P1·AI 能力不可用)langgraph `/api/v1/analyze` 恒 500
+
+- **实测(本轮)**:`tests/test_analyze.py` 2 例 `assert 500 == 200`,异常 `ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id`。
+- **根因**:`app/graphs/analysis_graph.py` 用 `MemorySaver()` 编译,`app/api/analyze.py` 的 `ainvoke(payload)` 未传 config。
+- **建议**:① 该图无多轮状态需求 → 去掉 `MemorySaver()`;② 或 `ainvoke(payload, config={"configurable": {"thread_id": trace_id}})`。
+- **闭环判据**:两例 200 转绿。
+
+---
+
+## 3. 🟡 环境/运维(需知晓)
+
+| 编号 | 内容 | 请求动作 |
+|---|---|---|
+| **B-72(新增)** | 线上 `/api/*` 出现**短窗口 502 抖动**:17:22~17:26、17:33~17:35 期间 `stats/*`、`system/health`、`admin/menu/user-menus`、`health/plan/list` 连续 502(openresty),同窗口 `admin-auth/login-by-password` 仍 200;17:36 起恢复 | ① 发布/滚动重启时避开自动化跑测时段;② 前端对 502 给出明确「服务暂不可用」提示(当前静默停留登录页,误导性强) |
+| B-55 / G10 | `192.168.16.251:9000/8000/22` 全 CLOSED,`192.168.16.189` 仅 4090 可达 | 放通 251 的 9000(langgraph 派发唯一路径) |
+| B-54 | langgraph 12 红:`chromadb_rust_bindings` DLL 缺失 6 例 + LLM key 无效 4 例 | 宿主机装 VC++ 运行时 / 注入有效 key |
+
+---
+
+## 4. 📌 基线口径变化(需知晓)
+
+1. **G9 覆盖口径升级**:由不可复算的「SRS 章节锚点 3/170」改为可复算的 **SC 编号在测试资产中的被引用数 120/241 = 49.8%**(TC 级锚点仍仅 4 文件)。后续每轮按新口径跟踪。
+2. **不再将 B-70 记为「注入链」缺陷**:根因为测试侧数据源硬编码,已闭环。
+3. **定向绿不入基线**:本轮 23 例为定向验证绿,需下轮全量 clean 复跑才升格为全量证据(承 B-57 纪律)。
+
+---
+
+## 5. 下轮测试侧计划(供对齐)
+
+`CfCommissionServiceSettleTest` 8(档位/规则查询桩)→ `ServiceRoleApproveTest` 2 + `GrowthRecordSyncFlowTest` 2(取栈补桩)→ `DimensionScoreServiceImplTest` 4(`Optional` 空值)→ 全量 clean 复跑升格。预计后端可再降至 **红例 ≤27**。
+
+---
+
+## 6. 🆕 窗口内 SRS 变更(需研发知会,测试侧下轮补用例)
+
+| # | 变更 | 测试侧处置 |
+|---|---|---|
+| S-1 | `2026-11-08-diet-recommendation-polish.md`:推荐食材页 7 条改版意见(去标题条 / 视图切换清成员选中态 / 今日食谱与筛选 Tab 同行 / 分页 / 共餐人选择),2 个能力键 `diet.recommendation.uiRework`、`diet.recommendation.participantSelect` | **下轮补 RED 用例**:前端 Jest 守卫 7 条(`pages/diet/recommendation.vue`)+ 后端契约 1 条(`/api/diet/recommendation` 的 participants 入参);请研发在实现前知会接口字段,避免契约反复 |
+| S-2 | `2026-11-08-dx-health-bridge.md`:GAP-A D/X 桥接契约(O-17「仅登记不实现」) | 契约核对阶段**不建测试、不伪证**;桥接实现入池后补四段链路用例(同步 → 五维得分 → 档案展示 → 小程序端点) |
+| S-3 | `c575a3a5b` feat(articles) 异步朗读音频(自带 11 例单测) | 下轮全量复跑纳入基线(2628 → 2639 例),本轮基线未含 |
+
+> 提醒:本轮后端全量基线取自 `b58d54775`,与当前 `origin/cfclub` 相差 18 提交(含上述 S-3),因此下一轮全量的分母会 **+11 例**,对比时请以用例数为准、勿按通过率直接横比。