#!/bin/sh
set -u
umask 077

CONF="/etc/router-egress-emergency-refresh.conf"
RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
HOOK="/usr/local/sbin/router-egress-emergency-decision-hook.sh"
PLANNER="/usr/local/sbin/router-egress-hmn-plan-top5.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"
CODE_TEST="/root/hmn/hmn-code-test.sh"

HOOK_INIT="/etc/init.d/router-egress-emergency-decision"
WATCHER_INIT="/etc/init.d/router-egress-health-repair"
SLOTS_INIT="/etc/init.d/router-egress-slots"
MAPPER_INIT="/etc/init.d/router-egress-mapper"

CACHE_DIR="/root/hmn/cache"
POOL="${CACHE_DIR}/ok-awg1-strict-foreign-latest.tsv"
STATE_DIR="/var/lib/router-egress-recovery"

RUN_ID="$(date -u +%Y%m%d-%H%M%S)-$$"
BACKUP_ROOT="/root/step050m07c4-backup-${RUN_ID}"
ROLLBACK="/root/rollback-step050m07c4-${RUN_ID}.sh"

RUNNER_OUT="/tmp/step050m07c4-runner-${RUN_ID}.out"
RUNNER_ERR="/tmp/step050m07c4-runner-${RUN_ID}.err"
LOG_DELTA="/tmp/step050m07c4-log-${RUN_ID}.delta"
CODE_OUT="/tmp/step050m07c4-code-test-${RUN_ID}.out"

ROLLBACK_READY=false
ROLLBACK_DONE=false

HOOK_WAS_RUNNING=false
WATCHER_WAS_RUNNING=false

STREAM_NEW_OFFSET=0

fact() {
  printf '__FACT__ %s=%s\n' "$1" "$2"
}

block() {
  echo "__BLOCK_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__BLOCK_END__ $1"
}

json_block() {
  echo "__JSON_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__JSON_END__ $1"
}

bool_cmd() {
  if "$@" >/dev/null 2>&1; then
    printf true
  else
    printf false
  fi
}

state_value() {
  key="$1"
  fallback="$2"

  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_get_state "$key" "$fallback"
  )
}

repair_counter() {
  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_daily_repair_get
  )
}

strict_iface() {
  interface="$1"
  attempt=1

  while [ "$attempt" -le 3 ]; do
    if ping \
      -I "$interface" \
      -c 1 \
      -W 3 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))
    sleep 1
  done

  return 1
}

strict_all() {
  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    strict_iface "$interface" ||
      return 1
  done

  return 0
}

routes_all() {
  for table in 201 202 203 204 205; do
    ip route show table "$table" 2>/dev/null |
      grep -q '^default ' ||
      return 1
  done

  return 0
}

choose_bootstrap() {
  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    if strict_iface "$interface"; then
      printf '%s' "$interface"
      return 0
    fi
  done

  return 1
}

table200_hash() {
  ip route show table 200 2>/dev/null |
    sort |
    sha256sum |
    sed 's/[[:space:]].*$//'
}

restore_table200() {
  source_file="$1"
  restore_rc=0

  ip route flush table 200 >/dev/null 2>&1 ||
    restore_rc=1

  while IFS= read -r route_line; do
    [ -n "$route_line" ] || continue

    set -- $route_line

    ip route add table 200 "$@" >/dev/null 2>&1 ||
      restore_rc=1
  done < "$source_file"

  return "$restore_rc"
}

restore_services() {
  if [ "$HOOK_WAS_RUNNING" = true ]; then
    "$HOOK_INIT" start >/dev/null 2>&1 ||
      true
  fi

  if [ "$WATCHER_WAS_RUNNING" = true ]; then
    "$WATCHER_INIT" start >/dev/null 2>&1 ||
      true
  fi
}

