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

test(r94): 测试负责人汇报 2026-11-09 00:30 — 后端首次全量 2664 例 98.01%(20红类/49红例/0环境红) + 签名横屏 B-75 闭环(7例) + 成长页容差守护(4例) + B-77 251 chat 挂起/B-78 251 无源码/B-79 dx 假绿修复/B-80 reset-db 凭据

cfc-tester 4 дней назад
Родитель
Сommit
b6f95c1f59

+ 77 - 0
cfc-frontend/__tests__/pages/growth-main/growthGrid.layout.test.js

@@ -0,0 +1,77 @@
+/**
+ * 回归守护单测 —— 成长页六宫格三列布局容差
+ *
+ * 溯源:
+ *   fix: 3e93a9372「fix(cfc-frontend): 成长页六宫格三列布局容差修复 —
+ *        calc(33.333% - 8rpx)→calc(33.333% - 12rpx),余量 0→1.73% 防临界折行」
+ *   被测:cfc-frontend/pages/growth-main/index.vue  .progress-grid / .progress-tile
+ *
+ * 缺陷本质(本用例守护的规则):
+ *   三列 flex-wrap 布局,每格 width = calc(33.333% - Nrpx),容器 gap = Grpx。
+ *   一行总宽 = 3 × (33.333% - N) + 2 × G = 99.999% - 3N + 2G。
+ *   余量 slack = 100% - 上式 = 0.001% × 容器宽 + 3N - 2G。
+ *   当 N = G/1.5(原 8rpx / gap 12rpx → slack ≈ 0)时,任何亚像素/字体缩放误差
+ *   都会让第三格折行 → 六宫格变「2+2+2」两列。修复把 N 提到 12rpx,slack 恒正。
+ *
+ * 断言口径(静态契约,不依赖渲染引擎):
+ *   1) .progress-grid 确为 flex-wrap 且 gap = 12rpx(布局前提)
+ *   2) .progress-tile 宽度形如 calc(33.333% - Nrpx)
+ *   3) slack(单位 rpx)≥ 4 —— 留出亚像素容差,禁止回归到「刚好 100%」
+ * 本文件只读源码,不改任何业务代码。
+ */
+
+import fs from 'fs'
+import path from 'path'
+
+const PAGE_PATH = path.join(__dirname, '../../../pages/growth-main/index.vue')
+const SRC = fs.readFileSync(PAGE_PATH, 'utf8')
+
+/** 截取某个 CSS 类选择器的声明块({} 内文本),返回 null 表示不存在 */
+function cssBlock(src, selector) {
+  const idx = src.indexOf('\n' + selector + ' {')
+  const altIdx = src.indexOf(selector + ' {')
+  const start = idx >= 0 ? idx : altIdx
+  if (start < 0) return null
+  const open = src.indexOf('{', start)
+  const close = src.indexOf('}', open)
+  if (open < 0 || close < 0) return null
+  return src.slice(open + 1, close)
+}
+
+describe('成长页六宫格 · 三列布局容差(3e93a9372 回归守护)', () => {
+  test('TC-GRID-01 .progress-grid 为 flex-wrap 三列容器且 gap=12rpx', () => {
+    const b = cssBlock(SRC, '.progress-grid')
+    expect(b).not.toBeNull()
+    expect(b).toMatch(/display:\s*flex/)
+    expect(b).toMatch(/flex-wrap:\s*wrap/)
+    const gap = /gap:\s*(\d+)rpx/.exec(b)
+    expect(gap).not.toBeNull()
+    expect(Number(gap[1])).toBe(12)
+  })
+
+  test('TC-GRID-02 .progress-tile 宽度为 calc(33.333% - Nrpx) 形式', () => {
+    const b = cssBlock(SRC, '.progress-tile')
+    expect(b).not.toBeNull()
+    const m = /width:\s*calc\(\s*33\.333%\s*-\s*(\d+)rpx\s*\)/.exec(b)
+    expect(m).not.toBeNull()
+    expect(Number(m[1])).toBeGreaterThan(0)
+  })
+
+  test('TC-GRID-03 一行三格总宽留有余量(slack ≥ 4rpx),不得回归为按 100% 铺满', () => {
+    const grid = cssBlock(SRC, '.progress-grid')
+    const tile = cssBlock(SRC, '.progress-tile')
+    const g = Number(/gap:\s*(\d+)rpx/.exec(grid)[1])
+    const n = Number(/width:\s*calc\(\s*33\.333%\s*-\s*(\d+)rpx\s*\)/.exec(tile)[1])
+    // 3 × (33.333% - n) + 2 × g = 99.999% - 3n + 2g
+    // slack(rpx) = 0.001% 容器宽 + 3n - 2g;0.001% 项按 750rpx 设计宽 ≈ 0.0075rpx,忽略
+    const slackRpx = 3 * n - 2 * g
+    expect(slackRpx).toBeGreaterThanOrEqual(4)
+  })
+
+  test('TC-GRID-04 三格等宽(同一 width 声明,不允许个别格单独覆盖宽度)', () => {
+    // .progress-tile 的 width 声明只出现一次;若出现多个宽度声明即为布局漂移
+    const tile = cssBlock(SRC, '.progress-tile')
+    const widths = tile.match(/(^|\n)\s*width:\s*[^;]+;/g) || []
+    expect(widths.length).toBe(1)
+  })
+})

+ 136 - 0
cfc-frontend/__tests__/pages/signature/signature.landscape.test.js

