备份策略改为部署前手动快照:部署合同增加手动快照模式 [08-28-production-arm-backup-transfer-incident]

- 用户决定仅用部署前手动轻量快照,不再推进对象存储、ARM/hyinda-20c 备份
- refresh-deployment-contract 新增 /etc/hyb-oa-backup-mode 显式模式:
  无文件保持原自动模式;manual_snapshot 要求三个备份 timer 停用且备份
  service 已退出,ops timer 仍须运行;内容/属主/符号链接异常 fail-closed
- probe 部署前与 refresh 部署后均校验模式与 timer/service 一致性
- 新增 44 backup_mode_invalid / 45 backup_timer_not_disabled 分类及合同测试
- 记录 10-05 ARM 与 hyinda-20c 只读核验结果、远端切换顺序与回滚
- 远端切换未执行,需另行批准

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
2026-10-05 17:28:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 9cbefdf243
当前提交 f6d1025d85
共修改 9 个文件,包含 246 行新增和 7 行删除
@@ -37,6 +37,22 @@ operations require explicit approval and evidence.
`deploy/trialrun/.env.example` before deployment; `deploy/trialrun/deploy.sh`
runs `node deploy/trialrun/validate-env-contract.js` at step 0 and must fail
if the example contract drifts.
- Current backup policy is manual Tencent Lighthouse snapshots only, as selected
in `08-28-production-arm-backup-transfer-incident/prd.md`. Do not introduce
object storage, ARM/hyinda-20c backup or automatic snapshot rotation by default.
Before each approved production deployment, confirm the OA target and coverage
of database/attachment disks, then wait for snapshot creation to succeed.
Record target/coverage confirmation, creation time and success without resource
IDs. Check current console quota before any separately authorized old-snapshot
deletion; preserve an existing successful recovery point during rotation.
Recoverability is limited to existing snapshot times and covered disks; this
policy provides neither a daily recovery-point guarantee nor an independent copy.
- Policy selection does not prove automatic backups have stopped. Follow the
same task's reviewed transition plan: preserve the service-based deployment
identity contract, adapt backup timer checks before disabling those timers,
and separately approve remote configuration changes. Preserve historical
backups and independent public-health monitoring. Legacy backup contracts below
describe retained tooling; they do not authorize re-enabling backup services.
- After deployment, report commit hash, `/ready`, public smoke results and
whether the user can test.
- Real business writes are manual unless the current Trellis task explicitly
@@ -2489,6 +2505,7 @@ DEPLOY_SERVER=<tencent-oa-production-target> bash deploy/trialrun/deploy-existin
- An early aggregate item fails -> all independent items still run, the fixed summary reports every FAIL, and no remote write begins.
- Root SSH returns transport 255 -> `root_identity_unavailable`; remote 126/127 or missing probe marker -> `refresh_program_unavailable`.
- Helper runs but final service build differs -> `unit_build_not_refreshed`; builds match but any timer is disabled/inactive -> `timer_not_active`.
- Backup mode file `/etc/hyb-oa-backup-mode` absent -> automatic mode, all backup and ops timers must be enabled/active. Content exactly `manual_snapshot` (root-owned regular file) -> the three automatic-backup timers must be disabled/inactive, otherwise `backup_timer_not_disabled` (45); `hyb-oa-warch3-ops.timer` stays required (`timer_not_active`). Any other content, symlink or ownership -> `backup_mode_invalid` (44). In manual mode every backup `.service` must also be `inactive` or `failed`; an in-flight run -> 45. `probe` runs the same mode and timer/service checks, so a mismatch fails the pre-deploy aggregate as `refresh_program FAIL` (run the forced-command `probe` directly to read the category on stderr) before any release write; `refresh` re-checks after the release. Never mask or delete backup units: deploy identity still reads the daily backup service environment.
- A similarly named environment key such as `OLD_BUILD_SHA` matches the active value while exact `BUILD_SHA` remains stale -> treat the service as stale; build verification parses exact keys and applies the last systemd override.
- Post-deploy installer still embeds the previous build SHA, any managed timer is inactive, or the real backup/watchdog fails runtime identity or off-host freshness -> deployment recovery remains incomplete even when `/ready` is green.
- A later deployment attempt is blocked by `backup_build` after `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED` -> expected fail-closed behavior; repair with direct forced-command `refresh`, not another application release.
@@ -1,5 +1,17 @@
# 生产到 ARM 备份传输中断设计
## 2026-10-05 手动快照切换方案(本地适配已完成,远端切换待批准)
用户已选定仅手动快照,本节替代下方历史修复路径。用户 2026-10-05 批准后本地适配已实施并验证(结果见 implement.md);远端切换尚未执行,需另行批准。
1. 发布适配:`scripts/refresh-deployment-contract.sh` 的 `verify_timers` 当前强制要求三个备份 timer enabled/active;`deploy-existing.sh`、生产身份引导器和聚合检查仍从 daily service 读取部署身份。已增加显式、受控的手动快照模式(根属 `/etc/hyb-oa-backup-mode` 内容为 `manual_snapshot`),校验备份 timer disabled/inactive 且备份 service 已退出(inactive/failed),`probe` 部署前与 `refresh` 部署后均检查,保留身份、构建、数据目录及非备份运维 timer 的验证。不得删除单位、全局跳过 timer 检查或靠错误吞并放行。
2. 本地验证:扩展既有发布合同测试,覆盖手动模式三个备份 timer 停用可通过、意外启用失败、非备份 timer 失败仍阻断及旧模式回归。受控入口的模式配置与候选版本一并绑定,实际执行前完成可用的独立只读复核;当前 inline 限制下标记待独立复核,不虚报。
3. 远端切换范围:生产 `hyb-oa-automatic-backup-{daily,watchdog,drill-reminder}.timer` 与 ARM `hyb-oa-offhost-deadman.timer`。先核实际单位及既有运行状态;先禁用定时器阻止新任务启动,再有界等待在途 service 退出(上限预先写定),超时停止切换并报告、不强杀;最终核验含对应 service inactive/failed。ARM dead-man 由用户 sudo 停用并单独核验,生产侧 `refresh` 成功不代替 ARM 核验。源码默认名称须与现场一致,不猜测替代单位。
4. 保留 unit 文件及其环境、历史归档/partial、状态、凭据和共享告警工具,不调用带删除行为的 installer uninstall。公网健康探针与 `hyb-oa-warch3-ops.timer` 不在备份停用范围,hyinda-20c 未安装本项目备份任务,无迁移或清理动作。
5. 顺序为先完成兼容适配、再获批部署适配并停用定时器、最后只读核验与记录。执行前保留旧模式/单位启用状态作为回退依据;出现异常停止,恢复旧模式和定时器需在本次获批回滚范围内执行。不得出现脚本仍强制 active 而定时器已关闭的未处理状态。
本方案不购买存储、不创建或删除快照、不删备份、不恢复业务数据。远端具体配置、版本和回滚清单须在实施后形成可审阅结果再申请执行批准;当前选择不等于远端切换已完成。
## 设计结论
采用“先只读判因、再最小变更、最后两个自然周期验证”的事件处置路径。传输修复和容量处置属于同一个 P1 operations 任务,但每项远端写操作维持独立审批;恢复演练作为下游消费者保持阻塞,不参与本任务的根因修复。
@@ -1,5 +1,57 @@
# 生产到 ARM 备份传输中断实施计划
## 2026-10-05 最新决定与本地落实:仅手动快照
用户明确选择仅手动快照,不用其他厂商对象存储,也不再选 ARM/hyinda-20c 承担备份。此前对象存储选型、ARM 修复或替换、两个自然周期验收不再作为当前推进目标;原始结果保留,不勾成通过、不自动归档。
- [x] 复用本 task,更新 PRD 当前目标、design 切换方案及部署规则;不再询问备份产品选择。
- [x] 本地确认停用依赖:`scripts/refresh-deployment-contract.sh:verify_timers` 要求三个自动备份 timer 启用且运行;生产身份仍取自 daily service。不能直接卸载或停 timer 后宣称发布兼容。
- [x] 本地 task validate、Trellis governance、process baseline、敏感扫描及 diff 检查通过;docs registry 无错误,保留 22 个未登记文档的既有警告,本轮未改动 docs 或处理这些文件。
- [x] 用户 2026-10-05 批准适配后本地实施并验证:
- `scripts/refresh-deployment-contract.sh`:新增根属普通文件 `/etc/hyb-oa-backup-mode` 显式模式。文件不存在 = 原自动模式(行为不变);内容恰为 `manual_snapshot` 时要求三个自动备份 timer `disabled:inactive`(否则 exit 45 `backup_timer_not_disabled`),`hyb-oa-warch3-ops.timer` 仍须 `enabled:active`(43);其他内容/符号链接/非 root 属主 → exit 44 `backup_mode_invalid`,`probe` 同样校验。不删除、不 mask 备份 unit,部署身份仍取自 daily service。
- `deploy/trialrun/deployment-contract-lib.sh` 增加 44/45 分类;`validate-env-contract.js` 增加必需片段;部署规约 Validation Matrix 增加对应条目。
- 验证:`run-deployment-contract-refresh-contract.sh` PASS(新增自动模式下停用失败、手动模式通过/全部运行/部分运行/ops 停用/内容非法/多行/符号链接/移除模式文件回归共 10 组断言);两个源码变异(手动模式接受运行中 timer、跳过内容校验)均使测试变红;`validate-env-contract.js` exit 0;`deployment-safety-contract` 39/39、`deployment-db-lifecycle` 52/52。
- [x] 复核补充(同日):①`probe` 也执行模式与 timer/service 一致性检查,部署前即拦截,`refresh` 部署后复检;②手动模式还要求三个备份 `.service` 为 `inactive`/`failed`,在途运行 → 45。新增 7 条断言(probe 拦截运行中/部分运行、自动模式 probe 拦截停用、服务 active/activating 拒绝、failed 放行);`probe` 去掉 timer 检查、去掉 service 检查两个变异均使测试变红;合同测试、env 合同、部署安全 + DB 生命周期 91/91 通过。注意:自动模式下 timer 异常现在部署前就失败(原先只在部署后失败),属有意收紧。
- [ ] 远端切换(另批;生产需 root 安装权限,ARM 需用户 sudo)。顺序:
1. 用 `install-deployment-contract-refresh.sh install` 安装新 helper;无模式文件时仍为自动模式,forced-command `probe` 须 PASS。
2. 先 `systemctl disable --now` 三个生产备份 **timer**(只停定时器,阻止新任务启动,不动 service)。
3. 有界等待三个备份 service 变为 `inactive`/`failed`(上限预先写定,例如 90 分钟);超时则停止切换、保持现状并报告,**不强杀**。
4. 写入 `/etc/hyb-oa-backup-mode`(root:root 0644,内容 `manual_snapshot`),forced-command `refresh` 返回 PASS。
5. 由用户 sudo 在 ARM 上 `disable --now hyb-oa-offhost-deadman.timer` 并**单独核验** timer `disabled:inactive`、service 非 active;生产侧 `refresh` 成功不能代替 ARM 核验。未停 dead-man 时,生产停传后 ARM 会发一次 `archive_stale`。
- 回滚:删除模式文件,`enable --now` 三个 timer 与 dead-man,再跑 `refresh`。历史备份、unit 文件、公网健康探针均不动。
- [ ] 完成受限只读独立复核;当前 inline 模式下仅主会话检查,独立复核待完成。
- [ ] 形成固定版本、模式配置、四个备份 timer 及回滚清单,另获生产/ARM 配置变更批准后执行与核验。
运行状态:本轮未连接生产或 ARM,自动备份/告警可能仍按旧配置运行;未停用定时器、未删除历史备份、未创建快照,未提交或归档。当前选择不自动停用独立公网健康监控。手动快照执行时记录目标确认、覆盖确认、创建时间及成功状态,不记录真实资源标识。
## 2026-10-05 告警频繁的只读核验(用户当轮授权,ARM 普通账号)
背景:用户反馈群内仍频繁出现“内部 OA 运维恢复通知”及“OA 公网健康告警/恢复”。授权范围为生产与 ARM 只读核验;生产主机读取被本机工具权限策略拦截,未执行,生产侧数据全部为 NOT_RUN。
ARM 侧结果(13:30–13:45 CST,只记聚合值,不记地址/路径/日志原文):
- dead-man:timer active/enabled,10-05 06:30 service success/0。说明当日 06:30 时 ARM 最新归档未超 26h 阈值;归档完整性未复核(目录及 sudo 仍不可用)。
- 公网健康探针:timer active/enabled,13:30 那次 service 以 exit-code/1 失败,耗时约 11s(与 10s 超时一致);具体类别在 root-only 状态/journal,普通账号读不到(journal 返回“无条目”)。
- ARM → OA 公网入口:ping 三轮丢包 67% / 74% / 52%,RTT 约 143ms;HTTPS `/ready` 40 次中 11 次超时(10s)、13 次 TCP 建连 >1s、平均 3.35s。
- 对照:ARM → 其他三个国内目标 ping 0% 丢包;ARM 本机 DNS 解析约 1ms(已缓存),当前不是 DNS 阶段问题。本机开发环境经代理(假 IP 段)访问,40/40 成功,但路径不同,不能证明生产入口对普通用户正常。
判断(假设,未结案):当前公网健康告警的主要成因是 ARM 到生产入口这条线路持续丢包导致请求超时,不是 OA 应用不可用。同一条线路的丢包也可能是历史 rsync 传输中断(partial)的成因,需生产侧传输记录印证。丢包发生在 ARM 运营商一侧还是生产入口一侧尚未区分;若在生产入口一侧,真实用户也可能受影响。
未完成 / 下一步:
- 生产侧(备份/watchdog 服务结果、告警状态、容量、近 7 天告警类别与部署时间对照):需用户放开本机对生产主机只读命令的权限,或由用户手动执行。
- ARM root-only 公网健康状态文件和 journal 中的类别统计:需用户以 sudo 手动执行固定只读命令。
- 区分线路问题在哪一侧:需要第三个直连观测点(不经代理)对生产入口测丢包。
### 2026-10-05 候选替代节点 hyinda-20c 只读摸底(用户当轮授权)
- 形态:Windows 主机上的 WSL2(Ubuntu 22.04,x86_64,20 核,15 GiB 内存),经动态域名 + 非标端口映射访问;普通账号 sudo 需密码;无 Docker。
- 可用性:开机约 9 天;近两个月重启记录集中在 09-26(多次)与 08-22(多次),生命周期受 Windows 开关机/更新/WSL 实例回收影响。
- 存储:WSL ext4 显示 1 TiB 虚拟盘,实际受宿主 Windows 盘余量约束(宿主两盘分别约 167G / 88G 可用);未见 Linux 层磁盘加密,宿主 BitLocker 未知。
- 网络:节点流量(含指定公共 DNS 的解析)均被本机透明代理接管为假 IP 段,到 OA 入口 HTTPS 70/70 成功、平均约 0.13s,但不是直连测量,不能作为线路证据。
- 容量需求参照:既有记录单批次聚合约 11 MB 量级,容量和带宽对任何候选节点都不是约束;选型关键是常在线、独立、可控访问与恢复能力。
- 用户决定(2026-10-05):不购买轻量“自动备份”(备份点按 60GB 盘容量预付至实例到期,约 983 元);采用**每次部署前手动创建轻量免费快照**(配额 2,满则删最旧),已写入 `.trellis/spec/hyb-oa/deployment-trialrun.md` Rules。快照随实例销毁而删除,不替代实例外副本;实例外副本(另一厂商对象存储)与 ARM 链路退役待用户另行决定。用户确认控制台中名为“Windows Server”的轻量实例即 OA 生产(实际为 Linux,仅实例名称历史遗留),快照对象即此实例。
- 结论:不建议 hyinda-20c 作为主要异机备份节点(WSL 生命周期、代理接管网络、虚拟盘依赖宿主、无加密证据);可作为额外副本候选。主要节点建议改评国内云的独立厂商对象存储或小型云主机,待用户决策。
## 2026-09-17 结案缺口与最小补证方案(本地整理,未执行远端观察)
@@ -1,5 +1,18 @@
# 生产到 ARM 备份传输中断
## 2026-10-05 当前决策:仅使用手动快照
用户明确决定不使用其他厂商对象存储、ARM 或 hyinda-20c 备份,只使用已确认 OA 生产轻量实例的手动快照。此决策取代本任务继续修复异机链路、选型迁移及两个自然备份周期验收的目标;以下原始要求和 AC 保留为历史,未完成项不追认为通过。关联 `08-23-isolated-backup-restore-drill` 的历史部分恢复结果不变,本决策不授权继续真实恢复。
- 当前备份方式为部署前手动快照;确认目标、磁盘覆盖和创建成功后再部署,不新增自动快照脚本或其他副本方案。恢复范围受现存成功快照的时间点与覆盖磁盘限制,不承诺每日恢复点或实例外副本。
- 选择备份方式与实际停用分开记录。本轮落实本地合同;发布脚本适配、生产/ARM 定时任务停用方案见 design/implement,非微小代码实施和远端配置变更待方案批准。
- 历史备份、快照、凭据和 unit 文件不因本决策自动删除;公网健康监控是独立功能,不把“停用备份”解释为关闭全部监控。
### 当前验收
- [x] S1:当前选择已同步到任务及部署规约,本地文档/治理检查通过;旧未验项与当前决策明确区分。文档审计保留既有未登记文件警告,详见 implement。
- [ ] S2:获批的发布适配与备份任务停用完成,停用状态及发布兼容性有据;此前不得称“已经只运行手动快照方案”。
## Goal
恢复腾讯云生产到 ARM 的自动异机备份链,消除持续扩大的异机恢复点缺口,同时把生产备份落盘容量恢复到可持续范围,并证明修复不是一次偶然成功。
@@ -12,7 +12,7 @@
"assignee": "shunli",
"createdAt": "2026-08-28",
"completedAt": null,
"branch": null,
"branch": "main",
"base_branch": "main",
"worktree_path": null,
"commit": null,