stream_file() {
  file="$1"
  old_offset="$2"
  destination="$3"
  label="$4"
  append_file="$5"

  if [ -f "$file" ]; then
    current_size="$(
      wc -c < "$file" 2>/dev/null |
        tr -d ' ' ||
      echo 0
    )"
  else
    current_size=0
  fi

  case "$current_size" in
    ''|*[!0-9]*)
      current_size=0
      ;;
  esac

  if [ "$current_size" -gt "$old_offset" ]; then
    count=$((current_size - old_offset))
    chunk="/tmp/step050m07c4-chunk-${RUN_ID}-$$"

    dd \
      if="$file" \
      bs=1 \
      skip="$old_offset" \
      count="$count" \
      2>/dev/null \
      > "$chunk"

    if [ "$destination" = stderr ]; then
      echo "__LIVE_CHUNK__ source=${label} bytes=${count}" >&2
      cat "$chunk" >&2
    else
      echo "__LIVE_CHUNK__ source=${label} bytes=${count}"
      cat "$chunk"
    fi

    if [ "$append_file" != "-" ]; then
      cat "$chunk" >> "$append_file"
    fi

    rm -f "$chunk"
  fi

  STREAM_NEW_OFFSET="$current_size"
}

detect_stage() {
  if [ ! -s "$LOG_DELTA" ]; then
    echo "runner_starting"
    return
  fi

  tail_text="$(
    tail -n 160 "$LOG_DELTA" 2>/dev/null ||
    true
  )"

  if printf '%s\n' "$tail_text" |
    grep -q '=== hmn-refresh-pool-safe done ==='
  then
    echo "refresh_finished_rebalance_running"

  elif printf '%s\n' "$tail_text" |
    grep -q '=== run manager once after validation ==='
  then
    echo "manager_after_validation"

  elif printf '%s\n' "$tail_text" |
    grep -q '=== done validate-current-pool ==='
  then
    echo "pool_validation_completed"

  elif printf '%s\n' "$tail_text" |
    grep -q 'strict foreign head'
  then
    echo "strict_foreign_pool_validation"

  elif printf '%s\n' "$tail_text" |
    grep -Ei \
      'download|serverlist|fresh_download' \
      >/dev/null
  then
    echo "provider_download"

  elif printf '%s\n' "$tail_text" |
    grep -Ei \
      'validate|testing|strict' \
      >/dev/null
  then
    echo "tunnel_validation"

  else
    echo "hmn_refresh_running"
  fi
}

auto_rollback() {
  exit_rc="$?"
  trap - EXIT

  if [ "$exit_rc" -ne 0 ] &&
     [ "$ROLLBACK_READY" = true ] &&
     [ "$ROLLBACK_DONE" != true ] &&
     [ -x "$ROLLBACK" ]
  then
    echo
    echo "=== REMOTE AUTO ROLLBACK START ==="

    if sh "$ROLLBACK"; then
      ROLLBACK_DONE=true
      fact auto_rollback true
    else
      fact auto_rollback false
    fi

    echo "=== REMOTE AUTO ROLLBACK END ==="
  elif [ "$exit_rc" -ne 0 ]; then
    restore_services
  fi

  rm -f \
    "$RUNNER_OUT" \
    "$RUNNER_ERR" \
    "$LOG_DELTA" \
    "$CODE_OUT"

  exit "$exit_rc"
}

trap auto_rollback EXIT

for required in \
  "$CONF" \
  "$RUNNER" \
  "$HOOK" \
  "$PLANNER" \
  "$HELPER" \
  "$CODE_TEST" \
  "$HOOK_INIT" \
  "$WATCHER_INIT" \
  "$CACHE_DIR" \
  "$POOL" \
  "$STATE_DIR" \
  /etc/config/network
do
  [ -e "$required" ] || {
    echo "__ERROR__ missing=$required"
    exit 21
  }
done

for command_name in \
  sh \
  sed \
  grep \
  find \
  sort \
  cp \
  mv \
  rm \
  mkdir \
  date \
  wc \
  sha256sum \
  dd \
  cat \
  tail \
  kill \
  tee \
  uci \
  wg \
  ip \
  ping
do
  command -v "$command_name" >/dev/null 2>&1 || {
    echo "__ERROR__ command_missing=$command_name"
    exit 22
  }
