Browse Source

test(R196): 定时自动化闭环 - 后端全量3737例92.80% + 浏览器28过2跳 + API全量990例首测(22红) + S-cfc-227使绿并补8例未到行为契约

test-lead-automation 24 minutes ago
parent
commit
f5de4c3e7b

+ 173 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherAppointmentNoShowBehaviorContractTest.java

@@ -0,0 +1,173 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.entity.AssessmentAppointment;
+import com.etotem.cfc.mapper.AssessmentAppointmentMapper;
+import com.etotem.cfc.service.AssessmentAppointmentService;
+import com.etotem.cfc.service.EnergyService;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 契约级单测(行为面):能力键 {@code teacher.appointment.guard} 的「未到(no-show)」分支。
+ *
+ * <p><b>为什么需要本类(R196 新增)</b>:S-cfc-227(`ab349b35a`)落地
+ * `AssessmentAppointmentService#noShowAppointment` 与
+ * `POST /api/assessment/appointment/no-show/{id}`,同批
+ * {@code TeacherAppointmentGuardContractTest} 中的 G-1 两条用例只做
+ * <b>「方法/端点是否存在」的反射探测</b>(`g1_service_hasNoShowMethod`、
+ * `g1_controller_hasNoShowEndpoint`),<b>对状态机行为零断言</b>——
+ * 方法体写成 `return true` 也能绿。本类把 G-1 从「存在性」升级到「行为契约」,
+ * 补齐「同源变更→用例更新」闭环(SRS:API_REFERENCE SC-T-10 已把 G-1 标为落地)。</p>
+ *
+ * <p><b>被校验的契约行为</b>(来源:API_REFERENCE §4.57 SC-T-10 +
+ * 《2026-11-07-planner-zone-contract.md》§5):</p>
+ * <ul>
+ *   <li>仅 待确认(0) / 已确认(1) 可流转到 未到(4),成功时写库一次且 status=4;</li>
+ *   <li>已完成(2) / 已取消(3) / 已未到(4) 属终态,一律拒绝且<b>不写库</b>(拒绝不得产生副作用);</li>
+ *   <li>操作者必须是该预约的成长规划师(`guideId`),否则拒绝且不写库(数据隔离 SC-T-13 同源);</li>
+ *   <li>预约不存在 / 状态为空(历史脏数据)→ fail-closed 返回 false,不写库。</li>
+ * </ul>
+ *
+ * <p><b>测试范围</b>:纯 Mockito + 反射注入,无 Spring 上下文 / 无 DB / 无外部服务。</p>
+ */
+class TeacherAppointmentNoShowBehaviorContractTest extends AssessmentPlanContractTestBase {
+
+    private static final long APPOINTMENT_ID = 301L;
+    private static final long GUIDE_ID = 7L;
+    private static final long OTHER_TEACHER_ID = 9L;
+
+    // ============================== 合法流转(绿色锚) ==============================
+
+    @Test
+    @DisplayName("SC-T-10 未到:待确认(0) → 未到(4) 允许,且写库一次")
+    void noShow_pending_toNoShow_allowed() throws Exception {
+        AssessmentAppointmentMapper mapper = mock(AssessmentAppointmentMapper.class);
+        AssessmentAppointmentService svc = newService(mapper);
+        AssessmentAppointment a = buildAppointment(0);
+        when(mapper.selectById(APPOINTMENT_ID)).thenReturn(a);
+        when(mapper.updateById(any())).thenReturn(1);
+
+        boolean ok = svc.noShowAppointment(APPOINTMENT_ID, GUIDE_ID);
+
+        assertTrue(ok, "[teacher.appointment.guard] 待确认(0) 应可标记未到(4)");
+        assertEquals(4, a.getStatus(), "标记未到后状态应为 4");
+        verify(mapper).updateById(any());
+    }
+
+    @Test
+    @DisplayName("SC-T-10 未到:已确认(1) → 未到(4) 允许,且写库一次")
+    void noShow_confirmed_toNoShow_allowed() throws Exception {
+        AssessmentAppointmentMapper mapper = mock(AssessmentAppointmentMapper.class);
+        AssessmentAppointmentService svc = newService(mapper);
+        AssessmentAppointment a = buildAppointment(1);
+        when(mapper.selectById(APPOINTMENT_ID)).thenReturn(a);
+        when(mapper.updateById(any())).thenReturn(1);
+
+        boolean ok = svc.noShowAppointment(APPOINTMENT_ID, GUIDE_ID);
+
+        assertTrue(ok, "[teacher.appointment.guard] 已确认(1) 应可标记未到(4)");
+        assertEquals(4, a.getStatus(), "标记未到后状态应为 4");
+        verify(mapper).updateById(any());
+    }
+
+    // ============================== 非法跃迁(终态拒绝、拒绝不写库) ==============================
+
+    @Test
+    @DisplayName("SC-T-10 未到守卫:已完成(2) 不可标记未到,且不写库")
+    void noShow_afterCompleted_rejected() throws Exception {
+        assertRejected(2, GUIDE_ID, "已完成(2) 属终态,不得回退为未到(4)");
+    }
+
+    @Test
+    @DisplayName("SC-T-10 未到守卫:已取消(3) 不可标记未到,且不写库")
+    void noShow_afterCancelled_rejected() throws Exception {
+        assertRejected(3, GUIDE_ID, "已取消(3) 属终态,不得改为未到(4)");
+    }
+
+    @Test
+    @DisplayName("SC-T-10 未到守卫:重复标记未到(status 已为 4)应拒绝且不重复写库(幂等)")
+    void noShow_alreadyNoShow_rejected() throws Exception {
+        assertRejected(4, GUIDE_ID, "已是未到(4) 时重复标记应拒绝(幂等,不得产生二次写库)");
+    }
+
+    // ============================== 数据隔离(仅所属规划师可操作) ==============================
+
+    @Test
+    @DisplayName("SC-T-13 数据隔离:非该预约的成长规划师不可标记未到,且不写库")
+    void noShow_nonGuideCaller_rejected() throws Exception {
+        assertRejected(1, OTHER_TEACHER_ID, "非 guideId 的调用者(越权)应被拒绝且不写库");
+    }
+
+    // ============================== fail-closed 边界 ==============================
+
+    @Test
+    @DisplayName("SC-T-10 未到边界:预约不存在 → 返回 false 且不写库")
+    void noShow_notFound_false() throws Exception {
+        AssessmentAppointmentMapper mapper = mock(AssessmentAppointmentMapper.class);
+        AssessmentAppointmentService svc = newService(mapper);
+        when(mapper.selectById(APPOINTMENT_ID)).thenReturn(null);
+
+        boolean ok = svc.noShowAppointment(APPOINTMENT_ID, GUIDE_ID);
+
+        assertFalse(ok, "预约不存在时应返回 false");
+        verify(mapper, never()).updateById(any());
+    }
+
+    @Test
+    @DisplayName("SC-T-10 未到边界:历史脏数据 status=null → fail-closed 拒绝且不写库")
+    void noShow_nullStatus_rejected() throws Exception {
+        AssessmentAppointmentMapper mapper = mock(AssessmentAppointmentMapper.class);
+        AssessmentAppointmentService svc = newService(mapper);
+        AssessmentAppointment a = buildAppointment(1);
+        a.setStatus(null);
+        when(mapper.selectById(APPOINTMENT_ID)).thenReturn(a);
+        when(mapper.updateById(any())).thenReturn(1);
+
+        boolean ok = svc.noShowAppointment(APPOINTMENT_ID, GUIDE_ID);
+
+        assertFalse(ok, "status=null 时应 fail-closed 拒绝,不得默认放行");
+        verify(mapper, never()).updateById(any());
+    }
+
+    // ============================== 辅助 ==============================
+
+    private void assertRejected(int currentStatus, Long callerId, String reason) throws Exception {
+        AssessmentAppointmentMapper mapper = mock(AssessmentAppointmentMapper.class);
+        AssessmentAppointmentService svc = newService(mapper);
+        AssessmentAppointment a = buildAppointment(currentStatus);
+        when(mapper.selectById(APPOINTMENT_ID)).thenReturn(a);
+        when(mapper.updateById(any())).thenReturn(1);
+
+        boolean ok = svc.noShowAppointment(APPOINTMENT_ID, callerId);
+
+        assertFalse(ok, "[teacher.appointment.guard] " + reason);
+        assertEquals(currentStatus, a.getStatus(), "被拒绝时内存对象状态不得被改写");
+        verify(mapper, never()).updateById(any());
+    }
+
+    private AssessmentAppointmentService newService(AssessmentAppointmentMapper mapper) throws Exception {
+        AssessmentAppointmentService svc = new AssessmentAppointmentService();
+        inject(svc, "assessmentAppointmentMapper", mapper);
+        inject(svc, "energyService", mock(EnergyService.class));
+        return svc;
+    }
+
+    private AssessmentAppointment buildAppointment(int status) {
+        AssessmentAppointment a = new AssessmentAppointment();
+        a.setId(APPOINTMENT_ID);
+        a.setStatus(status);
+        a.setGuideId(GUIDE_ID);
+        a.setAssessorId(5L);
+        a.setTeacherId(GUIDE_ID);
+        return a;
+    }
+}

+ 48 - 0
docs/tdd/automation-run-2026-11-13-R196.md

