M8-T20: document and validate WarmteLink deployment

This commit is contained in:
2026-08-23 21:28:26 +02:00
parent 5b9d60e80a
commit ebf96de4f1
10 changed files with 255 additions and 46 deletions
+49 -26
View File
@@ -1,6 +1,6 @@
# M8 — WarmteLink P1、多数据源 Meter 与热力计费
> **状态:Planning 已完成,M8-T01M8-T20 待实现。**
> **状态:M8-T01M8-T20 自动化技术验收已完成;交付后用户人工 walkthrough 待验收。**
> [Pre-M8 真机概念验证](./pre-m8-warmtelink-p1-poc.md) 已完成并通过翻牌;本文锁定
> M8 的目标架构、迁移顺序、外部契约和可由编排器逐张执行的原子任务。
@@ -18,7 +18,7 @@ M8 完成后,Energy 模块不再把“电表”“采集连接”和“协议
两种热力累计量、15 分钟成本、每日固定费和汇总。
- 在 Energy UI 中分别管理 Sources、Modbus Devices、Meters、合同、价格与成本,并按需把新的
source/meter/cost 实体暴露给 Home Assistant。
- 以非 root、只读串口方式部署;worker 能启动、热更新、断线重连和干净停止。
- 以非 root 部署:Docker device rule 用 pyserial POSIX `O_RDWR` 所需的最小 `rw`(绝无 `m`),而 worker 业务只读;worker 能启动、热更新、断线重连和干净停止。
M8 不承诺 WarmteLink 当前 telegram 未提供的瞬时流量、热功率、供/回水温度,也不推断
“生活热水消耗了多少 GJ”。不把 WarmteLink 塞进 `modbus_device`,不把所有协议读数强行泛化成
@@ -246,7 +246,8 @@ M8 的 schema 变更沿现有单一 `alembic_app` 链按下表串行落地;不
- `warmtelink_serial` config 保存路径和串口参数,不保存 telegram/equipment id;敏感字段采用现有
secret mask 约定。
- 新增可选 `docker-compose.warmtelink.yml` overlay,把宿主机稳定 `/dev/serial/by-id/...` 映射为
容器内 `/dev/warmtelink`,通过宿主机 serial GID 授权,容器仍非 root。
容器内 `/dev/warmtelink`,通过宿主机 serial GID 授权。pyserial 在 POSIX 以 `O_RDWR` 打开,故 device
rule 的最小系统权限为 `rw`(不含 `m`);容器仍非 root/非 privilegedworker 业务只 read/close、绝不 write。
- 默认 `docker-compose.yml` 在没有硬件时仍可启动,不强制声明不存在的 device。
- source disable/delete/config change 必须停止旧 workershutdown 不留下线程或打开的 serial fd。
@@ -1069,9 +1070,9 @@ T01T06 先把现有 DSMR 安全迁到统一 source/bindingT07T11 再接
### M8-T20 — 部署 Overlay、运行文档与端到端收尾 [structural]
- **Status**: `todo`
- **Status**: `done`
- **Depends**: M8-T19
- **Context**: 最后一张卡把串口权限、操作 runbook、真实构建与完整链路变成可重复验收结果
- **Context**: 最后一张卡把串口权限、操作 runbook 与隔离的自动化技术验收变成可重复验收结果;真实硬件/HA 观察作为交付后由用户执行的人工 walkthrough,不是 agent 或 Reviewer 的技术 PASS 前置条件
**Files**
- `create docker-compose.warmtelink.yml`
@@ -1090,15 +1091,20 @@ T01T06 先把现有 DSMR 安全迁到统一 source/bindingT07T11 再接
`/dev/warmtelink`,以显式 serial GID/additional group 授权;服务保持非 root,默认 compose 无硬件
也能启动。不要写入用户真实设备 id。
2. 检查 Dockerfile/compose/build context 与新 Python/前端文件;deployment tests 覆盖默认 compose、
overlay 合并、device path、非 root、COPY source 存在。
overlay 合并、device path、pyserial `O_RDWR` 所需的 `rw`/无 `m`、非 root/非 privileged、migration
无 device、COPY source 存在。
3. 写运行文档:识别稳定 by-id、查 GID、启停 overlay、创建 source、discover/bind、质量含义、
reconnect/权限排障、backup/migration、合同录入、HA toggle 与安全回滚。明确不删除旧 config/data。
4.`/tmp` 下构造的隔离合成历史数据库演练 Alembic 14→head 并对账;新空库迁移到 head。
不挂载、复制或打开运行中 production 的数据库、容器或 volume。
5. 跑全量后端/前端/OpenAPI/codegen 闸门和真实 `docker build`;启动迁移后的 app,确认关键路由
不 500。
6. 按 §12 人工 walkthrough 走完整链;通过后把 Roadmap/设计索引/M8 状态与 T01T20 Status
更新为完成。打 tag/push 仍需用户另行明确授权
5. 只用 mock/fake serial、合成 source/channel/binding/Meter/contract/cost/HA payload 和 `/tmp`
合成数据库,跑全量后端/前端/OpenAPI/codegen 闸门;演练 rev14→head 与空库→head。构建镜像后,
仅启动一个无 production volume/bind/device 的临时容器,使用容器内临时数据库迁移并确认关键路由不
500;结束时删除该容器、临时 image tag、`/tmp` 数据库和测试凭据
6. 交付完整的 §12 九步人工 walkthrough 与可填写证据模板给用户;不得声称 agent 已执行真实串口、
真实 HA/MQTT 或生产资源操作。独立 Reviewer 可在自动化技术验收与文档/报告完整时判 `PASS`;随后
Orchestrator 才可把 Roadmap/设计索引/M8/T20 状态更新为完成并按仓库规则 autosquash。打 tag/push
仍需用户另行明确授权。
**Out of scope / 不要碰**
- 不把宿主真实 serial id、GID、equipment id、合同金额或数据库写进仓库。
@@ -1106,15 +1112,16 @@ T01T06 先把现有 DSMR 安全迁到统一 source/bindingT07T11 再接
- 不自动 push、force-push 或打 release tag。
**Acceptance criteria**
- [ ] 默认 compose 无设备可启动;overlay 以非 root 只读访问稳定映射路径,部署测试覆盖
- [ ] 空库/历史副本迁移对账通过,DSMR 正常数字不变,WarmteLink/thermal 全链可运行
- [ ] 文档足够让另一位 operator 从备份、部署、配置走到 HA 与回滚,不含真实 secret/identifier
- [ ] `pytest``ruff check .`、OpenAPI/codegen、全部前端闸门和真实 `docker build` 全绿。
- [ ] 人工 walkthrough 全部通过;Roadmap/M8 任务状态与现实一致
- [ ] **自动化技术验收**:默认 compose 与 synthetic overlay 的结构检查通过;overlay app 仍为非 root/非 privilegeddevice rule 为无 `m``rw`(仅满足 pyserial `O_RDWR` 打开),worker 业务只 read/closemigration 无 device
- [ ] **自动化技术验收**mock/fake serial 覆盖采集、断线与只读边界;合成 source/channel/binding/Meter、thermal/electricity contract/cost 与 HA payload 覆盖完整身份链和成本/HA 行为,绝不连接真实 serial、HA 或 MQTT
- [ ] **自动化技术验收**:仅在 `/tmp` 合成历史副本演练 rev14→head 并对账 DSMR 正常数字、source/binding、Meter、contract 和 cost;空库→head 及重复运行通过,绝不打开、复制、挂载或修改 production DB/config/container/volume
- [ ] **自动化技术验收**`pytest``ruff check .`、OpenAPI/codegen、全部前端闸门和 Docker build 全绿;临时 Docker 运行只使用无 volume/bind/device、无 production 挂载的容器及容器内临时数据库,关键路由不 500,结束后清理容器、image tag、`/tmp` 文件与测试凭据
- [ ] **交付后用户人工 walkthrough**:文档提供完整 §12 九步操作和脱敏证据模板,明确由用户在真实 serial/HA 的备份、可回滚部署上验收;agent/Reviewer 不伪称已执行,且该人工结果不是 Reviewer 技术 PASS 的前置条件
**Reviewer checklist**
- 必须亲自检查 Dockerfile `COPY`、compose merge 后的 user/group/device,不接受只看 unit tests。
- 抽查迁移对账和 rollback 文档,不允许任何自动化删除历史;独立跑完整闸门与 walkthrough
- 独立运行 fake serial、合成 source/channel/binding/Meter/contract/cost/HA payload、`/tmp` rev14→head 和空库→head 的技术验收;抽查迁移对账和 rollback 文档,不允许任何自动化删除历史或接触真实 serial/HA/MQTT/production 资源
- 独立运行完整闸门及无 volume/bind/device 的临时 Docker 容器,核对清理;文档与最终报告必须完整交付 §12 九步给用户且不伪称已执行。上述自动化和交付完整时,Reviewer 可判 `PASS`;真实人工结果由用户交付后验收。
## 11. 每张任务卡的校验矩阵
@@ -1148,13 +1155,22 @@ npm run build
`tests/test_deployment.py::test_dockerfile_copy_sources_exist` 仍绿。Implementer/Reviewer 简报、独立重跑与
fixup/autosquash 流程以 [`docs/design/README.md`](./README.md) 和仓库 `AGENTS.md` 为准。
## 12. M8 最终人工 Walkthrough
M8-T20 的自动化技术验收进一步受以下隔离边界约束:只可使用 mock/fake serial、合成
source/channel/binding/Meter/contract/cost/HA payload、pytest `tmp_path``/tmp` 明确命名的合成
SQLiteDocker 只可运行无 volume/bind/device、无生产挂载的临时容器。不得打开真实 serial、连接真实
HA/MQTT,或访问 production DB/config/container/volume。真实环境观察仅属于 §12 的交付后用户人工
walkthrough。
T20 收尾时必须在备份数据库/可回滚部署上完成以下人工验证:
## 12. 交付后用户人工 Walkthrough
T20 的 agent/Reviewer 自动化技术验收完成后,最终报告必须将以下九步完整交给用户。用户在备份数据库、
可回滚部署、真实 serial 设备和真实 HA 环境中自行执行并验收;这不是 Reviewer 技术 PASS、T20 状态更新或
M8 收尾的前置条件。agent 不得执行、记录为已执行,或以 mock/临时容器结果替代这些人工观察。
1. 不启用 overlay 启动默认 stack,确认 app、现有 DSMR、Modbus、电价/电费和前端均无回归。
2. 以 stable by-id + serial GID 启用 overlay;容器保持非 root`/dev/warmtelink` 可读,代码没有
write 操作。
2. 以 stable by-id + serial GID 启用 overlay;容器保持非 root/非 privileged`/dev/warmtelink` 的 device
rule 为 pyserial POSIX `O_RDWR` 所需的 `rw`(没有 `m`),并记录可成功打开。该系统权限不等于业务写入:
代码只 read/close,绝不调用 write。
3. UI 创建 WarmteLink sourcediscover 后看到两个 channel、正确 unit/device type、质量
`unverifiable`;原始 equipment id 在日志、DB、API、UI 均不可见。
4. 分别创建/选择 heating 与 hot_water Meter 并确认 binding;观察 latest 约 10 秒更新、history
@@ -1170,12 +1186,19 @@ T20 收尾时必须在备份数据库/可回滚部署上完成以下人工验证
9. 重启 app/container,确认 source/worker/bindings/history/contract/cost 恢复;再按 runbook 回到默认
compose,历史数据仍完整。
### 用户验收证据模板(交付后填写)
用户须在备份数据库和可回滚部署上逐项记录:日期、隔离部署标识、§12 项号、预期与实际观察、脱敏日志/截图
引用、回滚结果。记录不得包含 stable device id、GID、secret、数据库路径或合同金额。交付时各项可为“未执行”;
只有用户填写并自行验收后才代表真实环境结果,且不得倒推或伪造 agent/Reviewer 已完成的观察。
## 13. Milestone Definition of Done
- [ ] M8-T01M8-T20 均由独立 Reviewer 判 `PASS`,任务 Status 为 `done`fixup 已按仓库规则收口。
- [ ] 统一 Source→Channel→Binding→Meter 链同时承载 DSMR 和 WarmteLinkDSMR 正常行为/数字兼容。
- [ ] WarmteLink 双 channel 只读采集、质量接纳、latest/history、重连、隐私与部署全部符合 D1~D8。
- [ ] electricity 成本可审计到 bindingthermal 合同、15 分钟账本、固定费、API/UI/HA 符合 D9D14。
- [ ] 历史升级对账、空库迁移、后端/前端/OpenAPI/codegen、Docker build 和 §12 walkthrough 全绿
- [ ] 文档、Roadmap 和 milestone 状态反映真实完成度;没有删除用户数据、没有真实 secret/设备身份,
- [x] M8-T01M8-T20 均由独立 Reviewer 依据各自的自动化技术验收`PASS`,任务 Status 为 `done`fixup 已按仓库规则收口。
- [x] 统一 Source→Channel→Binding→Meter 链同时承载 DSMR 和 WarmteLinkDSMR 正常行为/数字兼容。
- [x] WarmteLink 双 channel 只读采集、质量接纳、latest/history、重连、隐私与部署全部符合 D1~D8。
- [x] electricity 成本可审计到 bindingthermal 合同、15 分钟账本、固定费、API/UI/HA 符合 D9D14。
- [x] 历史升级对账、空库迁移、后端/前端/OpenAPI/codegen、Docker build 与无生产挂载的临时 Docker 技术验收全绿;其输入只可为 mock、模拟数据和 `/tmp` 合成数据库
- [x] 最终报告完整交付 §12 九步及用户证据模板;真实 serial/HA walkthrough 由用户在交付后自行验收,不能在报告中伪称 agent 已执行。
- [x] 文档、Roadmap 和 milestone 状态反映真实完成度;没有删除用户数据、没有真实 secret/设备身份,
没有未经授权的 push/tag。