done

CONFIG_RAW_PRE="$(
  (
    . "$CONF"
    printf '%s' "${EMERGENCY_COMMIT_ENABLED:-UNSET}"
  )
)"

CONFIRM_TOKEN="$(
  (
    . "$CONF"
    printf '%s' "${EMERGENCY_CONFIRM_TOKEN:-}"
  )
)"

EMERGENCY_LOG="$(
  (
    . "$CONF"
    printf '%s' "${EMERGENCY_LOG:-/var/log/router-egress-emergency-refresh.log}"
  )
)"

fact commit_raw_pre "$CONFIG_RAW_PRE"
fact emergency_log "$EMERGENCY_LOG"

case "$CONFIG_RAW_PRE" in
  1|true)
    ;;
  *)
    echo "__ERROR__ unsupported_commit_raw=$CONFIG_RAW_PRE"
    exit 23
    ;;
esac

[ -n "$CONFIRM_TOKEN" ] || {
  echo "__ERROR__ confirm_token_empty"
  exit 24
}

RUNNER_PRE="$("$RUNNER" --dry-run)"
PLANNER_PRE="$("$PLANNER")"

STATE_EPOCH_PRE="$(
  state_value last_emergency_refresh_epoch 0
)"

STATE_STATUS_PRE="$(
  state_value last_emergency_refresh_status NONE
)"

COUNTER_PRE="$(repair_counter)"

POOL_HASH_PRE="$(
  sha256sum "$POOL" |
    sed 's/[[:space:]].*$//'
)"

NETWORK_HASH_PRE="$(
  sha256sum /etc/config/network |
    sed 's/[[:space:]].*$//'
)"

TABLE200_HASH_PRE="$(table200_hash)"

json_block runner_pre "$RUNNER_PRE"
json_block planner_pre "$PLANNER_PRE"

fact state_epoch_pre "$STATE_EPOCH_PRE"
fact state_status_pre "$STATE_STATUS_PRE"
fact repair_counter_pre "$COUNTER_PRE"
fact pool_hash_pre "$POOL_HASH_PRE"
fact network_hash_pre "$NETWORK_HASH_PRE"
fact table200_hash_pre "$TABLE200_HASH_PRE"

TABLE200_PRE="$(
  ip route show table 200 2>&1 ||
  true
)"

block table200_before "$TABLE200_PRE"

HEALTHY_PRE=0

for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  if strict_iface "$interface"; then
    value=true
    HEALTHY_PRE=$((HEALTHY_PRE + 1))
  else
    value=false
  fi

  fact "pre.strict.${interface}" "$value"
done

fact healthy_slots_pre "$HEALTHY_PRE"
fact routes_all_pre "$(bool_cmd routes_all)"

[ "$HEALTHY_PRE" -ge 4 ] || {
  echo "__ERROR__ fewer_than_four_healthy_slots"
  exit 25
}

[ "$(bool_cmd routes_all)" = true ] || {
  echo "__ERROR__ routes_201_205_not_ready"
  exit 26
}

mkdir -p "$BACKUP_ROOT"

cp -a "$CONF" "$BACKUP_ROOT/config.before"
cp -a "$CACHE_DIR" "$BACKUP_ROOT/cache.before"
cp -a "$STATE_DIR" "$BACKUP_ROOT/state.before"
cp -a /etc/config/network "$BACKUP_ROOT/network.before"

ip route show table 200 2>/dev/null |
  sort \
  > "$BACKUP_ROOT/table200.before"

find "$STATE_DIR" \
  -maxdepth 4 \
  -type f \
  -name 'rollback-egress*.sh' \
  2>/dev/null |
  sort \
  > "$BACKUP_ROOT/slot-rollbacks.before"

HOOK_WAS_RUNNING="$(bool_cmd "$HOOK_INIT" running)"
WATCHER_WAS_RUNNING="$(bool_cmd "$WATCHER_INIT" running)"