@@ -0,0 +1,48 @@
+# 自动化执行记录 R196(2026-11-13 23:49 ~ 2026-11-14 01:30)
+
+> 渠道:定时自动化「测试负责人闭环」|模式:review 离线档 + 内网浏览器 E2E + 251 分派|全程静默(未提问)
+> 基线:`123c6db97` → 收尾 rebase `b44051e3f`|策略:先探活 → 起后端全量 → 并行浏览器/Jest/251 → 收尾双 fetch → 增量复验 → 提交推送
+
+## 1. 时间线
+
+| 时刻 | 动作 | 结果 |
+|---|---|---|
+| 23:49 | 读自动化记忆(R191 末条)+ git 基线 | HEAD `123c6db97`,origin 落后(SSH 不可用,B-189-1) |
+| 23:50 | 环境探活 | JDK 1.8.0_25 / mvn 3.6.1(`/d/maven/apache-maven-3.6.1`,PATH 无 mvn)/ node 22.22.2 / playwright 1.63.0 |
+| 23:50 | 节点探活 | 189:4090=200 · 251:4096=200 · 204:4091=200 · **251:9000=closed** · 站点 /admin 302 /frontend 302 / 404 · 9082=000 |
+| 23:52 | 启动后端全量(后台) | `mvn -o -B clean test` |
+| 23:53 | 派发 189 admin-desktop | 5/5 EXIT=0(1.1m),节点 HEAD `ec8ced27a`、spec blob `4703d73fb` |
+| 23:53 | 派发 251 pytest(kick) | 117.54s → 10F/41P |
+| 23:54 | 本机串行跑 fe/shop/dx/health | 6/6、4/4、2 跳、13/13 |
+| 23:56 | 三套 Jest(web→fe→根) | web 116/116、fe 609/3/12、根级口径错误(见 B-196-3) |
+| 00:00 | 线上 API 点名 4 文件 | 32 = 25/0/7 |
+| 00:03 | SRS 闸门 + 度量 | specs 204 / test-cases 16 / scenarios 11;API_REFERENCE **有变更**(SC-T-10) |
+| 00:03 | **SRS 变更同步**:改台账 + 新增 8 例行为契约 | `deep-dive-assessment-appointment.md` §2/§3/§4/§5/§8 + 新测试类 |
+| 00:43 | 后端全量完成 | 51:42 → 423 类 / 3737 / 3468 / 265 红 / 4 跳 = 92.80% |
+| 00:48 | 定向跑新增 8 例 | **8/8 绿**(3.462s,BUILD SUCCESS) |
+| 00:52 | 收尾 fetch(HTTP 兜底) | 远端 +4 提交,含 `cfc-frontend` 访客态三页改造 |
+| 00:53 | rebase + 增量复验 | fe Jest 仍 609/3/12(逐例一致);fe E2E 复跑 6/6 |
+| 00:57 | 线上 API 全量(990 例) | 118 过 / 22 红 / 794 跳 / 56 未跑(14.5m) |
+| 01:30 | 写报告 + 通知 + 推送 | 见 §4 |
+
+## 2. 关键决策
+
+1. **不按速率外推后端耗时**(B-188-1):前 30 分钟只跑到 61/423 类,若按 2 类/分钟外推会判「需 3 小时」并 kill;坚持等到 `Total time 51:42`,全量正常收尾。
+2. **后端根目录必须是 `cfc-backend`**;`mvn` 不在 Git Bash PATH,须用绝对路径 `/d/maven/apache-maven-3.6.1/bin/mvn.cmd` + `JAVA_HOME=/d/jdk1.8.0_25`。
+3. **根级黑盒口径纠正**(B-196-3):仓库根 `npx jest` 触发 haste-map 冲突且误扫全仓 → 正确入口 `npx jest -c tests/jest.config.js`(23 例 / 21s)。
+4. **SRS 变更必须落用例**:API_REFERENCE 的 SC-T-10 变更 → 台账 4 条 TC + 8 例行为断言;并且识别出原 G-1 用例只做「存在性反射探测」属假绿风险。
+5. **收尾双 fetch**:第二次 fetch(HTTP 兜底)抓到访客态三页改造 → 立即 rebase + fe Jest/E2E 增量复验,避免把新功能带进盲区(B-191-1 纪律)。
+6. **API 全量首次纳入**:`Running 990 tests` 实证 B-173-2(不带文件参数会展开近千例),并首次拿到 22 红的可归因清单。
+
+## 3. 未实测项(如实声明)
+
+压测 / SAST / DAST / 依赖漏洞 / JaCoCo / PIT / 多浏览器 / 无障碍 / UAT / 探索性 / langgraph live 链路面(251 无 9000 服务)—— 均未执行,原因见报告附录 C。
+
+## 4. 产出
+
+- `docs/tdd/orchestration/report-test-2026-11-13-R196.md`
+- `docs/tdd/automation-run-2026-11-13-R196.md`(本文件)
+- `docs/tdd/notice-2026-11-13-R196-研发通知.md`
+- `docs/tdd/test/blockers.md`(R196 段)
+- `tests/test-cases/deep-dive-assessment-appointment.md`(SRS 同步)
+- `cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherAppointmentNoShowBehaviorContractTest.java`(新 8 例)

+ 50 - 0
docs/tdd/notice-2026-11-13-R196-研发通知.md

@@ -0,0 +1,50 @@
+# 研发通知 R196(2026-11-14 01:30 · 测试负责人自动化闭环)
+
+> 来源:定时自动化「测试负责人闭环(全用例覆盖 + SRS/场景变更同步 + 浏览器 E2E + langgraph 分派 251)」
+> 基线:`b44051e3f`|结论:**不通过(有条件)** —— 无新增确定性回归,但有 3 项 P0 待处理
+
+## 一、需要研发立即处理(P0)
+
+### 1. `cancelAppointment` 越权仍未修(第 N 轮悬置)
+
+- 位置:`cfc-backend/src/main/java/com/etotem/cfc/controller/assessment/AssessmentAppointmentController.java` 的 `cancelAppointment(@PathVariable Long id)`
+- 现象:任意登录用户凭 id 可取消他人预约(无 `userId` / 归属校验)。
+- 修法:加 `@RequestAttribute("userId") Long userId`,service 改 `cancelAppointment(id, userId)`,校验 `userId ∈ {预约 userId, guideId, assessorId}`。
+- 闭环:台账 TC-DV-01 复测返回拒绝;本轮已补的 `TeacherAppointmentNoShowBehaviorContractTest` 可作为同风格模板。
+
+### 2. 线上 API 全量 22 红(本轮首次全量实测)
+
+| 归因 | 例数 | 处置建议 |
+|---|---|---|
+| `direct-register` 生产不可用(造数失败) | 6 | 用例侧改为 SKIP + 原因,或迁测试环境;**非产品缺陷** |
+| HTTP 500 | 6 | 逐条查服务端堆栈(wish-flow / task-checkin / energy 等) |
+| 504 / NON_JSON_RESPONSE / aborted | 4 | 网关瞬态,需确认是否需重试策略 |
+| 语义不符 | 6 | 并发兑换应仅 1 成功、`至少选择一个提醒渠道`、`请设置所需积分`、`id不能为空`、`申请兑换失败` → **请确认是契约缺失还是用例预期错** |
+
+复现:`cd tests && npx playwright test -c playwright.config.api.js --project=desktop --reporter=list --workers=1`(990 例 / 14.5m)
+
+### 3. langgraph 上游配额/key(P0,连续多轮)
+
+- 现象:51 例 10 红(analyze 1 / auto_tag 5 / intent 4),日志 **429 `rate_limit_exceeded`(AgnesAI)+ 401 `Authentication Fails, Your api key …`**。
+- 需运维轮换 key / 升配额;测试侧建议加「录音回放档」把上游可用性与本服务逻辑解耦。
+
+## 二、需要研发配合(P1)
+
+1. **CI 排除清单已严重滞后**:`.gitlab-ci.yml:61` 33 类 vs 本轮实测 **49 红类** → **漏拦 42 类**、**陈旧 26 类**(已转绿仍排除)。建议清单改为「每轮实测快照 + 差集守卫」(差集非空即 CI 失败)。
+2. **240 个 TDD 前瞻契约红**待分批使绿(每批 ≤30),当前拉低 G1 至 92.80%(后端)。
+3. **存量 13 类 25 红**:EnergySystemFlow 6F、DimensionRegistryMapContract 3F、GrowthPublicEndpointMapping 5E、TaskReviewScP11 2F(反射装置浮动,B-178-1)、Orchestration×2、MembershipPaySwitch、PaymentServiceORD、UserServiceWechatLogin×2、GuideModuleIntegration、TeamProfitCron、MembershipPlanPurchase。
+4. **JaCoCo 未接入** → 覆盖率不可度量(G9 只能用锚点率 20.8% 代理)。
+
+## 三、本轮正向(已闭环)
+
+- **S-cfc-227 使绿**:预约状态机守卫(confirm 仅 0→1、complete 仅 1→2、cancel 仅 0/1→3)+ **`POST /api/assessment/appointment/no-show/{id}`**(仅 0/1→4,仅 `guideId`)。`TeacherAppointmentGuardContractTest` 15 例绿。
+- 测试侧补充 **8 例行为契约** `TeacherAppointmentNoShowBehaviorContractTest`(8/8 绿):合法流转、终态拒绝且不写库、越权拒绝、不存在/status=null fail-closed。
+- **SRS 变更同步已完成**:`tests/test-cases/deep-dive-assessment-appointment.md` 状态机/接口清单/缺陷表(F2 ✅ 闭环、F3 🟡 部分闭环)/新增 TC-DV-11~14。
+- 浏览器 28 过 + 2 跳;cfc-web 116/116;cfc-frontend 609/3/12(3 全预期 RED,**访客态三页改造后增量复验零回归**)。
+
+## 四、测试侧已知问题(供知悉,不需研发处理)
+
+- 根级黑盒 16 红:本机 9082 未启(B-36)。
+- dx 两端点 404:规格明示「仅登记不实现」(B-DX-1)。
+- 251 无 langgraph HTTP 服务(仅 :80/:4096),链路面未实测 —— 历轮「live `/health` 200」为 opencode 端口误读,已更正。
+- 251 pytest 打印 summary 后进程不退出(B-177-1 第 18 轮),已 `kill -9` 清理;建议 `app/main.py` shutdown 补 `await client.aclose()`。

+ 373 - 0
docs/tdd/orchestration/report-test-2026-11-13-R196.md

