Преглед изворни кода

docs(d2-02): 第一阶段真机全过回写 + 舱室故障隔离专项spec

- D2-02 第一阶段(control后端)代码+真机全部通过:27单测绿+真机完整冒烟
  (借真实舱6读温36.46℃/握手/电机越界HTTP400实拒/超时14s自动回收)
- 真机踩坑记录:僵尸operate不挡control / 登录真因=测试目录缺tl-shared.config
  致BaseUrl坏(非凭据) / auth库本零用户已插admin/123456(可逆)
- 新增加固专项spec:舱室故障隔离(运行期已隔离✓/启动期半坏拖垮全体✗)
  + 双端故障提示硬约束(operate+front都明确显示哪个舱什么故障)
- 回写 进度状态.yaml/交接卡/待验证清单/工作计划表/进度数据.js

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie пре 2 дана
родитељ
комит
100e5f8

+ 22 - 0
项目文档/进度/交接卡.md

@@ -416,3 +416,25 @@
 - **核实**:每 Task 都贴了真实 red→green 输出。全量 `dotnet test IvfTl.ControlHost.Tests` = **25 绿 0 失败**(Smoke1 + MotorClamp 10 + SessionManager 6 + Execute 8)。codegraph sync = Already up to date(监视器实时同步)。蓝本零偏离:核对 FakeSerial.Calls 记录格式(VMoveTo({p})/WriteScanStep({p})/Temp/OpenLed)、ISerialChannel 方法签名(WriteOpenIntakeTimeWait 第二参 bool 用默认)、批A 数据类字段(CurrentHor/CurrentVer 默认 -1)全对得上,照抄即编译0错+测试绿。
 - **8 个 commit(本分支)**:批A b4da856/e3fa590/ae80804/be88acf + 批B 968ad59/b7e8e40/cdc4976/5922797。
 - **下一步**:批C = Task8 ControlHttpServer 加 /debug 路由(acquire/heartbeat/release/command 转 DebugSessionManager) + Task9 Program.cs 装配 DebugSessionManager(按舱取 gate 委托)+ 起 SweepExpired 看门狗定时器;再 Task10 真机 curl 冒烟(Claude 自主跑)。本分支待并 main。
