Эх сурвалжийг харах

docs(config): 配置收敛文档同步——换机清单/部署指南/两全景/续接三件套

- 连接配置清单§三:C# 换机改为只改 operate 根 tl-shared.config 一处(operate+control 共享)
- 双进程部署指南:产物加 tl-shared.config(operate 根,control 经 ..\ 读)+ 自检清单 + 升级 PreserveNewest 说明
- operate/control 两全景:连接键章节标注迁入 tl-shared.config、operate 删 12 换气/CCD 死键
- 续接三件套(进度状态.yaml/交接卡.md/工作计划表.md)+ 进度数据.js:配置收敛 ☑ 完成

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie 2 өдөр өмнө
parent
commit
1020e22141

+ 7 - 4
项目文档/开发环境/双进程部署指南.md

@@ -16,6 +16,8 @@
 
 **关键**:control 必须放在 operate 输出目录的 `control\` 子目录(operate `App.config` 的 `controlExePath=control\ivf_tl_ControlHost.exe` 据此相对定位;`ControlProcessLauncher.ResolveExePath` 解析)。换布局则同步改 `controlExePath`。
 
+> 🟢 **2026-06-23 配置收敛 —— 共享连接文件 `tl-shared.config`(必带)**:operate 与 control 共享的 7 个连接键(urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter)收敛为**唯一数据源** `tl-shared.config`,**放在 operate 输出根目录**(operate.exe 同级)。operate `App.config` 经 `<appSettings file="tl-shared.config">`、control `ControlHost/App.config` 经 `<appSettings file="..\tl-shared.config">`(`..` 指 control 子目录的父目录 = operate 根)只读合并 → 两进程读取代码零改动。**`tl-shared.config` 只放 operate 根这一份,`control\` 子目录下不要再放**(control 靠 `..\` 向上读)。由 `ivf_tl_Operate.csproj` 以 `PreserveNewest` 随 operate 输出;升级再部署时 `PreserveNewest` 会**保留站点已改的值**(若仓库内默认值变更过则会覆盖,升级后按需核对)。换中间件服务器 = 只改这一份(见 `连接配置清单-换服务器必读.md §三`)。
+
 > ⚠ **看门狗部署必须拷"全目录",别只拷 exe+dll(真机踩坑实锤)**:watchdog 用 DPAPI(`System.Security.Cryptography.ProtectedData`)读凭据,真 Windows 实现在 **`runtimes/win/lib/net6.0/System.Security.Cryptography.ProtectedData.dll`**,由 **`ivf_tl_Watchdog.deps.json`** 在运行期定位。只拷根目录那个 `ProtectedData.dll`(=非 Windows 占位 stub)会让看门狗起 control 时抛 `PlatformNotSupportedException: DPAPI is not supported`、无法重拉。**正确做法:把 watchdog 的 `bin/Release/net6.0-windows/` 整个目录(含 `*.deps.json` + `runtimes/`)拷进 `control\`**;与 control 文件同目录共存(两 exe 各有自己的 `*.deps.json`,不冲突)。
 
 **构建命令**(真机/连内网必须 Release):
@@ -29,7 +31,8 @@ dotnet build control/ivf_tl_ControlHost/ivf_tl_ControlHost.csproj -c Release
 ```
 
 **control 必带(否则起不来,阶段1 真机实锤)**:
-- `ivf_tl_ControlHost.dll.config`(=自带 App.config,缺它 control AppData 构造读 gbTime/csTime 等键 `.ToString()` NPE);**内容与 operate `App.config` 的 control 业务键 + 连接键保持同步**。
+- `ivf_tl_ControlHost.dll.config`(=自带 App.config,缺它 control AppData 构造读 gbTime/csTime 等键 `.ToString()` NPE);**含 control 独有键(换气/CCD + 凭据等);共享连接键(urlIp/urlPort/mqtt*/kfka*/outInter)经 `<appSettings file="..\tl-shared.config">` 从 operate 根的共享文件读**(2026-06-23 配置收敛后不再在本文件内重复)。
+- **`tl-shared.config`**(在 operate 根,非 control\ 内):共享连接键唯一数据源,control 经 `..\` 读到。
 - `DependFile/`(ccd 7 个相机 DLL + DB/aivfoTL.db + newccd/),由 `ivf_tl_Control.csproj` 经 `CopyToOutputDirectory=Always` 传递拷贝,核对真落地。
 
 ## 二、两进程契约(本地 HTTP,仅 127.0.0.1)
@@ -64,9 +67,9 @@ Remove-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
 
 ## 四、装机自检清单
 
-1. operate 目录 + `control/` 子目录就位;`control/ivf_tl_ControlHost.dll.config` + `control/DependFile/` 齐。
-2. operate `App.config`:`controlPort=38080`、`controlExePath=control\ivf_tl_ControlHost.exe`、`urlIp/urlPort` 指向现场网关、`kfkaIP/mqttIp` 指向现场服务器
-3. control `ivf_tl_ControlHost.dll.config` 的连接键与 operate 一致
+1. operate 目录 + `control/` 子目录就位;operate 根有 **`tl-shared.config`**(`control/` 下**无**此文件);`control/ivf_tl_ControlHost.dll.config` + `control/DependFile/` 齐。
+2. **`tl-shared.config`(operate 根)**:`outInter=0`、`urlIp/urlPort` 指现场网关、`kfkaIP/mqttIp` 指现场服务器。operate `App.config`:`controlPort=38080`、`controlExePath=control\ivf_tl_ControlHost.exe`。
+3. control `ivf_tl_ControlHost.dll.config` 开标签是 `<appSettings file="..\tl-shared.config">`(经它从 operate 根共享文件读连接键,无需单独配)
 4. 起 operate → 登录 → 观察 `control/` 下 `ivf_tl_ControlHost.exe` 被拉起、`http://127.0.0.1:38080/status` 返回 `started:true`、监控页显示各舱。
 5. 设开机自启(§三)→ 重启验证开机自动起 operate→拉起 control→机器被驱动。
 