@@ -0,0 +1,373 @@
+# 测试负责人工作汇报 R196(2026-11-13 23:49 ~ 2026-11-14 01:xx · 定时自动化闭环)
+
+> 测试负责人:陆谨(软件测试专家团)|执行档:**review(离线)** + 内网浏览器 E2E|静默无人值守(未提任何问题)
+> 基线:`123c6db97`(R195 后)→ 收尾 rebase 至 **`b44051e3f`**(远端窗口新增访客态三页改造)|提交见 §9
+> 本轮实跑:后端全量 **423 类 / 3737 例**(51:42)· 浏览器 **28 过 + 2 跳** · Jest 三套 · 线上 API · langgraph@251
+
+---
+
+## 1. 执行总览
+
+| 套件 | R196(本轮实测) | R195 | 漂移 |
+|---|---|---|---|
+| 后端全量(`cd cfc-backend` + mvn 3.6.1 `-o -B clean test`) | **3737 run / 3468 过 / 259F+6E / 4 跳 = 92.80%**(423 类,51:42) | 3725 / 3453 / 92.70%(422 类,52:54) | **+12 例 / +15 过 / +0.10pp**,红 272→265 |
+| └ 补测(本轮新增行为面) | **+8 例全绿**(`TeacherAppointmentNoShowBehaviorContractTest`,定向 2:27) | — | 新增 |
+| 浏览器 E2E | **28 过 + 2 跳**(189 admin 5/5、本机 fe 6/6、shop 4/4、health 13/13、dx 2 跳) | 28 + 2 跳 | 零漂移 |
+| cfc-web Jest | **116/116**(17 套件) | 116/116 | 零漂移 |
+| cfc-frontend Jest | **624 = 609 过 / 3 红 / 12 跳**(3 红全预期 RED) | 同 | 零漂移(**合入访客态改造后增量复验仍一致**) |
+| 根级黑盒 | **23 = 7 过 / 16 红**(全 `ECONNREFUSED 9082`) | 7/16 | 零漂移(B-36 第 79 轮) |
+| langgraph@251 | **51 = 41 过 / 10 红**(117.54s) | 40/51(11 红) | **+1 过**(4→10 类集合不变,环境红) |
+| 线上 API(点名 4 文件 32 例) | 32 = **25 过 / 0 红 / 7 跳**(39.5s) | 32 承袭 | 持平 |
+| 线上 API(**全量 990 例**,含 generated) | **118 过 / 22 红 / 794 跳 / 56 未跑**(14.5m) | 未跑全量 | **口径升级:首次全量实测** |
+
+**全站合计(含 API 全量)**:**5579 例 / 4395 过 / 316 红 / 868 跳(含 56 未跑)= 78.78%**
+**剔 skip/未跑口径:4395 / 4711 = 93.29%**(剔全部预期 RED **99.2%**)
+**变化**:例 +980(API 全量首次纳入)、过 +116、红 +22(全为 API 全量口径新增,后端 −7 / lg −1)、跳 +843(generated 占位用例)→ **无新增确定性回归**。
+
+
+---
+
+## 2. 项目画像(本轮视角)
+
+- **业务**:家庭教育 SaaS —— 微信小程序(家长/孩子/规划师三端)+ Web 管理端 + 独立商城 + Python LangGraph AI 服务。
+- **技术栈**:Spring Boot 2.7.18 / MyBatis-Plus / Java 8(`cfc-backend`,9082)· uni-app Vue2(`cfc-frontend`)· Vue2+Element(`cfc-web`,8082)· FastAPI+LangGraph(`cfc-langgraph`,9000)· 商城 `dan/danshop`(8888)。
+- **关键路径**:登录鉴权(JWT/微信)→ 家庭与成员 → 五维能量 → 任务/打卡 → 健康报告上传解析(9000 依赖)→ 测评预约/订单/规划师录入 → 商城订单履约。
+- **本轮窗口变更(11 提交)**:① `cfc-backend` 3 文件 —— **S-cfc-227 预约状态机守卫 + 未到(no-show)端点**;② `cfc-frontend` 3 页 —— **访客态健康/成长/我的令牌化重做 + 弱文字对比度**;③ `cfc-web` 构建元数据与 admin-tokens 密度;④ `docs/superpowers/api/API_REFERENCE.md` —— **SC-T-10 契约由「缺口 G-1」改为「已落地」**;⑤ 其余 docs。
+- **质量目标(门禁)**:全量通过率 ≥95%、红例 0、浏览器全绿、langgraph ≥50/51、SRS 变更必须同步用例、CI 不得常红。
+- **风险点**:24 个 TDD 前瞻契约类(240 红)拉低 G1;CI 排除清单严重滞后;langgraph 上游 LLM 配额;`cancelAppointment` 越权未修。
+
+---
+
+## 3. 历史审查记录(跨轮对照,仅作参照,结论重新取证)
+
+| 项 | 内容 |
+|---|---|
+| 上次审查 | **R195**(2026-11-13 22:45,判「不通过·承袭·有条件」) |
+| 遗留缺口 | ① langgraph 429/401 环境红(P0);② 24 个前瞻契约类待使绿;③ CI 清单漏拦/陈旧(G11);④ 存量 13 类装置红;⑤ B-36 根级 16 红;⑥ 锚点率 ~20%(G9) |
+| 本轮改善 | S-cfc-227 **使绿**(23 例:15 守卫 + 8 未到行为);langgraph 40→41;后端红 272→265 |
+| 仍然未解 | 上游配额(429 ×N / 401 key invalid)、CI 清单(漏拦 42)、存量 13 类、B-36、锚点率 |
+| 新发现 | **B-196-1 ~ B-196-4**(见 §7) |
+
+> 未命中即声明:本轮为该对象**第 196 次**审查(非首次),历史结论仅作对照,未直接沿用。
+
+---
+
+## 4. 执行明细
+
+### 4.1 后端全量(唯一权威口径:surefire XML 聚合)
+
+```
+cd cfc-backend && JAVA_HOME=D:\jdk1.8.0_25 mvn -o -B clean test      → Total time 51:42
+聚合:classes=423  tests=3737  pass=3468  F=259  E=6  S=4  rate=92.80%  red_classes=49  red_cases=265
+编译:main 2121 + test 430 源,0 编译错误;423/423 XML 无沉默类;dump 0
+```
+
+**红例三层归因(265)**
+
+| 层级 | 例数 | 类数 | 说明 |
+|---|---|---|---|
+| A 存量装置/数据 | **25** | 13 | EnergySystemFlow 6F、DimensionRegistryMapContract 3F、GrowthPublicEndpointMapping 5E、TaskReviewScP11 2F(B-178-1 反射装置浮动)、OrchestrationEngine/Integration 各 1F、MembershipPaySwitch 1F、PaymentServiceORD 1F、UserServiceWechatLogin 2F、GuideModuleIntegration 1E、TeamProfitCron 1F、MembershipPlanPurchase 1F → **零漂移第 28 次** |
+| B TDD 前瞻契约(研发已声明 RED) | **240** | 36 | CoachProactive 24、SupplierManageAdmin 16、ChildMindCapture 15、DimensionConfigAdmin 13、SupplierNutritionSupply 13、AdminSupplierManage 10、MindModelManage 10、ChildFeedbackSubmit 9、ChildEnergyGain/AdminDataMigration 各 8、ChildAiChat/AdminUserGovern 各 7、AdminArticleManage/SupplierInventoryManage/ChildAuthLogin/ChildNotificationReceive 各 6、AdminOrderManual/AICoachConsult/ChildShopPointsExchange/AdminBadgeManage/TaskCheckinChild/SupplierActivityAttend/AdminSystemMonitor/SupplierExportCompliance/AdminActivityConfig 各 5、TeacherPlanCreate/PlanContentSafetyReview/ChildGameReportScore/SupplierAfterSalesHandle 各 4、ChildDietViewMenu/ChildGrowthArchive 各 3、ChildEnergyView 3、TeacherFamilyBind/TaskReviewScP11 补 2、ChildGrowthArchiveExport 2、PlanActivationScT05/TeacherDataIsolationScT13 各 1 |
+| C 环境/其余 | 0 | 0 | 本轮无 ApplicationContext / JDBC 类环境红(`env_red_cases=0`) |
+
+**慢类 Top(B-191-2 观察,第 3 轮)**:UserServiceFamilyNameLink 180.6s/2 例、GuidePackageService 173.8s/13 例、GrowthPublicEndpointMapping 151.6s/5 例、ActivityController 105.4s/16 例、AIChatController 96.4s、ProductController 88.3s、DimensionAdminController 86.5s、GuideRolePermission 86.2s;51:42 总耗时中 `cfc-scheduler` 日志占比高(`@SpringBootTest` × 34 次上下文 + 每次 `DatabaseInitializer` 全量迁移)。
+
+### 4.2 浏览器 E2E(每节点单任务,串行)
+
+| 节点 | 项目 | 结果 | 证据 |
+|---|---|---|---|
+| **189**(`192.168.16.189:4090`) | admin-desktop | **5/5 通过,EXIT=0,1.1m** | 节点 HEAD `ec8ced27a`、spec blob `4703d73fb`(= 本机 spec)→ 用例一致 |
+| **本机** | frontend-mobile | **6/6(55.6s,复跑 57.0s)** | TC-F-U01~U06(首页/五维沙盘/TabBar×2/我的鉴权引导/资源零失败) |
+| **本机** | shop-api | **4/4(7.0s)** | 价格排序 asc/desc/默认/白名单拦截 |
+| **本机** | health(`playwright.config.health.js`) | **13/13(8.1s)** | 含 huawei-health/xiaomi-health 全端点 401 |
+| **本机** | dx-bridge | **2 跳过(404/404)** | 规格明示「仅登记不实现」→ 待派发(B-DX-1 第 35 轮) |
+| 合计 | — | **28 过 + 2 跳** | G6 ✅ |
+
+### 4.3 Jest 三套
+
+- `cfc-web`:**17 套件 / 116 例全绿**
+- `cfc-frontend`:**42 套件 / 624 例 = 609 过 / 12 跳 / 3 红**;3 红 = `AC-R1`、`AC-R2`、`AC-2 RED-A7`(S-cfc-132/133 预期 RED)。**收尾合入访客态三页改造后增量复验:仍 609/3/12,逐例一致 → 零回归。**
+- 根级:`npx jest -c tests/jest.config.js` → **23 例 = 7 过 / 16 红**(全 `ECONNREFUSED 127.0.0.1:9082`,本机后端未起,B-36 第 79 轮)
+
+### 4.4 线上 API
+
+- **点名 4 文件(api-smoke / nonfunc-security / nonfunc-perf-compat / traceability-p94-p95-c26)**:`Running 32 tests` → **25 过 / 0 红 / 7 跳 / 39.5s**
+- **全量(`npx playwright test -c playwright.config.api.js --project=desktop`,含 `e2e/api/generated/tc-scripted.spec.js`)**:`Running 990 tests` → **118 过 / 22 红 / 794 跳 / 56 未跑 / 14.5m**(B-173-2 口径警示成立:不带文件参数即展开近千例)
+  - **22 红归因分布**:`direct-register` 种子写入不可用 6(生产禁测试注册 → 设计缺陷,非产品缺陷)、HTTP **500** 6、HTTP **504 / NON_JSON_RESPONSE / aborted** 4(网关瞬态)、语义类 6(并发兑换应仅 1 成功、`至少选择一个提醒渠道`、`请设置所需积分`、`id不能为空`、`申请兑换失败` 等 → **需研发确认契约**)
+  - 涉及规格:wish-flow 6、task-checkin 3、butler-fallback 2、badge/coach-food/contact/dimension-energy/energy-api/energy-sc/incentive-loop/task-c14/task-frequency/wechat-login 各 1~2
+
+
+### 4.5 langgraph@251(分派 251 节点)
+
+- 派发:`dispatch_251_pytest.cjs kick`,`.venv/bin/python -m pytest -q --ignore=tests/qna --ignore=tests/test_chat.py`,节点 HEAD `3c02011ed`
+- 结果:**10 failed / 41 passed / 117.54s**;红例 = `test_analyze::test_analyze_missing_report` 1 + `test_auto_tag` 5 + `test_intent` 4
+- 归因:日志**实证 429 `rate_limit_exceeded`(AgnesAI 免费额度)+ 401 `Authentication Fails, Your api key …`**,`cfc-langgraph` 源码零变更 → **环境红(B-142-2 / B-195-1 延续)**
+- **B-177-1 第 18 轮复现**:summary 已打印但 `EXIT=` 未落盘;`ps` 显示 2 个 pytest 进程存活(本轮 4m08s + 另有一枚 38m13s 残留)→ `kill -9` 后 `ps`=0、`:4096/health` 200/3.9ms
+- **★ 口径更正(B-196-5)**:251 仅监听 `:80` 与 `:4096`(opencode),**无 9000 监听** → 历轮所称「langgraph live `/health` 200」实为 opencode 端口;**langgraph 无独立 HTTP 服务暴露,链路面只能 in-process 实测**
+
+### 4.6 线上黑盒(HTTP 直采)
+
+| 检查 | 结果 |
+|---|---|
+| `/admin` `/frontend` `/` | **302 / 302 / 404** |
+| 匿名 6 端点(mind/energy/health-plan/xiaomi-health/huawei-health/admin-families) | **6/6 = 401 `{"code":401,"message":"请先登录"}`**(G8 ✅,B-142-1 销号维持第 24 轮) |
+| dx 两端点 | 404/404(B-DX-1) |
+| 节点探活 | `189:4090`=200 · `251:4096`=200 · `204:4091`=200 · **`251:9000`=closed** |
+
+---
+
+## 5. 20 类测试覆盖矩阵(三态,逐类给证据)
+
+| # | 测试类型 | 状态 | 证据 |
+|---|---|---|---|
+| 1 | 单元测试 | **已发现** | 423 类 / 3737 例(surefire 聚合),`unit/` 为主 |
+| 2 | 组件测试 | **已发现** | cfc-frontend 42 套件(含 AC-R1/R2 组件契约)、cfc-web 17 套件 |
+| 3 | 集成测试 | **已发现** | `EnergySystemFlowTest`、`OrchestrationEngine/IntegrationTest`、`GuideModuleIntegrationTest`、`DimensionRegistryMapContractTest` |
+| 4 | 接口(API)测试 | **已发现** | `tests/e2e/api/*.spec.js` 26 份;点名 4 文件 32 例 0 红;**全量 990 例 = 118 过 / 22 红 / 794 跳 / 56 未跑** |
+| 5 | 端到端(E2E) | **已发现** | `tests/e2e-live/`:admin-desktop 5、frontend-mobile 6、shop-api 4、dx 2、health 13 |
+| 6 | 冒烟测试 | **已发现** | `api-smoke.spec.js`(4 例,随 32 例套件全绿);`_smoke_browser.spec.js` 存在 |
+| 7 | 回归测试 | **已发现** | 本轮全站 5579 例回归执行;后端/浏览器/Jest 红例集合与 R195 逐类一致 |
+| 8 | 探索性测试 | **未发现** | 仓库无 charter/会话记录(`tests/` 无 exploratory 资产)→ 覆盖缺口 G-196-1 |
+| 9 | 验收 UAT / BDD | **未发现** | 无 `*.feature`、无 UAT 脚本;`tests/BLACKBOX-TEST-PLAN.md` 为手工计划非可执行验收 → G-196-2 |
+| 10 | 性能测试 | **待核实** | `nonfunc-perf-compat.spec.js` 4 过 3 跳;压测需联网授权 → **未授权未执行** |
+| 11 | 可靠性测试 | **待核实** | 有实证缺陷(B-177-1 pytest 不退出、B-196-4 网关瞬态),但无故障注入/混沌资产 |
+| 12 | 安全测试 | **已发现** | `nonfunc-security.spec.js` 11 例;匿名 6 端点 401;**代码审阅发现 `cancelAppointment` 越权(F1,未修)**;SAST/DAST/依赖扫描**未授权未执行** |
+| 13 | 兼容性测试 | **待核实** | 仅 chromium 单浏览器;`nonfunc-perf-compat` 部分 skip → G-196-3 |
+| 14 | 可用性测试 | **未发现** | 无真实用户可用性证据(仅有 UI 可渲染断言) |
+| 15 | 无障碍测试 | **未发现** | 无 axe/WCAG 断言;本轮访客态对比度 4.83:1 系研发自查,非测试取证 → G-196-4 |
+| 16 | 覆盖率度量 | **待核实** | JaCoCo 未配置(`pom.xml` 无 jacoco 插件);仅 cfc-web 有历史 coverage 记录 ~9.95% |
+| 17 | 变异测试 | **未发现** | 无 PIT/pitest 依赖 → G-196-5 |
+| 18 | 缺陷度量 | **已发现** | `docs/tdd/test/blockers.md`(1424 行,本轮 +4 条);本报告 §7 |
+| 19 | 测试流程(QA) | **已发现** | 本报告 + `automation-run-2026-11-13-R196.md` + 门禁表 + 双 fetch/增量复验纪律 |
+| 20 | 全项目代码审查 | **已发现(抽样)** | 本轮静态审阅 `AssessmentAppointmentController/Service`(S-cfc-227)+ `cfc-frontend` 访客态三页 diff + `admin-tokens.css`;**非全量逐行审查** |
+
+---
+
+## 6. SRS / 场景变更同步(本轮强制动作)
+
+**变更**:`docs/superpowers/api/API_REFERENCE.md` SC-T-10 —— 「未到(status=4)无独立端点 → 缺口 G-1」改为「`POST /api/assessment/appointment/no-show/{id}`(S-cfc-227 落地,仅待确认/已确认可流转)」。
+
+**同步动作(已完成)**
+
+1. 台账 `tests/test-cases/deep-dive-assessment-appointment.md`:
+   - §2 状态机 `status=4` 由「待核实入口」改为 `noShowAppointment`,并补「四条守卫权威口径」;
+   - §3 接口清单新增 `no-show/{id}`(权限:仅 `guideId`);
+   - §4 缺陷表 F2 标 ✅ 已闭环、F3 标 🟡 部分闭环、**F1 标注「R196 复核仍未修」**;
+   - §5 新增 **TC-DV-11~14**(合法流转 / 终态拒绝 / 越权 / fail-closed);
+   - §8 新增「需求变更同步」小节,写明变更源、动作与可执行锚点。
+2. 补可执行用例:`cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherAppointmentNoShowBehaviorContractTest.java`(**8 例,定向实跑 8/8 绿,3.462s**)。
+
+**为什么必须补**:同批 `TeacherAppointmentGuardContractTest#g1_service_hasNoShowMethod` / `#g1_controller_hasNoShowEndpoint` **仅做反射存在性探测**(方法体写成 `return true` 亦绿),对状态机行为零断言 → 属「用例假绿」风险,本轮将其升级为行为契约。
+
+**其余 SRS**:`docs/superpowers/specs/` 204 份、`tests/test-cases/` 16 份、`tests/user-scenarios/` 11 份窗口零增减 → G12 ✅。
+
+---
+
+## 7. 问题清单(五要素,逐条)
+
+### P0-1|线上 API 全量 22 红(B-196-6,首次全量实测暴露)
+
+1. **问题描述**:`990 例 → 118 过 / 22 红 / 794 跳 / 56 未跑`。22 红中:`direct-register` 种子写入不可用 6、HTTP 500 六例、504/NON_JSON/aborted 四例、语义不符 6。
+2. **测试 bug 点**:`tests/e2e/api/{wish-flow-sc-p13-p14-c04,task-checkin-sc-c05,butler-fallback,badge-sc-c09-c10-a05,coach-food-sc-b07,contact-api,dimension-energy-contract,energy-api,energy-sc-p87-p88-c24,incentive-loop-sc-x05,task-c14-reject-recycle,task-frequency-sc-p12,wechat-login}.spec.js`;共性根因是**以生产环境做写路径造数**(`/api/auth/direct-register` 在生产被关)→ 属用例设计缺陷。
+3. **最优修改方案**:① 把写路径用例迁到专用测试环境(或提供 `CFC_API_BASE` 指向本地 9082 的 `write` 档),生产只跑只读/鉴权断言;② 对 `direct-register` 失败给出「SKIP + 原因」而非 FAIL(不可造数 ≠ 产品缺陷);③ 6 条语义红(并发兑换仅 1 成功、提醒渠道必填、积分必填、`id不能为空`)逐条交研发确认是「契约缺失」还是「用例预期错」。
+4. **修改目的**:让 API 全量套件成为**可信回归网**(当前 22 红噪声会掩盖真实回归,与 G11 同类问题)。
+5. **闭环**:改造后重跑全量,期望「红 = 0 或全部标注为已知且带 issue 号」;语义 6 例由研发逐条答复后转绿或改用例。
+
+### P1-1|CI 后端排除清单严重滞后,流水线必红(B-196-1)
+
+1. **问题描述**:`.gitlab-ci.yml:61` `BACKEND_OBSERVED_CLASSES` 列 33 类,本轮实测红类 **49** 类。CI 的 `backend-blocking-junit` 以 `-Dtest='!${BACKEND_OBSERVED_CLASSES}'` 只排除这 33 类,其余全跑 → 16 个红类必让流水线红。
+2. **测试 bug 点**:`.gitlab-ci.yml:61`。双向差集:**漏拦 42 类**(AICoachConsult、Admin*、Child*、Supplier*、CoachProactive、DimensionConfigAdmin、GrowthPublicEndpointMapping、MembershipPlanPurchase、MindModelManage、PlanActivationScT05、PlanContentSafetyReview、TaskCheckinChild、TaskReviewScP11、TeacherDataIsolationScT13、TeacherFamilyBind、TeacherPlanCreate、TeamProfitCron、UserServiceWechatLogin×2 …),**陈旧 26 类**(已转绿仍排除,遮蔽回归)。
+3. **最优修改方案**:由 CI 维护方把清单替换为**本轮 49 类实测快照**,并加一条流水线前置作业:跑全量后 `grep` surefire XML 生成红类清单,与 yml 清单做差集,差集非空即 `exit 1`(清单即事实)。
+4. **修改目的**:让「已知红」与「新增红」在 CI 层面可区分,恢复 CI 的回归拦截能力(当前 CI 红是噪声,会掩盖真实回归)。
+5. **闭环**:修后跑一次 `backend-blocking-junit`,期望 **BUILD SUCCESS**;下轮报告 G11 差集双向为 0 即闭环。
+
+### P1-2|langgraph 上游 LLM 配额/key 失效,10 例环境红(B-196-2)
+
+1. **问题描述**:51 例中 10 红(analyze 1 / auto_tag 5 / intent 4),日志实证 `429 rate_limit_exceeded`(AgnesAI 免费额度)+ `401 Authentication Fails, Your api key …`;同窗口 `/health` 200、源码零变更。
+2. **测试 bug 点**:`cfc-langgraph/tests/test_auto_tag.py`(5 例)、`tests/test_intent.py`(4 例)、`tests/test_analyze.py::test_analyze_missing_report`(1 例)。
+3. **最优修改方案**:运维轮换/升级 LLM key 与配额;测试侧把这三类用例改为**双档**:`QUOTA_OK=1` 时断言真实模型输出,未设时改用录音回放(VCR/mock)断言解析与降级契约,避免配额波动污染 G7。
+4. **修改目的**:把「上游可用性」与「本服务逻辑正确性」解耦,让 G7 只度量本服务质量。
+5. **闭环**:连续 2 轮 `pytest` ≥ 50/51 且日志无 429/401 → 销号 B-195-1;双档改造后 mock 档全绿即闭环逻辑面。
+
+### P1-3|`cancelAppointment` 无调用者身份校验(越权,F1 长期未修)
+
+1. **问题描述**:`POST /api/assessment/appointment/cancel/{id}` 仅接 `@PathVariable Long id`,任意登录用户凭 id 可取消他人预约。
+2. **测试 bug 点**:`AssessmentAppointmentController#cancelAppointment:112-116` + `AssessmentAppointmentService#cancelAppointment`(仅 status 守卫,无归属校验);台账 TC-DV-01 长期红。
+3. **最优修改方案**:controller 加 `@RequestAttribute("userId") Long userId`,service 增 `cancelAppointment(id, userId)`:`userId.equals(a.getUserId()) || userId.equals(a.getGuideId()) || userId.equals(a.getAssessorId())` 才放行,否则 `Result.error`。
+4. **修改目的**:消除跨家庭数据篡改面(与 SC-T-13 数据隔离、B-142-1 鉴权域同源)。
+5. **闭环**:补 2 例(非归属者取消 → 拒绝且不写库;归属者取消 → 成功),TC-DV-01 复测拒绝即闭环。
+
+### P2-1|根级黑盒口径错误:仓库根 `npx jest` 不可用(B-196-3)
+
+1. **问题描述**:在仓库根直接 `npx jest` 会扫描全仓 → `jest-haste-map` 命名冲突(`.wt-f3c/package.json`)+ `xiaofat-miniprogram/package.json` JSON 解析失败,且会误跑子项目套件(本轮实测空转 12 分钟无结果)。
+2. **测试 bug 点**:无根级 `jest.config.js`;正确入口是 `tests/jest.config.js`(23 例 / 21.0s)。
+3. **最优修改方案**:在根 `package.json` 补 `"test:root": "jest -c tests/jest.config.js"`,并在 `tests/AGENTS.md` 写明「根级黑盒必须带 `-c tests/jest.config.js`」;同时把 `xiaofat-miniprogram`、`.wt-f3c` 加入根 `.gitignore`/`testPathIgnorePatterns`。
+4. **修改目的**:杜绝口径漂移与误判「套件超时」,保证 B-36 的 7/16 基线可比。
+5. **闭环**:`npm run test:root` 一次出 `23 例 / 7 过 / 16 红` 即闭环。
+
+### P2-2|匿名端点探针路径错误导致鉴权度量假红/假绿(B-196-4)
+
+1. **问题描述**:历轮用 `/api/xiaomi/status`、`/api/huawei/status` 作匿名探针,本轮实测两条 **404**(路径不存在),真实路径为 `/api/xiaomi-health/status`、`/api/huawei-health/status`(类级 `@RequestMapping("/api/xiaomi-health")`)→ 4 报 401 + 2 报 404 时仍被记成「6/6=401」,度量含误。
+2. **测试 bug 点**:报告/脚本中的探针清单(非代码资产,属报告口径)。与 TC-A-N05、TC-P-N11 同类(探针/路径错配)。
+3. **最优修改方案**:把匿名端点清单固化到 `docs/tdd/tools/anon_probe.sh`,路径一律由控制器类级 `@RequestMapping` 推导;断言 = `HTTP 401 且 body.code=401`,404 单列为「路径不存在」而非鉴权通过。
+4. **修改目的**:让 G8 的 6/6 可由脚本一键复现,避免探针漂移。
+5. **闭环**:脚本跑出 `6/6=401` 且 404 计数为 0 → 闭环。
+
+### P2-3|251 无 langgraph HTTP 服务,live 链路面不可测(B-196-5)
+
+1. **问题描述**:`ss -ltnp` 显示 251 仅 `:80`、`:4096`(opencode);`:9000` 无监听 → 历轮「langgraph live `/health` 200」实为 opencode 端口回包,非被测服务。
+2. **测试 bug 点**:历轮报告「live 探活」章节口径(把 opencode 4096 当 langgraph 服务)。
+3. **最优修改方案**:若需链路面实测,在 251 起 `uvicorn app.main:app --port 9000` 并纳管(systemd/supervisor);否则报告统一写「langgraph 仅 in-process(TestClient)实测,链路面未实测」。
+4. **修改目的**:避免把「单测绿」误读为「线上可用」,也避免把「opencode 200」误读为「langgraph 服务健康」。
+5. **闭环**:`curl 251:9000/health` 返回业务 JSON 且 `/api/v1/chat` 200 → live 面转「已测」;否则维持「未实测」标注。
+
+### P2-4|`TeacherAppointmentGuardContractTest` G-1 用例仅做存在性探测
+
+1. **问题描述**:`g1_service_hasNoShowMethod` / `g1_controller_hasNoShowEndpoint` 只断言「方法与注解存在」,不校验状态机行为;实现写空壳也绿 → 存在「用例假绿」风险(本轮已补 8 例行为断言作为补救)。
+2. **测试 bug 点**:`cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherAppointmentGuardContractTest.java:331-370`。
+3. **最优修改方案**:保留存在性用例作为快速失败哨兵,另建行为类(本轮已建 `TeacherAppointmentNoShowBehaviorContractTest`,8 例);后续同类「缺口探测」用例一律配一条行为断言。
+4. **修改目的**:让「缺口闭环」由行为证据背书,而不是由符号存在背书。
+5. **闭环**:行为类 8/8 绿(本轮已达成,定向 3.462s)。
+
+---
+
+## 8. 覆盖缺口清单
+
+| 编号 | 缺口类型 | 现状 | 风险 | 建议测试设计 | 优先级 | 闭环方式 |
+|---|---|---|---|---|---|---|
+| G-196-1 | 探索性测试 | 无 charter/会话记录 | 未知缺陷发现能力弱 | 每轮 1 个 30min charter,记录于 `docs/tdd/test/exploratory/` | P2 | 连续 2 轮有记录即闭环 |
+| G-196-2 | 验收 UAT/BDD | 无 feature/UAT 脚本 | 业务验收无据 | 对 SC-T-09/SC-T-10 等核心场景写 5 条 Gherkin | P2 | 5 条 feature 可执行即闭环 |
+| G-196-3 | 兼容性 | 仅 chromium | 移动端真机/Safari 盲区 | 加 `--project=webkit` + 2 个真机抽样 | P2 | webkit 套件全绿即闭环 |
+| G-196-4 | 无障碍 | 无 axe 断言 | WCAG 回归无守护 | 对 /frontend 三页接 `@axe-core/playwright`,断言 critical=0 | P2 | 三页 critical=0 即闭环 |
+| G-196-5 | 变异测试 | 无 PIT | 断言强度不可度量 | 对 `unit/` 抽 2 个类跑 PIT,目标 mutation score ≥60% | P3 | 首次基线产出即闭环 |
+| G-196-6 | 覆盖率 | JaCoCo 未配置 | 覆盖率不可度量(G9 只能用锚点率代理) | `cfc-backend` 加 jacoco-maven-plugin,`test` 阶段出 XML | P1 | 首份 `jacoco.xml` 产出即闭环 |
+| G-196-7 | 性能压测 | 未授权联网 | 容量未知 | 授权后对 3 个核心接口做 50/100/200 并发阶梯 | P1 | 出基线报告即闭环 |
+| G-196-8 | 前瞻契约 240 红 | 研发已声明 RED | 拉低 G1 至 92.8% | 按类分批「使绿」,每批 ≤30 红 | P1 | 红例逐批下降到 0 |
+| G-196-9 | 根级黑盒 16 红 | 9082 未起 | 黑盒链路无守护 | 本地常驻 `cfc-backend`(或 CI service) | P2 | 16 红转绿即闭环 |
+| G-196-10 | dx 端点 404 | 规格「仅登记不实现」 | 2 例长期 skip | 规格标 `blocked: pending implementation`,实现后自动转断言 | P3 | 端点上线即闭环 |
+| G-196-11 | API 全量 22 红 | 含 6 例生产造数不可用 | 全量套件不可信 | 见 §7 P0-1(迁测试环境 + 不可造数改 SKIP + 语义 6 例交研发) | **P0** | 红=0 或全部带 issue 号 |
+
+---
+
+## 9. 域结论表
+
+| 审查维度 | 负责专家 | 结论 | 关键证据 |
+|---|---|---|---|
+| 单元·组件级 | 苏雅 | **不通过**(92.80%,红 265) | surefire 423 类 / 3737 例;49 红类三层归因;新增 8 例行为契约全绿 |
+| 集成·接口级 | 柯砚 | **不通过**(全量 22 红) | 集成类 4 类红(存量);点名 32 例 0 红 / 7 跳;**全量 990 例 22 红**(6 造数不可用 + 4 网关 + 6 语义 + 6×500) |
+| 系统·验收级 | 白樾 | **有条件通过** | E2E 28 过 + 2 跳;UAT/BDD 缺口 G-196-2 |
+| 测试保障级 | 闻朔 | **通过(有限)** | 冒烟 4/4;回归 4621 例;探索性缺口 G-196-1 |
+| 性能·可靠性 | 骆骋 | **未实测(降级)** | 压测未授权;可靠性有实证缺陷 B-177-1;慢类 Top8 已记录 |
+| 安全 | 关屿 | **不通过** | 匿名 6/6=401 ✅;但 `cancelAppointment` 越权未修(P1-3);SAST/DAST 未授权 |
+| 兼容·体验 | 阮清 | **未实测(降级)** | 单浏览器;无 axe/WCAG;可用性无证据 |
+| 流程·度量·代码 | 何序 | **不通过** | CI 清单漏拦 42 / 陈旧 26;锚点率 20.8%;JaCoCo 缺失;代码审阅见 P1-3 |
+
+**冲突裁决**:柯砚「接口有条件通过(0 红)」与关屿「安全不通过(越权)」冲突时**从严重**——采信关屿,因为越权用例在接口层为「200 成功」形态,接口断言无法识别为缺陷,必须由安全维度判定。
+
+---
+
+## 10. 门禁结论
+
+| 门禁 | 阈值 | 实测 | 判定 |
+|---|---|---|---|
+| G1 全量通过率 | ≥95% | **93.29%**(剔 skip/未跑;含 skip 78.78%;剔预期 RED 99.2%) | ❌ |
+| G2 红例数 | 0 | **316**(后端 265 / API 22 / 根级 16 / fe 3 / lg 10) | ❌ |
+| G3 浏览器 E2E | 全绿 | **28 过 + 2 跳** | ✅ |
+| G4 编译 | 0 错 | main 2121 + test 430 源 0 错 | ✅ |
+| G5 surefire XML 完整 | 100% | **423/423**,无沉默类 | ✅ |
+| G6 浏览器流水线 | 全通过 | 5 套件 EXIT=0 | ✅ |
+| G7 langgraph | ≥50/51 | **41/51**(环境红) | ❌ |
+| G8 匿名端点鉴权 | 6/6=401 | **6/6=401**(真实路径复核) | ✅ |
+| G9 用例锚点率 | ≥60% | **180/865 = 20.8%** | ❌ |
+| G10 多节点可比 | ≥2 节点 | 189 + 本机 + 251 + 204 | ✅ |
+| G11 CI 清单一致 | 差集=0 | 漏拦 **42** / 陈旧 **26** | ❌ |
+| G12 SRS 变更同步 | 变更即更新用例 | 本轮 API_REFERENCE 变更 → 台账 +4 TC + 8 例实测 | ✅ |
+| G13 存量零漂移 | 逐类逐例一致 | 13 类 / 25 例,**第 28 次** | ✅ |
+| G14 场景映射 | ≥90% | **156/215 = 72.6%** | ❌ |
+
+### ⛔ 门禁结论:**不通过(有条件)**
+
+- **无新增确定性回归**:所有红例可归因到「研发已声明的 TDD 前瞻契约(240)」或「存量装置/数据(25)」或「环境(langgraph 上游配额 10 / 网关 5xx 4)」或「API 全量首测暴露(22,其中 6 为用例造数缺陷)」,**没有一类是本轮新引入的功能回归**。
+- **正向变化**:S-cfc-227 使绿(23 例)、后端红 272→265、langgraph 41/51、覆盖矩阵新增 8 例行为契约、SRS 变更完成「变更→台账→用例→实测」闭环。
+- **放行建议**:可作为「持续集成可继续推进」的判断,**不建议**据此宣布质量达标;复评门槛见 §11。
+
+---
+
+## 11. 复评门槛(优先级降序)
+
+| # | 事项 | 责任 | 优先级 |
+|---|---|---|---|
+| 1 | langgraph key/配额恢复(429/401 → 连续 2 轮 ≥50/51) | 运维/研发 | **P0** |
+| 2 | API 全量 22 红分流(造数不可用改 SKIP / 语义 6 例研发答复) | 研发/测试 | **P0** |
+| 3 | `cancelAppointment` 补归属校验(F1 越权) | 研发 | **P0** |
+| 4 | CI 排除清单改为实测快照 + 差集守卫 | 研发/CI | **P1** |
+| 5 | 240 前瞻契约红分批使绿(每批 ≤30) | 研发 | **P1** |
+| 6 | JaCoCo 接入(G9 代理指标落地) | 研发 | **P1** |
+| 7 | 存量 13 类 25 红归因(EnergySystemFlow 6 / DimensionRegistryMap 3 / GrowthPublic 5E) | 研发/测试 | **P1** |
+| 8 | 根级黑盒 16 红(9082 常驻) | 测试/运维 | P2 |
+| 9 | 锚点率 20.8% → 60%(台账与锚点双向对齐) | 测试 | P2 |
+| 10 | 探索性/UAT/兼容/无障碍四类缺口补齐 | 测试 | P2 |
+| 11 | dx 端点派发实现 | 研发 | P3 |
+
+---
+
+## 附录 A:本轮命令与口径
+
+```bash
+# 后端全量(工程根必须是 cfc-backend)
+cd cfc-backend && JAVA_HOME=/d/jdk1.8.0_25 /d/maven/apache-maven-3.6.1/bin/mvn.cmd -o -B clean test   # 51:42
+python docs/tdd/tools/test_report_metrics.py --reports cfc-backend/target/surefire-reports --out-json _r196_agg.json
+# → classes=423 tests=3737 pass=3468 F=259 E=6 S=4 rate=92.80% red_classes=49 red_cases=265
+
+# 补测(新增行为契约,定向)
+mvn -o -B test -Dtest=TeacherAppointmentNoShowBehaviorContractTest     # 8/8 GREEN 3.462s
+
+# 浏览器(每节点单任务)
+node docs/tdd/tools/dispatch_189_admin.cjs                              # 189 admin-desktop 5/5 EXIT=0
+cd tests && npx playwright test -c playwright.config.live.js --project=frontend-mobile --workers=1   # 6/6
+cd tests && npx playwright test -c playwright.config.live.js --project=shop-api --workers=1          # 4/4
+cd tests && npx playwright test -c playwright.config.live.js --project=dx-bridge --workers=1         # 2 skipped
+cd tests && npx playwright test -c playwright.config.health.js --workers=1                          # 13/13
+
+# Jest
+cd cfc-web && npx jest          # 116/116
+cd cfc-frontend && npx jest     # 624 = 609/3/12
+npx jest -c tests/jest.config.js  # 23 = 7/16(9082 未起)
+
+# 线上 API(点名 4 文件)
+cd tests && npx playwright test e2e/api/api-smoke.spec.js e2e/api/nonfunc-security.spec.js \
+  e2e/api/nonfunc-perf-compat.spec.js e2e/api/traceability-p94-p95-c26.spec.js \
+  -c playwright.config.api.js --project=desktop --workers=1     # 32 = 25/0/7
+
+# 线上 API 全量(含 generated,990 例)
+cd tests && npx playwright test -c playwright.config.api.js --project=desktop --reporter=list --workers=1
+# → 118 passed / 22 failed / 794 skipped / 56 did not run (14.5m)
+
+# langgraph@251
+MSYS2_ENV_CONV_EXCL=LOG node docs/tdd/tools/dispatch_251_pytest.cjs kick
+MSYS2_ENV_CONV_EXCL=LOG node docs/tdd/tools/dispatch_251_pytest.cjs read   # 10F/41P/117.54s
+```
+
+**口径声明**:后端例数以 surefire XML 聚合为唯一权威;浏览器例数以 `Running N tests` + summary 双校验;API 全量例数 990 含 `generated/tc-scripted.spec.js`(B-173-2)。
+
+## 附录 B:线上 API 全量(990 例,本轮首次纳入)
+
+```
+cd tests && npx playwright test -c playwright.config.api.js --project=desktop --reporter=list --workers=1
+Running 990 tests using 1 worker  →  118 passed / 22 failed / 794 skipped / 56 did not run (14.5m)
+```
+
+- 794 跳主要来自 `e2e/api/generated/tc-scripted.spec.js`(按 `api-routes.json` 生成的占位用例,端点或前提未满足即 skip)→ 属「形状用例」,计入总数但不计入通过率。
+- 22 红详见 §4.4 与 §7 P0-1;其中 **6 例为 `direct-register` 生产不可用**(用例设计缺陷),**4 例为网关 5xx/非 JSON**(环境),**6 例为语义契约待研发确认**,其余为各域 500。
+
+## 附录 C:未实测项(如实声明)
+
+- 压力测试 / 容量测试:**未授权联网,未执行**
+- SAST / DAST / 依赖漏洞扫描:**未授权,未执行**
+- JaCoCo 行覆盖率 / PIT 变异:**本机未配置,未实测**
+- 多浏览器(firefox/webkit)/ 真机兼容:**未执行**
+- 无障碍(axe/WCAG 自动扫描):**未执行**
+- UAT / BDD:**无资产,未执行**
+- 探索性测试:**无 charter,未执行**
+- langgraph 链路面(live HTTP):**251 无 9000 服务,未实测**(B-196-5)
+- 全项目逐行代码审查:**仅抽样**(appointment 变更 + 访客态三页 + admin tokens)

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

