From 1989f666ac3489069a7ac95de10c24c98c491840 Mon Sep 17 00:00:00 2001 From: shunli Date: Tue, 6 Oct 2026 10:51:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E6=89=8B=E5=8A=A8=E5=BF=AB?= =?UTF-8?q?=E7=85=A7=E6=A8=A1=E5=BC=8F=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=B8=8E=E5=88=87=E6=8D=A2=E8=AF=B4=E6=98=8E=EF=BC=8808-28-pro?= =?UTF-8?q?duction-arm-backup-transfer-incident=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合同测试补齐:watchdog/drill 服务仍运行、timer 已 disable 但仍 active、 断链模式文件三类场景,probe 与 refresh 两阶段均断言 - 三个源码变异在旧测试中全部放过、新测试全部拦住 - 切换说明补充超时处置需预先批准、安装前同步并核验源码、 回滚时 Persistent timer 可能立即补跑并告警 - 报错分类细化(复核第 5 条)后置;未推送、未做远端切换 Co-Authored-By: Claude Opus 5.5 --- .../design.md | 8 +++-- .../implement.md | 22 ++++++++++--- ...un-deployment-contract-refresh-contract.sh | 32 ++++++++++++++----- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/.trellis/tasks/08-28-production-arm-backup-transfer-incident/design.md b/.trellis/tasks/08-28-production-arm-backup-transfer-incident/design.md index 257292a1..64f98cf4 100644 --- a/.trellis/tasks/08-28-production-arm-backup-transfer-incident/design.md +++ b/.trellis/tasks/08-28-production-arm-backup-transfer-incident/design.md @@ -5,11 +5,15 @@ 用户已选定仅手动快照,本节替代下方历史修复路径。用户 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 核验。源码默认名称须与现场一致,不猜测替代单位。 +2. 本地验证:扩展既有发布合同测试,覆盖手动模式三个备份 timer 停用可通过、意外启用失败、非备份 timer 失败仍阻断及旧模式回归。受控入口的模式配置与候选版本一并绑定。用户回传 `f6d1025d` 同模型独立只读复核通过、无阻断项;本轮仅补其测试与说明缺口,原结论不自动延伸到后续新提交。 +3. 远端切换范围:生产 `hyb-oa-automatic-backup-{daily,watchdog,drill-reminder}.timer` 与 ARM `hyb-oa-offhost-deadman.timer`。先核实际单位及既有运行状态;先禁用定时器阻止新任务启动,再有界等待在途 service 退出(上限预先写定),超时停止切换并报告、不强杀。执行批准中须预先选择超时处置:允许回滚则恢复本次停用的 timer 原启用状态并验证自动模式;选择保持停用则不写模式文件,明确记录“切换未完成、部署被阻断”,等待后续处置。没有获批回滚时不擅自重新启用,不将该中间态报告为完成;最终核验含对应 service inactive/failed。ARM dead-man 由用户 sudo 停用并单独核验,生产侧 `refresh` 成功不代替 ARM 核验。源码默认名称须与现场一致,不猜测替代单位。 4. 保留 unit 文件及其环境、历史归档/partial、状态、凭据和共享告警工具,不调用带删除行为的 installer uninstall。公网健康探针与 `hyb-oa-warch3-ops.timer` 不在备份停用范围,hyinda-20c 未安装本项目备份任务,无迁移或清理动作。 5. 顺序为先完成兼容适配、再获批部署适配并停用定时器、最后只读核验与记录。执行前保留旧模式/单位启用状态作为回退依据;出现异常停止,恢复旧模式和定时器需在本次获批回滚范围内执行。不得出现脚本仍强制 active 而定时器已关闭的未处理状态。 +安装前置:`install-deployment-contract-refresh.sh` 从所在代码目录的 `scripts/refresh-deployment-contract.sh` 复制 helper,不会自动取得本地新提交。必须先把最终获批提交中的安装器与 helper 送达生产目标代码目录,校验来源提交和文件摘要,再安装;不能在尚为旧源码的目录直接执行 installer。来源同步也是远端写入,纳入切换批准清单,不把它等同于应用发布或默认重启 OA。 + +回滚副作用:生产三个备份 timer 与 ARM dead-man 的生成配置均为 `Persistent=true`。若停用期间错过日历触发点,`enable --now` 可能立即补跑备份/新鲜度检查/季度提醒或 dead-man,并可能产生告警。现场须核对实际配置;回滚授权应包含该可能的自然补跑和通知,不承诺“重新启用只等下一次计划时间”。不清除 timer 时间戳、改 Persistent 或手工补跑来规避此行为。 + 本方案不购买存储、不创建或删除快照、不删备份、不恢复业务数据。远端具体配置、版本和回滚清单须在实施后形成可审阅结果再申请执行批准;当前选择不等于远端切换已完成。 ## 设计结论 diff --git a/.trellis/tasks/08-28-production-arm-backup-transfer-incident/implement.md b/.trellis/tasks/08-28-production-arm-backup-transfer-incident/implement.md index 185a08ab..1bfd31a0 100644 --- a/.trellis/tasks/08-28-production-arm-backup-transfer-incident/implement.md +++ b/.trellis/tasks/08-28-production-arm-backup-transfer-incident/implement.md @@ -1,5 +1,17 @@ # 生产到 ARM 备份传输中断实施计划 +## 2026-10-05 同模型独立复核回执及第 1–4 项补漏 + +用户回传对 `f6d1025d` 的同模型独立只读复核:通过、无阻断项、5 条非阻断问题;复核代理使用独立上下文,未修改仓库或访问远端。此处引用用户回执,不将其写成主会话独立执行,也不将旧提交结论自动套用到新改动。 + +- [x] 补齐三个测试缺口:watchdog 与 drill-reminder 分别仍在运行、timer disabled 但 active、模式文件为断链;分别覆盖 probe/refresh,沿用现有合成 systemctl/SSH/Docker fixture,不改生产脚本。 +- [x] design 与下方切换步骤同步超时后的两种明确处置、安装前最终获批源码必须送达并核验、Persistent timer 重新启用可能立即补跑及告警。 +- [x] `bash scripts/qa/run-deployment-contract-refresh-contract.sh` PASS;`node deploy/trialrun/validate-env-contract.js` PASS。三个 helper 临时副本分别注入“只检查 daily service”“手动模式忽略 timer active”“断链当不存在”:使用 `git show f6d1025d:scripts/qa/run-deployment-contract-refresh-contract.sh` 的旧测试三次均 exit 0,使用当前测试三次均 exit 1(`KILLED_BY_NEW_TEST`),证明此次新增断言分别捕获原来的覆盖缺口。变异仅在自动清理的临时目录运行,生产 helper 源码未改。 +- [x] `python3 .trellis/scripts/task.py validate 08-28-production-arm-backup-transfer-incident`、`node scripts/qa/check-trellis-governance.mjs`、`node scripts/qa/check-process-contract.mjs --baseline --json`、三个改动文件的 `scanSensitiveText` 检查及 `git diff --check` 均通过;`node scripts/qa/audit-docs.mjs --check-registry` 无错误,保留既有 22 个未登记文档警告。本轮只改测试与说明,不重复执行未受影响的业务/数据库生命周期测试。 +- 第 5 条 `refresh_program FAIL` 分类细化保留为非阻断后续,不扩入本轮。 + +本轮仅测试和任务说明补漏,不提交、不推送、不连接或修改生产/ARM。远端批准清单仍待形成;本轮结果不代表实际停用完成。 + ## 2026-10-05 最新决定与本地落实:仅手动快照 用户明确选择仅手动快照,不用其他厂商对象存储,也不再选 ARM/hyinda-20c 承担备份。此前对象存储选型、ARM 修复或替换、两个自然周期验收不再作为当前推进目标;原始结果保留,不勾成通过、不自动归档。 @@ -13,16 +25,16 @@ - 验证:`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。 + 1. 先将最终获批提交的安装器和 helper 同步到生产目标代码目录,核对来源提交与文件摘要(同步本身也需远端写入批准);installer 从该目录取源,不会自动取得本地提交。再用 `install-deployment-contract-refresh.sh install` 安装新 helper;无模式文件时仍为自动模式,forced-command `probe` 须 PASS。 2. 先 `systemctl disable --now` 三个生产备份 **timer**(只停定时器,阻止新任务启动,不动 service)。 - 3. 有界等待三个备份 service 变为 `inactive`/`failed`(上限预先写定,例如 90 分钟);超时则停止切换、保持现状并报告,**不强杀**。 + 3. 有界等待三个备份 service 变为 `inactive`/`failed`(上限预先写定,例如 90 分钟);超时则停止切换,**不强杀**。执行前明确批准超时处置:恢复本次停用 timer 的原启用状态并验证自动模式,或保持停用且不写模式文件、记录“切换未完成,部署被阻断”。回滚未获批时使用后一种停止状态并报告,不擅自重启或宣称切换完成。 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;原为自动模式时移除本次模式文件,重新启用原本 enabled 的 timer,再跑 `refresh`。源码确认生产备份及 ARM dead-man timer 均配置 `Persistent=true`;现场复核后,将重新启用可能立即补跑及产生告警纳入回滚批准,不将其描述为无执行副作用。历史备份、unit 文件、公网健康探针均不动。 +- [x] 用户回传 `f6d1025d` 同模型独立只读复核通过,无阻断项;5 条非阻断问题及本轮补漏见顶部。新改动的本地验证与该次独立复核分别记录。 - [ ] 形成固定版本、模式配置、四个备份 timer 及回滚清单,另获生产/ARM 配置变更批准后执行与核验。 -运行状态:本轮未连接生产或 ARM,自动备份/告警可能仍按旧配置运行;未停用定时器、未删除历史备份、未创建快照,未提交或归档。当前选择不自动停用独立公网健康监控。手动快照执行时记录目标确认、覆盖确认、创建时间及成功状态,不记录真实资源标识。 +运行状态:未连接生产或 ARM,自动备份/告警可能仍按旧配置运行;未停用定时器、未删除历史备份、未创建快照。原适配已提交为 `f6d1025d`,本轮测试与说明补漏未提交,未推送或归档。当前选择不自动停用独立公网健康监控。手动快照执行时记录目标确认、覆盖确认、创建时间及成功状态,不记录真实资源标识。 ## 2026-10-05 告警频繁的只读核验(用户当轮授权,ARM 普通账号) diff --git a/scripts/qa/run-deployment-contract-refresh-contract.sh b/scripts/qa/run-deployment-contract-refresh-contract.sh index 0fcd7aef..c4ccd6c5 100755 --- a/scripts/qa/run-deployment-contract-refresh-contract.sh +++ b/scripts/qa/run-deployment-contract-refresh-contract.sh @@ -137,7 +137,7 @@ case "$action" in unit="${2:-}" if [[ "$unit" == hyb-oa-automatic-backup-* ]]; then case "${FIXTURE_BACKUP_TIMERS:-running}" in - disabled) echo disabled; exit 1 ;; + disabled|disabled_active) echo disabled; exit 1 ;; mixed) if [[ "$unit" == *watchdog* ]]; then echo enabled; else echo disabled; exit 1; fi ;; @@ -150,18 +150,21 @@ case "$action" in is-active) unit="${2:-}" if [[ "$unit" == *.service ]]; then - if [[ "$unit" == hyb-oa-automatic-backup-daily.service \ - && -n "${FIXTURE_DAILY_SERVICE_STATE:-}" ]]; then - echo "$FIXTURE_DAILY_SERVICE_STATE" - [[ "$FIXTURE_DAILY_SERVICE_STATE" == "active" ]] || exit 3 - exit 0 - fi - echo inactive; exit 3 + case "$unit" in + hyb-oa-automatic-backup-daily.service) state="${FIXTURE_DAILY_SERVICE_STATE:-inactive}" ;; + hyb-oa-automatic-backup-watchdog.service) state="${FIXTURE_WATCHDOG_SERVICE_STATE:-inactive}" ;; + hyb-oa-automatic-backup-drill-reminder.service) state="${FIXTURE_DRILL_SERVICE_STATE:-inactive}" ;; + *) state=inactive ;; + esac + echo "$state" + [[ "$state" == "active" ]] || exit 3 + exit 0 fi if [[ "${FIXTURE_TIMER_INACTIVE:-0}" == "1" ]]; then echo inactive; exit 3; fi if [[ "$unit" == hyb-oa-automatic-backup-* ]]; then case "${FIXTURE_BACKUP_TIMERS:-running}" in disabled) echo inactive; exit 3 ;; + disabled_active) echo active ;; mixed) if [[ "$unit" == *watchdog* ]]; then echo active; else echo inactive; exit 3; fi ;; @@ -228,6 +231,14 @@ manual_output="$(HYB_CONTRACT_REFRESH_TEST_ROOT="$FIXTURE_ROOT" FIXTURE_BACKUP_T [[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled FIXTURE_DAILY_SERVICE_STATE=active)" -eq 45 ]] [[ "$(run_refresh_status probe FIXTURE_BACKUP_TIMERS=disabled FIXTURE_DAILY_SERVICE_STATE=activating)" -eq 45 ]] [[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled FIXTURE_DAILY_SERVICE_STATE=failed)" -eq 0 ]] +# Each non-daily service can still be running independently of the daily job. +# disable without --now also leaves a timer active despite its disabled state. +for phase in probe refresh; do + for service_state in FIXTURE_WATCHDOG_SERVICE_STATE FIXTURE_DRILL_SERVICE_STATE; do + [[ "$(run_refresh_status "$phase" FIXTURE_BACKUP_TIMERS=disabled "$service_state=active")" -eq 45 ]] + done + [[ "$(run_refresh_status "$phase" FIXTURE_BACKUP_TIMERS=disabled_active)" -eq 45 ]] +done # Non-backup ops timer is still required in manual mode. [[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled FIXTURE_OPS_TIMER_INACTIVE=1)" -eq 43 ]] @@ -240,6 +251,11 @@ rm -f -- "$mode_file" printf 'manual_snapshot\n' >"$FIXTURE_ROOT/backup-mode-target" ln -s "$FIXTURE_ROOT/backup-mode-target" "$mode_file" [[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled)" -eq 44 ]] +# A dangling link is invalid, not an absent file selecting automatic mode. +rm -f -- "$FIXTURE_ROOT/backup-mode-target" +[[ -L "$mode_file" && ! -e "$mode_file" ]] +[[ "$(run_refresh_status probe)" -eq 44 ]] +[[ "$(run_refresh_status refresh)" -eq 44 ]] rm -f -- "$mode_file" [[ "$(run_refresh_status refresh)" -eq 0 ]]