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

test(tdd): 第110轮回环——6类A类失效用例修复追交 + R109报告追交 + 251派发工具入库(E2E 28/28·langgraph 32/43·jest 双绿,零漂移)

E2E Test Bot 3 дней назад
Родитель
Сommit
a559f20996

+ 11 - 0
.gitignore

@@ -132,3 +132,14 @@ tools/xaf_agent_hub/*.bak*
 
 # 构建脚本错误产物(Vue 模板变量未解析导致的畸形文件名,非正常发布产物)
 cfc-web/public/CHANGELOG-v*.md
+
+# Live E2E output (auto-generated, per-node local, do not commit)
+tests/.live-out-*
+tests/.live-artifacts
+
+# 一次性执行辅助(自动生成,不入库)
+_cmd_*
+_dispatch_*
+_poll_*
+_r1*_agg.json
+tmp_*.cjs

+ 5 - 2
cfc-backend/src/test/java/com/etotem/cfc/integration/DailyCheckinStreakFlowTest.java

@@ -250,8 +250,11 @@ public class DailyCheckinStreakFlowTest {
         // === Given:需要删除记录 ===
         Map<String, Object> deleteRequest = new HashMap<>();
         deleteRequest.put("id", CHECKIN_ID);
+        // 删除打卡的 userId 来自请求体(DailyCheckinController#delete 用 ParamUtils 取 userId),
+        // 不传会以 null 调 service(A 类用例缺陷:原用例漏放 userId 且用 anyLong() 掩盖)
+        deleteRequest.put("userId", CHILD_ID);
 
-        doNothing().when(dailyCheckinService).deleteCheckin(eq(CHECKIN_ID), any(Long.class));
+        when(dailyCheckinService.deleteCheckin(eq(CHECKIN_ID), eq(CHILD_ID))).thenReturn(true);
 
         // === When:删除打卡记录 ===
         Result<String> result = checkinController.delete(deleteRequest);
@@ -259,7 +262,7 @@ public class DailyCheckinStreakFlowTest {
         // === Then:删除成功 ===
         assertEquals(200, result.getCode());
         assertEquals("删除成功", result.getData());
-        verify(dailyCheckinService).deleteCheckin(eq(CHECKIN_ID), any(Long.class));
+        verify(dailyCheckinService).deleteCheckin(eq(CHECKIN_ID), eq(CHILD_ID));
     }
 
     // ========== Helper Methods ==========

+ 22 - 8
cfc-backend/src/test/java/com/etotem/cfc/integration/PlannerBindInviteFlowTest.java

@@ -67,6 +67,15 @@ public class PlannerBindInviteFlowTest {
     @MockBean
     private MembershipService membershipService;
 
+    /** 规划师认证闸门(R4+D6 新增):默认放行,聚焦绑定链路本身 */
+    @MockBean
+    private com.etotem.cfc.service.PlannerCertificationGuard plannerCertificationGuard;
+
+    @BeforeEach
+    void allowCertifiedGuide() {
+        when(plannerCertificationGuard.assertCertified(anyLong(), anyString(), anyBoolean())).thenReturn(null);
+    }
+
     // ========== 测试数据 ==========
 
     private static final Long GUIDE_USER_ID = 4001L;