@@ -1422,3 +1422,21 @@
 | **S-cfc-239~245(RED 前瞻确认)** | supplier.* + ai.coach.* | 研发(P1) | 窗口 7 类:SupplierInventoryManage 12/6、SupplierAfterSalesHandle 6/4、SupplierActivityAttend 9/5、SupplierNutritionSupply 13/13、SupplierExportCompliance 6/5、AICoachConsult 16/5、CoachProactive 25/24 —— **全部与提交声明预期红数逐条吻合**,待研发实现使绿。 | 测试(R195 全量实测) | 2026-11-13 22:45 | 待研发实现使绿 |
 | **S-cfc-201~209(RED 前瞻确认)** | admin.* 系列 | 研发(P1) | AdminArticleManage 24/6、AdminOrderManual 24/5、AdminActivityConfig 5/5、AdminDataMigration 10/8、AdminBadgeManage 11/5、AdminUserGovern 9/7、AdminSystemMonitor 7/5 —— 与声明一致,待研发实现使绿。 | 测试(R195 全量实测) | 2026-11-13 22:45 | 待研发实现使绿 |
 | **R195 门禁** | 全量 **3725 / 3453** | 测试 | **不通过(承袭·有条件)**。✅G3 浏览器 28/28 + 2 跳 / G4 编译 0 错 / G5 XML 完整 / G6 流水线 / G8 匿名 6/6=401 / G10 双节点 / G12 SRS 零变更 / G13 存量零漂移 78+ 轮;❌G1 92.70% /G2 红例 167(后端 272 + fe 3 + lg 11 + 根级 16 + API 18)/G7 40/51/G9 锚点 ~22% /G11 CI 陈旧 /G14 ~72%。**变化**:后端因 24 新契约类预期红降至 92.70%(真实红持平 ~26);langgraph 4→11 环境红恶化;浏览器/Jest 零漂移。 | 测试(R195 闭环) | 2026-11-13 22:45 | 已记录 |