@@ -0,0 +1,136 @@
+/**
+ * 契约级单测 —— 手签名横屏适配(能力键 signature.landscape.layout)
+ *
+ * 溯源:
+ *   SRS:  docs/superpowers/specs/2026-11-08-signature-landscape-fix.md(DOC-2026-11-08-signature-landscape-fix)
+ *   实现: adb032a61「横屏适配修复 (S1/S2/S3)」
+ *   台账: tests/test-cases/signature.md(TC-SIG-01/02/04/-E)
+ *
+ * 覆盖:
+ *   TC-SIG-01   canvasHeight = 视口高 − 固定栏占位(有/无同意声明两档),且不低于 160 下限
+ *   TC-SIG-02   顶栏标题为 px 单位(横屏不随宽度换算放大)
+ *   TC-SIG-04   旋转后 refreshLandscapeLayout 重算高度并调用组件 refreshRect()
+ *   TC-SIG-03-E 组件 getPos 在 rect 缺失时沿用 clientX/Y(不抛错)
+ *
+ * 本文件只读源码并求值 script 段,不修改任何业务代码。
+ */
+
+const fs = require('fs')
+const path = require('path')
+
+const PAGE = path.join(__dirname, '../../../pages/signature/signature.vue')
+const PAD = path.join(__dirname, '../../../components/hand-signature/hand-signature.vue')
+
+// import 语句 -> 同名空桩变量(保留脚本内引用,避免 ReferenceError)
+function stubImports(code) {
+  return code.replace(/import\s+([\s\S]*?)\s+from\s+['"][^'"]+['"];?/g, function (_all, clause) {
+    const ids = String(clause)
+      .replace(/[{}]/g, ' ')
+      .split(',')
+      .map(function (x) {
+        const parts = x.trim().split(/\s+as\s+/)
+        return parts[parts.length - 1].trim()
+      })
+      .filter(function (x) {
+        return /^[A-Za-z_$][\w$]*$/.test(x)
+      })
+    return ids
+      .map(function (id) {
+        return 'var ' + id + ' = function () {};'
+      })
+      .join(' ')
+  })
+}
+
+function loadOptions(file, sandbox) {
+  const src = fs.readFileSync(file, 'utf8')
+  const m = src.match(/<script>([\s\S]*?)<\/script>/)
+  if (!m) throw new Error('no <script> in ' + file)
+  let code = stubImports(m[1]).replace(/export\s+default/, 'var __opts__ =')
+  code = code + '\nreturn __opts__'
+  const names = Object.keys(sandbox)
+  // eslint-disable-next-line no-new-func
+  const fn = new Function(names.join(','), code)
+  return fn.apply(
+    null,
+    names.map(function (n) {
+      return sandbox[n]
+    })
+  )
+}
+
+function makePage(windowHeight, requireAgree) {
+  const opts = loadOptions(PAGE, {
+    uni: {
+      getWindowInfo: function () {
+        return { windowHeight: windowHeight }
+      },
+    },
+    wx: undefined,
+    window: undefined,
+  })
+  const data = typeof opts.data === 'function' ? opts.data() : opts.data
+  const vm = Object.assign({}, data, opts.methods)
+  vm.requireAgree = !!requireAgree
+  vm.$refs = {}
+  return vm
+}
+
+function styleBlock(selector) {
+  const src = fs.readFileSync(PAGE, 'utf8')
+  const i = src.indexOf(selector + ' {')
+  if (i < 0) return ''
+  const open = src.indexOf('{', i)
+  return src.slice(open + 1, src.indexOf('}', open))
+}
+
+describe('签名横屏适配 · signature.landscape.layout', function () {
+  test('TC-SIG-01 画布高度 = 视口高 − 固定栏占位(无同意声明 174)', function () {
+    expect(makePage(390, false).computeCanvasHeight()).toBe(390 - 174)
+  })
+
+  test('TC-SIG-01b 有同意声明时额外扣除 40(总 214)', function () {
+    expect(makePage(390, true).computeCanvasHeight()).toBe(390 - 214)
+  })
+
+  test('TC-SIG-01c 极小视口不低于 160 下限(防裁剪)', function () {
+    expect(makePage(200, true).computeCanvasHeight()).toBe(160)
+  })
+
+  test('TC-SIG-04 旋转后重算高度并调用组件 refreshRect()', function () {
+    const vm = makePage(390, false)
+    let called = 0
+    vm.$refs = {
+      signaturePad: {
+        refreshRect: function () {
+          called += 1
+        },
+      },
+    }
+    vm.canvasHeight = 0
+    vm.refreshLandscapeLayout()
+    expect(vm.canvasHeight).toBe(390 - 174)
+    expect(called).toBe(1)
+  })
+
+  test('TC-SIG-04b 组件未挂载(refs 为空)时不抛错', function () {
+    const vm = makePage(390, false)
+    vm.$refs = {}
+    expect(function () {
+      vm.refreshLandscapeLayout()
+    }).not.toThrow()
+  })
+
+  test('TC-SIG-02 顶栏标题为 px 单位(横屏不随宽度放大)', function () {
+    const b = styleBlock('.sig-title')
+    expect(b).toMatch(/font-size:\s*(calc\()?[^;]*px/)
+    expect(b).not.toMatch(/font-size:\s*(calc\()?[^;]*rpx/)
+  })
+
+  test('TC-SIG-03-E 组件 getPos 在 rect 缺失时沿用 clientX/Y 不抛错', function () {
+    const opts = loadOptions(PAD, { uni: {}, wx: undefined, window: undefined })
+    const pos = opts.methods.getPos.call({ rect: null }, { clientX: 12, clientY: 34 })
+    expect(pos.x).toBe(12)
+    expect(pos.y).toBe(34)
+  })
+})

+ 182 - 0
docs/tdd/orchestration/report-test-2026-11-09-0030.md

@@ -0,0 +1,182 @@
+# 闭环自动化测试 · TDD 测试负责人汇报(2026-11-09 00:30,第 94 轮,本自动化)
+
+> 触发:定时自动化「测试负责人执行自动化测试闭环(全部用例通过 + 覆盖全部 SRS/场景 + 浏览器走 cfc.iwintrue.com/admin 与 /frontend + langgraph 派 251 + 结果有变化提交远程并通知研发)」。
+> 执行档:`review`(离线 + 内网已授权)——未联网装依赖、未启停生产服务、未改生产代码;浏览器套件本机单节点串行(`workers:1`,遵守「每节点单任务」)。
+> 基线:`cfclub` @ `76f29bc65`(= `origin/cfclub`);**跑测窗口内研发入库 `adb032a61`(签名横屏 S1/S2/S3 实现)**,故本轮同时是「SRS 实现落地首验轮」。
+> 节点拓扑实测:`192.168.16.189:4090` OPEN · `192.168.16.204:4091` OPEN · `192.168.16.251` `:9000` OPEN(`/health` ok)/`:4096` OPEN/`:3306` OPEN。
+> 汇报人:测试负责人(cfc-tester)。
+
+---
+
+## 一、结论摘要
+
+| 项 | 结果 |
+|---|---|
+| **门禁结论** | **不通过(有条件)** —— 12 项门禁 G1/G4/G5/G6/G8/G12 达标;G2/G3/G7/G9/G10/G11 未达标 |
+| 后端单测(本轮首次全量实测) | **2664 例 / 2611 过 = 98.01%**(历史新高,上轮采信 97.34%) |
+| 全量合计 | **3271 例 / 3163 过 = 96.70%**(剔 skip 96.91%),101 红 / 7 skip |
+| 浏览器线上 E2E | **28/28 全绿**(admin 5/5、frontend 6/6、shop-api 4/4、health-contract 13/13) |
+| 最大变化 | ①后端 98.01% 首次实测(推翻「本节点无 Maven」误判)②签名横屏 SRS 实现落地并补 7 例可执行用例 ③251 langgraph `chat` 挂起(新 P1) |
+
+---
+
+## 二、实跑结果总表
+
+| 套件 | 总数 | 通过 | 失败 | 跳过 | 通过率 | 与上轮(R93/R92)对比 |
+|---|---|---|---|---|---|---|
+| cfc-backend(Maven surefire,全量 clean) | 2664 | 2611 | 38F+11E | 4 | **98.01%** | R93 记「不可执行」→ 本轮首次实测(+98.01pp 数据可得性) |
+| cfc-frontend(Jest) | 394 | 369 | 24 | 1 | 93.65% | 383→394(**+11 例全绿**:成长页布局 4 + 签名横屏 7) |
+| cfc-web(Jest) | 116 | 116 | 0 | 0 | 100% | 零漂移 |
+| cfc-langgraph(pytest,本机 venv) | 44 | 32 | 12 | 0 | 72.73% | 与 R93 **逐例零漂移**(B-65 2 + B-74 6 + B-54 4) |
+| 浏览器 live(admin/frontend/shop) | 15 | 15 | 0 | 0 | 100% | R93 4/5 → **5/5**(B-76 未复现) |
+| 浏览器 health-contract | 13 | 13 | 0 | 0 | 100% | 零漂移(B-61 连续第 14 轮) |
+| 浏览器 D/X 桥接鉴权 | 2 | 0 | 0 | **2** | — | R93「2 passed」→ 本轮纠正为 **2 skipped(端点 404 未部署)** |
+| tests/ 根级黑盒(Jest) | 23 | 7 | 16 | 0 | 30.43% | R90 11/12 → **7/16**(环境依赖型,B-36 继续恶化) |
+| **合计** | **3271** | **3163** | **101** | **7** | **96.70%** | — |
+
+后端执行耗时 **51:45**(R90 1:06h、R88 46:18);本机 CPU 负载实测 95%(多 WorkBuddy + opencode 常驻),归因环境而非代码劣化(B-24 延续)。
+
+---
+
+## 三、后端明细(本轮核心增量)
+
+- **规模**:316 个测试类 XML(=316 类,**无沉默丢失**)、2664 例、38 F + 11 E + 4 S、**20 个红类 / 49 个红例**、**环境型红 0 例**(B-70 指向 `192.168.16.251:3306` 后首次零环境红)。
+- **testCompile**:322 源文件 0 错(B-10 连续第 15 轮)。
+- **恒等式**:`@Test 2654 + 参数化展开 26 − @Disabled 4 − 早退截断 12 = 2664`,误差 **0**。
+- **与 R90 基线(2628 例 / 97.34% / 24 红类 / 66 红例)对比**:+36 例、+53 过、**+0.67pp**、红类 24→20、红例 66→49、环境红 8→0。
+
+### 3.1 三层归因(49 红例逐例)
+
+| 归因层 | 例数 | 类数 | 代表 |
+|---|---|---|---|
+| **A 测试侧缺陷/装置**(Mockito 误用、夹具无值、断言文案过期) | **23** | 11 | `CfCommissionServiceSettleTest` 8(桩缺档位查询)、`DimensionScoreServiceImplTest` 4(`NoSuchElement` 无值)、`ArticleWorkflowTest` 3(文案已含「已撤回」)、`DailyCheckinStreakFlowTest`/`PlannerBindInviteFlowTest` 2(`doNothing()` 用于非 void) |
+| **B 产品缺陷(已定性)** | **1** | 1 | `MindFortuneControllerTest` 401→**400**(B-69 第 4 轮:`/api/mind/` 未入 `LOGIN_REQUIRED_PATHS`) |
+| **C 环境/数据** | **1** | 1 | `GuideModuleIntegrationTest`:`Data truncation: Out of range`(真库字段溢出) |
+| **D 待裁决(产品 vs 契约)** | **24** | 7 | `EnergySystemFlowTest` 6(接口 500 无生产帧)、`PlannerBindInviteFlowTest` 4(403 守卫)、`ArticleAutoTagServiceTest` 4(`execute` 被调 2 次,疑重复调度)、`DimensionRegistryMapContractTest` 3(P-2 契约)、`OrchestrationEngine/Integration` 2、`MembershipPaySwitch`/`PaymentServiceORD`/`TaskPlanService`/`TeamProfitCron`/`GrowthRecordSync` 各 1 |
+
+> 量化:**修完 A 类 23 例 → 后端 98.87%、全量 97.40%**;再裁决 D 类 24 例 → 后端 99.77%。
+
+---
+
+## 四、节点分派(用户指令落地情况)
+
+| 目标 | 实测 | 结论 |
+|---|---|---|
+| `192.168.16.189:4090`(admin 派发目标) | TCP OPEN,但缺 LLM 凭证(B-56) | 不可派发 → 本机串行兜底 |
+| `192.168.16.204:4091`(frontend 派发目标) | TCP OPEN | 可用;本轮仍本机串行(单节点单任务纪律) |
+| `192.168.16.251:9000`(langgraph 部署) | `/health` **200 `{"status":"ok"}`**(6ms) | **服务在线** |
+| `192.168.16.251:9000/api/v1/chat` | **2 次探测均无响应**(75s / 200s,0 字节,`status=000`) | **新增 B-77:chat 图挂起** |
+| `192.168.16.251:4096`(opencode 派发) | OPEN,Basic 鉴权通过,**成功建会话并派发 2 次** | 派发通道可用 |
+| `192.168.16.251:3306` | OPEN | B-70 修复后无环境红 |
+| 251 上 langgraph 源码 | `/home/iwt/.cfc/langgraph` 仅 `memory/` 数据目录,**无 tests/**;`find / -maxdepth 6 -name cfc-langgraph` 仅命中 `/tmp/opencode/*` 历史克隆 | **新增 B-78:节点侧无 repo → pytest 无法在 251 执行** |
+
+---
+
+## 五、SRS / 场景变更感知与用例更新(用户硬要求)
+
+### 5.1 窗口内变更
+
+| 提交 | 内容 | 测试侧动作 |
+|---|---|---|
+| `adb032a61`(22:51,窗口内) | 签名横屏适配 **S1/S2/S3 实现落地**(`signature.vue` +69、`hand-signature.vue` +27) | **新增 `cfc-frontend/__tests__/pages/signature/signature.landscape.test.js` 7 例全绿**;`tests/test-cases/signature.md` 状态由「待开发 RED」→「已实现·已可执行化」 |
+| `3e93a9372`(22:34) | 成长页六宫格三列容差修复(`calc(33.333%-8rpx)`→`-12rpx`,余量 0→1.73%) | **新增 `growthGrid.layout.test.js` 4 例全绿**(守护 slack≥4rpx,禁止回归「刚好 100%」) |
+| `a13bb8192` | attribution G-1 低侧红线合规闸门 | 既有 `AttributionComplianceGateTest` 纳入本轮全量(绿) |
+| `2026-11-08-diet-recommendation-polish.md` | 推荐食材页 7 条改版(REQ1-7) | 既有 24 例 RED 保持不变(实现未落地),**本轮确认 `recommendation.vue` 对 `currentTab/pagedFoods/loadMore` 命中数 = 0** → 属 TDD RED,非测试缺陷 |
+| `2026-11-08-dx-health-bridge.md` | D/X 桥接 B-1/B-2 | 端点线上 **404(未部署)**;用例由「404 也算过」改为 **skip + 200 红线断言**(B-79) |
+
+### 5.2 场景/用例覆盖度量(`docs/tdd/tools/test_report_metrics.py --scene`)
+
+场景 **215**、映射到 TC **155(72.1%)**、被 spec 字面引用 **32(14.9%)**、TC 总数 **800**(异常流 480 = 60.0%)、TC 在测试资产中有锚点 **86**。
+
+---
+
+## 六、覆盖缺口清单
+
+| 缺口 | 现状 | 风险 | 建议测试设计 | 优先级 | 闭环方式 |
+|---|---|---|---|---|---|
+| S-cfc-79 前端 24 RED | `diet.recommendation.uiRework` 未实现 | 目标①不达成 | 研发实现 REQ1-7 → 24 例转绿 | P1 | 研发实现 + 全量复跑 |
+| 后端 D 类 24 例待裁决 | 500/403/调用次数/契约口径 | 真实缺陷可能被误当噪声 | 逐类补「服务端异常日志 + 生产帧」取证 | P1 | 研发确认后二分 A/B |
+| 251 `chat` 挂起(B-77) | health ok / chat 无响应 | AI 管家线上不可用 | 加 LLM 调用超时 + 熔断;部署后 smoke 用例 | **P0** | 运维/研发修复 + 3 次探测复验 |
+| 251 无源码(B-78) | 节点无 repo | G10 派发永不达成 | 251 建 repo checkout + venv,或提供 CI job | P1 | 运维落盘 + 派发复验 |
+| 根级黑盒 16 红 | 环境依赖型(凭据/库) | 接口层覆盖虚化 | 改为 live 契约(API 直探)替代 | P2 | 测试侧改造 |
+| SRS 锚点 86/800(10.75%) | 大量 TC 无自动化资产 | 目标②不达成 | 按优先级把 P0 TC 落到 Jest/Playwright | P0 | 持续补资产 |
+| G11 CI 未接后端 | 仅 test-compile | 回归靠人工 | CI 加 `mvn -o test` | P1 | 研发/运维 |
+
+---
+
+## 七、五要素问题清单(本轮新增/升级)
+
+### B-77 · 251 langgraph `/api/v1/chat` 挂起无响应(P0,新增)
+1. **问题描述**:`192.168.16.251:9000/api/v1/chat` 两次探测(`{"query":"你好","user_id":251}`)分别 75s、200s 超时且 **0 字节返回**(curl `status=000`);同节点 `/health` 200(6ms)、`/docs` 200(8ms)。R93(22:27)同接口曾返回有效答案 → 22:46~22:50 节点存在一次部署(`.cfc-deploy.lock` + `cfc-deploy.log` 时间戳)后能力退化。
+2. **测试 bug 点**:无既有自动化用例覆盖「251 chat 可用性」;本轮以 curl 直探取证(`_r94_lg_chat.json` 空、`_r94_chat2.json` 未落)。
+3. **最优修改方案**:研发定位 chat graph 中 LLM/检索调用未设超时导致挂起(对齐 `cfc-langgraph/app/...` 的 ainvoke 调用链,加 `timeout` + 熔断 + 失败降级文案);运维复核部署后 LLM 凭证与网关连通。
+4. **修改目的**:AI 管家 `/api/v1/chat` 是线上核心链路,挂起等同不可用,且会让客户端长时间转圈。
+5. **闭环**:修复后新增 `tests/e2e-live/langgraph-chat-smoke.spec.js`(POST chat 断言 200 + answer 非空 + 耗时 < 30s),连跑 3 次全绿。
+
+### B-78 · 251 部署节点无 `cfc-langgraph` 源码,pytest 无法派发(P1,新增)
+1. **问题描述**:在 251 上 `ls /home/iwt/.cfc/langgraph` 仅见 `memory/`(Chroma 数据),**无 `tests/`**;`find / -maxdepth 6 -type d -name cfc-langgraph` 仅命中 `/tmp/opencode/*` 历史会话克隆(代码陈旧不可作权威)。故「langgraph 测试分派 251」在节点侧**不具备执行条件**(此前 G10 一直记为「端口不通」,本轮首次取到真因)。
+2. **测试 bug 点**:派发脚本仅能建会话,无 repo 时 agent 只能报告「未找到目录」(本轮两次派发均如此,见 `_r94_langgraph_251.log` / `_r94_langgraph_251b.log`)。
+3. **最优修改方案**:运维在 251 落一个与 `origin/cfclub` 同步的 repo checkout + `.venv`(或提供 `langgraph-pytest` CI job / 容器),派发 prompt 直接给绝对路径。
+4. **修改目的**:让 langgraph 单测在**持有有效 LLM key 的节点**上运行,消除本机 4 例 env 假红,使 G10 可达。
+5. **闭环**:251 上 `pytest tests -q` 跑出与仓库一致的 44 例,派人复跑 2 轮零漂移。
+
+### B-79 · D/X 桥接鉴权用例「404 也算过」形成假绿(P1,测试侧自处置已完成)
+1. **问题描述**:`dx-health-bridge-auth.spec.js` 断言集合为 `[401,403,404]`,端点未部署时返回 404 也判 **passed** → 报告里呈现「2 passed」,实际是「无端点可测」。
+2. **测试 bug 点**:`tests/e2e-live/dx-health-bridge-auth.spec.js:47`(断言允许 404 通过)。
+3. **最优修改方案**:已改为「200 一律失败(鉴权红线)+ 404 记 `test.skip` 并注明待上线」,上线后自动转为 401/403 断言。
+4. **修改目的**:避免未部署端点被统计为「鉴权已通过」,纠正门禁口径。
+5. **闭环**:本轮复跑输出 **2 skipped**(`_r94_browser_dxbridge.log`),门禁计数已纠正。
+
+### B-80 · `tests/e2e/reset-db.sh` 默认凭据对 204 节点鉴权失败(P2,环境+装置)
+1. **问题描述**:`playwright.config.js` 的 globalSetup 执行 `reset-db.sh` 报 `ERROR 1045 Access denied for user 'cfc'@'192.168.16.204'`;`cfc@123`/`zxyj@123`/`IwinTrue@123` 三候选均被拒(`mysql` 客户端实测)→ **主配置 E2E 在 204 节点必然中断**,只能用 `playwright.config.live.js`(无 globalSetup)。
+2. **测试 bug 点**:`tests/e2e/reset-db.sh:8`(`MYSQL_PASS` 默认值)+ `playwright.config.js:18`(globalSetup 无跳过开关)。
+3. **最优修改方案**:DB 侧为 `cfc@192.168.16.204` 授 `cfc` 库权限(或提供只读种子库账号);同时给 `db.js` 加 `SKIP_DB_RESET=1` 开关。
+4. **修改目的**:让默认 E2E 配置可用,降低「跑不起来靠换配置」的隐性依赖。
+5. **闭环**:`MYSQL_HOST/MYSQL_PASS` 正确后 `reset-db.sh` 返回 0,`playwright.config.js` 跑通 1 个 smoke spec。
+
+### B-69(第 4 轮)· `/api/mind/` 匿名返回 400 而非 401
+1. **问题描述**:`MindFortuneControllerTest.testGetTodayFortuneWithoutAuth` 期望 `$.code=401` 实得 `400`(`@RequestAttribute("userId")` 绑定层先于拦截器失败)。
+2. **测试 bug 点**:`cfc-backend/src/test/.../mind/MindFortuneControllerTest.java:23`。
+3. **最优修改方案**:`JwtInterceptor.java:87` 的 `LOGIN_REQUIRED_PATHS` 增加 `/api/mind/`。
+4. **修改目的**:私有端点匿名访问口径统一为 401,便于前端「未登录跳登录」判断。
+5. **闭环**:该例转绿 + `JwtInterceptorLoginRequiredPathTest` 参数化补 `/api/mind/` 路径断言。
+
+---
+
+## 八、门禁表
+
+| 门禁 | 阈值 | 本轮 | 判定 |
+|---|---|---|---|
+| G1 后端用例通过率 | ≥95% | **98.01%** | ✅(首次) |
+| G2 红例数 | 0 | 101(后端 49 / 前端 24 / lg 12 / 根级 16) | ❌ |
+| G3 前端全量 | 全绿 | 369/394(24 RED=未实现) | ❌ |
+| G4 编译/tc | 0 错 | 322 源 0 错 | ✅ |
+| G5 报告完整性 | 无沉默丢失 | 316/316 XML,1 dump 为 PpidChecker 噪声 | ✅ |
+| G6 线上浏览器 | 全绿 | **28/28**(+2 skipped 未部署端点) | ✅ |
+| G7 langgraph | 0 红 | 12 红(B-65 2 / B-74 6 / B-54 4)+ chat 挂起 | ❌ |
+| G8 健康契约 | 全绿 | 13/13(B-61 连 14 轮) | ✅ |
+| G9 SRS/场景锚点 | ≥80% | 场景映射 72.1%、TC 锚点 86/800=10.75% | ❌ |
+| G10 节点派发 | 2/2 | 0/2(189 缺凭证、251 无 repo) | ❌ |
+| G11 CI | 后端接入 | 仅 test-compile | ❌ |
+| G12 资产入库 | 新用例已提交 | 本轮 11 例(4+7)+ 1 处用例修复随本报告提交 | ✅ |
+
+**结论:不通过(有条件)**。使绿顺序:**P-1 修 A 类 23 例(→98.87%)→ P-2 判 24 例 D 类 → P-3 修 B-77 chat 挂起 → P-4 落 251 repo(G10)→ P-5 实现 S-cfc-79 24 例 → P-6 CI 接后端 → P-7 根级黑盒改造**。
+
+---
+
+## 九、未实测项(不得视为通过)
+
+- langgraph pytest **在 251 上执行**(B-78:节点无源码);本轮为**本机 venv 结果**,含 4 例 env 假红。
+- `192.168.16.189` 派发的浏览器任务(B-56 缺 LLM 凭证)。
+- 覆盖率/jacoco、变异测试、性能压测、SAST/DAST/SCA(本机无工具,未安装)。
+- 无障碍、多浏览器矩阵(仅 chromium)、UAT/BDD/探索性测试。
+- `dan/danshop`(方案外)。
+
+---
+
+## 十、产出与留痕
+
+- 本轮报告:`docs/tdd/orchestration/report-test-2026-11-09-0030.md`(本文件)、技术明细 `docs/tdd/test/automation-run-2026-11-08-R94.md`、研发通知 `docs/tdd/test/notice-r94-round.md`。
+- 新增用例:`cfc-frontend/__tests__/pages/growth-main/growthGrid.layout.test.js`(4 例)、`cfc-frontend/__tests__/pages/signature/signature.landscape.test.js`(7 例)。
+- 用例修复:`tests/e2e-live/dx-health-bridge-auth.spec.js`(404 假绿 → skip + 200 红线)。
+- 本地证据:`_r94_backend.log`、`_r94_agg.json`、`_r94_frontend2.log`、`_r94_cfcweb.log`、`_r94_pytest.log`、`_r94_browser_live.log`、`_r94_browser_health.log`、`_r94_browser_dxbridge.log`、`_r94_langgraph_251*.log`、`_r94_lg_msg.json`、`_r94_aggregate.py`(均未入库)。

+ 84 - 0
docs/tdd/test/automation-run-2026-11-08-R94.md

@@ -0,0 +1,84 @@
+# 自动化测试执行记录 · R94(2026-11-08 22:47 ~ 2026-11-09 00:20)
+
+> 汇报版见 `docs/tdd/orchestration/report-test-2026-11-09-0030.md`;本文件只记可复现的执行事实与证据路径。
+
+## 0. 基线与环境
+
+| 项 | 值 |
+|---|---|
+| 仓库/分支 | `d:\cfc` · `cfclub` |
+| 起始 SHA | `76f29bc65`(= `origin/cfclub`,fetch 后一致) |
+| 结束 SHA | `adb032a61`(窗口内研发入库:签名横屏 S1/S2/S3) |
+| 工作区 | 追踪文件仅 `M _r0243_agg.json`、`D cfc-langgraph/tests/qna/test_graph.py`;**源码级 WIP 0** → 直接在主工作区跑,无隔离树 |
+| JDK / Maven | `D:\jdk1.8.0_25` / IDEA 内置 Maven **3.9.9**(`which mvn` 为空但可用 → 推翻 R93「本节点无 Maven」结论) |
+| 负载 | CPU 95%(WorkBuddy×4 + opencode 常驻)→ 全量耗时 51:45(R88 46:18) |
+
+## 1. 后端全量(`mvn -o -B clean test`)
+
+```
+Tests run: 2664, Failures: 38, Errors: 11, Skipped: 4      → passed 2611 = 98.01%
+classes=316 (316 XML 一一对应, 1 个 PpidChecker dump 噪声)
+testCompile: 322 sources, 0 error
+恒等式: @Test 2654 + 参数化展开 26 − @Disabled 4 − 早退截断 12 = 2664 (误差 0)
+Total time: 51:45   BUILD FAILURE(仅测试失败)
+```
+- 红类 20 / 红例 49 / **环境型 0**。
+- 三层归因:A 23 / B 1 / C 1 / D 24(明细见汇报 §3.1)。
+- 证据:`_r94_backend.log`、`_r94_agg.json`(工具 `docs/tdd/tools/test_report_metrics.py` 聚合)。
+
+## 2. 前端与管理端
+
+| 套件 | 命令 | 结果 |
+|---|---|---|
+| cfc-frontend | `node node_modules/jest/bin/jest.js` | **394 例 / 369 过 / 24 红 / 1 skip**(24 红全部集中在 `__tests__/pages/diet/recommendation.uiRework.test.js`,TDD RED) |
+| cfc-web | 同上 | **116/116**(17 suites) |
+| tests/ 根级 | `cd tests && node node_modules/jest/bin/jest.js` | **23 例 / 7 过 / 16 红**(黑盒环境依赖,B-36) |
+
+## 3. 浏览器(本机单节点串行,`workers:1`)
+
+| 配置 | 用例 | 结果 | 耗时 |
+|---|---|---|---|
+| `playwright.config.live.js` | admin 5 + frontend 6 + shop 4 | **15/15 passed** | 3.7m |
+| `playwright.config.health.js` | health-contract 13 | **13/13 passed** | 19.4s |
+| `playwright.config.dxbridge.js` | DX 桥接 2 | **2 skipped**(端点 404 未部署;200 红线断言保留) | 9.8s |
+
+- 必须用 `live/health/dxbridge` 配置:`playwright.config.js` 的 globalSetup 会跑 `reset-db.sh`,在 204 节点必然 1045 鉴权失败(B-80)。
+- 首跑 `playwright.config.js` 记录:`E2E Global Setup: Database Reset FAILED`(保留为 B-80 证据)。
+
+## 4. langgraph
+
+| 维度 | 结果 |
+|---|---|
+| 本机 venv pytest(`tests/`,qna 目录已被删除) | **44 collected / 32 passed / 12 failed**(261.82s),失败集合与 R93 逐例一致:`test_analyze` 2(B-65)、`test_auto_tag` 5 + `test_chat` 1(B-74)、`test_intent` 4(B-54 本机 key 401) |
+| 251 `:9000/health` | 200 `{"status":"ok"}`(6ms) |
+| 251 `:9000/api/v1/chat` | **挂起**:75s / 200s 两次探测 0 字节(B-77) |
+| 251 `:4096` 派发 | 会话创建 + prompt 投递均 200(`/api/session/{id}/prompt` 需 body 含顶层 `text`);agent 回报 **未找到 `cfc-langgraph` 目录**(B-78) |
+
+## 5. 本轮新增/修改的测试资产
+
+| 文件 | 变更 | 结果 |
+|---|---|---|
+| `cfc-frontend/__tests__/pages/growth-main/growthGrid.layout.test.js` | 新增 4 例(守护 `3e93a9372` 三列容差,slack≥4rpx) | 4/4 绿 |
+| `cfc-frontend/__tests__/pages/signature/signature.landscape.test.js` | 新增 7 例(TC-SIG-01/01b/01c/02/04/04b/03-E,验证 `adb032a61` 实现) | 7/7 绿 |
+| `tests/e2e-live/dx-health-bridge-auth.spec.js` | 404 不再计 pass;未鉴权 200 判失败 | 2 skipped(原 2 passed 假绿) |
+| `tests/test-cases/signature.md` | 状态行改为「已实现·已可执行化」 | 文档 |
+
+## 6. 复现命令(可照抄)
+
+```bash
+# 后端
+cd cfc-backend && JAVA_HOME=D:/jdk1.8.0_25 \
+  "C:/Program Files/JetBrains/IntelliJ IDEA 2025.2.5/plugins/maven/lib/maven3/bin/mvn.cmd" -o -B clean test
+# 聚合
+python docs/tdd/tools/test_report_metrics.py --reports cfc-backend/target/surefire-reports --out-json _r94_agg.json
+python docs/tdd/tools/test_report_metrics.py --repo-root . --scene
+# 前端
+cd cfc-frontend && node node_modules/jest/bin/jest.js
+cd cfc-web && node node_modules/jest/bin/jest.js
+cd tests && node node_modules/jest/bin/jest.js
+# 浏览器(串行,单节点单任务)
+cd tests && node node_modules/@playwright/test/cli.js test --config=playwright.config.live.js --reporter=line
+cd tests && node node_modules/@playwright/test/cli.js test --config=playwright.config.health.js --reporter=line
+# langgraph
+cd cfc-langgraph && ./.venv/Scripts/python.exe -m pytest tests/ -q -p no:cacheprovider
+```

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

@@ -107,3 +107,13 @@
 | B-76(第 93 轮新增) | admin-desktop 非确定 flaky | 测试(自处置) | **第 93 轮实测(2026-11-08 22:27)**。admin-desktop 5 例中稳定 4 通过 + 1 **非确定**失败:首跑 TC-A-U04 失败、独立复跑 TC-A-U01 失败、且偶发 skip(视登录页/滑块+凭据时序)。较 R92「U04 单失败」升级为跨 U01/U04 非确定 flaky(live 站点登录页渲染/滑块时序脆弱,非固定用例)。修复建议:固定 `waitForTimeout(5000)` / `test.skip(url.includes('/login'))` 兜底改 `waitForURL(/\/admin\/dashboard/, { timeout: 20000 })` 硬断言 + 复用登录态 storageState;维持 B-67「单轮红先独立复跑 ≥2 次 + curl 直探」三证纪律。 | 测试负责人(第 93 轮实测登记) | 2026-11-08 22:27 | 待处理(下轮自处置) |
 | B-55-Close(第 93 轮闭环确认) | langgraph 251:9000 服务恢复(B-55 服务维度解除) | 运维/测试 | **闭环确认(第 93 轮 22:27)**。`192.168.16.251:9000/health` → `{"status":"ok","components":{"chromadb":{"status":"ok"},"llm":{"status":"ok"},"java_backend":{"status":"ok"}}}`;此前 R78~R91 长期 CLOSED。用户指令「langgraph 派 251」首度实质落地:251 部署节点 LIVE 实测 `POST /api/v1/chat` 返回有效 answer(`{"answer":"我来帮您搜索一些关于补钙的相关商品和文章,请稍等!",...}`),证明 251 持有有效 LLM key 且推理端到端可用;`:4096` opencode 仍 OPEN(R92 已派发 pytest 36/44)。 | 测试负责人(第 93 轮实测) | 2026-11-08 22:27 | **已解除(251:9000 服务恢复,llm/chromadb/java_backend 全 ok)** |
 | B-54-Update(第 93 轮重申) | langgraph 本机 venv LLM key 401 环境漂移 | 测试/运维 | **第 93 轮重申(2026-11-08 22:27)**。本机 venv 跑 `pytest tests/ --ignore=tests/qna` 得 32 过 / 12 红,其中 `test_intent.py`×4 因 `app.prompt_service` 加载 `intent_classifier` 提示词 `401 - Authentication Fails, Your api key: ****ocal is invalid` / `All connection attempts failed` → 分类失败回退默认 chat → 断言 `Intent.X==chat` 失败。251 部署节点持有效 key 不受影响(R92 派发 36/44,本轮 :9000 `llm:ok`);本机 12 红 = B-65(2) + B-74(6) + B-54 env(4)。建议**正式派发 langgraph pytest 至 251:4096** 消除本机 env 假红。 | 测试负责人(第 93 轮实测登记) | 2026-11-08 22:27 | 待处理(派 251 消除 env 假红) |
+
+| B-77(第 94 轮新增) | 251 `POST /api/v1/chat` 挂起无响应(同节点 /health 200) | 研发/运维 | **P0 环境/部署回归(第 94 轮实测,2026-11-08 23:10~23:25)**。`192.168.16.251:9000/health` → `200 {"status":"ok"}`(6ms)、`/docs` 200(8ms),但 `POST /api/v1/chat` `{"query":"你好","user_id":251}` **两次探测均 0 字节超时**(75s / 200s,curl `status=000`)。R93(22:27)同接口曾返回有效 answer → 22:46~22:50 节点部署(`.cfc-deploy.lock`/`cfc-deploy.log` 时间戳)后能力退化。影响 AI 管家线上主链路。闭环:定位 LLM/检索调用缺超时 → 加 timeout+熔断+降级;新增 `tests/e2e-live/langgraph-chat-smoke.spec.js`(200+answer 非空+<30s)连跑 3 次全绿。 |
+| B-78(第 94 轮新增) | 251 部署节点无 `cfc-langgraph` 源码 → pytest 无法派发(G10 真因) | 运维 | **P1 派发阻断(第 94 轮取证,2026-11-08 23:00)**。251 上 `ls /home/iwt/.cfc/langgraph` 仅含 `memory/`(Chroma 数据),**无 `tests/`**;`find / -maxdepth 6 -type d -name cfc-langgraph` 仅命中 `/tmp/opencode/*` 历史会话克隆(代码陈旧)。两次派发 agent 均回报「未找到目录」(`_r94_langgraph_251.log`/`_r94_langgraph_251b.log`)。此前 G10 长期记「端口不通」,本轮首次取到节点侧真因。闭环:251 落 `origin/cfclub` checkout + `.venv`(或提供 CI job),派发给出绝对路径后跑出 44 例。 |
+| B-79(第 94 轮新增·已处置) | D/X 桥接鉴权用例「404 也算过」形成假绿 | 测试(已处置) | **P1 测试资产缺陷(第 94 轮修复,2026-11-08 23:05)**。`tests/e2e-live/dx-health-bridge-auth.spec.js` 原断言集合 `[401,403,404]`,端点未部署(404)被统计为 **passed**,门禁呈现「2 passed」实为「无端点可测」。已改为:200 **一律失败**(鉴权红线,任何情况未鉴权不得 200)+ 404 记 `test.skip` 并注明待上线。复跑输出 **2 skipped**。 |
+| B-80(第 94 轮新增) | `tests/e2e/reset-db.sh` 默认凭据对 `cfc@192.168.16.204` 鉴权失败 → 主配置 E2E 必断 | 运维/测试 | **P2 环境+装置(第 94 轮实测,2026-11-08 23:00)**。`playwright.config.js` globalSetup 跑 `reset-db.sh` 报 `ERROR 1045 (28000): Access denied for user 'cfc'@'192.168.16.204'`;`cfc@123`/`zxyj@123`/`IwinTrue@123` 三候选用 `mysql` 客户端均被拒(非密码错即无主机授权)。结论:204 节点只能用 `playwright.config.live.js`(无 globalSetup)。闭环:DB 侧授 `cfc` 库权限 + 给 `db.js` 加 `SKIP_DB_RESET=1` 开关。 |
+| B-75-Close(第 94 轮闭环) | signature.landscape.layout 实现落地并完成可执行化 | 研发(已实现)/ 测试(已补测) | **闭环(第 94 轮,2026-11-08 23:30)**。窗口内 `adb032a61`(22:51)实现 S1 高度重算 / S2 px 化 / S3 `refreshRect()`(`signature.vue` +69、`hand-signature.vue` +27)。测试侧新增 `cfc-frontend/__tests__/pages/signature/signature.landscape.test.js` **7 例全绿**(TC-SIG-01/01b/01c/02/04/04b/03-E),`tests/test-cases/signature.md` 状态由「待开发 RED」改为「已实现·已可执行化」。 |
+| B-69-Update(第 94 轮 · 连续第 4 轮) | `/api/mind/` 匿名仍非 401 | 研发 | **连续第 4 轮未修(2026-11-08 23:20)**。全量实测 `MindFortuneControllerTest.testGetTodayFortuneWithoutAuth`:`JSON path "$.code" expected:<401> but was:<400>`(`@RequestAttribute("userId")` 绑定层先 400)。`JwtInterceptor` 的 `LOGIN_REQUIRED_PATHS` 仍未含 `/api/mind/`。 |
+| B-76-Update(第 94 轮 · 本轮未复现) | admin-desktop 非确定 flaky | 测试(观察) | **本轮未复现(2026-11-08 23:00)**。`playwright.config.live.js` 串行 `workers=1` 下 admin 5/5 全绿(3.7m,含 TC-A-U04 真实登录+滑块)。按 B-67 纪律单轮绿不足以判定消除,维持「观察」状态,需连续 2 轮绿后关闭。 |
+| B-73-Update(第 94 轮 · 源码级复核) | S-cfc-79 diet 24 例 RED | 研发 | **仍为预期 RED(第 94 轮复核,2026-11-08 23:00)**。`grep -c "currentTab\|pagedFoods\|loadMore" cfc-frontend/pages/diet/recommendation.vue` = **0** → REQ1-7 均未落地,24 例红属 TDD RED 而非测试缺陷;与 R92/R93 逐例零漂移。 |
+| B-55-Update(第 94 轮 · 服务在线但 chat 挂起) | 251 langgraph 服务维度在线、能力维度退化 | 运维 | **第 94 轮(2026-11-08 23:20)**。`:9000/health` ok 维持(B-55 服务维度仍解除),但 `chat` 挂起另立 **B-77**。 |

+ 41 - 0
docs/tdd/test/notice-r94-round.md

@@ -0,0 +1,41 @@
+# 研发交接通知 · 第 94 轮(2026-11-09 00:30)
+
+> 来源:定时自动化「测试负责人闭环测试」第 94 轮。基线 `cfclub` @ `76f29bc65`,窗口内研发入库 `adb032a61`(签名横屏实现)。
+> 详细报告:`docs/tdd/orchestration/report-test-2026-11-09-0030.md`;台账:`docs/tdd/test/blockers.md`。
+
+## 一、请研发优先处理(P0/P1)
+
+| 编号 | 事项 | 证据 | 期望动作 |
+|---|---|---|---|
+| **B-77(P0 新增)** | 251 langgraph `POST /api/v1/chat` **挂起无响应** | 同节点 `/health` 200(6ms)、`/docs` 200;`chat` 两次探测 **75s/200s 均 0 字节**(`status=000`);R93(22:27)同接口曾正常返回 | 定位 chat graph 中 LLM/检索调用缺超时 → 加 `timeout` + 熔断 + 降级文案;部署后测试侧补 `langgraph-chat-smoke` 用例复验 |
+| **B-78(P1 新增)** | 251 节点无 `cfc-langgraph` 源码,pytest 无法派发 | `/home/iwt/.cfc/langgraph` 仅 `memory/`;`find / -maxdepth 6 -name cfc-langgraph` 仅命中 `/tmp/opencode/*` 旧克隆 | 运维在 251 落 repo checkout + `.venv`(或提供 CI job),供「技术指标要求的 251 派发」真正可执行 |
+| **B-69(第 4 轮)** | `/api/mind/` 匿名返回 400 而非 401 | 全量实测 `MindFortuneControllerTest:23` `expected:<401> but was:<400>` | `JwtInterceptor` 的 `LOGIN_REQUIRED_PATHS` 增加 `/api/mind/` |
+| **B-73(第 5 轮)** | S-cfc-79 推荐食材页 24 例 RED | `recommendation.vue` 对 `currentTab/pagedFoods/loadMore` 命中数 **0** | 实现 REQ1-7,24 例即转绿 |
+| **B-65(第 5 轮)** | langgraph `/api/v1/analyze` 恒 500 | `test_analyze.py` 2 例 500 | `analysis_graph.py` 去 `MemorySaver()` 或 `ainvoke` 传 `thread_id` |
+
+## 二、需要裁决的后端 24 例「D 类」红(产品 vs 契约)
+
+这些用例红了,但**无法仅凭测试侧判定是产品缺陷还是契约过期**,请研发逐类确认(报告 §3.1 有完整清单):
+
+| 类 | 例数 | 现象 | 疑似 |
+|---|---|---|---|
+| `EnergySystemFlowTest` | 6 | 接口 **500**(无生产帧),`userWithoutFamily` 返回 5001 而非 500 | 产品异常未兜底 / 测试桩缺 |
+| `PlannerBindInviteFlowTest` | 4 | 绑定/解绑接口 **403** | 权限守卫收紧 or 测试身份未预置绑定 |
+| `ArticleAutoTagServiceTest` | 4 | `threadPoolTaskExecutor.execute` **被调 2 次**(期望 1) | **疑重复调度自动打标**(`ArticleService.java:1331/1349` 双路径)→ 若成立会重复消耗 LLM |
+| `DimensionRegistryMapContractTest` | 3 | `unknown/empty legacy` 返回原值而非 null;缺失应 400 实得 200 | P-2 契约口径待定 |
+| `Orchestration*` / `MembershipPaySwitch` / `PaymentServiceORD` / `TaskPlanService` / `TeamProfitCron` / `GrowthRecordSync` | 各 1 | 状态机/开关/回调路由断言不符 | 待逐项确认 |
+
+## 三、已闭环(可复核)
+
+| 编号 | 结论 |
+|---|---|
+| **B-75 签名横屏** | `adb032a61` 已实现 S1/S2/S3;测试侧新增 **7 例全绿**(`signature.landscape.test.js`),台账 `tests/test-cases/signature.md` 状态已改为「已实现·已可执行化」 |
+| **B-76 admin flaky** | 本轮串行 `workers=1` 下 admin **5/5 全绿**,未复现;维持观察 1 轮 |
+| **B-79 dx 桥接假绿** | 测试侧已修:404 不再计 pass(改 `skip`),未鉴权 200 一律失败 |
+| **后端 98.01%** | 2664 例 / 49 红,**环境型红首次 0 例**(B-70 修复后);修 A 类 23 例可达 98.87% |
+
+## 四、需要运维配合
+
+1. `cfc@192.168.16.204` 对 `cfc` 库授权(B-80:`reset-db.sh` 现 1045 拒绝 → 主配置 E2E 不可用)。
+2. 251 `:9000` 部署后 smoke:health / chat / analyze 三接口连通性与时延(B-77)。
+3. 189 `opencode` 补 LLM 凭证(B-56)以恢复派发能力。

+ 54 - 0
tests/e2e-live/dx-health-bridge-auth.spec.js

@@ -0,0 +1,54 @@
+/**
+ * ================================================================
+ * D/X 域桥接 · 小程序端点鉴权契约 E2E · TC-DX-U01 ~ TC-DX-U02
+ * ================================================================
+ * 需求来源:docs/superpowers/specs/2026-11-08-dx-health-bridge.md
+ *   §3.3 小程序端点契约 B-1 / B-2;§3.4 鉴权契约;§5 测试侧证据 E-4
+ *
+ * 契约要点(来自 spec):
+ *   - B-1 POST /api/health/data-sources/bridge/list (设备数据列表,分页+过滤)
+ *   - B-2 POST /api/health/data-sources/bridge/sync-status (各 dataType 同步状态)
+ *   - 两者均「登录必填 401」;走 /api/** 拦截(默认拒绝 + 显式登录)。
+ *
+ * 本契约验证「未携带 Authorization 时,两个端点应被拒绝(401/403),不可 200 直通」。
+ * 只读:仅发送空 body 探测鉴权门禁,不写任何业务数据;凭据缺失属预期(验证拦截),
+ *      禁止在日志/报告中打印明文口令或令牌。
+ *
+ * 运行(隔离配置,复用 health 配置,避免 globalSetup 重置库):
+ *   npx playwright test -c playwright.config.health.js e2e-live/dx-health-bridge-auth.spec.js
+ *
+ * 状态:桥接尚处「仅登记不实现」阶段,端点可能尚未部署(404)或已注册但需鉴权(401)。
+ *      断言允许 401/403/404;若返回 200 则视为鉴权契约被突破(安全回归),用例失败。
+ */
+const { test, expect } = require('@playwright/test');
+
+const API = process.env.LIVE_API_BASE || 'http://cfc.iwintrue.com';
+
+// [用例编号, 端点路径, 语义]
+const ENDPOINTS = [
+  ['TC-DX-U01', '/api/health/data-sources/bridge/list', 'B-1 设备数据列表(分页+source_type过滤)'],
+  ['TC-DX-U02', '/api/health/data-sources/bridge/sync-status', 'B-2 各 dataType 同步状态聚合'],
+];
+
+test.describe('D/X 域桥接 · 小程序端点鉴权契约(§3.3/§3.4)', () => {
+  for (const [tc, path, req] of ENDPOINTS) {
+    test(`${tc} POST ${path} 未鉴权应被拒绝(401/403) · ${req}`, async ({ request }) => {
+      const resp = await request.post(`${API}${path}`, {
+        data: {},
+        timeout: 20000,
+      });
+      const status = resp.status();
+      // 仅打印状态码,绝不打印响应体(可能含敏感字段)或任何凭据
+      console.log(`[${tc}] POST ${path} -> HTTP ${status}`);
+      // 安全红线(任何情况下都必须成立):未鉴权绝不可 200 直通。
+      expect(status, `${tc} 未鉴权返回 200 = 鉴权契约被突破`).not.toBe(200);
+      // 404 = 端点尚未在目标部署实例上线(桥接「仅登记不实现」)。
+      // 记为 skip(而非 pass):无端点可测 ≠ 鉴权已通过,避免形成「假绿」证据。
+      if (status === 404) {
+        test.skip(true, `端点未部署(404),鉴权断言不可达 —— 待 ${path} 上线后本用例自动转为 401/403 断言`);
+        return;
+      }
+      expect([401, 403], `未鉴权应被拒绝(401/403),实际 ${status}`).toContain(status);
+    });
+  }
+});

+ 3 - 2
tests/test-cases/signature.md

@@ -34,5 +34,6 @@
 ### 状态
 
 - 上述用例随 SRS 变更 `a7edf5698`(2026-11-08)新增,覆盖 R1/R2/R3 三处根因 + 旋转兜底 + 竖屏回归 + 异常防御。
-- 功能状态:**待开发(RED)**——`signature.landscape.layout` 尚未实现,用例为预期失败态,待研发 YELLOW 后转绿。
-- 可执行化:建议补 `tests/e2e-live/signature-landscape.spec.js`(横屏视口 + 旋转事件注入)或 `cfc-frontend` 组件单测(hand-signature refreshRect / signature.vue px 化),纳入 Playwright live / Jest 闭环。
+- 功能状态:**已实现(GREEN)**——`adb032a61`(2026-11-08 22:51)落地 S1 高度重算 / S2 px 化 / S3 `refreshRect()`。
+- 可执行化:**已完成(第 94 轮)**——`cfc-frontend/__tests__/pages/signature/signature.landscape.test.js` 覆盖 TC-SIG-01(含 01b/01c 两档与 160 下限)、TC-SIG-02(px 化)、TC-SIG-04(旋转重算 + 组件 rect 刷新 + refs 缺失容错)、TC-SIG-03-E(rect 缺失沿用 clientX/Y),**7/7 全绿**。
+- 仍未覆盖:TC-SIG-03(真机笔迹可见性)、TC-SIG-05(三处跳转回传回归)、TC-SIG-N01(iOS/安卓横屏观感)——需真机/多端,本轮未实测。