fact hook_was_running "$HOOK_WAS_RUNNING"
fact watcher_was_running "$WATCHER_WAS_RUNNING"

cat > "$ROLLBACK" <<EOF
#!/bin/sh
set -u
umask 077

CONF='$CONF'
CACHE_DIR='$CACHE_DIR'
STATE_DIR='$STATE_DIR'
BACKUP_ROOT='$BACKUP_ROOT'

HOOK_INIT='$HOOK_INIT'
WATCHER_INIT='$WATCHER_INIT'
SLOTS_INIT='$SLOTS_INIT'
MAPPER_INIT='$MAPPER_INIT'

HOOK_WAS_RUNNING='$HOOK_WAS_RUNNING'
WATCHER_WAS_RUNNING='$WATCHER_WAS_RUNNING'

command_errors=0

"\$HOOK_INIT" stop >/dev/null 2>&1 || true
"\$WATCHER_INIT" stop >/dev/null 2>&1 || true

find "\$STATE_DIR" \
  -maxdepth 4 \
  -type f \
  -name 'rollback-egress*.sh' \
  2>/dev/null |
  sort -r \
  > "\$BACKUP_ROOT/slot-rollbacks.after"

while IFS= read -r rollback_path; do
  [ -n "\$rollback_path" ] || continue

  if ! grep -Fxq \
    "\$rollback_path" \
    "\$BACKUP_ROOT/slot-rollbacks.before"
  then
    sh "\$rollback_path" ||
      command_errors=\$((command_errors + 1))
  fi
done < "\$BACKUP_ROOT/slot-rollbacks.after"

cp -a \
  "\$BACKUP_ROOT/config.before" \
  "\$CONF" ||
command_errors=\$((command_errors + 1))

rm -rf "\$CACHE_DIR"
cp -a \
  "\$BACKUP_ROOT/cache.before" \
  "\$CACHE_DIR" ||
command_errors=\$((command_errors + 1))

rm -rf "\$STATE_DIR"
cp -a \
  "\$BACKUP_ROOT/state.before" \
  "\$STATE_DIR" ||
command_errors=\$((command_errors + 1))

cp -a \
  "\$BACKUP_ROOT/network.before" \
  /etc/config/network ||
command_errors=\$((command_errors + 1))

rm -rf \
  /var/lock/router-egress-emergency-refresh.lock \
  /tmp/hmn-refresh-pool-safe.lock \
  2>/dev/null ||
true

/etc/init.d/network reload >/dev/null 2>&1 ||
  command_errors=\$((command_errors + 1))

sleep 8

[ ! -x "\$SLOTS_INIT" ] ||
  "\$SLOTS_INIT" restart >/dev/null 2>&1 ||
  command_errors=\$((command_errors + 1))

[ ! -x "\$MAPPER_INIT" ] ||
  "\$MAPPER_INIT" restart >/dev/null 2>&1 ||
  command_errors=\$((command_errors + 1))

ip route flush table 200 >/dev/null 2>&1 ||
  command_errors=\$((command_errors + 1))

while IFS= read -r route_line; do
  [ -n "\$route_line" ] || continue

  set -- \$route_line

  ip route add table 200 "\$@" >/dev/null 2>&1 ||
    command_errors=\$((command_errors + 1))
done < "\$BACKUP_ROOT/table200.before"

if [ "\$HOOK_WAS_RUNNING" = true ]; then
  "\$HOOK_INIT" start >/dev/null 2>&1 ||
    command_errors=\$((command_errors + 1))
fi

if [ "\$WATCHER_WAS_RUNNING" = true ]; then
  "\$WATCHER_INIT" start >/dev/null 2>&1 ||
    command_errors=\$((command_errors + 1))
fi

config_expected="\$(
  sha256sum "\$BACKUP_ROOT/config.before" |
    sed 's/[[:space:]].*\$//'
)"

config_live="\$(
  sha256sum "\$CONF" |
    sed 's/[[:space:]].*\$//'
)"