+
+## R196 段(2026-11-14 01:30,HEAD `b44051e3f`,后端全量 51:42 + API 全量首测)
+
+| 编号 | 关联键/对象 | 类型 | 描述与处置 | 经办 | 登记时间 | 状态 |
+|---|---|---|---|---|---|---|
+| **B-196-1(P1 新增)** | `.gitlab-ci.yml:61` CI 排除清单 | 研发/CI | 清单 33 类 vs 实测 **49 红类** → **漏拦 42 类**(AICoachConsult/Admin* 9 类/Child* 11 类/Supplier* 6 类/CoachProactive/DimensionConfigAdmin/GrowthPublicEndpointMapping/MembershipPlanPurchase/MindModelManage/PlanActivationScT05/PlanContentSafetyReview/TaskCheckinChild/TaskReviewScP11/TeacherDataIsolationScT13/TeacherFamilyBind/TeacherPlanCreate/TeamProfitCron/UserServiceWechatLogin×2)+ **陈旧 26 类** → `backend-blocking-junit` 必红(G11 ❌)。建议清单改为实测快照 + 差集守卫 | 测试(R196 全量实测) | 2026-11-14 01:30 | 待处理(P1) |
+| **B-196-2(P0 延续 B-195-1)** | langgraph 上游 LLM 配额/key | 运维/研发 | 51 例 **41 过 / 10 红**(117.54s),红 = analyze 1 + auto_tag 5 + intent 4;日志 **429 rate_limit_exceeded(AgnesAI)+ 401 Authentication Fails api key** ×多;源码零变更 → 环境红 | 运维/研发 | 2026-11-14 01:30 | 待轮换 key/升配额(P0) |
+| **B-196-3(P2 工具/口径)** | 根级 Jest 入口 | 测试 | 仓库根裸跑 `npx jest` → jest-haste-map 命名冲突(`.wt-f3c/package.json`)+ `xiaofat-miniprogram/package.json` JSON 解析失败,并误扫全仓(本轮空转 12 分钟);正确入口 **`npx jest -c tests/jest.config.js` → 23 例 / 7 过 / 16 红 / 21.0s** | 测试(R196) | 2026-11-14 01:30 | 待修(补 `test:root` 脚本 + 忽略目录) |
+| **B-196-4(P2 度量)** | 匿名端点探针路径 | 测试 | 历轮探针 `/api/xiaomi/status`、`/api/huawei/status` 实测 **404**(路径不存在),真实为 `/api/xiaomi-health/status`、`/api/huawei-health/status`(类级 `@RequestMapping`)→ 「6/6=401」曾含误判。本轮按真实路径复核 **6/6=401 且 body.code=401**,404 单列 | 测试(R196) | 2026-11-14 01:30 | 已更正口径,建议脚本化 |
+| **B-196-5(P2 部署)** | 251 langgraph HTTP 服务 | 部署/运维 | `ss -ltnp` 显示 251 仅 `:80`、`:4096`(opencode),**`:9000` 无监听** → 历轮「langgraph live `/health` 200」实为 opencode 端口误读;**链路面不可实测**,只能 in-process(pytest TestClient) | 部署/运维 | 2026-11-14 01:30 | 待派发(如需 live 测需纳管 uvicorn) |
+| **B-196-6(P0 新增)** | 线上 API 全量 22 红 | 研发/测试 | 首次全量:**990 = 118 过 / 22 红 / 794 跳 / 56 未跑**(14.5m)。22 红 = `direct-register` 造数不可用 6(用例设计缺陷)+ HTTP 500 6 + 504/NON_JSON/aborted 4(网关)+ **语义不符 6**(并发兑换应仅 1 成功、`至少选择一个提醒渠道`、`请设置所需积分`、`id不能为空`、`申请兑换失败`)。涉及 wish-flow 6 / task-checkin 3 / butler-fallback 2 等 13 份规格 | 研发/测试 | 2026-11-14 01:30 | 待分流(P0) |
+| **B-177-1(第 18 轮复现)** | 251 pytest 收尾不退出 | 研发/测试 | summary(`10 failed, 41 passed`)已打印但 `EXIT=` 未落盘;`ps` 见 2 个 pytest 进程存活(本轮 4m08s + 另有一枚 38m13s 残留)→ `kill -9` 后 `ps`=0、`:4096/health` 200/3.9ms。解除:`app/main.py:84` shutdown 补 `await client.aclose()` + 派发器加 `timeout -k 10 900` | 研发/测试 | 2026-11-14 01:30 | 已清理,待复验 |
+| **S-cfc-227(使绿确认)** | teacher.appointment.guard + no-show | 研发(已实现) | confirm/complete/cancel 状态守卫 + `noShowAppointment` + `POST /no-show/{id}` 全部落地;`TeacherAppointmentGuardContractTest` **15/15 绿**;测试侧补 `TeacherAppointmentNoShowBehaviorContractTest` **8/8 绿**(定向 3.462s) | 测试(R196 定向实测) | 2026-11-14 01:30 | **已闭(G-1 缺口闭环)** |
+| **F1(台账长期项)** | `cancelAppointment` 越权 | 研发(P0) | R196 复核**仍未修**:controller 依旧 `cancelAppointment(@PathVariable Long id)`,无调用者身份 → 任意登录用户可取消他人预约。台账 §4 已标注 + 本轮新增 §8 说明 | 研发 | 2026-11-14 01:30 | 待修(P0) |
+| **B-142-1(销号维持第 24 轮)** | 匿名端点鉴权 | 研发(已闭环) | 6 端点全 **401 `请先登录`**(按真实路径复核:mind/energy/health-plan/xiaomi-health/huawei-health/admin-families) | 测试(R196) | 2026-11-14 01:30 | 已闭(观察维持) |
+| **B-36(第 79 轮零漂移)** | 根级黑盒 23 例 16 红 | 测试(P2 环境) | `npx jest -c tests/jest.config.js` → **7 过 / 16 红**(21.0s),全 `ECONNREFUSED 127.0.0.1:9082`(本机后端未起) | 测试 | 2026-11-14 01:30 | 待处理 |
+| **B-DX-1(第 35 轮)** | dx-bridge 端点 404 | 部署/研发(P1) | 两端点 404/404 → 2 skip(规格「仅登记不实现」) | 部署/研发 | 2026-11-14 01:30 | 待处理 |
+| **R196 门禁** | 全量 **5579 / 4395** | 测试 | **不通过(有条件)**。✅G3 浏览器 28+2 跳 / G4 编译 0 错 / G5 XML 423/423 / G6 EXIT=0 / G8 6/6=401 / G10 三节点 / G12 **SRS 变更已同步**(台账 +4 TC + 8 例)/ G13 零漂移**第 28 次**;❌G1 **93.29%**(剔 skip/未跑)/G2 **316 红**(后端 265 + API 22 + 根级 16 + fe 3 + lg 10)/G7 41-51/G9 20.8%/G11 漏拦 42 陈旧 26/G14 72.6%。**变化** = API 全量首纳(+990 例 / +22 红)、后端 −7 红、lg 41/51、S-cfc-227 使绿 23 例、访客态三页增量复验零回归 | 测试(R196 闭环) | 2026-11-14 01:30 | 已记录 |

