文件
hyb-oa/.trellis/tasks/08-28-production-arm-backup-transfer-incident/implement.md
T

91 行
9.5 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 生产到 ARM 备份传输中断实施计划
## Phase 0:规划与授权
- [x] 创建 P1、`operations` Trellis task,记录来源事件和与恢复演练的阻塞关系。
- [x] 固定只读调查、生产/ARM 写操作、手工补跑和测试告警为不同审批边界。
- [x] 用户审阅并批准本规划及补充项后运行 `task.py start`;该批准只授权进入本地实施,不自动授权连接远端。
## Phase 1:本地只读调查准备
- [x] 从 `scripts/run-automatic-backup.sh`、receiver、dead-man 和 systemd installer 提取真实单位名、状态路径来源、阶段和退出码合同,禁止手写近似常量。
- [x] 实现只输出 allowlist 的跟踪只读采集器:生产本地完整恢复点合同、本地成功/失败清理保留语义、partial 匿名字节比例、rsync 原始码、无法取得的传输时钟、两端容量;timer/service 继续复用既有只读 systemd 聚合合同,不在 Python 中另写一套。
- [x] 为本地完整/无完整批次、成功清理/失败保留、相近/不同停止偏移、rsync `23/30/12/11/255`、缺失持续时间、权限拒绝、畸形 manifest 和敏感输出补合成回归。
- [x] 本地语法、合同、敏感扫描全部通过后,提交远端只读调查摘要与精确权限边界,单独申请执行批准。
### 本地准备结果(2026-08-28)
- 跟踪调查器:`scripts/qa/inspect-backup-transfer-incident.py`;仅接受显式绝对路径参数,不回显参数值,未知异常也只输出稳定类别。
- 可复跑合同:`scripts/qa/run-backup-transfer-incident-contract.sh`;初始 19 项合成检查通过;现场分类缺陷修复后扩展为 20 项,fixture 与 Python cache 零残留。权限拒绝不会被误写成“无有效备份”,其他阶段的旧日志不会冒充本轮 rsync 诊断;生产多个批次时只哈希最新元数据合格候选,最新哈希失败不回退扫描旧批次;停止偏移必须同时满足 5% 和 64 MiB 双阈值。
- 既有 `run-automatic-backup-contract.sh` 74 项通过;生产 wrapper 未改。现状没有持久化传输专属开始/结束时刻,故持续时间必须为 `NOT_AVAILABLE`。
- rsync 原始码只在安全摘要明确为 `phase_offhost_transfer_failed` 时从 root 私有 `last-error.log` 固定正则提取,避免把 paired-backup/finalize 的旧诊断错误归因给传输。
## Phase 2:获批的远端只读调查
- [x] 在不修改生产/ARM 的前提下采集四类证据;每个临时入口在同轮精确清理并由外层独立复核零残留。
- [x] 先判定“生产单点”或“无已证明可用备份”,再交叉判定主要根因、次要候选、清理/累积语义和容量预算;无法取得的字段标为 `NOT_AVAILABLE/NOT_MEASURED`。
- [x] 记录过渡恢复点决策;只读证据无法收敛时停止扩读,提交一次受控观测性重传的独立审批方案,不直接执行。
- [ ] 更新本 task 证据,给出单一最小修复方案、回滚方案和生产写操作清单;停止并请求用户审阅。
### 远端只读调查结果(2026-08-28,追加时间线)
- 公网 `/ready` 调查前后均 PASS,12 位构建前缀均为 `7b86ffb3c9ef`;生产固定入口与 staging 经外层独立枚举均为 0,ARM `arm_independent_residue=0`。调查未改变在线 OA、正式归档、timer 或业务数据。
- 生产:`complete_local_recovery_point=yes`、`backup_risk_classification=production_single_point`;完整哈希通过的最新批次 1 个、元数据完整批次 7 个,最新候选年龄 42885 秒、聚合 11400180 bytes;生产文件系统 82% 使用、10 GiB 可用。源码顺序证明传输失败保留完整本地批次、异机成功后 prune、生成临时对象在失败路径清理,故过渡动作是 `wait_natural`。
- 生产最近 daily/watchdog service 均为 `success/0`、timer active;历史失败摘要未保留可绑定到本轮的 rsync 原始码或传输专属时钟,因此退出码和时长均为 `NOT_AVAILABLE`,不得用 service exit、mtime 或对话时刻替代。
- ARM 原始输出:partial 2 个,聚合 2682168 bytes,样本总字节分别为 2682168 与 0;调查器当时输出 `arm_partial_stop_offset_class=similar`。该原始结果不回写。
- 分类缺陷修订:旧实现以 `max(64 MiB, 5%)` 为接近上限,使 2.68 MB 与 0 字节被 64 MiB 阈值错误吞并。按固定阈值必须同时满足的合同,人工重算为 `different`;一个约 32 日龄 partial 满足完整合同但未 finalize,另一个约 36 小时龄 partial 为零字节,不能据此声称两次都停在同一确定性偏移。
- ARM 正式原子归档 7 个、无无效归档,最新完整归档年龄 45756 秒;inbox/archive 文件系统 54% 使用、89 GiB 可用;dead-man service `success/0`、timer active、最近状态 `PASS_OFFHOST_DEADMAN_FRESH/healthy`。链路已自然恢复一个新鲜原子批次,可解除恢复演练的事件阻塞,但 P1 仍需第二个连续自然周期及容量验收后才能结案。
- `arm_deadman_alert_state_finding_count=invalid` 不表示远端状态损坏。仓库 helper 明确把 alert state 写成仅含 `schemaVersion/status/category` 的去抖状态;`findingCount` 属于 `last-deadman.json` summary。私有探针错误复用了 summary 读取模板,后续证据不得把这个表示缺陷升级为告警链 finding。
- 独立 finding:ARM health service 为 `failed/1`,但公网 `/ready` 实测前后一致且 PASS。这是健康监控执行/状态/通知链问题,不是 OA 当前不可用的证据;本次授权不覆盖进一步调查或修改。
- 根因裁决:历史单一 rsync 原始码和可靠传输时长不可得,两个 partial 的真实停止位置明显不同,现有证据不能收敛为单一根因。不得把自然恢复一次冒充“已实施修复”;后续若再失败,候选动作是另行批准的一次受控观测性重传。生产 10 GiB 余量仍需独立容量方案。
### 分类缺陷复盘
- 根因类别是“隐含假设 + 测试覆盖缺口”:实现用 `max(absolute, relative)` 把两个独立上限误写成二选一宽松上限;旧正例夹具只证明“绝对差异较小”,没有证明相对差异也在 5% 内,因而为错误实现背书。
- 修复机制已闭环:实现改为 `min(...)`;合同直接覆盖现场 `2682168/0`、同时满足双阈值的正例、分别越过相对/绝对阈值的负例;稳定 spec 写明公式、错误写法与断言点。仓库搜索确认该分类公式只有这一个实现位置,不存在需要同步的第二套逻辑。
- 原始远端 `similar` 保留,人工修订追加为 `different`;以后任何现场分类缺陷都按同样的不可变时间线处理,不回写旧证据。
## Phase 3:本地修复与变更审批
- [ ] 仅修改证据支持的脚本/installer/合同;不轮换已通过初始认证的密钥,不放宽 manifest/finalize 门禁。
- [ ] 容量方案优先扩容或精确处置已确认的自动备份自有对象,证明不会触碰唯一恢复点、数据库或附件源。
- [ ] 运行自动异机备份、receiver、dead-man、故障注入、回滚和敏感输出合同。
- [ ] 列出生产/ARM 的每个写目标、旧值指纹、候选 SHA、执行/回滚命令和停止条件,单独申请实际变更批准。
## Phase 4:获批的实际修复
- [ ] 执行最小变更和容量处置;不做未批准的手工备份、timer 修改、partial 清理或测试告警。
- [ ] 若另获手工补跑批准,只运行一次并记录真实 rsync 码、时长、原子 finalize 和残留;失败不自动重试。
- [ ] 验证至少一个新原子批次、manifest/三文件/字节/SHA-256、三组 service exit `0` 和容量预算。
## Phase 5:两个自然周期观察
- [ ] 自然周期 1:daily 新原子批次 PASS;watchdog/dead-man service success;记录生产/ARM 容量、ARM partial 数量/聚合字节趋势和适用的告警响应锚点。
- [ ] 自然周期 2:重复同一组断言;不以手工补跑或旧批次替代;ARM partial/容量趋势不得继续恶化。
- [ ] 两个周期均通过后标记事件 `GO`,否则保留失败并返回 Phase 2/3,不临时放宽阈值。
## Phase 6:解除阻塞与收口
- [ ] 在 `08-23-isolated-backup-restore-drill` 追加本任务结果交叉引用并解除事件阻塞;恢复演练仍需新批次身份探针和单独实际恢复审批。
- [ ] 运行 task validate、Trellis governance、process baseline、docs registry、敏感扫描和 `git diff --check`。
- [ ] 中文提交引用 `08-28-production-arm-backup-transfer-incident`;不自动 push,不归档未满足两个自然周期的任务。
## 停止条件
- 只读操作将输出路径、身份、凭据、原始日志/JSON 或业务内容。
- 需要新增 sudo、读取超出 allowlist 的 root 状态或改变生产/ARM 状态但尚未获独立批准。
- 清理对象、配置目标、旧值或回滚路径不唯一。
- 新原子批次不完整、service 非零、容量低于预算或任一自然周期失败。
- 告警发送/人工确认锚点缺失时不得补造响应延迟。
## 验证命令
- `bash -n` / `python3 -m py_compile` 覆盖新增或修改的运维脚本。
- 运行自动备份、receiver、dead-man 和相关敏感输出合同。
- `python3 ./.trellis/scripts/task.py validate .trellis/tasks/08-28-production-arm-backup-transfer-incident`
- `node scripts/qa/check-trellis-governance.mjs`
- `node scripts/qa/check-process-contract.mjs --baseline --json`
- `node scripts/qa/audit-docs.mjs --check-registry`
- `node scripts/qa/check-sensitive-content.mjs`
- `git diff --check`