pool_expected="\$(
  sha256sum \
    "\$BACKUP_ROOT/cache.before/ok-awg1-strict-foreign-latest.tsv" |
    sed 's/[[:space:]].*\$//'
)"

pool_live="\$(
  sha256sum \
    "\$CACHE_DIR/ok-awg1-strict-foreign-latest.tsv" |
    sed 's/[[:space:]].*\$//'
)"

network_expected="\$(
  sha256sum "\$BACKUP_ROOT/network.before" |
    sed 's/[[:space:]].*\$//'
)"

network_live="\$(
  sha256sum /etc/config/network |
    sed 's/[[:space:]].*\$//'
)"

table200_expected="\$(
  sort "\$BACKUP_ROOT/table200.before" |
    sha256sum |
    sed 's/[[:space:]].*\$//'
)"

table200_live="\$(
  ip route show table 200 2>/dev/null |
    sort |
    sha256sum |
    sed 's/[[:space:]].*\$//'
)"

strict=true

for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  ping -I "\$interface" -c 1 -W 3 1.1.1.1 \
    >/dev/null 2>&1 ||
    strict=false
done

routes=true

for table in 201 202 203 204 205; do
  ip route show table "\$table" |
    grep -q '^default ' ||
    routes=false
done

hook_restored=true
watcher_restored=true

if [ "\$HOOK_WAS_RUNNING" = true ]; then
  "\$HOOK_INIT" running >/dev/null 2>&1 ||
    hook_restored=false
fi

if [ "\$WATCHER_WAS_RUNNING" = true ]; then
  "\$WATCHER_INIT" running >/dev/null 2>&1 ||
    watcher_restored=false
fi

config_match=false
pool_match=false
network_match=false
table200_match=false

[ "\$config_expected" = "\$config_live" ] &&
  config_match=true

[ "\$pool_expected" = "\$pool_live" ] &&
  pool_match=true

[ "\$network_expected" = "\$network_live" ] &&
  network_match=true

[ "\$table200_expected" = "\$table200_live" ] &&
  table200_match=true

echo "rollback_command_errors=\$command_errors"
echo "rollback_config_match=\$config_match"
echo "rollback_pool_match=\$pool_match"
echo "rollback_network_match=\$network_match"
echo "rollback_table200_match=\$table200_match"
echo "rollback_strict_all=\$strict"
echo "rollback_routes_all=\$routes"
echo "rollback_hook_restored=\$hook_restored"
echo "rollback_watcher_restored=\$watcher_restored"

# Командные предупреждения публикуются, но результат определяется
# конечными инвариантами, а не промежуточным ненулевым rc.
if [ "\$config_match" = true ] &&
   [ "\$pool_match" = true ] &&
   [ "\$network_match" = true ] &&
   [ "\$table200_match" = true ] &&
   [ "\$strict" = true ] &&
   [ "\$routes" = true ] &&
   [ "\$hook_restored" = true ] &&
   [ "\$watcher_restored" = true ]
then
  echo "rollback_validated=true"
  exit 0
fi

echo "rollback_validated=false"
exit 1
EOF

chmod 700 "$ROLLBACK"
ROLLBACK_READY=true

fact rollback "$ROLLBACK"
fact rollback_exists "$(bool_cmd test -x "$ROLLBACK")"
fact backup_root "$BACKUP_ROOT"

# С этого момента любое завершение с ошибкой обязано откатиться.
fact mutation_started true

"$WATCHER_INIT" stop >/dev/null 2>&1 || true
"$HOOK_INIT" stop >/dev/null 2>&1 || true

sleep 2

fact watcher_stopped "$(
  if "$WATCHER_INIT" running >/dev/null 2>&1; then
    echo false
  else
    echo true
  fi
)"

fact hook_stopped "$(
  if "$HOOK_INIT" running >/dev/null 2>&1; then
    echo false
  else
    echo true
  fi
)"

BOOTSTRAP_IFACE="$(
  choose_bootstrap ||
  true
)"

[ -n "$BOOTSTRAP_IFACE" ] || {
  echo "__ERROR__ no_healthy_bootstrap_iface"
  exit 27
}