+ 34 - 5
tests/test-cases/deep-dive-assessment-appointment.md

@@ -33,7 +33,12 @@
 | 1 | 已确认 | `confirmAppointment`(`service` 第 63 行) |
 | 2 | 已完成 | `completeAppointment`(`service` 第 73 行) |
 | 3 | 已取消 | `cancelAppointment`(`service` 第 91 行) |
-| 4 | 未到 | 代码中无 setter 调用(**待核实**由何入口置 4) |
+| 4 | 未到 | `noShowAppointment`(`service`,`POST /api/assessment/appointment/no-show/{id}`)— **S-cfc-227 落地,2026-11-13** |
+
+> **状态机守卫(S-cfc-227 `ab349b35a`,2026-11-13 起为权威口径)**:
+> confirm 仅 `0→1`;complete 仅 `1→2`;cancel 仅 `0/1→3`;no-show 仅 `0/1→4`。
+> 其余跃迁(含重复操作、终态回退)一律拒绝且**不写库**(`status=null` 也 fail-closed)。
+> 证据:`TeacherAppointmentGuardContractTest`(15 例)+ `TeacherAppointmentNoShowBehaviorContractTest`(8 例,R196 补行为面)。
 
 能量发放:`create` 时给 member +10(`service` 第 48 行);`complete` 时给 childId +30(`service` 第 78 行);均 `try/catch` 吞异常仅打 warn。
 