+ 14 - 10
项目文档/开发环境/连接配置清单-换服务器必读.md

@@ -59,22 +59,25 @@
 
 ## 三、C# 客户端 —— 配 IP/端口的地方(operate / front)
 
+> 🟢 **2026-06-23 配置收敛**:operate 与 control(同机两进程)的共享连接键(urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter)已收敛为**唯一数据源** `tl-shared.config`(在 operate 输出根目录)。operate 与 control 各自 App.config 经 `<appSettings file="(..\)tl-shared.config">` 只读合并 → **换机改这一份即可,两进程同时生效**(不再 operate/control 各改一遍)。详见 `双进程部署指南.md` + `specs/2026-06-23-配置收敛-operate-control连接组单一数据源-design.md`。front 仍独立(另一台机/另一份 App.config)。
+
 ### ★★ 两个大坑(改前必看)★★
-1. **`#if DEBUG` 会覆盖 App.config**:`AppData.cs` 构造函数里有 `#if DEBUG` 块,**DEBUG 编译时无条件写死**指向外网 `test-gateway.aivfo.com` / `211.149.139.131`,**App.config 完全不生效**。→ 部署给客户/连测试服务器,必须 **Release 编译**。
-2. **`outInter` 内外网开关**:App.config `outInter=1` 时,代码改用硬编码外网地址(忽略 App.config 的 url/mqtt)。→ 连内网中间件必须 **`outInter=0`**。
-   - 满足"Release + outInter=0"后,下表 App.config 的键才真正生效。
+1. **`#if DEBUG` 会覆盖配置**:`AppData.cs` 构造函数里有 `#if DEBUG` 块,**DEBUG 编译时无条件写死**指向外网 `test-gateway.aivfo.com` / `211.149.139.131`,**App.config/tl-shared.config 完全不生效**。→ 部署给客户/连测试服务器,必须 **Release 编译**。
+2. **`outInter` 内外网开关**:`outInter=1` 时,代码改用硬编码外网地址(忽略 url/mqtt)。→ 连内网中间件必须 **`outInter=0`**(该键现也在 `tl-shared.config`)
+   - 满足"Release + outInter=0"后,下表的键才真正生效。
 
-### operate(`ivf_tl_operate_2.0/ivf_tl_Operate/`)
-App.config(`App.config`)键 → 代码读取处 `AppData.cs:79-82`、Kafka 在 `App.xaml.cs:72`:
+### operate + control(共享:`<operate输出根>/tl-shared.config`)
+operate 与 control **都读这一份**;operate 读取处 `AppData.cs`/`App.xaml.cs`,control 读取处 `control/ivf_tl_Control/AppData.cs`,均经 `ConfigurationManager.AppSettings`(`file=` 合并)。换机改 `tl-shared.config` 一处:
 
 | 键 | 含义 | 当前值 | 换机设为 |
 |---|---|---|---|
 | `outInter` | 内/外网(0=内网读本表) | 0 | 0 |
 | `urlIp` + `urlPort` | **gateway** 地址 | http://127.0.0.1 : 10010 | gateway 所在机(微服务在本机→127.0.0.1) |
 | `mqttIp` + `mqttPort` | **MQTT** broker | 192.168.0.108 : 1883 | MQTT 所在机=192.168.0.108 |