fact bootstrap_iface "$BOOTSTRAP_IFACE"

ip route replace \
  default \
  dev "$BOOTSTRAP_IFACE" \
  table 200 ||
{
  echo "__ERROR__ temporary_table200_route_install_failed"
  exit 28
}

TABLE200_TEMP="$(
  ip route show table 200 2>&1 ||
  true
)"

block table200_with_temporary_bootstrap "$TABLE200_TEMP"

ip route show table 200 |
  grep -q "^default dev ${BOOTSTRAP_IFACE}\([[:space:]]\|$\)" ||
{
  echo "__ERROR__ temporary_table200_route_not_visible"
  exit 29
}

fact temporary_table200_bootstrap_installed true

echo
echo "=== LIVE HMN CODE TEST START ==="

set +e
"$CODE_TEST" > "$CODE_OUT" 2>&1
CODE_RC=$?
set -e

cat "$CODE_OUT"

echo "=== LIVE HMN CODE TEST END ==="
echo

fact code_test_rc "$CODE_RC"

if [ "$CODE_RC" -ne 0 ]; then
  echo "__ERROR__ bootstrap_code_test_failed"
  exit 30
fi

fact code_test_passed_with_temporary_bootstrap true

if [ "$CONFIG_RAW_PRE" = "1" ]; then
  TEMP_CONF="${CONF}.step050m07c4.$$.tmp"

  sed \
    's/^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=.*/EMERGENCY_COMMIT_ENABLED=true/' \
    "$CONF" \
    > "$TEMP_CONF"

  [ "$(
    grep -c \
      '^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=true[[:space:]]*$' \
      "$TEMP_CONF"
  )" -eq 1 ] || {
    rm -f "$TEMP_CONF"
    echo "__ERROR__ commit_boolean_normalization_failed"
    exit 31
  }

  chmod 600 "$TEMP_CONF"
  chown 0:0 "$TEMP_CONF"
  mv "$TEMP_CONF" "$CONF"
fi

CONFIG_RAW_POST="$(
  (
    . "$CONF"
    printf '%s' "${EMERGENCY_COMMIT_ENABLED:-UNSET}"
  )
)"

fact commit_raw_post "$CONFIG_RAW_POST"

[ "$CONFIG_RAW_POST" = "true" ] || {
  echo "__ERROR__ commit_value_not_true_after_normalization"
  exit 32
}

LOG_SIZE_PRE="$(
  if [ -f "$EMERGENCY_LOG" ]; then
    wc -c < "$EMERGENCY_LOG" |
      tr -d ' '
  else
    echo 0
  fi
)"

fact emergency_log_size_pre "$LOG_SIZE_PRE"

: > "$RUNNER_OUT"
: > "$RUNNER_ERR"
: > "$LOG_DELTA"

"$RUNNER" \
  --commit \
  --confirm "$CONFIRM_TOKEN" \
  > "$RUNNER_OUT" \
  2> "$RUNNER_ERR" &

RUNNER_PID=$!
START_EPOCH="$(date +%s)"
LAST_HEARTBEAT=0

OUT_OFFSET=0
ERR_OFFSET=0
LOG_OFFSET="$LOG_SIZE_PRE"

echo
echo "=== LIVE HMN REFRESH START ==="
echo "__HEARTBEAT__ stage=runner_starting elapsed=0s runner_pid=${RUNNER_PID}"
echo