@@ -43,7 +48,8 @@
 |---|---|---|
 | `POST /api/assessment/appointment/create` | 需 `userId`(JWT),`memberId+quotaId` 必填,`assessorId`∨`guideId` 必填 | 消耗额度、建执行记录、发 10 能量 |
 | `POST /api/assessment/appointment/confirm/{id}` | `userId` 须等于被指派者(`assessorId` 优先,否则 `guideId`) | 消息「您不是被指派的测评师」 |
-| `POST /api/assessment/appointment/cancel/{id}` | **仅 `@PathVariable Long id`,无 `userId`/`role`** | ⚠️ 无归属校验 |
+| `POST /api/assessment/appointment/cancel/{id}` | **仅 `@PathVariable Long id`,无 `userId`/`role`** | ⚠️ 无归属校验(F1 仍未修,R196 复核) |
+| `POST /api/assessment/appointment/no-show/{id}` | `userId` 须等于 `guideId`(仅成长规划师) | **S-cfc-227 新增**;置 `status=4`;非 guide 或被拒时返回「标记未到失败:预约状态不允许流转或您不是该预约的成长规划师」 |
 | `POST /api/assessment/appointment/my-list` | `userId` | 家长视角 |
 | `POST /api/assessment/appointment/guide-list` | `role == "teacher"` | 非 teacher 拒绝 |
 | `POST /api/assessment/appointment/assessor-list` | `userId` | 无 role 校验(仅按自身 userId 查,影响面有限) |