+
+---
+
+## 2026-06-23 · D2-02 第一阶段【真机全过】+ C-1红线修复 + 批C + 登录排障始末 + 舱室故障隔离分析
+
+- **C-1 红线修复(批B 审查发现,commit 91afb03)**:opus 审查揪出 Critical——相对运动(Forward/Backward)钳位 base 用会话跟踪位 `CurrentVer/Hor`(初值-1→fallback 0),真机电机在 120000 发 Forward 10000 时按 base=0 算 target=10000 放行,实际走到 130000 越 125000 红线。**修为:钳位前回读真实物理位 `ReadVerticalPositionWait/ReadHorizontalPositionWait` 作 base + 成功后回读刷新(自愈漂移)**;顺带 motorDelay 透传 / Reset 加 ok 守卫 / Dispose 记日志。新增非零起点越界回归测试 red→green。
+- **批C(commit 7c43933 + b342374)**:Task8 `ControlHttpServer` 加可选参 `DebugSessionManager` + `/debug/acquire|command|release|heartbeat` 路由(状态码 acquire 200/409、heartbeat 200/410、command 按 code 410/400/200)+ `ReadBody`;Task9 `Program.cs` 装配 debugMgr(`HardwareAccessLayer.GetHouseGate`+`DateTime.UtcNow`+ttl10s)+ `_exitEvent` new 后起 TTL 看门狗(每3s SweepExpired)。Debug/Release 0 错,**27 单测全绿**。
+- **11 个代码 commit(本分支)**:批A b4da856/e3fa590/ae80804/be88acf + 批B 968ad59/b7e8e40/cdc4976/5922797 + C-1 91afb03 + 批C 7c43933/b342374。
+- **真机完整冒烟排障始末(Claude 自主,UAC 提权)** 卡很久,根因全查实:
+  1. **僵尸 operate 20268**:用户以为重启了,但系统开机时间还是 6/21(开机2天半)、僵尸还在杀不掉(提权也拒绝)。**但实测它不占 COM 口、不占 control 的 38080/Mutex**,不挡 control。我起的 control 也提权、非提权 bash 杀不掉(需提权 taskkill)。
+  2. **control 登录失败→不扫串口→/debug 借到 lease 但 Serial=null→命令 NO_HANDLE**。
+  3. **auth 库 `aivfo-auth.user` 本零用户**(全库唯一用户表,dump 只建表)。按用户要求插 admin:密码=`md5(AUTH_SALT+123456+AUTH_SALT)`=`582de128a6d0050981bf90059ceeb2d9`(`CryptoUtils.encode` 盐固定),`deleted='2017-01-01 00:00:00'`(`BaseEntity @TableLogic` 未删哨兵)。**直连 /api/gateway/auth/login 返 success+token**——用户没问题。无 mysql 客户端,用 **JDBC(maven-repo mysql-connector 8.0.28+JDK11)** 连 192.168.0.108:3306 root/root 直插(`临时文件/DbSql.java`)。
+  4. **真因(非凭据)**:control 从**原始构建目录** `bin/Release/net6.0-windows/` 跑,`dll.config` 是 `<appSettings file="..\tl-shared.config">`、urlIp/urlPort 配置收敛后只在共享文件;`..\tl-shared.config`(=`bin/Release/tl-shared.config`)**不存在**→urlIp 读空→BaseUrl 拼坏→登录发错地址。真实部署 control 在 operate 的 `control\` 子目录、`..\` 指 operate 根,所以没问题。**修:把 `ivf_tl_Operate/tl-shared.config` copy 到 `bin/Release/`**。
+  5. 修后:control 登录→ScanDevices(发现 2/4/6/7/8/9/11)→`/debug` 通真串口。
+- **Task10 完整冒烟真机结果(舱6)**:借真 sid→**ReadTemp 36.46℃**/ReadPressure/ReadDoor/**ShakeHands=6**→**垂直越界130000 HTTP400实拒(不下发)/水平越界300000 HTTP400**→heartbeat/release(旧sid 410)→再借不发心跳 **14s 后 sid2 410=超时自动回收**。**D2-02 安全核心真机端到端全过**。
+- **逐舱普查找坏舱(用户告知有2个坏舱)**:2/4/6/7/8/9 全握手正常、温度 36.18~36.49℃(都健康)、13:05日志相机init全0、DB house 配置正好7舱与扫描一致。**非破坏性手段查不出哪2个**——坏舱是功能性损坏(电机/相机/门),需电机走位(V-012)或 MJPEG 出图才暴露。
+- **舱室故障隔离分析(用户关切:单舱坏不拖垮别舱 + 双端必须提示)** 源码逐行核实:
+  - **运行期单舱坏:已隔离 ✓**(每舱独立 HouseBin/ComBin/Channel/COM口/线程 + `MainThread`/`StartSendCommandThread` 是 `while(true){try/catch}`,异常捕获记录、循环继续、别舱无感)。
+  - **启动期:有缺口 ✗**——舱"半坏"(串口活但相机坏致 CCDSN 不匹配 `SerialBin.cs:252`/舱号或CCDSN重复)→`errorlist.Add`→`InitTL` 只要 `errorList.Any()` 就整体 return(`StartMain.cs:89`)→好舱也起不来。完全坏死/没接的舱则静默跳过(口打不开/握手失败 `goto CC`)。模块数≠11 已修成可继续。
+  - **产出 spec** `需求文档/specs/2026-06-23-舱室故障隔离与双端故障提示-design.md`:启动期按舱容错 + **双端故障提示硬约束**(operate监控页+front 都明确显示哪个舱/什么故障/时间,复用现有 alarm_data+MQTT 管道)。
+- **遗留环境**:control 15840 仍在跑(提权,停需提权 taskkill);僵尸 operate 20268 仍需真重启清;admin/123456 用户已留库(用户要的,可逆);`临时文件/` 下留 JDBC 工具 + 冒烟 ps1。
+- **下一步(用户已定 a+c)**:① 收尾提交 D2-02 第一阶段(本回写);② 拆"舱室故障隔离+双端提示"实现计划。D2-02 第二阶段 MJPEG 按需。本分支待并 main。

+ 16 - 0
项目文档/进度/工作计划表.md

@@ -52,3 +52,19 @@
 子代理驱动开发(subagent-driven-development):每个 Task 派全新子代理实现 + 两阶段审查(spec 合规 → 代码质量),主线收结论。先在 feature 分支干 Task1-6(纯编码),Task7 真机验证(真机已连,由 Claude 自主跑完、无需用户在场/配合;仅「水平电机」「垂直 Z 电机」运动范围需守安全区间,参考 `临时文件/相关参数.html`)。
 
 提交边界 = 文档已同步(CLAUDE.md 第三节)。
+
+---
+
+## 追加 · 2026-06-23 进展
+
+### D2-02 第一阶段(control 后端)= 🟢 代码完成 · ☑ 真机验证通过
+- Task0-9 全落地,**27 单测全绿**(含 C-1 非零起点红线回归),Debug/Release 0 错,11 个 commit 在 `feature/d2-02-debug-command-proxy`。
+- **真机完整冒烟过**:借真实舱6→读温 36.46℃/握手6→电机越界 HTTP400 实拒(不下发)→心跳/归还→超时14s自动回收。逐舱(2/4/6/7/8/9)读温均≈36℃健康。详见交接卡/待验证清单 2026-06-23。
+- 真机踩坑均已解(僵尸不挡 control / 登录真因=测试目录缺 tl-shared.config 致 BaseUrl 坏 / auth 库本零用户已插 admin/123456 可逆)。
+- 第二阶段(MJPEG 出图)、第三阶段(operate 接入 + V-012 电机真机走位)待拆。
+
+### 新加固专项 · 舱室故障隔离 + 双端故障提示 = ☐ 设计完成待拆实现计划
+- spec `需求文档/specs/2026-06-23-舱室故障隔离与双端故障提示-design.md`。
+- **结论**:运行期单舱坏已隔离 ✓;**启动期有缺口**——舱"半坏"(串口活相机坏/编号冲突)致 InitTL 整体中止、好舱起不来 ✗。
+- **目标**:① 启动期按舱容错(单舱坏只排除该舱、好舱继续培养)② **用户硬约束:任何舱异常 operate+front 双端都明确提示哪个舱/什么故障/时间**。
+- 改 control 启动核心,独立分支 bite-sized TDD + 真机拔插验收。待拆实现计划。

+ 29 - 0
项目文档/进度/待验证清单.md

@@ -106,3 +106,32 @@
 > - **TDD red→green**:新增 `ivf_tl_SerialHelper.Tests/CustomProtocolLengthTests.cs`(18 测试:0x12=12/0x10=7/0x08=9 三焦点 + 整表 14 项逐项对齐 operate 基线 Theory + 未知码回退6)。先 RED(0x12 实得 6,2 处失败:[Fact] + Theory 行),改 `Commander.cs:76` 0x12→12 后 **22 单测全绿**(含既有 4 个 M-01/02/03)。
 > - **真机修复验证(紧凑写→立即读,无丢帧 workaround)**:经真实 `SerialChannelImpl`,连续多轮"写原值→立即读"——舱9(V0=200)12 轮、舱8(V0=90)12 轮,**24/24 全干净**(读回精确=写入值,无 -1/垃圾);旧码此处必污染。回归 M-01/02/03 三条仍 PASS。control sln + operate Release 双编译 0 错。
 > - **残留(非回归,未做)**:`Write*Wait` 仍"收回复即 return",未把已落到正确位置 `[10]` 的状态字作为 bool 成功传回调用方——属"成功语义传播",基线 operate 同样无条件 true,非合并回归,列后续按需。
+
+---
+
+## D2-02 调试页命令代理 · 第一阶段(control 后端) — 2026-06-23【真机全过】
+
+> control 后端 27 单测全绿 + 真机完整冒烟过。详见交接卡 2026-06-23 真机段。环境踩坑(僵尸/登录/配置)均已解,见下。
+
+| 编号 | 验证项 | 门控 | 状态 |
+|------|--------|------|------|
+| D2-02a | 27 control 单测全绿(会话/超时回收/分发/红线钳位含 C-1 非零起点回归) | 单测 | ☑ |
+| D2-02b | control 带账号起→登录→ScanDevices 发现 2/4/6/7/8/9/11→/debug 通真串口 | **真机** | ☑ |
+| D2-02c | `/debug/acquire` 借真实舱6→真 sid、`ReadTemp=36.46℃`、`ShakeHands=6` | **真机** | ☑ |
+| D2-02d | **红线**:`VerticalMoveTo 130000`/`HorizontalMoveTo 300000`→HTTP400 OUT_OF_RANGE **不下发** | **真机** | ☑ |
+| D2-02e | heartbeat 续约 / release 归还(旧sid 410) / 再借不发心跳 **14s 超时自动回收(sid2 410)** | **真机** | ☑ |
+| D2-02f | 逐舱(2/4/6/7/8/9)借用读温均≈36℃健康、握手正常 | **真机** | ☑ |
+
+> **真机踩坑(均已解,记录备查)**:① 僵尸 operate 20268 杀不掉(需真重启,但不占串口/Mutex,不挡 control)。② control 登录卡点真因=从原始构建目录跑、`..\tl-shared.config` 缺失致 BaseUrl 拼坏(**非凭据**);修=copy `tl-shared.config` 到 `bin/Release/`。③ auth 库本零用户,按用户要求插 `admin/123456`(`md5(盐+pwd+盐)`,`deleted='2017-01-01'` 哨兵,JDBC 直插,可逆),直连登录接口验 success+token。
+> **第二阶段(MJPEG 出图)/第三阶段(operate 接入 + V-012 电机真机走位)仍待做**——电机真机走位(红线两轴守安全区间)、operate 崩溃后 control 自动回收恢复采集,放第三阶段。
+
+## 加固专项 · 舱室故障隔离 + 双端故障提示 — 2026-06-23 设计完成待实现
+
+> spec `需求文档/specs/2026-06-23-舱室故障隔离与双端故障提示-design.md`。源码逐行核实结论 + 用户硬约束。
+
+| 编号 | 验证项 | 门控 | 状态 |
+|------|--------|------|------|
+| H-01 | **现状**:运行期单舱坏已隔离(每舱独立线程+循环try-catch,别舱无感) | 分析 | ☑ 源码核实(StartMain/BufferBottleBin/ComBin) |
+| H-02 | **缺口**:启动期舱"半坏"(串口活相机坏/编号冲突)进 errorlist→InitTL 整体中止,好舱也起不来 | 分析 | ☑ 源码核实(StartMain.cs:89 / SerialBin.cs:252/212/260) |
+| H-03 | 改:启动期按舱容错——单舱任何阶段坏只排除该舱,好舱继续 StartAsync | **真机** | ☐ 待实现(拔相机/串口验好舱不中断) |
+| H-04 | **硬约束**:舱任何异常(启动排除/运行突发)→ operate 监控页 + front 都明确提示哪个舱/什么故障/时间 | **真机** | ☐ 待实现(复用 alarm_data+MQTT 管道) |

+ 4 - 4
项目文档/进度/进度数据.js

@@ -1,10 +1,10 @@
 // 实时面板数据源(监控面板.html 读 window.PROGRESS_DATA)。每推进一步更新本文件。
 window.PROGRESS_DATA = {
   project: "operate/control 双进程拆分",
-  generatedAt: "2026-06-23 21:30",
-  phase: "三阶段主体完成;M区全闭合;配置收敛真机验证;D2-02 调试页命令代理 第一阶段 control 后端 批A+批B 落地(TDD 25单测绿)",
-  currentTask: "D2-02 调试页跨进程命令代理 · 第一阶段(control后端):批A(单测工程 IvfTl.ControlHost.Tests + 数据类 DebugSession/DebugCommandResult + 红线电机钳位 MotorClamp + 测试替身 Fakes)+ 批B(DebugSessionManager:acquire/release/heartbeat/幂等 + 超时自动回收 SweepExpired + Execute 命令分发 读数/握手/阀/LED/电机红线钳位/EEPROM写)。全程严格 TDD red→green,逐 Task 单独 commit,全量 25 单测绿(Smoke1+MotorClamp10+SessionManager6+Execute8)。8 commit 在 feature/d2-02-debug-command-proxy。剩 批C(Task8 ControlHttpServer /debug 路由 + Task9 Program 装配 SweepExpired 看门狗)+ Task10 真机 curl 冒烟。",
-  note: "安全地基(spec §5):DebugSessionManager 注入 Func<int,IHouseGate>+Func<DateTime> 时钟可纯单测;会话表 ConcurrentDictionary;绝不指望 operate 主动还,SweepExpired 超时(TTL)自动 Dispose 归还兜底,心跳续约只罚失联不罚操作时长。红线电机钳位放 control:VerticalMoveTo/HorizontalMoveTo 及 Forward/Backward 相对目标越界(垂直[0,125000]/水平[0,220000])返 OUT_OF_RANGE 且不下发串口。Execute 失效 sessionId 一律拒(SESSION_EXPIRED 防抢串口)。Task5 踩坑:批A FakeLease.Dispose 不触发 gate.ResumeCapture 致超时回收测试红,改 FakeLease 持 gate 引用 Dispose 时恢复采集(对齐真实 HardwareLeaseImpl)。8 commit:批A b4da856/e3fa590/ae80804/be88acf + 批B 968ad59/b7e8e40/cdc4976/5922797。分支 feature/d2-02-debug-command-proxy 待并 main。",
+  generatedAt: "2026-06-23 22:30",
+  phase: "三阶段主体完成;M区全闭合;配置收敛真机验证;D2-02 调试页命令代理 第一阶段 control 后端【代码+真机全过】(27单测绿+真机完整冒烟);新增加固专项 舱室故障隔离+双端提示(spec)",
+  currentTask: "D2-02 第一阶段(control后端)= 代码+真机全部通过:批A/B/C + C-1红线修复,27单测绿,11 commit 在 feature/d2-02-debug-command-proxy。【真机完整冒烟过】借真实舱6→ReadTemp 36.46℃/ShakeHands=6→垂直越界130000/水平越界300000 HTTP400实拒(不下发)→心跳/归还/超时14s自动回收。逐舱2/4/6/7/8/9读温均≈36℃健康。下一步(用户定 a+c):①收尾提交本阶段 ②拆'舱室故障隔离+双端提示'实现计划。第二阶段MJPEG/第三阶段operate接入V-012待拆。",
+  note: "真机踩坑全解:①僵尸 operate 20268 杀不掉(需真重启,但不占串口/Mutex不挡control)②control登录卡点真因=从原始构建目录跑、..\\tl-shared.config缺失致BaseUrl坏(非凭据),修=copy tl-shared.config到bin/Release ③auth库本零用户,按用户要求JDBC插admin/123456(md5(盐+pwd+盐)=582de128...,deleted='2017-01-01'哨兵,可逆),直连登录接口验success+token。新加固专项结论:运行期单舱坏已隔离(每舱独立线程+循环try-catch);启动期有缺口(舱半坏→errorlist→InitTL整体中止,StartMain.cs:89/SerialBin.cs:252),需按舱容错+双端故障明确提示(用户硬约束,spec 2026-06-23-舱室故障隔离与双端故障提示-design.md)。11 commit:批A be88acf等+批B 5922797等+C-1 91afb03+批C 7c43933/b342374。",
   milestones: [
     { name: "阶段1 · control 独立进程骨架(完成)", tasks: [
       { id: "Task1-7", name: "全过+D1-08死锁修复+operate真外壳E2E+数据入库DB铁证", status: "☑" }

+ 20 - 20
项目文档/进度/进度状态.yaml

@@ -1,17 +1,17 @@
 # 续接断点状态(机器可解析)。换会话/换电脑后首先读它定位。
 # 状态取值: 未开始 / 进行中 / 完成 / 代码完成待验证
 # 纪律:本字段只存【当前断点】,历史细节进 交接卡.md(见 CLAUDE.md 第三节)。
-更新时间: 2026-06-23 D2-02 第一阶段(control后端)批A+批B 已落地:单测工程+数据类(DebugSession/DebugCommandResult)+红线钳位 MotorClamp + 测试替身 + DebugSessionManager(借用/归还/心跳/超时自动回收/命令分发含电机红线钳位)全 TDD,全量 25 单测绿,8 个 commit 在 feature/d2-02-debug-command-proxy。当前无 control 在跑、无活体培养(僵尸 operate 20268 仍在但不占舱口、需重启清)。
+更新时间: 2026-06-23 D2-02 第一阶段(control 后端)代码+【真机】全部通过:27 单测绿 + 真机完整冒烟过(借真实舱6读温36.46℃/握手/电机越界HTTP400实拒/超时14s自动回收),11 commit 在 feature/d2-02-debug-command-proxy。新增加固专项 spec(舱室故障隔离+双端提示)。
 当前任务: >
-  【D2-02 调试页命令代理 · 第一阶段批A+批B 完成,待做批C(Task8-9)+真机冒烟(Task10)】(分支 feature/d2-02-debug-command-proxy)
-  · 已完成:批A(Task0-3 单测工程/数据类/MotorClamp 钳位/Fakes)+ 批B(Task4-7 DebugSessionManager:
-    acquire/release/heartbeat/幂等 + 超时自动回收(改 FakeLease 持 gate)+ Execute 分发(读数/阀/LED/电机红线钳位/EEPROM写)),全 TDD red→green,全量 25 绿
-  · 下一步:批C = Task8 ControlHttpServer /debug 路由(acquire/heartbeat/release/command) + Task9 Program 装配 DebugSessionManager
-    + SweepExpired 看门狗定时器;然后 Task10 真机 curl 冒烟(Claude 自主跑)。本分支待并 main。
+  【D2-02 第一阶段 control 后端 = 代码+真机全过,待收尾提交文档】(分支 feature/d2-02-debug-command-proxy)
+  · D2-02 第一阶段:批A/B/C + C-1红线修复全落地,27单测绿,Debug/Release 0错;**真机完整闭环已验**(见交接卡 2026-06-23 真机段)。
+  · 真机踩坑已解:control 登录卡点=测试目录缺 ..\tl-shared.config 致 BaseUrl 坏(非凭据);auth 库本零用户,已插 admin/123456(md5(盐+pwd+盐)哈希,可逆),登录通
+  · 下一步(按用户已定 a+c):① 收尾 D2-02 第一阶段(本批文档回写+提交);② 新加固专项"舱室故障隔离+双端故障提示"(spec 已出,待拆实现计划)——用户硬约束:任何舱异常 operate+front 都要明确提示哪个舱/什么故障。
+  · D2-02 第二阶段(MJPEG)/第三阶段(operate接入V-012)仍待拆。本分支待并 main。
 说明: >
-  operate/control 双进程拆分三阶段主体早已完成;合并遗留 M 区 M-01~M-07 本轮全部闭合
-  (M-01/02/03 builder去桩、M-04 存图代码定论、M-05 0x12帧长回归、M-06 按well焦点零点、M-07 网关)
-  仅剩延后专项(D2-02 命令代理设计 / D3-04 两栈去重风险重构 / 整机自启复测需重启),需用户决策或重启
+  D2-02 安全核心真机验证齐全:会话借用/心跳/归还/超时自动回收/红线钳位(越界不下发)全过,逐舱(2/4/6/7/8/9)读温均≈36℃健康。
+  新加固专项结论:运行期单舱故障已隔离(每舱独立线程+循环try-catch);**启动期有缺口**——舱"半坏"(串口活但相机坏/编号冲突)会进 errorlist 致 InitTL 整体中止、好舱也起不来(StartMain.cs:89/SerialBin.cs:252);需改按舱容错。两个坏舱:非破坏性手段(握手/温度/相机init/配置)查不出,需电机走位(V-012)或MJPEG出图才暴露
+  环境:僵尸 operate 20268 仍杀不掉(需真重启,但不占串口不挡 control);后端 108+网关10010 全在线
 阶段概览:
   - id: 阶段1
     名称: control 独立进程骨架
@@ -20,17 +20,17 @@
   - id: 阶段2
     名称: 监控补全 + 调试借串口 + 受护栏停止
     状态: 代码完成待验证
-    备注: "三端点真机验+operate客户端+监控页+受护栏按钮;调试页完整借串口延后(命令代理设计+电机验门控)。"
+    备注: "三端点真机验+operate客户端+监控页+受护栏按钮;调试页完整借串口=D2-02。"
   - id: 阶段3
     名称: 清理老壳 + 装机收尾
     状态: 代码完成待验证
-    备注: "退役删ControlTest(两编译0错)+部署指南+开机自启方案验;ComBin两栈去重延后专项。"
-  - id: 阶段2
-    名称: 监控补全 + 调试借串口 + 受护栏停止
-    状态: 未开始
-    备注: "/status补全(各舱实时活动/线程心跳/串口借用)+ /serial/pause|resume跨进程借串口 + /shutdown受护栏停止。待阶段1完成后拆计划"
-  - id: 阶段3
-    名称: 清理老壳 + 装机收尾
-    状态: 未开始
-    备注: "退役删ivf_tl_ControlTest脏壳 + operate开机自启 + ComBin两套栈去重(G1-2) + 部署文档。待阶段2完成后拆计划"
-下一步: D2-02 spec + 第一阶段实现计划已出(分支 feature/d2-02-debug-command-proxy)。执行第一阶段 control 后端(子代理驱动逐 Task,纯单测+curl 真机冒烟)。完成后第二阶段 MJPEG、第三阶段 operate 接入(解锁 D3-04)。D3-02 整机自启需重启。按用户优先级
+    备注: "退役删ControlTest+部署指南+开机自启;ComBin两栈去重延后。"
+  - id: 阶段2-D2-02
+    名称: D2-02 调试页命令代理 · 第一阶段(control 后端)
+    状态: 代码完成·真机验证通过
+    备注: "Task0-9 全落地,27单测绿(含C-1非零起点红线回归)。真机完整冒烟过(借真实舱/读温/越界实拒/超时回收)。第二(MJPEG)/三(operate接入V-012)阶段待拆。"
+  - id: 加固-舱室故障隔离
+    名称: 舱室故障隔离 + 双端故障提示(新专项)
+    状态: 设计完成待拆计划
+    备注: "spec 2026-06-23-舱室故障隔离与双端故障提示-design.md。启动期按舱容错(单舱坏不拖垮全体)+ operate/front 双端明确提示哪个舱什么故障(用户硬约束)。改 control 启动核心,单独分支 TDD+真机。"
+下一步: 先收尾提交 D2-02 第一阶段(文档回写)。再拆"舱室故障隔离+双端提示"实现计划。D2-02 第二阶段 MJPEG 按需推进。本分支待并 main

+ 59 - 0
项目文档/需求文档/specs/2026-06-23-舱室故障隔离与双端故障提示-design.md

@@ -0,0 +1,59 @@
+# 舱室故障隔离与双端故障提示 · 设计
+
+> 新增加固专项(2026-06-23 由真机调试中发现 + 用户明确要求)。独立于 D2-02,改 control 启动核心,需单独 TDD + 真机谨慎验证。
+> 设计依据:本次真机排障对 `StartMain`/`SerialBin`/`HouseBin`/`ComBin` 源码逐行核实(见下 §2 file:line 证据)。
+
+## 一、背景与问题
+
+培养箱是多舱室设备,**别的舱里有胚胎在养**。两个现实场景必须扛住:
+1. **开机时就有部分舱坏**(相机坏/串口坏/编号冲突),好的舱**仍要正常启动、继续培养**。
+2. **运行中某舱突然坏**,**不能影响其他舱**。
+
+且用户提出**硬约束**:**舱室的任何异常,operate 和 front 两个客户端都必须给用户明确提示(哪个舱、什么故障、何时),不能"坏了却不知道哪坏"。**
+
+## 二、现状分析(源码逐行核实,2026-06-23)
+
+| 场景 | 现状 | 证据 |
+|---|---|---|
+| **运行期单舱突然坏** | ✅ **已隔离** | 每舱独立 `HouseBin`→`ComBin`→`Channel`→独立 COM 口→独立线程;`HouseBin/BufferBottleBin.MainThread` 是 `Task.Factory.StartNew(LongRunning)` 的 `while(true){ try{采集/换气/气压} catch{记日志} }`,单舱异常被捕获记录、循环继续、别舱线程无感;`ComBin.StartSendCommandThread` 同构 `while(true){try/catch}`。`StartMain.cs:228-250`、`BufferBottleBin.cs:136-192`、`ComBin.cs:114-181` |
+| **舱完全坏死/没接** | ✅ **跳过,好舱继续** | 相机枚举 `GetCameraSn` 失败→SN 记 null 不报错(`SerialBin.cs:108-114`);扫口 `GetHouseInfo` 口打不开/握手失败→`goto CC` 跳过不报错(`SerialBin.cs:204/206`);模块数≠11 已改可继续(`ContinueOnModuleCountMismatch` 默认 true,`StartMain.cs:103-117`) |
+| **舱半坏(串口活、相机坏/编号冲突)** | ❌ **拖垮整机启动** | `GetHouseInfo` 对"相机列表无此舱 CCDSN"(`SerialBin.cs:252`)、舱号重复(`:212`)、CCDSN 重复(`:260`)、相机重复 SN(`:121`)→ `errorlist.Add`;而 `InitTL` 只要 `errorList.Any()` 就整体 `return 空`(`StartMain.cs:78-83`/`89-94`)→ `StartRun` 拿到 errorInfo 直接返回、`StartAsync()` 不执行 → **一个舱都起不来** |
+| **故障可见性** | ◑ **部分** | 已有 `alarm_data` 入库 + MQTT 上报管道(D1-07 实测舱11低压告警入库);但"舱启动被排除""舱串口/相机突然坏"这类**结构性故障**是否作为告警上报双端、front/operate 是否明确展示"哪个舱什么故障",未成体系 |
+
+**结论**:运行期隔离做到了;**启动期有"半坏拖垮全体"的真实缺口**;故障提示需成体系打通双端。
+
+## 三、目标
+
+1. **启动期按舱容错**:任何舱在任何阶段(相机枚举/扫口握手/EEPROM 读/舱初始化)坏 → **只把该舱登记为故障并排除**,其余好舱继续 `StartAsync` 正常培养。整机启动**绝不因单舱失败而中止**。
+2. **双端故障明确提示(硬约束)**:舱室任何异常(① 启动被排除 ② 运行期突发故障)→ 经统一故障通道上报 → **operate 监控页 + front 都明确显示:哪个舱 / 什么故障 / 发生时间 / 当前是否已被隔离**。
+
+## 四、设计要点
+
+### 4.1 启动期按舱容错(control)
+- `SerialBin.Start/UpdataCamera` 的 `errorlist`:**区分"致命"与"单舱故障"**。单舱故障(相机坏/CCDSN 不匹配/该舱编号冲突)→ 记入**坏舱清单**(houseSn + 原因 + 阶段),**不**作为整体中止信号。
+- `StartMain.InitTL`:不再"errorList 非空即整体 return";改为**剔除坏舱**后用好舱继续(`runHouses` 排除坏舱)。仅在**全部舱都失败/总控11失败**等真致命情形才中止。
+- `InitHouse`:大 try-catch 内**逐舱构造/StartTask 各自 try-catch**,单舱构造抛异常只跳过该舱。
+- 坏舱清单贯穿到状态快照(`MonitorSnapshot`)与告警上报。
+
+### 4.2 统一故障上报(复用现有管道)
+- 接**现有 `alarm_data` + MQTT 上报**机制(D1-07 已验通)。新增舱级故障告警类型:`HOUSE_INIT_EXCLUDED`(启动排除)、`HOUSE_RUNTIME_FAULT`(运行期串口/相机失联)、`HOUSE_CAMERA_FAULT`、`HOUSE_SERIAL_FAULT` 等。
+- control 的 `/status` 快照增"舱故障列表"字段(houseSn/类型/原因/时间/是否已隔离),供 operate 监控页轮询展示。
+- 运行期 `MainThread`/`ComBin` 的 catch 里:连续失败达阈值 → 升级为舱级故障告警(避免每圈刷屏,做去抖/阈值)。
+
+### 4.3 双端展示(硬约束落地)
+- **operate**:监控页新增"舱故障"区,红色高亮列出每个故障舱(舱号/故障类型/时间/已隔离),与现有监控三块并列。
+- **front**:接告警流,故障舱在舱位图/告警列表明确标红 + 文案("X 号舱相机故障,已隔离,其余舱正常")。
+- 两端文案统一、可定位(舱号 + 故障类型 + 时间),满足"一眼知道哪坏了"。
+
+## 五、验收(真机)
+1. **启动期**:制造一个舱"半坏"(如拔该舱相机 / 让 CCDSN 不匹配)→ control 启动:该舱被排除、**其余舱全部正常 StartAsync 培养**;control 不中止。
+2. **运行期**:运行中拔某舱串口/相机 → 该舱告警上报、被隔离,**其余舱采集不中断**。
+3. **双端提示**:上述两种情况,**operate 监控页 + front 都明确显示故障舱(舱号/类型/时间)**。
+
+## 六、与其他任务关系
+- **独立于 D2-02**(调试命令代理):本专项是 control 启动/运行健壮性 + 双端可观测性。
+- 改 **control 启动核心**(InitTL/InitHouse/SerialBin),风险较高 → 单独 feature 分支,bite-sized TDD(纯逻辑可单测:坏舱剔除、坏舱清单、阈值去抖)+ 真机拔插验证。
+- 故障上报可与 D1-10 oplog 审计、阶段2 监控页复用同一展示面。
+
+## 七、待拆
+- 实现计划(开发计划/*.md):①control 坏舱剔除(InitTL/SerialBin 重构,TDD)②故障上报/快照字段 ③operate 监控页展示 ④front 告警展示 ⑤真机拔插验收。第一阶段(①②control 侧)纯单测+真机可由 Claude 自主;③④双端 UI 真机门控。