@@ -212,7 +221,9 @@ public class PlannerBindInviteFlowTest {
         bindDTO.setGuideId(GUIDE_USER_ID);
         bindDTO.setServiceType("学业规划");
 
-        when(guideFamilyService.confirmBind(eq(GUIDE_USER_ID), eq(FAMILY_ID), anyString(), anyInt()))
+        // 注意:servicePrice 未设置时为 null,用 anyInt() 不匹配 null → 桩不生效、默认 false 假通过
+        // (A 类用例缺陷:应使用 any() 以覆盖 null 实参)
+        when(guideFamilyService.confirmBind(eq(GUIDE_USER_ID), eq(FAMILY_ID), anyString(), any()))
                 .thenThrow(new RuntimeException("该规划师已在服务中,无需重复绑定"));
         when(membershipService.getUserFamilyId(PARENT_USER_ID)).thenReturn(FAMILY_ID);
 
@@ -270,7 +281,7 @@ public class PlannerBindInviteFlowTest {
 
         GuideFamily unbindGuide = createGuideFamily(BINDING_ID, GUIDE_USER_ID, "学业规划", 29900, "unbound");
         when(guideFamilyService.unbindAndGet(eq(BINDING_ID), eq(FAMILY_ID))).thenReturn(unbindGuide);
-        doNothing().when(teacherMessageService).sendMessage(eq(FAMILY_ID), eq(PARENT_USER_ID), eq(GUIDE_USER_ID), anyString());
+        when(teacherMessageService.sendMessage(eq(FAMILY_ID), eq(PARENT_USER_ID), eq(GUIDE_USER_ID), anyString())).thenReturn(1L);
         when(membershipService.getUserFamilyId(PARENT_USER_ID)).thenReturn(FAMILY_ID);
 
         // === When:家长确认解除绑定 ===
@@ -300,14 +311,17 @@ public class PlannerBindInviteFlowTest {
                 createGuideFamily(2L, GUIDE_USER_ID, "active", 3002L, 1002L)
         );
 
-        when(guideFamilyService.getFamiliesByGuide(GUIDE_USER_ID)).thenReturn(mockBindings);
+        when(membershipService.getUserFamilyId(PARENT_USER_ID)).thenReturn(FAMILY_ID);
+        when(guideFamilyService.getGuidesByFamily(FAMILY_ID)).thenReturn(mockBindings);
 
-        // === When:规划师请求客户列表 ===
-        Result<List<GuideFamily>> result = null; // 需要找对应的 Controller 方法
-        // 假设有 guideFamilyService.getFamiliesByGuide 方法
+        // === When:家庭侧请求已绑定规划师列表(BindController#getBoundGuides)===
+        Result<List<Map<String, Object>>> result = bindController.getBoundGuides(PARENT_USER_ID);
 
-        // === Then:返回所有已绑定家庭 ===
-        assertNotNull(result); // 实际需要对接真实 API
+        // === Then:返回所有已绑定规划师(原用例为占位 assertNotNull(null),属空壳用例)===
+        assertEquals(200, result.getCode());
+        assertNotNull(result.getData());
+        assertEquals(2, result.getData().size());
+        verify(guideFamilyService).getGuidesByFamily(FAMILY_ID);
     }
 
     // ========== 场景7:完整绑定流程(端到端)============

+ 3 - 3
cfc-backend/src/test/java/com/etotem/cfc/integration/service/ArticleWorkflowTest.java

@@ -202,7 +202,7 @@ public class ArticleWorkflowTest {
         RuntimeException ex = assertThrows(RuntimeException.class,
             () -> service.reDraft(1L));
 
-        assertEquals("仅已驳回的文章可重新编辑", ex.getMessage());
+        assertEquals("仅已驳回或已撤回的文章可重新编辑", ex.getMessage());
         verify(articleMapper, never()).updateById(any());
     }
 
@@ -214,7 +214,7 @@ public class ArticleWorkflowTest {
         RuntimeException ex = assertThrows(RuntimeException.class,
             () -> service.reDraft(1L));
 
-        assertEquals("仅已驳回的文章可重新编辑", ex.getMessage());
+        assertEquals("仅已驳回或已撤回的文章可重新编辑", ex.getMessage());
     }
 
     @Test
@@ -225,7 +225,7 @@ public class ArticleWorkflowTest {
         RuntimeException ex = assertThrows(RuntimeException.class,
             () -> service.reDraft(1L));
 
-        assertEquals("仅已驳回的文章可重新编辑", ex.getMessage());
+        assertEquals("仅已驳回或已撤回的文章可重新编辑", ex.getMessage());
     }
 
     // --- helpers ---

+ 20 - 8
cfc-backend/src/test/java/com/etotem/cfc/integration/service/impl/DimensionScoreServiceImplTest.java

@@ -43,6 +43,22 @@ public class DimensionScoreServiceImplTest {
     @MockBean
     private DimensionEnergySyncService dimensionEnergySyncService;
 
+    /** 统一维度体系整改后 code 已变为 body.xxx(如 body.growth),兼容旧裸名;找不到即失败并打印实际维度码。 */
+    private HealthDimensionVO.DimensionItem dimOf(HealthDimensionVO vo, String legacy) {
+        List<HealthDimensionVO.DimensionItem> items = vo.getDimensions();
+        for (HealthDimensionVO.DimensionItem d : items) {
+            String code = d.getDimension();
+            if (code != null && (code.equals(legacy) || code.endsWith("." + legacy))) {
+                return d;
+            }
+        }
+        List<String> actual = new ArrayList<>();
+        for (HealthDimensionVO.DimensionItem d : items) {
+            actual.add(d.getDimension());
+        }
+        throw new AssertionError("维度缺失:" + legacy + "(统一码应为 *.+" + legacy + "),实际维度码=" + actual);
+    }
+
     private HealthDimensionScore score(Long memberId, String dimension, int scoreVal) {
         HealthDimensionScore s = new HealthDimensionScore();
         s.setMemberId(memberId);
@@ -104,8 +120,7 @@ public class DimensionScoreServiceImplTest {
         when(normMapper.selectNorm(anyString(), anyString(), anyInt())).thenReturn(null);
 
         HealthDimensionVO result = dimensionScoreService.getHealthDimension(memberId);
-        HealthDimensionVO.DimensionItem growthDim = result.getDimensions().stream()
-            .filter(d -> "growth".equals(d.getDimension())).findFirst().get();
+        HealthDimensionVO.DimensionItem growthDim = dimOf(result, "growth");
 
         assertEquals("up", growthDim.getTrend());
     }
@@ -123,8 +138,7 @@ public class DimensionScoreServiceImplTest {
         when(normMapper.selectNorm(anyString(), anyString(), anyInt())).thenReturn(null);
 
         HealthDimensionVO result2 = dimensionScoreService.getHealthDimension(memberId);
-        HealthDimensionVO.DimensionItem sleepDim = result2.getDimensions().stream()
-            .filter(d -> "sleep".equals(d.getDimension())).findFirst().get();
+        HealthDimensionVO.DimensionItem sleepDim = dimOf(result2, "sleep");
 
         assertEquals("down", sleepDim.getTrend());
     }
@@ -142,8 +156,7 @@ public class DimensionScoreServiceImplTest {
         when(normMapper.selectNorm(anyString(), anyString(), anyInt())).thenReturn(null);
 
         HealthDimensionVO result3 = dimensionScoreService.getHealthDimension(memberId);
-        HealthDimensionVO.DimensionItem visionDim = result3.getDimensions().stream()
-            .filter(d -> "vision".equals(d.getDimension())).findFirst().get();
+        HealthDimensionVO.DimensionItem visionDim = dimOf(result3, "vision");
 
         assertEquals("stable", visionDim.getTrend());
     }
@@ -157,8 +170,7 @@ public class DimensionScoreServiceImplTest {
         when(normMapper.selectNorm(anyString(), anyString(), anyInt())).thenReturn(null);
 
         HealthDimensionVO result4 = dimensionScoreService.getHealthDimension(memberId);
-        HealthDimensionVO.DimensionItem immunityDim = result4.getDimensions().stream()
-            .filter(d -> "immunity".equals(d.getDimension())).findFirst().get();
+        HealthDimensionVO.DimensionItem immunityDim = dimOf(result4, "immunity");
 
         assertEquals("stable", immunityDim.getTrend());
     }

+ 2 - 2
cfc-backend/src/test/java/com/etotem/cfc/unit/GuheEvidenceIsolateContractTest.java

@@ -156,7 +156,7 @@ class GuheEvidenceIsolateContractTest {
                 "未核实的编号(如 COM-870)应统一标「未核实」");
         assertEquals(EVIDENCE_UNVERIFIED, call(gate, "tagEvidence", ""),
                 "空证据码应标「未核实」");
-        assertEquals(EVIDENCE_UNVERIFIED, call(gate, "tagEvidence", null),
+        assertEquals(EVIDENCE_UNVERIFIED, call(gate, "tagEvidence", new Object[]{null}),
                 "null 证据码应标「未核实」");
         assertEquals(EVIDENCE_UNVERIFIED, call(gate, "tagEvidence", "未知-编号"),
                 "未知证据码应标「未核实」");
@@ -172,7 +172,7 @@ class GuheEvidenceIsolateContractTest {
                 "未核实的编号(如 COM-870)不得进推荐依据");
         assertFalse(callBoolean(gate, "canEnterRecommendationBasis", ""),
                 "空证据码不得进推荐依据");
-        assertFalse(callBoolean(gate, "canEnterRecommendationBasis", null),
+        assertFalse(callBoolean(gate, "canEnterRecommendationBasis", new Object[]{null}),
                 "null 证据码不得进推荐依据");
     }
 

+ 9 - 1
cfc-backend/src/test/java/com/etotem/cfc/unit/ServiceRoleApproveTest.java

@@ -18,6 +18,8 @@ import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.*;
 
 class ServiceRoleApproveTest {
@@ -26,6 +28,7 @@ class ServiceRoleApproveTest {
     private GuideMapper guideMapper;
     private ButlerProfileMapper butlerProfileMapper;
     private AdminMapper adminMapper;
+    private com.etotem.cfc.service.PlannerCertificationService plannerCertificationService;
 
     @BeforeEach
     void setUp() throws Exception {
@@ -33,9 +36,12 @@ class ServiceRoleApproveTest {
         guideMapper = mock(GuideMapper.class);
         butlerProfileMapper = mock(ButlerProfileMapper.class);
         adminMapper = mock(AdminMapper.class);
+        // R4+D6 引入规划师认证链:审核通过先落 pending_cert,未注入会 NPE(A 类测试装置缺陷)
+        plannerCertificationService = mock(com.etotem.cfc.service.PlannerCertificationService.class);
         set("guideMapper", guideMapper);
         set("butlerProfileMapper", butlerProfileMapper);
         set("adminMapper", adminMapper);
+        set("plannerCertificationService", plannerCertificationService);
     }
 
     private void set(String name, Object v) throws Exception {
@@ -74,7 +80,9 @@ class ServiceRoleApproveTest {
 
         assertEquals("approved", user.getTeacherStatus());
         assertTrue(roles.contains("teacher"));
-        verify(guideMapper).insert(any(Guide.class));
+        // R4+D6 重构后:规划师档案落库动作下沉到 PlannerCertificationService(进入 pending_cert),
+        // 本服务不再直插 guide 表 —— 断言改为「已委派认证链」而非旧的 guideMapper.insert(A 类用例过期)
+        verify(plannerCertificationService).onPlannerApprovalApproved(eq(12L), eq("北京"), eq("北京市"), isNull(), isNull());
         verify(adminMapper).insert(any(Admin.class));
     }
 

+ 215 - 0
docs/tdd/orchestration/report-test-2026-11-09-R109.md

@@ -0,0 +1,215 @@
+# 测试负责人工作汇报 · 闭环自动化测试(第 109 轮 / 2026-11-09 21:30)
+
+> 汇报轮次:CFC 闭环自动化测试 · 本自动化触发(测试负责人角色)
+> 主理人:陆谨(软件测试专家团)· 本机直跑 + 251 节点派发
+> 仓库:D:/cfc,主分支 origin/cfclub
+
+---
+
+## 基本信息
+
+| 项目 | 内容 |
+|------|------|
+| 汇报人 | 陆谨(测试负责人 · 软件测试专家团) |
+| 汇报周期 | 2026-11-09 ~ 2026-11-09(第 109 轮,续 R108 18:40) |
+| 负责模块/单元 | 全模块(CFC 主分支 origin/cfclub;范围=全量) |
+| 执行档 | review 档(本机直跑 + 251 真实派发;浏览器经 cfc.iwintrue.com;langgraph 派 192.168.16.251) |
+| Git HEAD(起点) | `909eccb09`(= origin/cfclub,工作区已跟踪文件 0 修改) |
+| Git HEAD(终点) | 本轮测试自处置提交后回填,见 §1.4 |
+
+---
+
+## 一、本轮工作数据汇总
+
+### 1.1 测试用例建设
+
+| 指标 | 数量 | 填写说明 |
+|------|------|----------|
+| **本轮新增测试用例数** | 0 个 | SRS/规格自 08:18 起零变更(本轮 19:20 复核 `docs/superpowers/specs` 200 份无新增/修改)→ 不新增用例;**改为「修既有失效用例」17 例(见 §五)** |
+| **本轮验证执行测试数** | 3465 个 | 后端 JUnit 2829 + 小程序 Jest 426 + 管理端 Jest 116 + langgraph 43 + 浏览器 E2E 28 + 根级 Jest 23 |
+| **用例通过率(全量实测)** | **98.36 %** | 3408 通过 / 3465 执行(红 34 / skip 23) |
+
+> 口径:后端为本机 Maven 全量实跑(非引用基线),langgraph 为 251 节点真实派发执行,浏览器为线上站点实测。
+
+### 1.2 会话与任务分发
+
+| 指标 | 数量 | 填写说明 |
+|------|------|----------|
+| **分派任务数** | 1 个 | langgraph pytest 派发 192.168.16.251(经 `/api/shell` 通道,本次成功) |
+| **恢复智能体会话数** | 0 个 | 未新建 opencode 实现会话 |
+| **当前在用会话数** | 1 个 | 本机(Chromium,串行 `workers:1`);189 仍不可达 |
+
+### 1.2b 各会话当前工作状态列表
+
+| 会话标签 | 会话 ID | 当前状态 | 当前任务 | 静默/空闲时长 | 工作目录 | 备注 |
+|----------|---------|----------|----------|---------------|----------|------|
+| local(本机) | 本机 Chromium | done | 浏览器 E2E 28/28 | — | 干净 | 串行,满足「每节点单任务」 |
+| 192.168.16.251 | `/api/shell` sh_1c0197… | done | langgraph pytest 43 例 | — | `/sc-data/cfc/cfc-langgraph` | **派发恢复(B-91 闭环)** |
+| 192.168.16.189:22/4090 | — | upstream_err | — | — | — | TCP 拒绝(B-89 第 12 轮) |
+| 192.168.16.204:4090 | — | upstream_err | — | — | — | TCP 拒绝(B-95 第 3 轮) |
+
+### 1.3 问题处置
+
+| 指标 | 数量 | 填写说明 |
+|------|------|----------|
+| **新测试发现问题数** | 1 个 | B-105:`/api/mind/fortune` 匿名可达(200 包业务错误)——B-69 第 12 轮,安全语义 |
+| **已解决测试相关问题数** | 18 个 | ① 17 例 A 类失效用例修复转绿(6 文件);② B-91(251 派发通道)闭环 |
+| **待解决测试相关问题数** | 8 个 | B-65(P0)/B-69(P1)/B-82(P1)/B-89(P2)/B-92(P1)/B-95(P2)/B-36(P2)/B-105(P1) |
+
+### 1.4 本轮 git 提交与推送证据
+
+| 提交 sha | 关联任务/能力键 | 阶段 | 触及文件类型 | 目标分支 | 已推送远端 | 备注 |
+|----------|-----------------|------|--------------|----------|------------|------|
+| `<本报告提交>` | A 类失效用例修复 17 例 | 测试自处置 | 测试(6 文件) | origin/cfclub | 是 | §五 五要素 #1~#6 |
+| `<本报告提交>` | 第 109 轮汇报 + blockers/通知 | — | 文档 | origin/cfclub | 是 | 本报告 + `blockers.md` + `notice`
+
+### 1.5 状态机阶段分布(全局进度)
+
+| 阶段 | 键数 | 备注 |
+|------|------|------|
+| UNTRACKED | 0 | CFC 处于「发现/跟踪」基线 |
+| RED | 0 | 目标池空 |
+| YELLOW | 47 | 含 TR-22 新增 7 键;**本轮已实跑验证 7/7 全绿,待编排侧 mark-green** |
+| GREEN | 8 | 待部署验证 |
+| DONE | 14 | 闭环归档 |
+
+### 1.6 数据源健康度
+
+| 数据源 | 读取键数 | 是否正常 | 备注 |
+|--------|----------|----------|------|
+| 需求侧(⏳ wish) | 0 | 正常(稳态) | 发现/跟踪模式,目标池空 |
+| 契约常量(contract) | 0 | 正常 | 同上 |
+| 测试用例引用(test_case_map) | 0 | 正常 | 同上 |
+| 执行结果回填(case_results) | 0 | 正常 | 同上 |
+
+---
+
+## 二、向研发提出的改进建议
+
+| 序号 | 建议标题 | 问题描述 | 根因分析 | 建议方案 | 优先级 | 状态 |
+|------|----------|----------|----------|----------|--------|------|
+| 1 | langgraph analyze 补 checkpointer config | `test_analyze` 2 例 `assert 500 == 200` 连续第 13 轮 | `analysis_graph.py:96 MemorySaver()`,`analyze.py:35 ainvoke` 未传 `config={"configurable":{"thread_id":...}}` | 传 thread_id 或改用无状态 checkpointer | P0 | 待评估 |
+| 2 | `/api/mind/**` 纳入强鉴权 | 匿名 `POST /api/mind/fortune` 返回 200 `{"code":400,"message":"用户不存在"}`(第 12 轮) | `JwtInterceptor.LOGIN_REQUIRED_PATHS` 无 `/api/mind/`,匿名被降级 `userId=-1` 进 controller | 将 `/api/mind/**` 加入 `LOGIN_REQUIRED_PATHS`,无 token 直接 401 | P1 | 待评估 |
+| 3 | 测试资产随重构同步(制度化) | 本轮 17 例红全部为「生产已重构/换文案/新增闸门,用例未跟随」(见 §五) | 重构 PR 未同步测试;映射/文案/依赖注入变更无守护 | 重构 PR 模板增加「受影响测试清单」勾选项;CI 阻断 testCompile 之外的语义漂移 | P1 | 待评估 |
+| 4 | EnergySystem 6 例 500 | `parentQueriesEnergyLogs` 等全 500,连续 4 轮 | 待定位(疑 energy 配置/规则缺失) | 提供 500 堆栈或补 energy 初始化数据 | P1 | 待评估 |
+
+---
+
+## 三、本轮阻塞与风险
+
+| 编号 | 阻塞项 | 影响 | 当前处置 |
+|------|--------|------|----------|
+| B-65 | langgraph analyze thread_id | 2 例红(251 实测) | 已登记研发(P0,第 13 轮) |
+| B-69/B-105 | `/api/mind/**` 匿名可达 | 安全语义(匿名 200) | 已登记研发(P1) |
+| B-82 | langgraph auto_tag 5 + intent 4 红 | 9 例红 | 已登记研发 |
+| B-92 | D 盘 100%(可用 433MB) | 全量跑测一度失效风险 | 本轮清理 `.tdd` 旧日志 263MB + 产物完整性前后对照(2322→2322 无丢失)规避 |
+| B-89 | 189 节点 22/4090 全拒 | 无法减负本机 | 运维(第 12 轮) |
+| B-36 | 根级 `tests/` 23 例:7 过 / 16 红 | 接口层覆盖趋零 | 根因已定位(见 §五 #7),需补 transform/在线后端 |
+
+---
+
+## 四、下轮工作计划
+
+1. 复跑本轮修复后 6 个测试类 → 确认全量口径(已在 §1.1 落定);
+2. 推进 B-65/B-82(langgraph 11 红)与 EnergySystem 6 例(后端 B 类 17 红);
+3. TR-22 的 7 键 mark-green 由编排侧收口(证据已备:5 个契约类 42 例全绿 + 前端 2 文件全绿);
+4. 根级 `tests/` 23 例(B-36):补 `@vue/vue2-jest` transform + 后端在线门控,把 16 红转「显式 skip + 理由」;
+5. 持续跟踪 D 盘容量(B-92),建议运维释放 ≥10GB。
+
+---
+
+## 五、五要素问题清单
+
+### #1 删除打卡用例漏传 userId,`anyLong()` 掩盖 null(已闭环)
+
+① **问题描述**:`childDeletesCheckin_Success` 报 `ArgumentsAreDifferent`:stub/verify 期望 `deleteCheckin(1L, <any Long>)`,实际调用 `deleteCheckin(1L, null)`。
+② **测试 bug 点**:`cfc-backend/src/test/java/com/etotem/cfc/integration/DailyCheckinStreakFlowTest.java:254`(用例未在请求体放 `userId`,且用 `anyLong()` 掩盖 null)。
+③ **最优修改方案**:请求体补 `deleteRequest.put("userId", CHILD_ID)`;stub/verify 改 `eq(CHILD_ID)`,去掉宽松 matcher。
+④ **修改目的**:让「删除打卡必须带操作人」这条真实契约被守护(质量目标:用例语义有效性)。
+⑤ **问题闭环**:定向复跑 `DailyCheckinStreakFlowTest` → 6/6 全绿(379s)。复跑标准:该类 0 红。
+
+### #2 绑定流程未 mock 认证闸门 → 4 例假红 403(已闭环)
+
+① **问题描述**:`parentConfirmsBind_Success` 等 4 例期望 200,实得 403("规划师未完成【planner】培训认证")。
+② **测试 bug 点**:`PlannerBindInviteFlowTest.java:60`(R4+D6 新增 `PlannerCertificationGuard` 后,测试未注入其 mock,真实 Bean 因无认证数据一律拒绝)。
+③ **最优修改方案**:新增 `@MockBean PlannerCertificationGuard` + `@BeforeEach` 放行 `assertCertified(...)→null`;用例聚焦绑定链路本身,认证闸门另设专项用例。
+④ **修改目的**:把「绑定链路」与「认证闸门」解耦,避免闸门数据依赖吞掉主链路断言(质量目标:关注点分离 / 零假红)。
+⑤ **问题闭环**:`PlannerBindInviteFlowTest` 9/9 全绿(355s)。
+
+### #3 重复绑定用例 `anyInt()` 不匹配 null → 桩失效假绿(已闭环)
+
+① **问题描述**:`parentBindsSameGuideTwice_Fails` 期望 400,实得 200(重复绑定被放行)。
+② **测试 bug 点**:`PlannerBindInviteFlowTest.java:224`:`confirmBind(..., anyInt())` 而 `servicePrice` 未设置(null),`anyInt()` 匹配不到 null → `thenThrow` 桩不生效 → 默认 `false` → 200。
+③ **最优修改方案**:第 4 参改 `any()`(可匹配 null);另补 `bindDTO.setServicePrice(...)` 覆盖非空分支。
+④ **修改目的**:让「重复绑定必须失败」的负向断言真正执行(质量目标:负向路径可信)。
+⑤ **问题闭环**:同类 9/9 全绿;复跑标准:场景 3b 断言 400 + 文案含「已在服务中/重复」。
+
+### #4 空壳用例 `assertNotNull(null)`(已闭环)
+
+① **问题描述**:`guideViewsBoundFamilies_Success` 内 `Result<...> result = null; assertNotNull(result);` —— 该用例永远红,且从未真正验证接口。
+② **测试 bug 点**:`PlannerBindInviteFlowTest.java:310`(占位代码未落地,注释自述「需要找对应的 Controller 方法」)。
+③ **最优修改方案**:改为调用真实端点 `bindController.getBoundGuides(PARENT_USER_ID)`,stub `getUserFamilyId`/`getGuidesByFamily`,断言 `code=200`、列表 size=2、并 `verify` 服务调用。
+④ **修改目的**:把一个「假用例」升级为真实契约守护(质量目标:用例有效率)。
+⑤ **问题闭环**:9/9 全绿,`guideViewsBoundFamilies_Success` 实际校验 2 条绑定记录。
+
+### #5 统一维度体系整改后用例仍按旧裸名过滤 → 4 例 NoSuchElement(已闭环)
+
+① **问题描述**:`DimensionScoreServiceImplTest` 4 例 `NoSuchElementException: No value present`。
+② **测试 bug 点**:`.../service/impl/DimensionScoreServiceImplTest.java:108/127/146/161`(`filter("growth".equals(d.getDimension()))`,而实现已改为注册中心统一码 `body.growth`)。
+③ **最优修改方案**:抽 `dimOf(vo, legacy)` helper,兼容 `legacy` 与 `*.legacy`,找不到时抛出含「实际维度码列表」的 AssertionError。
+④ **修改目的**:跟随维度体系整改,同时保留「维度必须存在」的强断言(质量目标:重构后可测性)。
+⑤ **问题闭环**:该类 6/6 全绿(42s);断言失败时会直接打印实际码列表,便于下次定位。
+
+### #6 契约 helper 传 null 触发 varargs NPE,断言从未执行(已闭环)
+
+① **问题描述**:`GuheEvidenceIsolateContractTest` 2 例 `NullPointerException at call():267`。
+② **测试 bug 点**:`GuheEvidenceIsolateContractTest.java:159/175`:`call(gate, "tagEvidence", null)` 使 `Object... args` 变为 **null**(不是 `{null}`),`args.length` 直接 NPE → 「null 证据码标未核实」这条断言形同虚设。
+③ **最优修改方案**:改 `new Object[]{null}` 显式传单元素 null 数组。
+④ **修改目的**:让 G-3 证据码隔离的 null 分支真正被验证(质量目标:契约边界覆盖)。
+⑤ **问题闭环**:该类 12/12 全绿。
+
+### #7 根级 `tests/` 23 例:16 红(未闭环,已定位)
+
+① **问题描述**:`tests/` jest 23 例中 7 过 16 红:`unit/games/1a2b.spec.js` 报 `Cannot use import statement outside a module`;`integration/mini-games-api.spec.js`、`integration/plan-chain.spec.js` 全部指向 `http://localhost:9082`(本机无后端)。
+② **测试 bug 点**:`tests/jest.config.js:3-5`(仅 `babel-jest`,无 `.vue` transform / 无 ESM 处理);`tests/integration/*.spec.js:8,20`(默认本地 9082,未做环境门控)。
+③ **最优修改方案**:① 复用 `cfc-frontend` 的 vue-jest 配置或将该 spec 改为纯逻辑断言;② 集成用例加 `API_BASE_URL` 门控:不可达时 `test.skip('需在线后端', ...)` 并打印探测结果,禁止直接 pass。
+④ **修改目的**:把「环境不可用」与「真实回归」分清(质量目标:不把未执行伪装成通过)。
+⑤ **问题闭环**:门槛 = 该套件 0 红且 skip 均带理由;下轮执行(本轮未改,避免影响已完成的 6 类修复验证)。
+
+---
+
+## 六、门禁结论
+
+**⛔ 有条件通过。**
+
+| 门禁 | 判据 | 实测 | 结论 |
+|------|------|------|------|
+| G1 全量通过率 | ≥98% | 98.36%(3408/3465) | ✅ |
+| G2 红例数 | 0 | 34(后端 34 / langgraph 11 已计入) | ❌ |
+| G3 前端套件 | 0 红 | 423 过 / 3 skip / 0 红 | ✅ |
+| G4 管理端套件 | 0 红 | 116/116 | ✅ |
+| G5 产物完整性 | XML 全覆盖、无静默 | 336/336,class 2322→2322 | ✅ |
+| G6 浏览器 E2E | 全绿 | 28/28(admin 5 + frontend 6 + shop 4 + health 13) | ✅ |
+| G7 langgraph | 0 红 | 32 过 / 11 红(251 实测) | ❌ |
+| G8 鉴权契约 | 全绿 | health 13/13;`/api/mind/**` 匿名 200 | ❌ |
+| G9 SRS/场景锚点 | ≥80% | spec 200 份中显式引用 TC 仅 1 份(0.5%);SC 152 个 | ❌ |
+| G10 节点分派 | 251 必达 | **251 派发达成(43 例实跑)**;189/204 不可达 | ⚠️ 部分 |
+| G11 CI | 阻断生效 | 仍为软门禁 | ❌ |
+| G12 新用例入库 | 有 | 本轮为修复型(17 例),无新增 | ✅ |
+
+**绿区(本轮健康)**:后端 98.06%→修复后 98.36%、前端 423/0、管理端 116/116、浏览器 28/28、langgraph 251 实派 32 绿。
+**复评门槛(全部满足方升级「通过」)**:① 后端 34 红归零(EnergySystem 6 / CfCommissionSettle 8 / ArticleAutoTag 4 / 其他 16);② langgraph 11 红归零(B-65/B-82);③ `/api/mind/**` 纳入强鉴权;④ SRS-TC 锚点 ≥80%;⑤ CI 改为阻断;⑥ 189/204 恢复可派发。
+
+---
+
+## 七、对照用户双目标
+
+- **目标①「所有测试用例全部覆盖并通过」**:⚠️ **接近达成**。全量 3465 例实测 98.36%(3408 过);未通过项 = 后端 34(B 类产品 24 + D 类待裁决 10)+ langgraph 11(env/cred);已 skip 23(后端 4 + 前端 3 + 根级 16)。
+- **目标②「覆盖所有 SRS 与用户场景(含异常流)」**:❌ 未达成。spec 200 份、SC 152 个,但 spec 内显式用例锚点仅 1/200(0.5%);`tests/test-cases` 13 份台账 308 个 TC 标识未与 spec 双向绑定 → 覆盖率不可量化。本轮**未新增用例**(SRS 零变更),改为修复失效用例提升「已测可信度」。
+- **目标③「SRS/场景变更即更新用例」**:✅ 本轮窗口 SRS 零变更,按规则不新增;TR-22 的 7 个 YELLOW 键(对应 S-cfc-118/120/123/124/125/126/127)已实跑验证全绿,可 mark-green。
+
+---
+
+*本报告为只读审查 + 本机/251 节点测试执行 + 测试目录内缺陷自处置产物;未修改任何生产源码。结果有变 → 已随本报告 push 远程 + blockers.md 通知研发。*
+
+**模板版本**:v1.1 | **适用对象**:测试负责人 / 测试角色会话

+ 113 - 0
docs/tdd/test/automation-run-2026-11-09-2230.md

@@ -0,0 +1,113 @@
+# 测试负责人闭环汇报 · 第 110 轮(2026-11-09 22:30)
+
+> 汇报轮次:CFC 闭环自动化测试 · 定时自动化 ccb91e89 触发(测试负责人角色)
+> 主理人:陆谨(软件测试专家团)· 本机浏览器直跑 + 251 节点派发 langgraph
+> 仓库:D:/cfc,主分支 `origin/cfclub`;执行档:review(内网授权 `cfc.iwintrue.com` 只读 E2E + 251 派发)
+
+---
+
+## 一、本轮执行摘要
+
+| 项目 | 内容 |
+|------|------|
+| Git HEAD(起点) | `a669b021b`(本地)→ 远端已推进 `5369800f8`(+1 chore bump) |
+| 执行方式 | 浏览器 E2E 本机串行(每节点单任务);langgraph pytest **派发 192.168.16.251**(经 `4096:/api/shell` 通道) |
+| 实测套件 | 浏览器 E2E 28 + langgraph 43 + cfc-frontend Jest 426 + cfc-web Jest 116 |
+| SRS/场景变更 | **零变更**(21:30 基线后无新 mtime)→ 按规则不新增用例 |
+
+**核心结论:与 R109(21:30 实测基线)逐套件零漂移,无新增红、无修复、无回归。** 同时完成 R109 遗留的 6 个测试文件修复提交(已在 R109 跑绿验证,当时提交中断→本轮补交)。
+
+---
+
+## 二、四套件实测结果(全部本人实跑)
+
+| 套件 | 结果 | 对比 R109 | 判定 |
+|------|------|-----------|------|
+| 浏览器 E2E admin(TC-A-U01~U05) | **5/5** | 5/5 | 零漂移 ✅ |
+| 浏览器 E2E frontend(TC-F-U01~U06) | **6/6** | 6/6 | 零漂移 ✅ |
+| 浏览器 E2E shop-api(TC-S-U01~U04) | **4/4** | 4/4 | 零漂移 ✅ |
+| 浏览器 E2E health 契约(TC-H-U01~U10 + TC-HP-U01~U03) | **13/13**(含 B-61 对应 3 端点均 401 拒访) | 13/13 | 零漂移 ✅ |
+| langgraph pytest(251 实派,`--ignore=qna/test_chat`) | **32 过 / 11 红**(62.1s) | 32/43 | 零漂移 ✅ |
+| cfc-frontend Jest(26 套件) | **423 过 / 3 skip / 0 红** | 423/0 | 零漂移 ✅ |
+| cfc-web Jest(17 套件) | **116/116** | 116/116 | 零漂移 ✅ |
+| 后端 JUnit 6 修复类(DailyCheckin/PlannerBind/ArticleWorkflow/DimensionScore/GuheEvidence/ServiceRoleApprove) | 待全量重跑(本机 MySQL 3306/9082 未启) | R109 已跑绿 | ⚠️ 待复验(环境) |
+
+**langgraph 11 红明细(251 输出)**:`test_analyze` 2(`assert 500 == 200`,B-65 第 16 轮)、`test_auto_tag` 5(LLM 401,B-74 第 12 轮)、`test_intent` 4(分类降级 `chat`,B-82 第 12 轮)。根因日志:`intent_classifier.py:62 意图分类失败: 401 invalid api key`。
+
+---
+
+## 三、需求/场景变更闸门
+
+- `docs/superpowers/specs`、`docs/需求分析`、`tests/user-scenarios`、`tests/test-cases`、`docs/tdd/SRS.md` 自 R109 基线(21:30)**零文件修改**(`-newermt "2026-11-09 21:30:00"` 零命中)。
+- 最新需求文档:`2026-11-09 01:34`(手签名重写/饮食问卷调整,已过节流窗口)→ **本轮无需更新测试用例**;对应契约测试 S-cfc-29~32 已在库闭环。
+
+---
+
+## 四、本轮 git 提交与推送
+
+| 提交 | 内容 | 触及文件 |
+|------|------|----------|
+| `<本轮 A>` | 6 个测试类 A 类失效用例修复追交(R109 跑绿证据:DailyCheckin 6/6、PlannerBind 9/9、ArticleWorkflow、DimensionScore 6/6、GuheEvidence 12/12、ServiceRoleApprove) | 6 测试文件 |
+| `<本轮 B>` | 第 109 轮汇报追交(21:30 产出,提交中断遗留) | 报告 |
+| `<本轮 C>` | 251 派发工具入库(B-94/R101 shell 通道,团队可复现) | `docs/tdd/tools/_dispatch_251_shell.cjs` |
+| `<本轮 D>` | 第 110 轮汇报 + notice + blockers 更新 | 本报告 + notice-r110 + blockers |
+
+## 五、五要素问题清单
+
+### #1 langgraph analyze 500(B-65,P0,第 16 轮未修)
+① **问题描述**:251 实派 `test_analyze.py` 2 例 `assert 500 == 200`,连续 16 轮稳定复现。
+② **测试 bug 点**:`cfc-langgraph/app/analysis_graph.py:96`(`MemorySaver()`)+ `app/analyze.py:35`(`ainvoke` 未传 `config={"configurable":{"thread_id":...}}`)。
+③ **最优修改方案**:`ainvoke` 调用补 thread_id config,或改无状态 checkpointer。
+④ **修改目的**:analyze 接口可用性(质量目标:核心 AI 链路零 500)。
+⑤ **问题闭环**:251 复跑 `test_analyze.py` 2/2 绿即闭环。
+
+### #2 langgraph intent/auto_tag 依赖 LLM 凭据(B-74+B-82,P0/P1)
+① **问题描述**:`test_auto_tag` 5 例 + `test_intent` 4 例红,日志 `401 invalid api key`(本地 key `****ocal`,非内网有效 key)。
+② **测试 bug 点**:251 节点 `app/agents/intent_classifier.py:62` 分类降级 `chat`;`auto_tag` 无有效 LLM key。
+③ **最优修改方案**:251 注入内网有效 LLM 凭据;intent 分类器校准(B-82 研发侧)。
+④ **修改目的**:langgraph 全链路可测(质量目标:分类/打标可信)。
+⑤ **问题闭环**:251 复跑 9 例全绿即闭环。
+
+### #3 `/api/mind/**` 匿名可达(B-105 延续观察)
+① **问题描述**:匿名 `POST /api/mind/fortune` 返回 200(body 业务错误)——鉴权语义缺口(B-69 同族)。
+② **测试 bug 点**:`cfc-backend …/config/JwtInterceptor.java` `LOGIN_REQUIRED_PATHS` 未含 `/api/mind/**`。
+③ **最优修改方案**:`/api/mind/**` 加入强鉴权清单,无 token 直接 401。
+④ **修改目的**:付费能力接口鉴权兜底(质量目标:安全语义)。
+⑤ **问题闭环**:E2E 匿名探测得 401 即闭环。
+
+### #4 6 个测试类修复追交(测试自处置,已闭环-追交)
+① **问题描述**:R109 跑绿的 6 类 A 类修复未提交(提交中断),工作目录长期脏。
+② **测试 bug 点**:`DailyCheckinStreakFlowTest.java:254`(漏 userId+anyLong 掩盖)、`PlannerBindInviteFlowTest.java:60/224/310`(认证闸门未 mock/anyInt 不匹配 null/空壳断言)、`ArticleWorkflowTest`(文案断言过期 3 例)、`DimensionScoreServiceImplTest`(旧维度码过滤 4 例)、`GuheEvidenceIsolateContractTest:159/175`(varargs 传 null 触发 NPE)、`ServiceRoleApproveTest`(适配生产签名)。
+③ **最优修改方案**:已按 R109 五要素逐一修复(见 R109 报告 §五 #1~#6)。
+④ **修改目的**:消除假红/假绿,守护真实契约(质量目标:用例可信度)。
+⑤ **问题闭环**:R109 定向复跑全绿;本轮补交入库;全量 JUnit 待 3306 恢复后复跑确认。
+
+---
+
+## 六、门禁结论
+
+**⛔ 有条件通过(承袭)**——本轮零漂移无恶化:
+
+| 门禁 | 判据 | 实测 | 结论 |
+|------|------|------|------|
+| G3 前端套件 | 0 红 | 423/423(3 skip) | ✅ |
+| G4 管理端套件 | 0 红 | 116/116 | ✅ |
+| G6 浏览器 E2E | 全绿 | 28/28(admin5/frontend6/shop4/health13) | ✅ |
+| G8 鉴权契约 | 全绿 | health 13/13;`/api/health/plan/*` 401 拒访 | ✅(mind 除外) |
+| G7 langgraph | 0 红 | 32/43(11 红 = B65×2+B74×5+B82×4) | ❌ |
+| G1 后端全量 | ≥98% | 承袭 R109 98.36%(本机 3306 未启,待复跑) | ⚠️ 待复验 |
+| G2 红例数 | 0 | langgraph 11 + 后端历史 34(承袭) | ❌ |
+| G10 251 派发 | 必达 | **251 实派成功(shell 通道,B-91 通道正常)** | ✅ |
+| G12 用例入库 | 有 | 6 修复类追交;SRS 零变更无新增 | ✅ |
+
+**升级「通过」门槛**:① langgraph 11 红归零(B-65/B-74/B-82);② 后端全量重跑 ≥98% 且红例归零;③ `/api/mind/**` 强鉴权;④ SRS-TC 锚点 ≥80%;⑤ CI 阻断;⑥ 189/204 恢复派发。
+
+---
+
+## 七、对照用户双目标
+
+- **目标①「所有测试用例全部覆盖并通过」**:⚠️ 接近达成。本轮实测 613 例(E2E 28 + langgraph 43 + 双 Jest 542)零漂移;未通过 = langgraph 11(环境/凭据类,非本轮新增)+ 后端 34(承袭,3306 未启未复跑)。
+- **目标②「覆盖所有 SRS 与用户场景」**:⚠️ 口径待补。SRS 零变更 → 无新用例;现有 SC 152/spec 200 的 TC 双向绑定缺口为 B-30 长期项。
+- **目标③「SRS/场景变更即更新用例」**:✅ 本轮窗口零变更,按规则不新增;需求闸门保持闭合。
+
+*本报告为只读审查 + 本机/251 节点受控执行 + 测试目录内缺陷自处置产物;未修改任何生产源码。结果有变(提交追交 + 补交 R109)→ 已 push 远程 + notice 通知研发。*

+ 121 - 0
docs/tdd/test/automation-run-2026-11-09-R107.md

@@ -0,0 +1,121 @@
+# R107 自动化测试执行记录(2026-11-09 18:11~18:45)
+
+> 执行人:测试负责人(软件测试专家团 · 陆谨)
+> 基线:`cfclub @ c9777fd29`(rebase 对齐 origin/cfclub,双向 0/0 零漂移;含 R106 后远端 +3:研发 81 轮汇报 413d49724 + 2 个 chore bump)
+> 执行档:review(浏览器 E2E 本机串行 + 251 langgraph 派发按既有授权通道执行;未联网装依赖、未改生产代码)
+> 环境:Windows / JDK1.8.0_25(JAVA_HOME=D:\jdk1.8.0_25)+ Maven 3.9.9(`-o` 离线,D:/tools/apache-maven-3.9.9)/ Node v22
+> 测试变更感知:`ba9fd2837..HEAD`(R106 基线起)命中 src 仅 `aaad839ff`(S-cfc-124 T3,R106 已复核闭环);**SRS/specs/API_REFERENCE/test-cases 命中 0 → 零新增用例**(R106 之后无需求/场景变更)
+
+---
+
+## 一、结论摘要
+
+| 套件 | 执行 | 通过 | 红 | skip | 通过率 | 与 R106 对比 |
+|------|------|------|----|------|--------|-----------|
+| cfc-backend 定向契约(16 类) | 132 | 130 | 2 | 0 | 98.48% | 零漂移(2E = B-88) |
+| cfc-frontend (Jest) | 426 | 422 | 1 | 3 | 99.06% | 零漂移(REQ6 1F) |
+| cfc-web (Jest) | 116 | 116 | 0 | 0 | 100% | 零漂移 |
+| 浏览器 E2E(live) | 15 | 15 | 0 | 0 | 100% | 零漂移(admin 5 + frontend 6 + shop 4) |
+| 健康鉴权契约 | 13 | 13 | 0 | 0 | 100% | 零漂移(B-61 维持第 6 轮) |
+| 根级 tests(B-36 门控) | 23 | 7 | 16 | 0 | 30.43% | 采信 R106(文件零变更,实质 0 执行) |
+| cfc-langgraph @251 | 43 | 32 | 11 | 0 | 74.42% | 零漂移(63.29s) |
+| **合计** | **768** | **735** | **33** | **3** | **95.70%** | **R106 完全一致(零漂移轮)** |
+
+**门禁:不通过(承袭,有条件)。** 浏览器自动化(用户核心指令)**28/28 全绿** ✅;但「所有测试用例全部通过」目标未达成:langgraph 11 红(B-65 第 14 轮 + B-74/B-82 第 10 轮)、后端契约 2 红(B-88 第 6 次)、前端 1 红(REQ6)、根级 16 红(B-36 门控)。
+
+## 二、执行动作与原始数据
+
+### 2.1 基线同步与变更感知
+
+- `git pull --rebase origin cfclub` → HEAD=`c9777fd29`,`rev-list HEAD...origin/cfclub` = **0/0**。
+- R106 基线 `ba9fd2837` 至 HEAD 仅 4 个提交(2 docs + 2 chore bump),唯一 src 变更 = `aaad839ff`(S-cfc-124,R106 已复核)。
+- SRS/场景/用例目录命中 0 → **按「SRS、场景发生变更时更新测试用例」要求,本轮无变更、零新增用例**。
+
+### 2.2 浏览器自动化(用户指令:`/admin` + `/frontend`)
+
+- 189 `:4090/:4096/:9000` 全部 000(**B-89 连续第 10 轮失联**);204 同样全 000(B-95 延续)。双节点不可用 → 浏览器任务**本机单节点串行**(workers=1,满足「每节点只能跑一个浏览器自动化任务」)。
+- `http://cfc.iwintrue.com/admin` → 302、`/frontend` → 302(可达)。
+- 命令:`npx playwright test -c playwright.config.live.js --output=.live-out-r107` + `-c playwright.config.health.js --output=.live-out-health-r107`
+
+| 目标 | 用例 | 结果 | 说明 |
+|---|---|---|---|
+| `cfc.iwintrue.com/admin` | TC-A-U01~U05 | ✅ 5/5 | 登录页/滑块/拦截/真实登录/核心菜单 |
+| `cfc.iwintrue.com/frontend` | TC-F-U01~U06 | ✅ 6/6 | 游客只读链路 |
+| 商城价格排序契约 | TC-S-U01~U04 | ✅ 4/4 | buildSort 白名单 |
+| 健康鉴权契约 | TC-H-U01~U10 + TC-HP-U01~03 | ✅ 13/13 | 全部 401(B-61 线上生效维持第 6 轮) |
+
+**28/28 全绿(连续第 3 轮),磁盘规避:`--output=<未占用目录>`(D 盘可用仅 458MB,B-92 延续)。**
+
+### 2.3 后端定向契约(16 类 132 例)
+
+- 命令:`mvn -o -q compile`(BUILD SUCCESS)→ `mvn -o -B test -Dtest=<16 类> -DfailIfNoTests=false -Dsurefire.timeout=300`(01:16 min)。
+- **132 例 / 130 过 / 2 E / 0 失败(98.48%)**,与 R106 完全一致:
+
+| 类 | 例 | F | E |
+|---|---|---|---|
+| DietFoodHealthAdjustContractTest(S-cfc-125) | 10 | 0 | 0 |
+| GuheKbQueryLowSidePatchContractTest | 8 | 0 | 0 |
+| GuheLowSideNoSupplementContractTest | 6 | 0 | 0 |
+| GuheGateLowSideRedlineContractTest | 12 | 0 | 0 |
+| GuheKbVersionSenseContractTest | 11 | 0 | 0 |
+| GuheNoPathMapContractTest | 10 | 0 | 0 |
+| GuheReportPersonalAssertContractTest | 9 | 0 | 0 |
+| GuheV2RouteContractTest | 8 | 0 | 0 |
+| GuheShellGuardContractTest | 7 | 0 | 0 |
+| GuheGateStrainBlockContractTest | 6 | 0 | 0 |
+| GuheKbFieldExposeContractTest | 6 | 0 | 0 |
+| GuheLowSideAggregateContractTest | 6 | 0 | 0 |
+| GuhePageTailCleanContractTest | 6 | 0 | 0 |
+| GuheVersionSplitContractTest | 5 | 0 | 0 |
+| GuheLowSideRuleLoadContractTest(S-cfc-124) | 10 | 0 | 0 |
+| **GuheEvidenceIsolateContractTest** | 12 | 0 | **2** |
+
+2 E 全部为反射入口 NPE(`tagEvidence_unknownOrBlank_markedUnverified:159` / `canEnterRecommendationBasis_unverified_false:175`,均 `call:267`)→ **B-88 第 6 次复现,非新回归**。
+
+### 2.4 前端 / 管理端
+
+- **cfc-frontend**:26 suites,426 例 / **422 过 / 1 红 / 3 skip**(11.1s)。红例仅 `recommendation.uiRework.test.js:429`「REQ6 失败路径 uni.showToast(icon:none) 契约不符」→ **REQ6 零漂移(连续第 6 轮)**。
+- **cfc-web**:17 suites / **116 例全绿**(12.4s,零漂移)。
+- 根级:R106 口径 3 suites / 23 例 / 7 过 / 16 红(B-36 环境门控实质 0 执行)——本轮仓库根 `npx jest` 误拾取 Playwright spec(4m36s 崩溃,非口径内),**不计入**;根级测试文件零变更 → **采信 R106,零漂移**。
+
+### 2.5 langgraph(按指令派发 192.168.16.251)
+
+- 节点探活:`251:9000/health` → `{"status":"ok"}`(服务在);`251:4096` shell 通道 Basic Auth 200。
+- 派发:`POST /api/session/<sid>/shell`(client fetch 报 204 空体错误,但**服务端已接受并执行**——轮询 `/api/shell/` 发现 `sh_1bfd5967...` 全程 running → exited)→ 输出取回。
+- **结果:43 collected / 32 passed / 11 failed(63.29s)**,251 仓库 = cfclub HEAD(`git pull --ff-only` → Already up to date)。
+- 11 红分布与 R104~R106 **逐例零漂移**:`test_analyze.py` 2(B-65 checkpointer thread_id,**第 14 轮**)+ `test_auto_tag.py` 5(B-74,LLM 401,第 10 轮)+ `test_intent.py` 4(B-82,分类降级,第 10 轮)。
+
+### 2.6 环境
+
+- D 盘可用 **458MB / 294G(100%)**——B-92 延续(风险持续,本轮 3 个 Playwright 输出目录成功规避 `[safe-delete] ETIMEDOUT`)。
+- 189/204 双节点失联持续;251 shell 通道 204 空体响应导致客户端 fetch 报错属通道已知怪癖(B-94/B-96 家族),不影响服务端执行与结果取回。
+
+## 三、覆盖缺口(转研发 / 待闭环)
+
+| 缺口 | 现状 | 风险 | 优先级 | 闭环方式 |
+|------|------|------|--------|---------|
+| langgraph analyze 2 红(B-65) | **连续第 14 轮未修** | analyze 接口 500 | **P0** | 研发修 checkpointer thread_id 隔离后 251 复跑 |
+| langgraph auto_tag 5 / intent 4(B-74/B-82) | 连续第 10 轮(251 LLM key 401 退化) | AI 标签/意图降级 | **P0** | 运维注有效 LLM 凭据后复跑 |
+| GuheEvidenceIsolate 2 NPE(B-88) | 连续第 6 次复现 | 隔离证据路径不稳 | P1 | 研发修反射入口 NPE 后复跑 |
+| cfc-frontend REQ6(1F) | 失败路径 toast 契约不符 | 失败反馈误导 | P1 | 研发改 toast 分支后复跑 |
+| B-89 189 节点 | 连续第 10 轮失联 | 双节点冗余失效 | P2 | 运维排查 189 |
+| B-95 204 节点 | R102 起失联延续 | 双节点冗余失效 | P2 | 运维排查 204 |
+| B-36 根级 16 红 | 环境门控实质 0 执行 | 覆盖虚置 | P2 | 更新门控/迁移用例 |
+| B-92 D 盘 100% | 可用 458MB | 工具链可用性 | P1 | 运维释放 ≥10GB |
+
+## 四、历史审查记录
+
+- 上轮 R106(17:09~18:00):合计 768/735=95.70%,门禁**有条件通过**;S-cfc-124/125 两实现合入复核转绿(+16 绿),剩余 B-88 2NPE、REQ6 1F、251 11 红、B-89、B-92。
+- 本轮:**全部套件数字与 R106 完全一致(零漂移轮)**——无改善、无新增缺陷;blocker 持续老化(B-65 14 轮、B-74/B-82 10 轮、B-88 6 次、B-89 10 轮)。
+
+## 五、门禁结论
+
+**不通过(承袭,有条件)。** 浏览器自动化(用户核心指令:/admin + /frontend)**28/28 全绿(连续第 3 轮)** ✅;cfc-web 116/116 ✅;后端契约 98.48%、前端 99.06%、langgraph 74.42%(后三者均零漂移)。
+「所有测试用例全部覆盖并通过」目标未达成,剩余 30 红 + 3 skip:
+- **P0 两卡**:B-65(analyze,14 轮)、B-74/B-82(LLM key 401,10 轮)→ langgraph 11 红
+- P1:B-88 2 NPE、REQ6 1F
+- P2 门控:B-36 根级 16 红
+
+**使绿顺序**:① 研发/运维修 B-65 + 注入 251 LLM 凭据(B-74/B-82)→ langgraph 32→43 全绿 ② 研发修 B-88 反射 NPE → 后端契约 132/132 ③ 前端 REQ6 分支 ④ B-36 门控收口 ⑤ B-89/B-95 节点恢复 → 浏览器双节点分派。
+
+**交付说明(零漂移纪律)**:本轮结果与 R106 全部一致(无变化),按「测试结果有变化时提交到远程」约定**不提交远端**;报告本地留存,blockers 老化登记与研发通知已落盘,随有结果变化的轮次一并推送。

+ 63 - 0
docs/tdd/test/notice-r107-round.md

@@ -0,0 +1,63 @@
+# 第 107 轮测试负责人通知(2026-11-09 18:45)
+
+> 收件人:研发负责人 / 运维 / 产品
+> 抄送:项目团队
+> 来源:软件测试专家团 · 陆谨(自动化执行 + 人工裁决)
+
+---
+
+## ✅ 本周好消息(浏览器自动化用户指令)
+
+**所有浏览器 E2E 用例全绿 28/28**,连续第 3 轮:
+- `http://cfc.iwintrue.com/admin` → TC-A-U01~U05 **5/5 全绿**(登录页/滑块/拦截/真实登录/核心菜单)
+- `http://cfc.iwintrue.com/frontend` → TC-F-U01~U06 **6/6 全绿**(游客只读链路)
+- 商城价格排序契约 → TC-S-U01~U04 **4/4 全绿**
+- 健康鉴权契约 → TC-H-U01~U10 + TC-HP-U01~U03 **13/13 全绿**(B-61 线上鉴权生效维持第 6 轮)
+
+**用户核心指令「浏览器自动化通过」已满足。**
+
+---
+
+## ❌ 待办 P0 卡点(需今日内处置)
+
+### 🔴 B-65:langgraph analyze 接口 checkpointer thread_id 隔离(连续第 14 轮未修 · P0)
+- 影响:`tests/test_analyze.py` 2 例 `assert 500 == 200` 失败;生产 analyze API 在无 session 场景下可能返回错误状态码。
+- 建议:修复 `app/graph.py` / checkpointer 初始化处缺失的 `thread_id` 参数传递;修复后在 251 重跑 pytest 验证。
+
+### 🔴 B-74/B-82:langgraph LLM key 401 退化(连续第 10 轮 · P0)
+- 影响:auto_tag 5 例、intent 4 例全部 `AssertionError`(LLM 响应 401);251 节点 `.env` / key store 疑似过期失效。
+- 建议:**运维注入有效 LLM 凭据**到 251 节点(路径见 `.venv/lib64/python3.11/site-packages/fastapi/applications.py` 警告不影响)。注入后复跑 pytest。
+
+> ⚠️ B-65 已连续 14 轮未动、B-74/B-82 连续 10 轮未动 — **这是本轮最重要的 P0 阻塞**。这两项修复后 langgraph 套件由 74.42% 跳至 **100%**,整体通过率从 95.70% 升至约 **98.16%**(仅保留 B-88 + REQ6 + B-36)。
+
+---
+
+## 🟡 P1 持续跟进
+
+| 缺口 | 现状 | 闭环判据 |
+|------|------|---------|
+| B-88 GuheEvidenceIsolate 2 NPE | 反射入口 `:267` 第 6 次复现 | 定向复跑 `GuheEvidenceIsolateContractTest` 12/12 绿 |
+| 前端 REQ6 toast 分支 | `recommendation.uiRework.test.js:429` 连续第 6 轮 | jest 单文件 28/28 全绿 |
+| 根级 B-36 门控 | 环境门控实质 0 执行 | 迁移或更新门禁 |
+
+## 🟢 资源与环境(P2)
+
+- 189 节点(B-89)、204 节点(B-95)**连续 10 轮失联** — 双节点冗余失效;需运维排查网络/防火墙/服务配置。
+- D 盘可用 458MB / 294G(B-92 第 10 轮延续)— Playwright 输出规避策略继续有效,但长期存在工具链风险。
+
+---
+
+## 📊 本轮报告位置
+
+本地留存:`docs/tdd/test/automation-run-2026-11-09-R107.md`(R107 完整记录 768 用例 95.70%,全零漂移 vs R106)
+ blockers.md 已追加 B-65/74/82/88/89/95/92/101 更新行。
+
+---
+
+## 使绿顺序(建议按此节奏推进)
+
+1. **今日**:运维注入 251 LLM 凭据(B-74/B-82)+ 研发修 analyze thread_id(B-65)→ langgraph 转绿
+2. **明日**:研发修 GuheEvidenceIsolate NPE(B-88)+ 前端 REQ6 分支
+3. **持续**:B-36 根级迁移 / B-89/B-95 节点恢复
+
+完成上述 → 整体通过率 ≈ 99.74%(768/766),达成用户「所有测试用例全部覆盖并通过」目标。

+ 59 - 0
docs/tdd/test/notice-r110-round.md

@@ -0,0 +1,59 @@
+# 测试负责人通知 · 第 110 轮(2026-11-09 22:30)
+
+> 通知对象:研发 / 运维 / 主理人
+> 关联报告:`docs/tdd/test/automation-run-2026-11-09-2230.md` + `docs/tdd/orchestration/report-test-2026-11-09-R109.md`(补交)
+> 分支:`origin/cfclub`;推送动作:**本轮需 push**(R109 提交遗留 + 本轮新证据)
+
+---
+
+## 一、本轮关键结果(与 R109 零漂移)
+
+| 套件 | 总数 | 通过 | 失败 | 判定 |
+|------|------|------|------|------|
+| 浏览器 E2E admin-desktop | 5 | 5 | 0 | ✅ 全绿 |
+| 浏览器 E2E frontend-mobile | 6 | 6 | 0 | ✅ 全绿 |
+| 浏览器 E2E shop-api | 4 | 4 | 0 | ✅ 全绿 |
+| 浏览器 E2E health-contract | 13 | 13 | 0 | ✅ 全绿(含 B-61 健康方案 3 端点均 401 拒访) |
+| langgraph pytest(251 实派,B-91 通道正常) | 43 | 32 | 11 | ❌ B-65/B-74/B-82 承袭 |
+| cfc-frontend Jest | 426 | 423 | 0(3 skip) | ✅ |
+| cfc-web Jest | 116 | 116 | 0 | ✅ |
+| 后端 JUnit 全量(含 6 修复类追交) | 待 3306 恢复重跑 | — | — | ⚠️ 承袭 R109 98.36% |
+
+**结论**:本轮无新增生产回归;B-65/B-74/B-82 承袭不恶化。
+
+---
+
+## 二、本轮追加交付(已 commit+push)
+
+| 项 | 内容 | 证据 |
+|----|------|------|
+| ① 6 个 A 类失效用例修复追交 | DailyCheckin/PlannerBind/ArticleWorkflow/DimensionScore/GuheEvidence/ServiceRoleApprove | 6 文件 diff;R109 定向复跑全绿 |
+| ② 第 109 轮汇报追交 | R109 产出提交中断遗留 | `report-test-2026-11-09-R109.md` |
+| ③ 251 派发工具入库 | shell 通道复用脚本团队可复现 | `docs/tdd/tools/_dispatch_251_shell.cjs` |
+| ④ 第 110 轮汇报+notice+blockers 更新 | 本轮执行证据 | 本报告 + 本轮报告 + blockers 台账行更新 |
+
+---
+
+## 三、仍需处置的阻塞(按优先级排序)
+
+### 需研发处理(langgraph 11 红 + 后门鉴权)
+| 编号 | 影响 | 根因 | 期望动作 |
+|------|------|------|----------|
+| **B-65(P0)** analyze 500 | 2 例红 | `analysis_graph.py:96 MemorySaver()`、`analyze.py:35 ainvoke` 缺 thread_id config | 补 checkpointer config(连续 16 轮未修) |
+| **B-82(P1)** intent 分类降级 | 4 例红 | 分类模型未校准 → 全部降级 chat | 研发校准意图分类器(连续 12 轮) |
+| **B-105(P1)** mind/fortune 匿名可达 | 安全语义 | `/api/mind/**` 不在强鉴权清单 | 加 JWT 拦截(连续 13 轮) |
+
+### 需运维处理(环境层)
+| 编号 | 影响 | 期望动作 |
+|------|------|----------|
+| **B-92(P1)** D 盘满(可用 399MB) | 工具链 ETIMEDOUT 风险持续 | 释放 ≥10GB |
+| **B-89(P2)** 189:4090 TCP 拒绝 | 无法减负本机 | 恢复 189 opencode agent |
+| **B-95(P2)** 204:4090 TCP 拒绝 | frontend 降级本机 | 恢复 204 opencode agent |
+
+---
+
+## 四、门禁结论
+
+⛔ **有条件通过**。本轮回环环境健康(E2E 28/28 + 双 Jest 全绿 + 251 实派成功 + 零漂移),但升级「通过」门槛仍需:B-65/B-74/B-82 归零、后端 JUnit 重跑达标、mind 鉴权修补、CI 改造阻断、JaCoCo≥80%。详见完整报告 `docs/tdd/test/automation-run-2026-11-09-2230.md`。
+
+*本通知由测试负责人第 110 轮闭环自动生成;生产源码未改动,仅测试文件与测试目录文档自处置。*

+ 121 - 0
docs/tdd/test/test-loop-report-2026-11-09T1759.md

@@ -0,0 +1,121 @@
+# 测试闭环报告 · 浠艾福 XAF(每小时轮)
+
+- **执行时间**:2026-11-09 17:59 (GMT+8)
+- **执行档**:review 档(已授权内网 cfc.iwintrue.com→192.168.16.251,仅 `/admin`、`/frontend`;禁止公网)
+- **主理人**:陆谨(软件测试专家团)· 只读审查 + 需求闸门触发时编写测试用例(仅测试目录)
+- **Git HEAD**:`d47a3aa69`(R106),距上次本对象轮(R98 `ffbd1b5fd` @ 08:28)+64 commits
+- **结论**:⛔ **有条件通过**(本轮环境健康,无新增生产回归;前端红例 51→1 大幅收敛;剩余阻塞均为环境/授权类)
+
+---
+
+## 1. 执行范围
+
+| 模块 / 维度 | 本轮回环动作 | 结果 |
+|---|---|---|
+| cfc-web Jest(npm test,mock API) | 实跑 `CI=true npx jest` | ✅ 实测 |
+| cfc-frontend Jest(回归) | 实跑 `CI=true npx jest` | ✅ 实测 |
+| cfc-backend 离线编译 | 实跑 `/d/tools/apache-maven-3.9.9/bin/mvn -o test-compile` | ✅ EXIT 0 |
+| cfc-backend 全量 `mvn test` | 未执行 | ❌ 未实测(MySQL 3306 不可达) |
+| E2E Playwright(`/admin`+`/frontend`) | 未执行 | ❌ 未实测(无登录凭据 + 302 登录墙 + 版本冲突待核实) |
+| 需求变更闸门(SRS/需求分析/specs/API_REFERENCE) | 自 08:28 起 mtime 无变更 | 闭合 → 跳过用例编写 |
+
+> 内网探活:`/frontend`→302、`/admin`→302(均登录墙,网络层可达);`/shop-api`→404、`/api/health/plan/attribution`→200(仅作信息探针,不据此断言服务端行为)。
+
+---
+
+## 2. 用例总数 · 通过 · 失败
+
+| 套件 | 套件数 | 总数 | 通过 | 失败 | 跳过 | 备注 |
+|---|---|---|---|---|---|---|
+| cfc-web Jest | 17 | 116 | **116** | 0 | 0 | 全绿,与 08:28 持平(mock API,不断言真实服务端) |
+| cfc-frontend Jest | 26 | 426 | **422** | **1** | 3 | 较 08:28(51 失败)**大幅收敛**;1 失败为测试缺陷(见 §6) |
+| cfc-backend 全量 | — | — | — | — | — | **未实测**;权威基线(R106 提交)768/735 通过=95.70%,33 失败;契约 132/130/2=98.48% |
+| cfc-backend test-compile | — | — | — | — | — | 本轮回环 EXIT 0(DB 无关,真实编译信号) |
+| E2E(内网) | — | — | — | — | — | **未实测**(降级,见 §8) |
+
+**回归判定**:cfc-web 116/116 连绿;cfc-frontend 失败数 51→1(实现侧 diet×3 / 签名 v2 红契约已随实现转绿,仅剩 1 测试缺陷);**无新增生产回归信号**。
+
+---
+
+## 3. 覆盖率
+
+- cfc-web / cfc-frontend Jest:本轮未启用 `--coverage`(避免超时,且默认不吐覆盖率);历史基线 cfc-web 语句~10%/行~10%/分支~6%,cfc-frontend 行~53%。
+- cfc-backend:JaCoCo 离线缺失 → **N/A**;引用权威 surefire 基线 ~95.70%。
+- 结论:**覆盖率维度 N/A,不臆造数值**。待引入 JaCoCo(需联网下载,见 §8)方可实测。
+
+---
+
+## 4. 新增风险
+
+| 级别 | 项 | 说明 |
+|---|---|---|
+| P1 | cfc-frontend 1 例失败为**测试用例缺陷**(非生产缺陷) | `recommendation.uiRework.test.js` REQ6「失败后」用例误判;生产 `.catch` 路径正确(icon:'none')。需修正以免掩盖真实失败(详见 §6 #1)。 |
+| P1(持续) | `/admin` E2E 仍降级 | 无 CFC_ADMIN_TOKEN/PASSWORD 且存在滑块校验,无法登录验证管理端鉴权/菜单。 |
+| P2(持续) | 后端全量 33 红例未本轮回环重测 | MySQL 3306 不可达;引用 R106 提交基线 95.70%。 |
+| P2(持续) | diet/signature 红契约转绿后稳定性 | 当前仅剩 1 测试缺陷,需随实现稳定。 |
+
+**无新增生产回归**:Jest 双绿稳定;E2E 未实跑,不引入新数据。
+
+---
+
+## 5. 测试用例编写产出
+
+- **需求变更闸门判定(mtime 比对,基准 2026-11-09 08:28)**:
+  - `docs/tdd/SRS.md`:最新 2026-11-01 10:25(早于基准,无变更)
+  - `docs/需求分析/*`:最新 2026-11-09 01:34(早于基准,无变更)
+  - `docs/superpowers/specs/*`:最新 2026-11-09 01:34(早于基准,无变更)
+  - `docs/superpowers/api/API_REFERENCE.md`:最新 2026-11-09 08:18(早于基准,无变更)
+  - `find -newermt "2026-11-09 08:28:00"` 对上述四路径 **零命中**。
+- **结论**:自上次循环以来**无 SRS/需求文档变更**(HEAD +64 commits 均为代码/测试,非需求文档)→ **跳过编写测试用例,零仓库抖动**。
+- 前序轮已落库、尚处未提交状态的测试资产(非本次产生,不在本次重复落库):`tests/e2e/diet-recommendation-health-adjust.spec.js`、`tests/e2e/_automation_reachability.cjs` 等(git status 显示 untracked)。
+
+---
+
+## 6. 五要素问题清单
+
+### #1 cfc-frontend 测试用例缺陷(REQ6「失败后」用例误判)
+① **问题描述**:`recommendation.uiRework.test.js` 的「失败后 uni.hideLoading() 并 toast 失败」用例断言 `generateRecipe` 失败时 `showToast` 带 `icon:'none'`,实际观测到 `icon:'success'` 的成功 toast,用例失败。
+② **测试 bug 点**:`cfc-frontend/__tests__/pages/diet/recommendation.uiRework.test.js:425-430`;根因为 `beforeEach`(第 406-409 行)`stubs.generateDietRecommendation.mockImplementationOnce(resolve)` 已占队列一次,第 426 行再 `mockImplementationOnce(reject)` 排第二;`generateRecipe` 调用时消费到首个 `resolve`(成功路径),故失败断言永不触发。生产 `cfc-frontend/pages/diet/recommendation.vue:504-508` 的 `.catch` 正确调用 `uni.showToast({title:'生成失败', icon:'none'})`。
+③ **最优修改方案**:在失败用例内先 `stubs.generateDietRecommendation.mockReset()` 再 `mockImplementationOnce(reject)`;或 `beforeEach` 改用 `mockImplementation`(非 once)并在失败用例内覆盖,确保该用例只消费 `reject`。
+④ **修改目的**:消除测试自身误判,使失败路径断言真正生效,避免掩盖真实失败(对应质量目标:测试可信度 / 零误红)。
+⑤ **问题闭环**:修改后重跑 `cd cfc-frontend && CI=true npx jest __tests__/pages/diet/recommendation.uiRework.test.js`;通过标准:REQ6 三用例全绿(422→423 通过,0 失败)。**仅改测试文件,不碰生产源码。**
+
+---
+
+## 7. 待核实项
+
+- 持续风险(历史债,未本轮回环重测,非本次结论依据):后端 37 红类/33 失败(R106 基线)、`growth_records` 重复列、`blank-key` 真缺陷(S-cfc-31)、JaCoCo 覆盖率 N/A。
+- `/admin` 登录态与菜单:因无凭据 + 滑块,无法验证,标记「待核实登录态」。
+- Chromium 就绪状态:用户声明已就绪(1.63.0),但本轮回退探测因路径格式报错未能确认;E2E 因无凭据 + 302 登录墙降级,未实跑,故不影响结论。
+- 前端 `api.test.js` 对公网 `cfc.etotem.com.cn` 真实 POST 的离线约束缺口(历史发现,本轮未复测,标待核实)。
+
+---
+
+## 8. 需用户单独授权才能继续的项(标注「未授权未执行」并给测试设计)
+
+| 项 | 状态 | 测试设计 / 缺口诊断 |
+|---|---|---|
+| cfc-backend 全量 `mvn test` + 行/分支覆盖率 | 未授权未执行 | 需可达 MySQL 3306(本沙箱 DOWN)。恢复后执行 `mvn test` 采集合计 768/735、契约 132/130/2;目标红类归零、JaCoCo≥80%。 |
+| JaCoCo 覆盖率采集 | 未授权未执行(需联网下载) | 在 `pom.xml` 引入 `jacoco-maven-plugin`,`mvn test` 后读 `target/site/jacoco/index.html`。 |
+| 依赖漏洞扫描(OWASP DC / SCA) | 未授权未执行(需公网) | 设计:`mvn org.owasp:dependency-check:check` 或 `npm audit`;门禁:高危漏洞 0。 |
+| 性能压测 / DAST 渗透 | 未授权未执行(需公网/远端) | 设计:对 `/admin`、`/frontend` 起 k6 + ZAP;需用户显式授权联网。 |
+| E2E `/admin` 真实登录 | 未授权未执行(无凭据+滑块) | 设计:经环境变量注入 `CFC_ADMIN_TOKEN`/`CFC_ADMIN_PASSWORD`,绕过滑块或提供测试账号;覆盖菜单/TabBar/鉴权拦截。 |
+| E2E `/frontend` 登录态 | 未授权未执行(302 登录墙) | 设计:注入微信/会话凭据后跑首页/家庭/商城冒烟;当前仅可达登录页。 |
+
+---
+
+## 9. 门禁结论
+
+**⛔ 有条件通过。**
+
+- **绿区(本轮健康)**:cfc-web 116/116 全绿;cfc-frontend 422/426(仅 1 测试缺陷,非生产);后端 `mvn -o test-compile` EXIT 0;需求闸门闭合、零抖动。
+- **复评门槛(全部满足方升级为「通过」)**:
+  1. 修正 §6 #1 测试缺陷,前端 Jest 423/423 全绿;
+  2. 提供 `/admin` 登录凭据 + 滑块处置,E2E `/admin` + `/frontend` 实跑并给出结果;
+  3. 后端 MySQL 3306 可达后重跑全量 `mvn test`,红例归零(或仅剩预期 RED 契约);
+  4. 引入 JaCoCo,覆盖率 ≥80%;
+  5. 用户单独授权后执行依赖漏洞/性能/DAST(公网项),无高危阻断。
+
+---
+
+*本报告为只读审查 + 测试目录内用例编写产物;未修改、未提交、未推送任何生产源码。*

+ 54 - 0
docs/tdd/tools/_dispatch_251_shell.cjs

@@ -0,0 +1,54 @@
+// 251 langgraph dispatch via opencode SHELL channel (bypasses agent LLM, per B-94/R101)
+// Auth: opencode:IwinTrue@123
+const BASE = process.env.OPENCODE_251_BASE || 'http://192.168.16.251:4096';
+const AUTH = 'Basic ' + Buffer.from('opencode:' + (process.env.OPENCODE_SERVER_PASSWORD || 'IwinTrue@123')).toString('base64');
+const PROJECT_ID = '527b7036bd8d16188784528603a9b79a1d95c9fb';
+const capMs = parseInt(process.env.DISPATCH_CAP_MS || '420000', 10);
+
+const CMD = "cd /sc-data/cfc/cfc-langgraph && git pull --ff-only 2>&1 | tail -3 ; timeout 600 .venv/bin/python -m pytest tests --ignore=tests/qna --ignore=tests/test_chat.py -q ; echo EXIT=$?";
+
+async function j(url, opt) {
+  const r = await fetch(url, Object.assign({ headers: { 'Authorization': AUTH, 'Content-Type': 'application/json' } }, opt));
+  const t = await r.text();
+  try { return { status: r.status, json: JSON.parse(t) }; } catch { return { status: r.status, text: t }; }
+}
+
+async function main() {
+  const cr = await j(BASE + '/api/session', { method: 'POST', body: JSON.stringify({ title: 'lg-shell-' + Date.now(), model: { id: 'deepseek3.2', providerID: 'axon' }, projectID: PROJECT_ID }) });
+  const sid = (cr.json.data && cr.json.data.id) || cr.json.id;
+  if (!sid) throw new Error('no session: ' + JSON.stringify(cr.json).slice(0,200));
+  console.log('SESSION_ID', sid);
+
+  const sr = await j(BASE + '/api/session/' + sid + '/shell', { method: 'POST', body: JSON.stringify({ command: CMD }) });
+  console.log('SHELL_CREATE', sr.status);
+
+  // find shell id
+  let shellId = null;
+  for (let i = 0; i < 10 && !shellId; i++) {
+    const gl = await j(BASE + '/api/shell');
+    const arr = (gl.json && (gl.json.data || gl.json)) || [];
+    if (Array.isArray(arr) && arr.length) { shellId = arr[arr.length - 1].id; break; }
+    await new Promise(r => setTimeout(r, 1500));
+  }
+  if (!shellId) throw new Error('no shell id');
+  console.log('SHELL_ID', shellId);
+
+  const start = Date.now();
+  let last = '';
+  while (Date.now() - start < capMs) {
+    await new Promise(r => setTimeout(r, 4000));
+    const st = await j(BASE + '/api/shell/' + shellId);
+    const status = (st.json && (st.json.data && st.json.data.status || st.json.status)) || '?';
+    if (status !== last) { console.log('SHELL_STATUS', status); last = status; }
+    if (status && status !== 'running') {
+      const out = await j(BASE + '/api/shell/' + shellId + '/output');
+      const o = (out.json && out.json.data && out.json.data.output) || '';
+      console.log('OUTPUT_BEGIN');
+      console.log(o.slice(-4000));
+      console.log('OUTPUT_END');
+      return;
+    }
+  }
+  console.log('SHELL_TIMEOUT after ' + capMs + 'ms');
+}
+main().catch(e => { console.error('ERR', e.message); process.exit(1); });