while kill -0 "$RUNNER_PID" >/dev/null 2>&1; do
  stream_file \
    "$RUNNER_OUT" \
    "$OUT_OFFSET" \
    stdout \
    runner_stdout \
    -

  OUT_OFFSET="$STREAM_NEW_OFFSET"

  stream_file \
    "$RUNNER_ERR" \
    "$ERR_OFFSET" \
    stderr \
    runner_stderr \
    -

  ERR_OFFSET="$STREAM_NEW_OFFSET"

  stream_file \
    "$EMERGENCY_LOG" \
    "$LOG_OFFSET" \
    stdout \
    emergency_log \
    "$LOG_DELTA"

  LOG_OFFSET="$STREAM_NEW_OFFSET"

  NOW_EPOCH="$(date +%s)"
  ELAPSED=$((NOW_EPOCH - START_EPOCH))

  if [ $((ELAPSED - LAST_HEARTBEAT)) -ge 15 ]; then
    CURRENT_REFRESH_STAGE="$(detect_stage)"

    echo
    echo "__HEARTBEAT__ stage=${CURRENT_REFRESH_STAGE} elapsed=${ELAPSED}s runner_pid=${RUNNER_PID}"
    echo

    LAST_HEARTBEAT="$ELAPSED"
  fi

  if [ "$ELAPSED" -ge 1800 ]; then
    echo "__ERROR__ runner_timeout_after=${ELAPSED}s"

    kill "$RUNNER_PID" >/dev/null 2>&1 || true
    sleep 2
    kill -9 "$RUNNER_PID" >/dev/null 2>&1 || true
    wait "$RUNNER_PID" >/dev/null 2>&1 || true

    exit 33
  fi

  sleep 2
done

set +e
wait "$RUNNER_PID"
RUNNER_RC=$?
set -e

stream_file \
  "$RUNNER_OUT" \
  "$OUT_OFFSET" \
  stdout \
  runner_stdout_final \
  -

OUT_OFFSET="$STREAM_NEW_OFFSET"

stream_file \
  "$RUNNER_ERR" \
  "$ERR_OFFSET" \
  stderr \
  runner_stderr_final \
  -

ERR_OFFSET="$STREAM_NEW_OFFSET"

stream_file \
  "$EMERGENCY_LOG" \
  "$LOG_OFFSET" \
  stdout \
  emergency_log_final \
  "$LOG_DELTA"

LOG_OFFSET="$STREAM_NEW_OFFSET"

FINISH_EPOCH="$(date +%s)"
RUNNER_ELAPSED=$((FINISH_EPOCH - START_EPOCH))

echo
echo "=== LIVE HMN REFRESH END ==="
echo "__HEARTBEAT__ stage=runner_finished elapsed=${RUNNER_ELAPSED}s runner_pid=${RUNNER_PID}"
echo

RUNNER_OUTPUT="$(
  cat "$RUNNER_OUT" 2>/dev/null ||
  true
)"

RUNNER_STDERR="$(
  cat "$RUNNER_ERR" 2>/dev/null ||
  true
)"

fact runner_commit_rc "$RUNNER_RC"
fact runner_elapsed_seconds "$RUNNER_ELAPSED"

json_block runner_commit "$RUNNER_OUTPUT"
block runner_commit_stderr_final "$RUNNER_STDERR"

OLD_POOL_FALLBACK=false
FRESH_DOWNLOAD_PROVEN=false

if grep -q \
  'pool_source=old-after-download-fail' \
  "$LOG_DELTA"
then
  OLD_POOL_FALLBACK=true
fi

if grep -Eq \
  'pool_source=fresh|last_fresh_download_rc=0' \
  "$LOG_DELTA"
then
  FRESH_DOWNLOAD_PROVEN=true
fi

fact old_pool_fallback "$OLD_POOL_FALLBACK"
fact fresh_download_proven "$FRESH_DOWNLOAD_PROVEN"

[ "$RUNNER_RC" -eq 0 ] || {
  echo "__ERROR__ runner_rc=$RUNNER_RC"
  exit 34
}

printf '%s\n' "$RUNNER_OUTPUT" |
  grep -q \
    '"decision"[[:space:]]*:[[:space:]]*"refresh_ok_rebalance_ok"' ||
{
  echo "__ERROR__ runner_decision_not_success"
  exit 35
}

[ "$OLD_POOL_FALLBACK" = false ] || {
  echo "__ERROR__ fresh_download_failed_old_pool_fallback"
  exit 36
}

[ "$FRESH_DOWNLOAD_PROVEN" = true ] || {
  echo "__ERROR__ fresh_download_not_proven"
  exit 37
}