-| `kfkaIP` + `kfkaPort` | **Kafka**(oplog 操作日志,M8) | 192.168.0.108 : 9092 | Kafka 所在机=192.168.0.108 |
+| `kfkaIP` + `kfkaPort` | **Kafka**(oplog 操作日志) | 192.168.0.108 : 9092 | Kafka 所在机=192.168.0.108 |
 
-> 硬编码默认(App.config 缺键/DEBUG 时才用):`AppData.cs:59` BaseUrl、:61 MqttIp;`App.xaml.cs:72` kfkaIP 默认 127.0.0.1。
+> operate/control 各自 App.config 里**不再有**这 7 个键(已迁出);它们只各自保留自己独有的键(operate:userName/passWord/engineerPwd/tlNum/cacheDisk/Language/houseEnabled/autoFocus/controlPort/controlExePath;control:换气/CCD 业务键 + 凭据等)。统一配置 UI(operate「统一配置」页)保存这 7 键时也写进 `tl-shared.config`(经 `AppConfigHelper`→`SharedConfigStore`)。
+> 硬编码默认(配置缺键/DEBUG 时才用):`AppData.cs` BaseUrl/MqttIp;`App.xaml.cs` kfkaIP 默认 127.0.0.1。
 
 ### front 中央管理端(`aivfo-front-manament-2.0/ivf_tl_Manage/`)
 App.config 键 → 代码读取处 `AppData.cs:83-86`:
@@ -88,8 +91,9 @@ App.config 键 → 代码读取处 `AppData.cs:83-86`:
 
 > ⚠ 订正(2026-06-22 源码核实):front **有** Kafka/oplog 接入 —— `ivf_tl_Manage/App.config` 含 `kfkaIP/kfkaPort`,`App.xaml.cs` 的 `InitOperationLog()`(project=front、topic=`tl-oplog`)启动 Kafka 产消息端,oplog 模块仅 界面点击/HTTP(详见 CLAUDE.md §5.1)。硬编码默认 BaseUrl/MqttIp 在 `AppData.cs:123-125`。
 
