mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
118 lines
6.0 KiB
Markdown
118 lines
6.0 KiB
Markdown
# ADR-0203:物理 Edge Compose 存储恢复候选证据
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-07-29
|
||
- 关联 RFC:QL-RFC-0001 D-86、D-87、D-175、D-192、D-193
|
||
- 关联 ADR:ADR-0088、ADR-0200、ADR-0201、ADR-0202
|
||
|
||
## 背景
|
||
|
||
ADR-0202 已证明 Compose recovery evidence collection 的逻辑状态机、崩溃重放和
|
||
保留底线,但开发机与容器测试不能回答两个物理 Edge 问题:
|
||
|
||
1. collection 在真实路由设备分区上会产生多少可观测写入;
|
||
2. durable rename stage 跨 Linux 重启后,正式 commit/replay 是否仍能收敛并回收
|
||
snapshot 占用。
|
||
|
||
这两个问题也不能直接等同于“突发断电已通过”。Linux block statistics 可能在重启
|
||
后重置,partition sectors 不是 eMMC/NAND FTL 内部写入量;普通 reboot 也不能证明
|
||
电源在某条 fsync 指令之后被物理切断。
|
||
|
||
## 决策
|
||
|
||
### 1. 记录器是两阶段、短生命周期工具
|
||
|
||
根命令 `evidence:physical-edge-compose-storage` 提供 `prepare` 和 `resume`。它是
|
||
repository script,不新增 workspace package、生产依赖、daemon、timer、watcher、
|
||
端口或后台扫描。
|
||
|
||
prepare 在 operator 提供的专用数据挂载内创建一个可丢弃 Edge scratch deployment,
|
||
通过正式 local deployment API:
|
||
|
||
1. 准备 Compose Edge deployment 并扩展 SQLite allocated pages;
|
||
2. 推进 generation 2、3、4,创建 3 份真实 rollout backup 与 terminal receipt;
|
||
3. 对最老 backup 执行 production evidence collection prepare;
|
||
4. 把该 backup rename 为生产协议的确定性 `.ql3-collection-stage` 并 fsync 目录;
|
||
5. 发布 `0600`、no-replace、SHA-256 绑定的 session,然后停止并要求 operator
|
||
外部重启。
|
||
|
||
工具永不调用 reboot、poweroff 或设备管理接口。
|
||
|
||
### 2. 专用真实块设备和路径边界
|
||
|
||
data path 必须是 canonical、当前 UID 拥有、`0700`、读写的 Linux mountpoint,
|
||
mount source 必须直接解析为 `/dev/*` block device;root filesystem、overlay、
|
||
容器 bind path、共享生产 deployment 和 symlink 均不接受。
|
||
|
||
session 只接受 exact shape,并绑定:
|
||
|
||
- device manifest digest、UID、boot ID、架构、文件系统、mount 与 block device;
|
||
- 固定的 `.ql3-compose-storage-<session UUID>` scratch deployment;
|
||
- generation 4、collection UUID、最老 rollout UUID 和 snapshot 完整事实;
|
||
- production prepare/commit command 的 exact shape、相同 options 和单调时间;
|
||
- target/stage 的确定性绝对路径及 prepare barrier tree。
|
||
|
||
resume 会再次 realpath 并检查 data root、deployment root、session 和 output 均未逃逸
|
||
专用挂载。即使当前用户重算 session digest,也不能把 production commit 指向任意
|
||
其他 deployment。
|
||
|
||
### 3. 跨启动只形成两个独立写入上界
|
||
|
||
prepare 在同一 boot 内记录操作前后 partition `sectors written` 差值;session 的
|
||
最终发布发生在 barrier 之后,因此不包含在 prepare 数值内。resume 在新 boot 内
|
||
重新取起始计数,并只对 production commit、exact replay、SQLite inspect 和结果
|
||
检查后的差值计量;它不包含 boot 自身写入。
|
||
|
||
两个差值分别除以本阶段逻辑 snapshot bytes,并向上取整为 permille 上界。不得跨
|
||
boot 相减,也不得把该数值命名为 whole-device 或 FTL write amplification。manifest
|
||
显式声明两个独立阈值,超出任一阈值即候选失败。
|
||
|
||
### 4. Resume 必须使用正式恢复路径
|
||
|
||
resume 只接受不同 boot ID、相同 UID/device/filesystem/architecture。它执行 session
|
||
内绑定的 production collection commit,再原样重放同一 commit,并验证:
|
||
|
||
- 首次结果为 `collected`,重放为 `existing`;
|
||
- 主 SQLite 仍通过 v35 snapshot inspection;
|
||
- deterministic stage 已消失,content-bound tombstone 已存在;
|
||
- 3 份 rollout backup 收集最老一份后正好保留 2 份;
|
||
- deployment tree allocated bytes 至少回收一页,且算术与 target snapshot
|
||
bytes 一致。
|
||
|
||
报告以 `0600`、no-replace、SHA-256 发布,永久为 `supported:false`。
|
||
|
||
### 5. 聚合器不得扩大结论
|
||
|
||
统一 physical Edge aggregator 只导入同 device/Profile/architecture/filesystem 和
|
||
当前 resume boot 的私有 digest-bound 报告。成功时新增
|
||
`compose_sqlite_collection_reboot_and_partition_write_upper_bound`,但仍保留:
|
||
|
||
- `power_loss_restart`;
|
||
- `whole_device_cpu_wakeups_and_flash_write_amplification`;
|
||
- release signature 与其他尚未完成的物理证据。
|
||
|
||
报告明确不证明真实突发断电来源、生产主 deployment、FTL/NAND 写放大、长期耐久、
|
||
MTD/UBI/UBIFS 设备写放大、Compose restore replacement 的断电窗口或
|
||
Standalone/Cluster。MTD/UBI 需要独立计量适配器,不能套用 block-sector 阈值。
|
||
|
||
## 验收证据
|
||
|
||
- 新 recorder manifest/CLI/block-stat/session/report 契约测试 6/6;
|
||
- 聚合器路径、私有文件、不同 boot/device binding 与不扩大断电声明测试 12/12;
|
||
- 合计专项 18/18;
|
||
- session 的 deployment/path/production command 漂移即使重算 SHA-256 仍失败关闭;
|
||
- 没有新增 workspace package 和生产依赖。
|
||
|
||
本地 macOS 开发机没有执行物理阶段。Accepted 表示协议与门禁已实现,不表示任何具体
|
||
路由设备已经获得支持;真实报告必须在固定 arm/arm64 设备、专用可丢弃分区上采集。
|
||
|
||
## 被拒绝的替代方案
|
||
|
||
- **在 Docker Desktop 或 loop file 上采数字**:不能代表设备 block/FTL 路径。
|
||
- **把 MTD/UBI 当作 `/dev` block device**:观测与磨损语义不同,必须独立设计。
|
||
- **跨 boot 直接相减 `/sys/class/block/*/stat`**:计数可能重置,结果无效。
|
||
- **脚本自动 reboot/poweroff**:扩大破坏权限,仍不能证明突发断电时点。
|
||
- **复用生产 deployment**:会污染用户恢复历史,并把证据工具变成生产 GC authority。
|
||
- **从 `process.io` 或 SQLite 文件大小推导闪存写放大**:观测层级错误。
|
||
- **新增 storage-evidence package/daemon**:没有独立交付边界,并增加 Edge 常驻成本。
|