@@ -53,8 +59,8 @@
 | 编号 | 严重度 | 位置 | 现象 | 期望 |
 |---|---|---|---|---|
 | F1 | **高(越权)** | `cancelAppointment` controller 第 112–116 行 / service 第 88–94 行 | 取消接口不接收调用者身份,**任何登录用户凭 id 即可取消他人预约** | 取消须校验 `userId` 为预约归属者或被指派者 |
-| F2 | 中 | `confirmAppointment` service 第 57–66 行 | 仅校验「是否是被指派者」,**不校验当前状态**;对已取消/已完成预约再 confirm 仍置 1 | 增加状态守卫:仅 `status=0` 可确认;非法跃迁返回错误 |
-| F3 | 中 | `completeAppointment` service 第 68–86 行 | 置 `status=2` 的方法**在本控制器无对应接口**;`status=4(未到)` 全代码无 setter | 确认「已完成/未到」的真实入口;补齐或说明 |
+| F2 | 中 | `confirmAppointment` service | 仅校验「是否是被指派者」,**不校验当前状态**;对已取消/已完成预约再 confirm 仍置 1 | 增加状态守卫:仅 `status=0` 可确认;非法跃迁返回错误 —— **✅ 已闭环(S-cfc-227,R196 复验 15/15 绿)** |
+| F3 | 中 | `completeAppointment` / 全代码 | 置 `status=2` 的方法在本控制器无对应接口;`status=4(未到)` 全代码无 setter | 补齐或说明入口 —— **🟡 部分闭环**:未到已补 `no-show/{id}`(S-cfc-227);完成仍走 `DanAssessmentController /appointment/complete`,无本控制器入口(设计如此,已在 API_REFERENCE SC-T-10 登记) |
 | F4 | 中 | `createAppointment` service 第 27–55 行 | **无幂等**:同 `quotaId/memberId` 重复提交会建 2 条预约并发 2 次 10 能量 | 增加「同额度/成员已有待确认预约则拒绝或返回既有」 |
 | F5 | 低 | `assessor-list` 第 140–146 行 | 无 role 校验(与 `guide-list` 不一致) | 统一鉴权策略(影响小,按自身 userId 隔离) |
 | F6 | 低 | `create` 第 50–51 行 | `appointmentDate/Time` 为自由字符串,**无格式/未来时间校验** | 增加日期格式与合理性校验 |
@@ -67,7 +73,7 @@
 | TC 编号 | 所属场景 | 模块 | 角色 | 优先级 | 类型 | 前置条件 | 测试步骤 | 预期结果 | 关联发现 |
 |---|---|---|---|---|---|---|---|---|---|
 | TC-DV-01 | P-7(修正) | 预约/权限 | 家长A vs 家长B | P0 | 权限 | A、B 各登录;A 有预约 id=X | B 调用 `POST /cancel/{X}` | **当前**:返回 success 且 status=3(越权成功)→【缺陷 F1】;期望:403/拒绝 | F1 |
-| TC-DV-02 | P-7 | 预约/状态机 | 规划师 | P1 | 异常 | 预约已 cancelled | 对该 id 调 `confirm` | **当前**:status 置 1(非法回退)→【缺陷 F2】;期望:失败 | F2 |
+| TC-DV-02 | P-7 | 预约/状态机 | 规划师 | P1 | 异常 | 预约已 cancelled | 对该 id 调 `confirm` | **R196 现状:拒绝且不写库(F2 已修)**;由 `TeacherAppointmentGuardContractTest#cancel_afterNoShow_rejected` / `confirm_duplicateReconfirmation_rejected` 覆盖 | F2✅ |
 | TC-DV-03 | P-7 | 预约/额度 | 家长 | P0 | 功能 | 无可用额度 | `POST /create`(memberId+quotaId 无效) | 返回「额度不足或已过期」;不建预约、不扣能量 | 修正口径 |
 | TC-DV-04 | P-7 | 预约/幂等 | 家长 | P1 | 异常 | 有 1 条有效额度 | 同一 quotaId 连续 `create` 两次 | **当前**:2 条预约 + 20 能量 →【缺陷 F4】;期望:第 2 次拒绝/返回既有 | F4 |
 | TC-DV-05 | T-7 | 预约/权限 | 非指派用户 | P0 | 权限 | 预约指派给规划师 T | 其他人调 `confirm/{id}` | 返回「您不是被指派的测评师」;status 不变 | 既有 |
@@ -76,6 +82,10 @@
 | TC-DV-08 | P-7 | 预约/校验 | 家长 | P2 | 异常 | — | `create` 传过去日期/乱码时间 | **当前**:无校验入库(F6);期望:400 | F6 |
 | TC-DV-09 | P-7 | 预约/状态机 | 规划师 | P1 | 功能 | 新预约 status=0 | `confirm`→`complete` | 0→1→2 正确;能量 10+30 发放且流水一致 | 状态机 |
 | TC-DV-10 | P-7(修正) | 订单↔额度↔预约 | 家长 | P0 | 功能 | 购买套餐(订单 paid) | 订单 paid 后生成额度 → `create` 消耗额度 | 额度扣减正确;额度为 0 时 `create` 失败 | 修正口径 |
+| TC-DV-11 | SC-T-10 | 预约/未到 | 成长规划师 | P1 | 功能 | 预约 status=0 或 1,guideId=T | `POST /no-show/{id}`(T 本人) | 成功;status=4;不发放/不回收能量;写库一次 | S-cfc-227 |
+| TC-DV-12 | SC-T-10 | 预约/未到守卫 | 成长规划师 | P1 | 异常 | 预约 status=2/3/4 | 对终态预约调 `no-show/{id}` | 拒绝(`code≠200`);status 不变;**不写库**(幂等) | S-cfc-227 |
+| TC-DV-13 | SC-T-13 | 预约/未到越权 | 非该预约规划师 | P0 | 权限 | 预约 guideId=T1 | T2 调 `no-show/{id}` | 拒绝;status 不变;不写库 | S-cfc-227 |
+| TC-DV-14 | SC-T-10 | 预约/未到边界 | 系统 | P2 | 异常 | 预约不存在 / status=null | 调 `no-show/{id}` | 返回 false(fail-closed);不写库、不 500 | S-cfc-227 |
 
 ## 6. 给研发的修复建议(五要素摘要)
 
@@ -100,3 +110,22 @@
 5. **自注册可用**:`/api/auth/direct-register`(测试专用)返回 `code=200` 并自动建家庭(familyId);可作为 e2e 的「写种子数据」手段,规避 globalSetup 对 `192.168.16.251` 的依赖。
 
 > 校正结论已沉淀为:`tests/defects/DEFECTS-assessment-appointment.md`(F1–F8 缺陷单)、`tests/e2e/assessment-appointment-functional.spec.js`(可跑规格)、`tests/playwright.config.func.js`(去 globalSetup 的独立配置)。
+
+---
+
+## 8. 需求变更同步(R196 · 2026-11-13 · S-cfc-227 落地后更新用例)
+
+**变更源**:`docs/superpowers/api/API_REFERENCE.md`(SC-T-10 由「未到无独立端点 → 缺口 G-1」改为「`POST /api/assessment/appointment/no-show/{id}`,S-cfc-227 落地」)+ 提交 `ab349b35a`(`AssessmentAppointmentController` +12 行 / `AssessmentAppointmentService` +26 行)。
+
+**本次同步动作**
+
+1. §2 状态机表 `status=4` 由「待核实入口」改为「`noShowAppointment`(`no-show/{id}`)」,并补全四条守卫的权威口径。
+2. §3 接口清单新增 `no-show/{id}` 行(权限:仅 `guideId`)。
+3. §4 缺陷表:F2 标 ✅ 已闭环;F3 标 🟡 部分闭环;**F1(取消越权)经 R196 复核仍未修**(controller 仍为 `cancelAppointment(@PathVariable Long id)`,无调用者身份)。
+4. §5 新增 TC-DV-11~14 四类用例(合法流转 / 终态拒绝 / 越权 / fail-closed)。
+
+**可执行锚点(新增,行为面)**:`cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherAppointmentNoShowBehaviorContractTest.java`
+(8 例:pending→4、confirmed→4、completed 拒绝、cancelled 拒绝、重复标记拒绝、非 guide 拒绝、不存在返回 false、status=null fail-closed;断言含「拒绝时 `verify(mapper, never()).updateById`」)。
+
+> 说明:原 `TeacherAppointmentGuardContractTest#g1_service_hasNoShowMethod` / `#g1_controller_hasNoShowEndpoint` 仅做反射存在性探测(方法写成 `return true` 亦绿),**不能替代行为断言**;本轮据此把 G-1 从「存在性」升级为「行为契约」,是「同源变更→用例更新」的强制动作。
+