备份策略改为部署前手动快照:部署合同增加手动快照模式 [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 行删除
@@ -133,9 +133,42 @@ case "$action" in
esac
;;
daemon-reload) exit 0 ;;
is-enabled) echo enabled ;;
is-enabled)
unit="${2:-}"
if [[ "$unit" == hyb-oa-automatic-backup-* ]]; then
case "${FIXTURE_BACKUP_TIMERS:-running}" in
disabled) echo disabled; exit 1 ;;
mixed)
if [[ "$unit" == *watchdog* ]]; then echo enabled; else echo disabled; exit 1; fi
;;
*) echo enabled ;;
esac
else
echo enabled
fi
;;
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
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 ;;
mixed)
if [[ "$unit" == *watchdog* ]]; then echo active; else echo inactive; exit 3; fi
;;
esac
elif [[ "${FIXTURE_OPS_TIMER_INACTIVE:-0}" == "1" ]]; then
echo inactive; exit 3
fi
echo active
;;
*) exit 1 ;;
@@ -162,6 +195,57 @@ unsupported_status=$?
set -e
[[ "$unit_status" -eq 42 && "$timer_status" -eq 43 && "$unsupported_status" -eq 64 ]]
run_refresh_status() {
local command="$1"
shift
set +e
env HYB_CONTRACT_REFRESH_TEST_ROOT="$FIXTURE_ROOT" "$@" \
SSH_ORIGINAL_COMMAND="$command" bash "$HELPER" >/dev/null 2>&1
local status=$?
set -e
printf '%s' "$status"
}
mode_file="$FIXTURE_ROOT/etc/hyb-oa-backup-mode"
# Automatic mode (no mode file): disabled backup timers stay fail-closed,
# both before (probe) and after (refresh) a release.
[[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled)" -eq 43 ]]
[[ "$(run_refresh_status probe FIXTURE_BACKUP_TIMERS=disabled)" -eq 43 ]]
[[ "$(run_refresh_status probe FIXTURE_TIMER_INACTIVE=1)" -eq 43 ]]
printf 'manual_snapshot\n' >"$mode_file"
manual_output="$(HYB_CONTRACT_REFRESH_TEST_ROOT="$FIXTURE_ROOT" FIXTURE_BACKUP_TIMERS=disabled \
SSH_ORIGINAL_COMMAND=refresh bash "$HELPER")"
[[ "$manual_output" == '[deployment-contract-refresh][PASS] refreshed' ]]
[[ "$(run_refresh_status probe FIXTURE_BACKUP_TIMERS=disabled)" -eq 0 ]]
# Manual mode must not silently accept running or partially running backup timers,
# and the pre-deploy probe must reject the mismatch before any release write.
[[ "$(run_refresh_status refresh)" -eq 45 ]]
[[ "$(run_refresh_status probe)" -eq 45 ]]
[[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=mixed)" -eq 45 ]]
[[ "$(run_refresh_status probe FIXTURE_BACKUP_TIMERS=mixed)" -eq 45 ]]
# Disabled timers with an in-flight backup run are not a finished switch.
[[ "$(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 ]]
# 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 ]]
printf 'manual\n' >"$mode_file"
[[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled)" -eq 44 ]]
[[ "$(run_refresh_status probe FIXTURE_BACKUP_TIMERS=disabled)" -eq 44 ]]
printf 'manual_snapshot\nautomatic\n' >"$mode_file"
[[ "$(run_refresh_status refresh FIXTURE_BACKUP_TIMERS=disabled)" -eq 44 ]]
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 ]]
rm -f -- "$mode_file"
[[ "$(run_refresh_status refresh)" -eq 0 ]]
[[ "$(deployment_contract_refresh_category 44 '')" == "backup_mode_invalid" ]]
[[ "$(deployment_contract_refresh_category 45 '')" == "backup_timer_not_disabled" ]]
public_key_file="$FIXTURE_ROOT/refresh.pub"
key_blob='AAAAC3NzaC1lZDI1NTE5AAAAIFixturerefreshkeymaterial000000000000'
printf 'ssh-ed25519 %s fixture-refresh\n' "$key_blob" >"$public_key_file"