-### control(历史路径 `ivf_tl_control_2.0/ivf_tl_ControlTest/App.config`,合并后已并入 `ivf_tl_operate_2.0/control/`)
-含 `urlIp/kfkaIP/mqttIp`(值 192.168.0.91),但 **control 合并为 operate 单进程后,运行期读 operate 的 App.config,ControlTest 这份失效**(且该工程计划退役,见 01 §5.5)。**换机无需改它。**
+### control(`ivf_tl_operate_2.0/control/ivf_tl_ControlHost/`)
+control 是**独立进程**(部署在 operate 根的 `control\` 子目录),其 `ivf_tl_ControlHost.dll.config` 经 `<appSettings file="..\tl-shared.config">` 读上一级 operate 根的共享文件 → **与 operate 同源、换机无需单独改 control**。control config 里只保留自己独有的键(换气/CCD 业务键 + 凭据等),不含上面 7 个共享连接键。
+> (历史:合并前 `ivf_tl_control_2.0/ivf_tl_ControlTest/App.config` 已退役删除,见交接卡 D3-01。)
 
 ---
 
@@ -97,7 +101,7 @@ App.config 键 → 代码读取处 `AppData.cs:83-86`:
 
 1. [ ] 新服务器装好 6 中间件,记下新 IP 与端口/账号密码(更新本文第一节 + `服务器测试环境.md`)。
 2. [ ] Java:改第二节 **8 个 `server.ip`** 为新 IP;如账号密码变,改各档 datasource user/pwd。
-3. [ ] C#:确认 **Release 编译 + `outInter=0`**;按第三节改 operate/front 的 `urlIp/mqttIp/(operate)kfkaIP`
+3. [ ] C#:确认 **Release 编译 + `outInter=0`**;改 **operate 根的 `tl-shared.config` 一处**(operate+control 共享 urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter);front 另按第三节 front 表改其 App.config
 4. [ ] Redis 若启用:确认 host/密码生效。
 5. [ ] 建库 + migration(见 `进度状态.yaml` 部署前置)。
 6. [ ] 起服务冒烟:gateway→各微服务注册 Nacos→operate 登录→MQTT 收发→拍图入 FastDFS→操作日志入 operation_log。

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

@@ -365,3 +365,26 @@
   - 开写开关:`HIL_ALLOW_WRITE=1 dotnet test` → **4/4 全过**(排气阀舱9 200→201→200;灯光逐舱落舱8 500→501→500,与原 harness 证据一致),值已恢复原值。
 - **核实**:真机两种模式实跑;control 全 sln + operate Release **双编译 0 错**;既有 SerialHelper **40 单测过**;codegraph sync 已跑(Already up to date);Xunit.SkippableFact 离线 restore 成功(未走回退)。僵尸 operate 20268 提权 taskkill 仍杀不掉(需重启清,符合既往记录),但未占舱口、不影响真机验证与编译。
 - **下一步**:工作计划剩延后专项(D2-02 调试页命令代理=多会话级设计→解锁 D3-04 删死栈 / D3-02 整机自启复测需真重启)+ 昨日建议配置收敛。均需用户定优先级或受控/重启。
+
+---
+
+## 2026-06-23 · 配置收敛(operate↔control 连接组单一数据源 + operate 死键清理)= 昨日建议「配置收敛」+ brainstorm/spec/plan + 子代理驱动 6 任务 + 真机验证
+
+- **背景**:用户选「配置收敛」。痛点(codegraph 坐实):双进程拆分后 operate `App.config` 与 control `ControlHost/App.config` 约 25 键逐字重复,control 那份注释明写「必须与 operate 手动同步」——换服务器/调参要改两处、漏一处两进程行为打架。核查厘清:① **operate 的 AppData 不读那 12 个换气/CCD 键**(合并单进程时代死副本,只 control 读)→ 该删;② 真正"两进程都读、漂移出 bug"的是连接组 7 键(urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter);③ operate 已有半成品「统一配置」UI(`UnifiedConfigViewModel`+`AppConfigHelper`,M5 治理)但只写 operate 自己 config = 缺口所在。
+- **流程(brainstorming→writing-plans→subagent-driven 执行)**:用户拍板:范围只 operate↔control;目标 = 单一数据源 + 清理死键;机制 = **方案一(共享片段 + `<appSettings file=>` 只读合并,写由 operate 收口)**(选 `file=` 不选 `configSource`:前者支持 `..` 上级路径,control 在子目录要指父目录);凭据组本轮不并入(避免动 control C#)。设计 `需求文档/specs/2026-06-23-配置收敛-operate-control连接组单一数据源-design.md` + 计划 `开发计划/2026-06-23-配置收敛-实现计划.md`。分支 `feature/config-consolidation`。
+- **实现(6 任务,每任务两阶段审查 spec→质量)**:
+  1. **Task1 file=+.. 硬验证**(临时 harness,gitignore):子目录进程经 `<appSettings file="..\probe-shared.config">` 真机实跑读到父目录键 `probeKey=HELLO`、exit=0 → 方案一命脉坐实。顺带确认 .NET6 产物名 `.dll.config`、外部文件与 inline 合并共存。
+  2. **Task2 SharedConfigStore + operate 单测工程**(TDD):新建 `Helpers/SharedConfigStore.cs`(XDocument 读写 appSettings 片段,`Read(path,key)`/`Write(path,key,value)`)+ operate 首个单测工程 `ivf_tl_Operate.Tests`;red→green 6 绿(含 null/XML 转义,审查后补)。
+  3. **Task3 AppConfigHelper.Save 分流**:加 `SharedKeys`(7 键 OrdinalIgnoreCase)+ `SharedConfigPath`(BaseDirectory\tl-shared.config);共享键落 SharedConfigStore、非共享键沿用 OpenExeConfiguration。读取侧零改。
+  4. **Task4 operate 接 file= + 删键**:新建 `ivf_tl_Operate/tl-shared.config`(7 键,108 现值)+ csproj Content/PreserveNewest 拷到输出根 + App.config 接 `file="tl-shared.config"` 删 7 共享键 + **删 12 换气/CCD 死键**(删前逐键 grep 坐实 operate 树零消费);保留 11 operate 独有键。顺带更正 AppConfigHelper 过时分层注释。
+  5. **Task5 control 接 ..\tl-shared.config**:`ControlHost/App.config` 接 `file="..\tl-shared.config"` 删 7 共享键,保留 22 个 control 独有/业务键(换气/CCD 全留——control 真读)。**C#/csproj 零改动**。
+  6. **Task6 真机 E2E + 回归**:见下「真机验证」。
+- **真机验证(UAC 静默提权,无 control 在跑/无活体培养,可自由启停)**:
+  - **部署布局**:control 部署到 operate 输出根的 `control\` 子目录;`tl-shared.config` 在 operate 根、control\ 下无副本。
+  - **control 读取坐实**:提权启动 control → `/status` `ServerUrl=http://127.0.0.1:10010/`(由共享文件 urlIp+urlPort 拼成;control 子目录无此文件 → 只能经 `..\tl-shared.config` 读到)。
+  - **单一数据源生效**:改共享文件 urlPort 10010→19999 重启 → `/status` ServerUrl 变 19999;改回 10010 重启 → 恢复。对称坐实 control 实际读的就是这一份。
+  - **回归**:operate 单测 6 绿 / SerialHelper 40 绿 / HIL 默认零写入 2 过 2 跳 / operate Release + control sln 双编译 0 错。control `/shutdown`(JSON body token=tl13579)干净退出释放 COM。
+  - **operate 外壳**:受僵尸 operate 20268 占单实例 Mutex 门控起不来(已知、需重启清),未深究;读取机制与 control 同源(均 ConfigurationManager+file= 合并),据 control 实证判定 operate 侧读取同样成立。
+- **核实**:6 任务每个经 spec 合规 + 代码质量两阶段子代理审查(独立读码/独立 grep/实跑核对);真机 /status 实读、单点改动对称验证、回归各项实际数字;工作区干净(取证改的 bin 共享文件已恢复)。提交链:spec f547ac9 / plan c9f1c70 / Task2 ca30cb2+8f3d096 / Task3 d687a3f / Task4 c3866440+199db15 / Task5 9235e10。
+- **诚实边界**:operate WPF 真外壳端到端(经 file= 读 + 登录 + 拉 control)受僵尸 Mutex 门控未现场跑通;连接变更走"改文件→重启进程"流程(重启读合并配置正确),未单独验"统一配置 UI 保存后本进程 RefreshSection 即时生效"(对连接键非常用路径,重启是现实流程)。凭据组未并入(独立小决策)。
+- **下一步**:本分支待并 main。剩延后专项(D2-02 命令代理设计/D3-04 删死栈被其阻塞/D3-02 整机自启需重启)+ 凭据收敛(可选)。按用户优先级。

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

@@ -27,7 +27,7 @@
 |------|------|------|----------|
 | **阶段1** | control 独立进程骨架 | 🟢 代码完成·真机闭环打通(待并 main) | control 独立 exe 能起✓、HTTP探活/读状态✓、续命✓、单实例✓、硬件获取✓、**真机自控环运行✓**;阻塞闭环的 D1-08 串口握手死锁已修复 |
 | **阶段2** | 监控补全 + 调试借串口 + 受护栏停止 | 🟢 监控/受护栏停止/借串口让路 已实现+真机验;调试页完整驱动待设计 | 监控页跨进程 /status 显示完整✓;受护栏 /shutdown 安全停✓;/serial 让路✓(调试页完整借串口需命令代理设计+受监督真机) |
-| **阶段3** | 清理老壳 + 装机收尾 | 🟢 退役删ControlTest+部署文档+开机自启 已做;**D1-10 control oplog审计埋点已迁移+真机验证**;**D3-05 control崩溃看门狗已实现+真机验证(2026-06-23)**;**HIL硬件在环回归套件已入库+真机验证(2026-06-23)**;删operate死栈延后 | 退役删 ivf_tl_ControlTest✓;双进程部署指南✓;开机自启✓;**D1-10 oplog审计迁移到control活栈✓**;**D3-05 看门狗(崩溃重拉/DPAPI凭据/可暂停停止卸载)✓**;**HIL套件 IvfTl.Hardware.HilTests(守护M-05帧长/M-06按well焦点/M-01-03 EEPROM写,门控Skip+默认零写入)✓**;ComBin删operate死栈(D3-04,被D2-02阻塞)仍延后 |
+| **阶段3** | 清理老壳 + 装机收尾 | 🟢 退役删ControlTest+部署文档+开机自启 已做;**D1-10 control oplog审计埋点已迁移+真机验证**;**D3-05 control崩溃看门狗已实现+真机验证(2026-06-23)**;**HIL硬件在环回归套件已入库+真机验证(2026-06-23)**;**配置收敛 已完成+真机验证(2026-06-23)**;删operate死栈延后 | 退役删 ivf_tl_ControlTest✓;双进程部署指南✓;开机自启✓;**D1-10 oplog审计迁移到control活栈✓**;**D3-05 看门狗(崩溃重拉/DPAPI凭据/可暂停停止卸载)✓**;**HIL套件 IvfTl.Hardware.HilTests(守护M-05帧长/M-06按well焦点/M-01-03 EEPROM写,门控Skip+默认零写入)✓**;**配置收敛(operate↔control连接组7键单一数据源tl-shared.config经<appSettings file=>合并读+operate删12换气CCD死键,真机改一处对称生效)✓**;ComBin删operate死栈(D3-04,被D2-02阻塞)仍延后 |
 
 ---
 

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

@@ -1,10 +1,10 @@
 // 实时面板数据源(监控面板.html 读 window.PROGRESS_DATA)。每推进一步更新本文件。
 window.PROGRESS_DATA = {
   project: "operate/control 双进程拆分",
-  generatedAt: "2026-06-23 17:30",
-  phase: "三阶段主体完成;M区全闭合;D1-10 oplog审计迁移真机验证;D3-05 看门狗真机验证;HIL硬件在环回归套件已入库真机验证",
-  currentTask: "HIL硬件在环回归套件入库(=昨日建议「HIL回归套件入库」):新增xUnit工程IvfTl.Hardware.HilTests经真实SerialChannelImpl端到端,把M-01/02/03/05/06真机检查固化为防回归用例,替代gitignore里跑完即弃的临时harness。门控Skip+默认零写入+写路径HIL_ALLOW_WRITE=1开关。真机零写入2过2跳、开写开关4/4全过。",
-  note: "HIL套件防M区修复被改回去:单测只锁纯逻辑(帧长表等),锁不住真机端到端行为;原验证harness都在gitignore临时目录跑完即弃,无入库护栏。经brainstorming→writing-plans→inline执行。新增ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests(net6.0-windows,入control sln,纯新增不动生产代码):HardwareRigFixture扫口握手收响应真舱+[SkippableFact](Xunit.SkippableFact)无真舱/被占即Skip+禁并行串行跑。覆盖FocusZero(M-06按well去重>1+安全Z区间)/FrameLength(M-05连读12轮全干净)纯读始终跑;EepromWrite(M-01/02/03写回环)默认Skip、HIL_ALLOW_WRITE=1才跑、逐舱取证、写后立即恢复原值。真机踩坑:首版写回环固定取舱9致M-03灯光(舱9无项)被永久跳过→改逐舱找到能读该项的舱再验,灯光自动落舱8真验证。真机:零写入FocusZero/FrameLength 2过+写2跳;开写4/4全过(排气阀舱9 200→201→200、灯光舱8 500→501→500、去重>1与raw抓包吻合)。control全sln+operate Release双编译0错+SerialHelper 40单测过+codegraph sync。剩余:D2-02命令代理(多会话设计→解锁D3-04)/D3-02整机自启(需重启)/配置收敛。",
+  generatedAt: "2026-06-23 20:30",
+  phase: "三阶段主体完成;M区全闭合;D1-10 oplog审计迁移;D3-05 看门狗;HIL硬件在环回归套件;配置收敛(连接组单一数据源+删死键)真机验证",
+  currentTask: "配置收敛(operate↔control 连接组单一数据源 + operate死键清理,=昨日建议「配置收敛」):7个共享连接键(urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter)收敛为唯一数据源tl-shared.config,operate/control各自App.config经<appSettings file=>只读合并→两进程读取C#零改动、换机只改一处;写入由operate AppConfigHelper经新SharedConfigStore(XDocument直写)收口;operate删12个换气/CCD死副本键。子代理驱动6任务+每任务两阶段审查。真机:control经..\\tl-shared.config读到ServerUrl=127.0.0.1:10010、改urlPort 10010↔19999重启对称生效、回归operate6/SerialHelper40/HIL2过2跳/双编译0错。",
+  note: "痛点:双进程拆分后operate与control的App.config约25键逐字重复、control那份注释明写'必须手动同步'=漂移源头。核查厘清:operate的AppData不读那12个换气/CCD键(合并时代死副本只control读)→删;真正两进程都读会漂移的是连接组7键。机制=方案一(共享片段+<appSettings file=..>只读合并,选file=不选configSource因前者支持..上级路径;写由operate收口),control只加一行XML、C#/csproj零改动。Task1临时harness真机坐实file=+..子目录读父目录(probeKey=HELLO/exit0)。Task2 SharedConfigStore+operate首个单测工程6绿。Task3 AppConfigHelper.Save按SharedKeys分流。Task4建tl-shared.config(108值)+csproj拷输出根+operate App.config接file=删7共享键+grep坐实后删12死键。Task5 control接..\\tl-shared.config删7共享键(换气/CCD全留)。Task6真机:部署布局下control经..读到共享连接配置、改一处重启对称生效(19999↔10010)、回归全绿。operate真外壳受僵尸20268 Mutex门控未跑通(读取机制与control同源据其判定成立)。凭据组本轮不并入(独立小决策)。分支feature/config-consolidation待并main。",
   milestones: [
     { name: "阶段1 · control 独立进程骨架(完成)", tasks: [
       { id: "Task1-7", name: "全过+D1-08死锁修复+operate真外壳E2E+数据入库DB铁证", status: "☑" }
@@ -21,6 +21,7 @@ window.PROGRESS_DATA = {
       { id: "D1-10", name: "control oplog审计埋点迁移到活栈(project=control真机入库)", status: "☑" },
       { id: "D3-05", name: "control崩溃看门狗(崩溃重拉/DPAPI凭据/可暂停停止卸载,TDD+真机5项)", status: "☑" },
       { id: "HIL", name: "硬件在环回归套件IvfTl.Hardware.HilTests(守护M-05帧长/M-06按well焦点/M-01-03 EEPROM写;门控Skip+默认零写入,真机4/4)", status: "☑" },
+      { id: "CONFIG", name: "配置收敛(operate↔control连接组7键单一数据源tl-shared.config经<appSettings file=>合并读+operate删12换气CCD死键;SharedConfigStore+operate单测工程;真机改一处对称生效)", status: "☑" },
       { id: "D3-04", name: "删operate死串口栈(去重·有风险删除·被D2-02阻塞)延后专项", status: "✗" }
     ]}
   ],

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

@@ -1,16 +1,18 @@
 # 续接断点状态(机器可解析)。换会话/换电脑后首先读它定位。
 # 状态取值: 未开始 / 进行中 / 完成 / 代码完成待验证
 # 纪律:本字段只存【当前断点】,历史细节进 交接卡.md(见 CLAUDE.md 第三节)。
-更新时间: 2026-06-23 HIL 硬件在环回归套件已入库并真机验证(= 昨日建议「HIL 回归套件入库」)。新增 IvfTl.Hardware.HilTests 守护 M-05/M-06/M-01-03 真机行为防回归。当前无 control 在跑、无活体培养(僵尸 operate 20268 仍在但不占舱口、需重启清)。
+更新时间: 2026-06-23 配置收敛(operate↔control 连接组单一数据源 + operate 死键清理)已完成并真机验证(= 昨日建议「配置收敛」)。当前无 control 在跑、无活体培养(僵尸 operate 20268 仍在但不占舱口、需重启清)。
 当前任务: >
-  【HIL 硬件在环回归套件入库 ☑ 完成】(= 昨日建议「HIL 回归套件入库」)
-  · 新增 xUnit 工程 IvfTl.Hardware.HilTests(入 control sln,经真实 SerialChannelImpl 端到端):
-    HardwareRigFixture 扫口握手收响应真舱 + [SkippableFact] 无真舱/被占即 Skip + 禁并行串行跑。
-  · 覆盖:FocusZero(M-06 按well去重>1)/FrameLength(M-05 连读12/12干净)纯读始终跑;
-    EepromWrite(M-01/02/03 写回环)默认 Skip,HIL_ALLOW_WRITE=1 才跑、逐舱取证、写后恢复原值(零写入默认)。
-  · 真机:零写入跑 2过2跳;开写开关 4/4 全过(排气阀舱9 200→201→200、灯光逐舱落舱8 500→501→500);
-    去重>1 与 raw 抓包诊断吻合;control全sln+operate Release 双编译0错;既有40单测过。
-  · 下一步:剩工作计划延后专项(D2-02 命令代理需多会话设计→解锁 D3-04 / D3-02 整机自启需重启)+ 昨日建议配置收敛。按用户优先级。
+  【配置收敛 ☑ 完成】(= 昨日建议「配置收敛」;分支 feature/config-consolidation)
+  · 7 个共享连接键(urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter)收敛为唯一数据源
+    新增文件 ivf_tl_Operate/tl-shared.config(放 operate 输出根);operate/control 各自 App.config
+    经 <appSettings file="(..\)tl-shared.config"> 只读合并 → 两进程读取 C# 零改动;换机只改这一份。
+  · 写入由 operate AppConfigHelper.Save 经新纯逻辑单元 SharedConfigStore(XDocument 直写)收口;
+    新建 operate 首个单测工程 ivf_tl_Operate.Tests(6 绿)。operate 删 12 个换气/CCD 死副本键(grep 坐实零消费)。
+  · 真机:部署布局下 control 经 ..\tl-shared.config 读到 ServerUrl=127.0.0.1:10010;改 urlPort 10010↔19999
+    重启 control 对称反映(单一数据源生效);回归 operate6/SerialHelper40/HIL 2过2跳/双编译0错。
+  · 下一步:剩工作计划延后专项(D2-02 命令代理需多会话设计→解锁 D3-04 / D3-02 整机自启需重启)。按用户优先级。
+    本分支待并 main。operate 真外壳 E2E 受僵尸 20268 Mutex 门控(需重启清),读取机制与 control 同源、据 control 实证判定成立。
 说明: >
   operate/control 双进程拆分三阶段主体早已完成;合并遗留 M 区 M-01~M-07 本轮全部闭合
   (M-01/02/03 builder去桩、M-04 存图代码定论、M-05 0x12帧长回归、M-06 按well焦点零点、M-07 网关)。
@@ -36,4 +38,4 @@
     名称: 清理老壳 + 装机收尾
     状态: 未开始
     备注: "退役删ivf_tl_ControlTest脏壳 + operate开机自启 + ComBin两套栈去重(G1-2) + 部署文档。待阶段2完成后拆计划"
-下一步: HIL 回归套件入库已完成并真机验证。剩工作计划延后专项(D2-02 命令代理设计→解锁 D3-04 删死栈 / D3-02 整机自启需重启)+ 昨日建议配置收敛。按用户优先级。
+下一步: 配置收敛已完成并真机验证。剩工作计划延后专项(D2-02 命令代理设计→解锁 D3-04 删死栈 / D3-02 整机自启需重启)。feature/config-consolidation 待并 main。按用户优先级。

+ 5 - 5
项目文档/需求文档/control-逻辑与配置全景.md

@@ -185,13 +185,13 @@
 ## 八、配置参数全表(一个不漏)
 
 ### 8.1 配置文件类(App.config / AppSettings)
-> control 实际读 **operate 的 App.config**(`ivf_tl_Operate/App.config`);`ivf_tl_ControlTest` 独立外壳读自己那份。control 端 `AppData.cs` 用 `.ToString()` 读,**换气/CCD 类键缺键会 NPE**(仅 url/kfka/mqtt 三组做了 null 容错)。
+> 🟢 **2026-06-23 双进程拆分 + 配置收敛后(本节为现状)**:control = **独立进程 ControlHost**,读自己的 `control/ivf_tl_ControlHost/App.config`(产物 `ivf_tl_ControlHost.dll.config`)。其中**共享连接键 urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort/outInter** 经 `<appSettings file="..\tl-shared.config">` 从上一级 operate 根的**唯一数据源** `tl-shared.config` 合并读(两进程同源,换机只改这一份);**换气/CCD 业务键 + 凭据等**仍在 control 自己 config 内。control 端 `AppData.cs` 用 `.ToString()` 读,**换气/CCD 类键缺键会 NPE**(仅 url/kfka/mqtt 三组做了 null 容错)。(历史:合并初期 control 曾随 operate 单进程读 operate 的 App.config;`ivf_tl_ControlTest` 外壳已退役删除。)
 
-| 参数 | 值(O=operate) | 默认 | 用途 | 读取处 |
+| 参数 | 值(S=tl-shared.config 共享) | 默认 | 用途 | 读取处 |
 |---|---|---|---|---|
-| urlIp/urlPort | 127.0.0.1 / 10010 | — | 内网接口,拼 BaseUrl | AppData.cs:49 |
-| kfkaIP/kfkaPort | 192.168.0.108 / 9092 | 127.0.0.1/9092 | Kafka | AppData.cs:50 |
-| mqttIp/mqttPort | 192.168.0.108 / 1883 | 空/1883 | MQTT broker | AppData.cs:52-53 |
+| urlIp/urlPort (S) | 127.0.0.1 / 10010 | — | 内网接口,拼 BaseUrl | AppData.cs:49 |
+| kfkaIP/kfkaPort (S) | 192.168.0.108 / 9092 | 127.0.0.1/9092 | Kafka | AppData.cs:50 |
+| mqttIp/mqttPort (S) | 192.168.0.108 / 1883 | 空/1883 | MQTT broker | AppData.cs:52-53 |
 | gbTime | 10 | — | 换气后缓冲瓶关阀等待(秒,→guanbiTime) | AppData.cs:55 |
 | csTime | 15 | — | 换气冲刷时间(秒,→TongQi) | AppData.cs:56 |
 | VentNum | 10 | — | 最大排气次数 | AppData.cs:58 |

+ 2 - 0
项目文档/需求文档/操作端逻辑与配置全景.md

@@ -146,6 +146,8 @@
 
 文件 `ivf_tl_Operate/App.config`。「基准」= 是否在基准版 `临时文件/.../App.config`(基准仅 11 键、无加密、无 oplog、无 control 业务键)。
 
+> 🟢 **2026-06-23 配置收敛(本表为收敛前历史快照,差异见下)**:① 共享连接键 **outInter/urlIp/urlPort/mqttIp/mqttPort/kfkaIP/kfkaPort** 已从 `ivf_tl_Operate/App.config` 迁出到 operate 输出根的**唯一数据源** `tl-shared.config`(operate/control 经 `<appSettings file=>` 合并读);② 表中那批 **CCDError/csTime/gbTime/VentNum/VentPre/VentWaitTimeB/VentWaitTimeD/AutoWaitTime/CCDAutoWaitTime/CCDFailedWaitTime/CCDFailedNumber/QueuAir** 是 operate **死副本**(operate 不读、只 control 读),已从 operate App.config **删除**(control 自己那份保留)。收敛后 operate App.config 只留:autoFocus/userName/passWord/engineerPwd/tlNum/houseEnabled/Language/StopPro/cacheDisk/controlPort/controlExePath。详见 `specs/2026-06-23-配置收敛-operate-control连接组单一数据源-design.md`。
+
 | key | 现值 | 类别 | 基准 / 合并新增 |
 |---|---|---|---|
 | autoFocus | 0 | 业务(下位机换气开关) | 基准有 |