# Временный bootstrap route больше не нужен.
restore_table200 "$BACKUP_ROOT/table200.before" || {
  echo "__ERROR__ table200_restore_failed"
  exit 38
}

TABLE200_HASH_POST="$(table200_hash)"
fact table200_hash_post "$TABLE200_HASH_POST"

[ "$TABLE200_HASH_POST" = "$TABLE200_HASH_PRE" ] || {
  echo "__ERROR__ table200_hash_mismatch_after_restore"
  exit 39
}

fact table200_restored true

restore_services
sleep 2

RUNNER_POST="$("$RUNNER" --dry-run)"
HOOK_POST="$("$HOOK")"
PLANNER_POST="$("$PLANNER")"

STATE_MODE_POST="$(state_value mode UNKNOWN)"
STATE_STATUS_POST="$(
  state_value last_emergency_refresh_status UNKNOWN
)"
STATE_EPOCH_POST="$(
  state_value last_emergency_refresh_epoch 0
)"

COUNTER_POST="$(repair_counter)"

json_block runner_post "$RUNNER_POST"
json_block hook_post "$HOOK_POST"
json_block planner_post "$PLANNER_POST"

fact state_mode_post "$STATE_MODE_POST"
fact state_status_post "$STATE_STATUS_POST"
fact state_epoch_post "$STATE_EPOCH_POST"
fact repair_counter_post "$COUNTER_POST"

fact hook_running_post "$(bool_cmd "$HOOK_INIT" running)"
fact hook_enabled_post "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_running_post "$(bool_cmd "$WATCHER_INIT" running)"
fact watcher_enabled_post "$(bool_cmd "$WATCHER_INIT" enabled)"

fact strict_all_post "$(bool_cmd strict_all)"
fact routes_all_post "$(bool_cmd routes_all)"

fact emergency_lock_present "$(
  bool_cmd test -e /var/lock/router-egress-emergency-refresh.lock
)"

fact refresh_lock_present "$(
  bool_cmd test -e /tmp/hmn-refresh-pool-safe.lock
)"

fact direct_failopen_changed false

[ "$STATE_MODE_POST" = "NORMAL" ] || {
  echo "__ERROR__ state_mode_not_normal"
  exit 40
}

[ "$STATE_STATUS_POST" = "refresh_ok_rebalance_ok" ] || {
  echo "__ERROR__ state_status_not_success"
  exit 41
}

[ "$STATE_EPOCH_POST" -gt "$STATE_EPOCH_PRE" ] || {
  echo "__ERROR__ success_epoch_not_advanced"
  exit 42
}

printf '%s\n' "$PLANNER_POST" |
  grep -q \
    '"changes_count"[[:space:]]*:[[:space:]]*0' ||
{
  echo "__ERROR__ planner_not_converged"
  exit 43
}

[ "$(bool_cmd strict_all)" = true ] || {
  echo "__ERROR__ strict_not_all"
  exit 44
}

[ "$(bool_cmd routes_all)" = true ] || {
  echo "__ERROR__ routes_not_all"
  exit 45
}

[ "$(bool_cmd "$HOOK_INIT" running)" = true ] || {
  echo "__ERROR__ hook_not_restored"
  exit 46
}

[ "$(bool_cmd "$WATCHER_INIT" running)" = true ] || {
  echo "__ERROR__ watcher_not_restored"
  exit 47
}

[ "$(bool_cmd test -e /var/lock/router-egress-emergency-refresh.lock)" = false ] || {
  echo "__ERROR__ emergency_lock_remains"
  exit 48
}

[ "$(bool_cmd test -e /tmp/hmn-refresh-pool-safe.lock)" = false ] || {
  echo "__ERROR__ refresh_lock_remains"
  exit 49
}

echo "__TRACE__ stage=complete"

trap - EXIT

rm -f \
  "$RUNNER_OUT" \
  "$RUNNER_ERR" \
  "$LOG_DELTA" \
  "$CODE_OUT"

exit 0
