#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

[ "$(id -un)" = "ops" ] || {
  echo "STOP: запускать на VM130/router-ops пользователем ops"
  exit 1
}

STEP="STEP_050M07B_CONTROLLED_REAL_FULL_HMN_REFRESH"
PASS_DECISION="PASS_STEP_050M07B_CONTROLLED_REAL_FULL_HMN_REFRESH"
PLAN_ID="vm101-hmn-autonomous-egress-recovery"

TOKEN="e94a0859747d7b96f29c7fdafc2d0351ba603bb0a7e9e5a4"
PUBLIC_BASE="https://helena-background-beam-harry.trycloudflare.com/r/${TOKEN}"

PREVIOUS_REPORT="${PUBLIC_BASE}/20260711-182203_step050m07a2_exact_refresh_contract_and_runtime_readiness/"
PREVIOUS_ARCHITECTURE_PLAN="${PUBLIC_BASE}/20260711-181158_local_architecture_plan_vm101_autonomous_hmn_recovery/"
XS_MAP="${PUBLIC_BASE}/20260711-120734_xs_map_audit_repair_publish/"
GLOBAL_PROJECT_PLAN="${PUBLIC_BASE}/20260711-123348_global_project_plan_wg_paid/"

ROOT="/opt/router-ops"
STATE_ROOT="${ROOT}/state"
PLAN_STATE_DIR="${STATE_ROOT}/local-plans/${PLAN_ID}"
PUBROOT="${ROOT}/public/r/${TOKEN}"

M07A2_DIR="${PUBROOT}/20260711-182203_step050m07a2_exact_refresh_contract_and_runtime_readiness"
M06A_DIR="${PUBROOT}/20260711-173308_step050m06a_compare_live_toolchain_to_frozen_map"

STATUS_TOOL="${ROOT}/bin/vm101-local-plan-status"
REPUBLISH_TOOL="${ROOT}/bin/vm101-local-plan-republish"

TS="$(date -u +%Y%m%d-%H%M%S)"
REPORT_SLUG="${TS}_step050m07b_controlled_real_full_hmn_refresh"
REPORT_DIR="${PUBROOT}/${REPORT_SLUG}"

TRYCF_REPORT="${PUBLIC_BASE}/${REPORT_SLUG}/"
REPORT_TXT="${TRYCF_REPORT}report.txt"
FACTS_JSON="${TRYCF_REPORT}facts.json"
ARCHITECTURE_PLAN="$PREVIOUS_ARCHITECTURE_PLAN"

mkdir -p \
  "$REPORT_DIR" \
  "$REPORT_DIR/sources" \
  "$STATE_ROOT"

# Точный copy-paste script сохраняется до проверок и изменений.
cp -a "$0" "$REPORT_DIR/step.sh"
chmod 600 "$REPORT_DIR/step.sh"

PROGRESS_LOG="$REPORT_DIR/progress.log"
: > "$PROGRESS_LOG"

CURRENT_STAGE="initialization"
LAST_SUCCESS="step_saved"

HASH_CHECK_RC="NOT_RUN"
VM101_RC="NOT_RUN"
ROLLBACK_RC="NOT_NEEDED"

VM101_CHANGED=false
VM101_ROLLBACK=""
PLAN_UPDATED=false
PRODUCTION_MODIFIED=false

stage() {
  CURRENT_STAGE="$1"

  echo
  echo ">>> [$1] $2" | tee -a "$PROGRESS_LOG"

  date -u '+%Y-%m-%dT%H:%M:%SZ' |
    sed 's/^/    utc=/' |
    tee -a "$PROGRESS_LOG"
}

mark_success() {
  LAST_SUCCESS="$1"
  echo "last_success=$LAST_SUCCESS" >> "$PROGRESS_LOG"
}

print_links() {
  echo
  echo "TRYCF_REPORT=$TRYCF_REPORT"
  echo "REPORT_TXT=$REPORT_TXT"
  echo "FACTS_JSON=$FACTS_JSON"
  echo "ARCHITECTURE_PLAN=$ARCHITECTURE_PLAN"
  echo "XS_MAP=$XS_MAP"
  echo "GLOBAL_PROJECT_PLAN=$GLOBAL_PROJECT_PLAN"
}

remote_vm101() {
  ssh pve-mgts \
    "ssh \
      -o BatchMode=yes \
      -o ConnectTimeout=8 \
      -o StrictHostKeyChecking=no \
      -o UserKnownHostsFile=/dev/null \
      -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
      root@10.71.100.2 \
      '$1'"
}

rollback_vm101() {
  if [ "$VM101_CHANGED" != true ]; then
    ROLLBACK_RC="NOT_NEEDED"
    return 0
  fi

  if [ -z "$VM101_ROLLBACK" ]; then
    ROLLBACK_RC="ROLLBACK_PATH_MISSING"
    PRODUCTION_MODIFIED=unknown
    return 1
  fi

  set +e

  remote_vm101 \
    "sh '$VM101_ROLLBACK'" \
    > "$REPORT_DIR/vm101-rollback.txt" \
    2> "$REPORT_DIR/vm101-rollback.stderr"

  ROLLBACK_RC=$?

  set -e

  if [ "$ROLLBACK_RC" -eq 0 ]; then
    VM101_CHANGED=false
    PRODUCTION_MODIFIED=false
    return 0
  fi

  PRODUCTION_MODIFIED=unknown
  return "$ROLLBACK_RC"
}

write_inline_diagnostic() {
  local reason="$1"
  local rc="$2"
  local line="$3"

  python3 - \
    "$REPORT_DIR" \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$CURRENT_STAGE" \
    "$LAST_SUCCESS" \
    "$HASH_CHECK_RC" \
    "$VM101_RC" \
    "$ROLLBACK_RC" \
    "$PRODUCTION_MODIFIED" \
    "$TRYCF_REPORT" \
    "$REPORT_TXT" \
    "$FACTS_JSON" \
    "$ARCHITECTURE_PLAN" \
    "$XS_MAP" \
    "$GLOBAL_PROJECT_PLAN" \
    > "$REPORT_DIR/diagnostic.json" <<'PY'
import json
import sys
from pathlib import Path

(
    report_dir,
    step,
    reason,
    rc,
    line,
    stage,
    last_success,
    hash_rc,
    vm101_rc,
    rollback_rc,
    production_modified,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

root = Path(report_dir)

def tail(path: Path, limit=30000):
    if not path.exists() or not path.is_file():
        return None

    text = path.read_text(
        encoding="utf-8",
        errors="replace",
    )

    return text[-limit:]


streams = {}

for pattern in ("*.txt", "*.stderr", "*.log"):
    for path in sorted(root.glob(pattern)):
        if path.name in {"report.txt", "progress.log"}:
            continue

        streams[path.name] = {
            "size_bytes": path.stat().st_size,
            "tail": tail(path),
        }

combined_stderr = "\n".join(
    item["tail"] or ""
    for name, item in streams.items()
    if name.endswith(".stderr")
)

combined_stdout = "\n".join(
    item["tail"] or ""
    for name, item in streams.items()
    if name.endswith(".txt")
)

if "refresh_failed_degraded_no_direct" in combined_stdout:
    classification = "HMN_REFRESH_FAILED_AND_ROLLBACK_ATTEMPTED"
elif "refresh_ok_rebalance_failed" in combined_stdout:
    classification = "HMN_REFRESH_OK_BUT_REBALANCE_FAILED"
elif "lock_busy" in combined_stdout:
    classification = "EMERGENCY_REFRESH_LOCK_BUSY"
elif "cooldown_active" in combined_stdout:
    classification = "UNEXPECTED_COOLDOWN_BLOCK"
elif "missing_or_wrong_confirm_token" in combined_stdout:
    classification = "CONFIRM_TOKEN_CONTRACT_FAILURE"
elif "not found" in combined_stderr:
    classification = "REMOTE_COMMAND_NOT_FOUND"
elif rollback_rc not in {"NOT_NEEDED", "0"}:
    classification = "ROLLBACK_FAILED"
elif vm101_rc not in {"NOT_RUN", "0"}:
    classification = "VM101_CONTROLLED_REFRESH_FAILED"
else:
    classification = "LOCAL_POSTVALIDATION_OR_PLAN_UPDATE_FAILURE"

artifacts = sorted(
    str(path.relative_to(root))
    for path in root.rglob("*")
    if path.is_file()
)

modified = {
    "true": True,
    "false": False,
}.get(production_modified, "unknown")

print(json.dumps({
    "schema": "router-step-inline-diagnostic-v3",
    "step": step,
    "failure": {
        "reason": reason,
        "rc": int(rc),
        "line": int(line),
        "stage": stage,
        "last_success": last_success,
    },
    "command_results": {
        "hash_check_rc": hash_rc,
        "vm101_rc": vm101_rc,
        "rollback_rc": rollback_rc,
    },
    "automatic_classification": classification,
    "production_modified_after_rollback": modified,
    "captured_streams": streams,
    "artifacts_present": artifacts,
    "recommended_next_step": (
        "Использовать automatic_classification, assessment.json, "
        "vm101.txt, vm101.stderr и vm101-rollback.* из этого же "
        "отчёта. Отдельный диагностический STEP не требуется."
    ),
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
    },
}, ensure_ascii=False, indent=2))
PY

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=STOP_${STEP}_${reason}
all_ok=false
mode=M07_CONTROLLED_REAL_FULL_REFRESH
error_rc=${rc}
error_line=${line}
failed_stage=${CURRENT_STAGE}
last_success=${LAST_SUCCESS}

command_results:
  hash_check_rc=${HASH_CHECK_RC}
  vm101_rc=${VM101_RC}
  rollback_rc=${ROLLBACK_RC}

safety:
  production_modified=${PRODUCTION_MODIFIED}
  vm101_rollback=${VM101_ROLLBACK:-UNAVAILABLE}
  direct_failopen_changed=false

inline_diagnostics:
  enabled=true
  diagnostic=diagnostic.json
  command_rc_captured=true
  stdout_and_stderr_captured=true
  failed_stage_captured=true
  automatic_classification_present=true
  separate_diagnostic_step_required=false

plan:
  current_milestone=M07
  milestone_completed=false
  plan_updated=${PLAN_UPDATED}

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
EOF

  python3 - \
    "$REPORT_DIR/diagnostic.json" \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$CURRENT_STAGE" \
    "$LAST_SUCCESS" \
    "$PRODUCTION_MODIFIED" \
    "$VM101_ROLLBACK" \
    "$TRYCF_REPORT" \
    "$REPORT_TXT" \
    "$FACTS_JSON" \
    "$ARCHITECTURE_PLAN" \
    "$XS_MAP" \
    "$GLOBAL_PROJECT_PLAN" \
    > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    diagnostic_path,
    step,
    reason,
    rc,
    line,
    stage,
    last_success,
    production_modified,
    rollback,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

with open(diagnostic_path, encoding="utf-8") as source:
    diagnostic = json.load(source)

modified = {
    "true": True,
    "false": False,
}.get(production_modified, "unknown")

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "assessment": {
        "decision": f"STOP_{step}_{reason}",
        "all_ok": False,
        "error_rc": int(rc),
        "error_line": int(line),
        "failed_stage": stage,
        "last_success": last_success,
    },
    "inline_diagnostic": diagnostic,
    "safety": {
        "production_modified": modified,
        "rollback_vm101": rollback or None,
        "direct_failopen_changed": False,
    },
    "plan": {
        "current_milestone": "M07",
        "milestone_completed": False,
    },
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
    },
}, ensure_ascii=False, indent=2))
PY

  cat > "$REPORT_DIR/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto;padding:0 20px;line-height:1.5">
<h1>${STEP}</h1>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="diagnostic.json">diagnostic.json</a></li>
<li><a href="assessment.json">assessment.json</a></li>
<li><a href="progress.log">progress.log</a></li>
<li><a href="step.sh">step.sh</a></li>
<li><a href="vm101.sh">vm101.sh</a></li>
<li><a href="vm101.txt">vm101.txt</a></li>
<li><a href="vm101.stderr">vm101.stderr</a></li>
<li><a href="vm101-rollback.txt">vm101-rollback.txt</a></li>
<li><a href="vm101-rollback.stderr">vm101-rollback.stderr</a></li>
</ul>
</body>
</html>
EOF

  find "$REPORT_DIR" \
    -type f \
    ! -name SHA256SUMS \
    -print0 |
    sort -z |
    xargs -0 sha256sum \
    > "$REPORT_DIR/SHA256SUMS"
}

fatal() {
  local reason="$1"
  local rc="${2:-1}"
  local line="${3:-$LINENO}"

  trap - ERR

  rollback_vm101 || true
  write_inline_diagnostic "$reason" "$rc" "$line"

  print_links
  exit "$rc"
}

on_error() {
  local rc="$?"
  local line="$1"

  fatal "UNEXPECTED_ERROR" "$rc" "$line"
}

trap 'on_error "$LINENO"' ERR

stage "01/10" "Проверяю M07A2, source contract и текущий план"

for required in \
  "$M07A2_DIR/report.txt" \
  "$M07A2_DIR/facts.json" \
  "$M07A2_DIR/assessment.json" \
  "$M07A2_DIR/exact-source-contract.json" \
  "$M07A2_DIR/exact-source-excerpts.txt" \
  "$M07A2_DIR/step.sh" \
  "$M07A2_DIR/sources/router-egress-emergency-refresh.sh" \
  "$M07A2_DIR/sources/hmn-refresh-pool-safe.sh" \
  "$M07A2_DIR/sources/router-egress-hmn-rebalance-top5-apply.sh" \
  "$M07A2_DIR/sources/router-egress-hmn-plan-top5.sh" \
  "$M07A2_DIR/sources/router-egress-recovery-state.sh" \
  "$M07A2_DIR/sources/router-egress-emergency-decision-hook.sh" \
  "$M07A2_DIR/sources/router-egress-emergency-decision.init" \
  "$M06A_DIR/vm101.sh" \
  "$STATUS_TOOL" \
  "$REPUBLISH_TOOL" \
  "$PLAN_STATE_DIR/milestones.json" \
  "$STATE_ROOT/current-local-architecture-plan-url.txt"
do
  [ -s "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    fatal "REQUIRED_ARTIFACT_MISSING" 2 "$LINENO"
  }
done

python3 - \
  "$M07A2_DIR/assessment.json" \
  "$M07A2_DIR/exact-source-contract.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    assessment = json.load(source)

with open(sys.argv[2], encoding="utf-8") as source:
    contract = json.load(source)

assert assessment["audit_completed"] is True
assert assessment["all_ok"] is True
assert assessment["source_contract_ready"] is True
assert assessment["runtime_ready"] is False
assert assessment["blocking_gaps"] == ["runtime:five_endpoints"]
assert assessment["failed_runtime_checks"] == ["five_endpoints"]

for name, value in assessment["runtime_checks"].items():
    if name != "five_endpoints":
        assert value is True, (name, value)

assert all(assessment["runtime"]["strict"].values())
assert all(assessment["runtime"]["routes"].values())

plan_rows = assessment["runtime"]["planner"]["plan"]

assert len(plan_rows) == 5
assert all(row["current"] for row in plan_rows)
assert all(":" in row["current"] for row in plan_rows)

assert contract["source_contract_ready"] is True
assert contract["blocking_gaps"] == []
assert contract["post_success_suppression"]["mode"] == "COOLDOWN_STATE"
assert all(contract["contract"].values())
PY

CURRENT_PLAN="$(
  tr -d '\r\n' \
    < "$STATE_ROOT/current-local-architecture-plan-url.txt"
)"

[ "$CURRENT_PLAN" = "$PREVIOUS_ARCHITECTURE_PLAN" ] ||
  fatal "CURRENT_PLAN_POINTER_MISMATCH" 3 "$LINENO"

python3 - \
  "$PLAN_STATE_DIR/milestones.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

items = {
    item["id"]: item
    for item in data["milestones"]
}

assert data["scope"] == ["VM101"]
assert data["current_milestone"] == "M07"
assert items["M06"]["status"] == "done"
assert items["M07"]["status"] == "in_progress"
assert items["M08"]["status"] == "pending"
PY

cp -a \
  "$M07A2_DIR/assessment.json" \
  "$REPORT_DIR/sources/step050m07a2-assessment.json"

cp -a \
  "$M07A2_DIR/exact-source-contract.json" \
  "$REPORT_DIR/sources/step050m07a2-exact-source-contract.json"

cp -a \
  "$M07A2_DIR/exact-source-excerpts.txt" \
  "$REPORT_DIR/sources/step050m07a2-exact-source-excerpts.txt"

cp -a \
  "$M07A2_DIR/step.sh" \
  "$REPORT_DIR/sources/step050m07a2-step.sh"

mark_success "m07a2_and_plan_verified"

stage "02/10" "Копирую точные исходники контролируемого toolchain"

for name in \
  router-egress-emergency-refresh.sh \
  hmn-refresh-pool-safe.sh \
  router-egress-hmn-rebalance-top5-apply.sh \
  router-egress-hmn-plan-top5.sh \
  router-egress-recovery-state.sh \
  router-egress-emergency-decision-hook.sh \
  router-egress-emergency-decision.init
do
  cp -a \
    "$M07A2_DIR/sources/$name" \
    "$REPORT_DIR/sources/$name"
done

for script in "$REPORT_DIR"/sources/*.sh "$REPORT_DIR"/sources/*.init; do
  sh -n "$script" || {
    echo "SYNTAX_FAILED=$script"
    fatal "SOURCE_SYNTAX_FAILED" 4 "$LINENO"
  }
done

mark_success "exact_sources_copied"

stage "03/10" "Публикую точный VM101 controlled-refresh script"

cat > "$REPORT_DIR/vm101.sh" <<'VM101'
#!/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"
REFRESH="/root/hmn/hmn-refresh-pool-safe.sh"
APPLY="/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"

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

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

SLOTS_INIT="/etc/init.d/router-egress-slots"
MAPPER_INIT="/etc/init.d/router-egress-mapper"

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

ROLLBACK_READY=false
ROLLBACK_DONE=false

HOOK_WAS_RUNNING=false
WATCHER_WAS_RUNNING=false

trace() {
  printf '__TRACE__ stage=%s\n' "$1"
}

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
}

config_bool() {
  name="$1"

  (
    . "$CONF"
    eval "value=\${$name:-false}"

    case "$value" in
      1|true|TRUE|yes|YES|on|ON)
        printf true
        ;;
      *)
        printf false
        ;;
    esac
  )
}

config_value() {
  name="$1"
  fallback="$2"

  (
    . "$CONF"
    eval "value=\${$name:-\$fallback}"
    printf '%s' "$value"
  )
}

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

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

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

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

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

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

  return 1
}

collect_slots() {
  prefix="$1"
  strict_all=true

  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    raw="$(
      wg show "$interface" endpoints 2>&1 ||
      true
    )"

    block "${prefix}_wg_endpoints_${interface}" "$raw"

    if strict_ping "$interface"; then
      strict=true
    else
      strict=false
      strict_all=false
    fi

    fact "${prefix}.strict.${interface}" "$strict"
  done

  fact "${prefix}.strict_all" "$strict_all"

  routes_all=true

  for table in 201 202 203 204 205; do
    route_text="$(
      ip route show table "$table" 2>&1 ||
      true
    )"

    block "${prefix}_route_${table}" "$route_text"

    if printf '%s\n' "$route_text" |
      grep -q '^default '
    then
      route=true
    else
      route=false
      routes_all=false
    fi

    fact "${prefix}.route.${table}" "$route"
  done

  fact "${prefix}.routes_all" "$routes_all"
}

restore_services_only() {
  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
}

auto_rollback_on_exit() {
  rc="$?"

  trap - EXIT

  if [ "$rc" -ne 0 ] &&
     [ "$ROLLBACK_READY" = true ] &&
     [ "$ROLLBACK_DONE" != true ] &&
     [ -x "$ROLLBACK" ]
  then
    if sh "$ROLLBACK"; then
      ROLLBACK_DONE=true
      fact auto_rollback true
    else
      fact auto_rollback false
    fi
  elif [ "$rc" -ne 0 ]; then
    restore_services_only
    fact auto_rollback false
  fi

  exit "$rc"
}

trap auto_rollback_on_exit EXIT

trace required_contract

for path in \
  "$CONF" \
  "$RUNNER" \
  "$HOOK" \
  "$PLANNER" \
  "$REFRESH" \
  "$APPLY" \
  "$HELPER" \
  "$HOOK_INIT" \
  "$WATCHER_INIT" \
  "$POOL"
do
  [ -e "$path" ] || {
    echo "__ERROR__ missing=$path"
    exit 21
  }
done

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

COMMIT_ENABLED="$(config_bool EMERGENCY_COMMIT_ENABLED)"
DIRECT_ENABLED="$(config_bool EMERGENCY_DIRECT_FAILOPEN_ENABLED)"
CONFIRM_TOKEN="$(config_value EMERGENCY_CONFIRM_TOKEN '')"
EMERGENCY_LOG="$(
  config_value \
    EMERGENCY_LOG \
    /var/log/router-egress-emergency-refresh.log
)"

fact commit_enabled "$COMMIT_ENABLED"
fact direct_failopen_enabled "$DIRECT_ENABLED"

[ "$COMMIT_ENABLED" = true ] || {
  echo "__ERROR__ emergency_commit_not_enabled"
  exit 23
}

[ "$DIRECT_ENABLED" = false ] || {
  echo "__ERROR__ direct_failopen_must_remain_disabled"
  exit 24
}

[ -n "$CONFIRM_TOKEN" ] || {
  echo "__ERROR__ emergency_confirm_token_empty"
  exit 25
}

trace preflight_state

COUNTER_PRE="$(repair_counter)"
RUNNER_PRE="$("$RUNNER" --dry-run)"
HOOK_PRE="$("$HOOK")"
PLANNER_PRE="$("$PLANNER")"

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

POOL_MTIME_PRE="$(
  date -r "$POOL" +%s
)"

POOL_ROWS_PRE="$(
  wc -l < "$POOL" |
    tr -d ' '
)"

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

STATE_MODE_PRE="$(state_value mode NORMAL)"
STATE_STATUS_PRE="$(
  state_value last_emergency_refresh_status NONE
)"
STATE_EPOCH_PRE="$(
  state_value last_emergency_refresh_epoch 0
)"

fact repair_counter_pre "$COUNTER_PRE"
fact pool_hash_pre "$POOL_HASH_PRE"
fact pool_mtime_pre "$POOL_MTIME_PRE"
fact pool_rows_pre "$POOL_ROWS_PRE"
fact network_hash_pre "$NETWORK_HASH_PRE"
fact state_mode_pre "$STATE_MODE_PRE"
fact state_status_pre "$STATE_STATUS_PRE"
fact state_epoch_pre "$STATE_EPOCH_PRE"

json_block runner_pre "$RUNNER_PRE"
json_block hook_pre "$HOOK_PRE"
json_block planner_pre "$PLANNER_PRE"

collect_slots pre

trace create_outer_backup

mkdir -p "$BACKUP_ROOT"

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

if [ -f "$STATE_DIR/state.kv" ]; then
  cp -a "$STATE_DIR/state.kv" "$BACKUP_ROOT/state.kv.before"
  echo true > "$BACKUP_ROOT/state.kv.existed"
else
  echo false > "$BACKUP_ROOT/state.kv.existed"
fi

if [ -f "$STATE_DIR/emergency-decision-last.json" ]; then
  cp -a \
    "$STATE_DIR/emergency-decision-last.json" \
    "$BACKUP_ROOT/emergency-decision-last.json.before"

  echo true > "$BACKUP_ROOT/emergency-decision-last.existed"
else
  echo false > "$BACKUP_ROOT/emergency-decision-last.existed"
fi

find "$STATE_DIR" \
  -maxdepth 3 \
  -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

STATE_DIR='$STATE_DIR'
CACHE_DIR='$CACHE_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'

rc=0

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

find "\$STATE_DIR" \
  -maxdepth 3 \
  -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" || rc=1
  fi
done < "\$BACKUP_ROOT/slot-rollbacks.after"

if [ -d "\$BACKUP_ROOT/cache.before" ]; then
  rm -rf "\$CACHE_DIR"
  cp -a "\$BACKUP_ROOT/cache.before" "\$CACHE_DIR" || rc=1
fi

if [ "\$(cat "\$BACKUP_ROOT/state.kv.existed")" = true ]; then
  cp -a \
    "\$BACKUP_ROOT/state.kv.before" \
    "\$STATE_DIR/state.kv" ||
    rc=1
else
  rm -f "\$STATE_DIR/state.kv"
fi

if [ "\$(cat "\$BACKUP_ROOT/emergency-decision-last.existed")" = true ]; then
  cp -a \
    "\$BACKUP_ROOT/emergency-decision-last.json.before" \
    "\$STATE_DIR/emergency-decision-last.json" ||
    rc=1
else
  rm -f "\$STATE_DIR/emergency-decision-last.json"
fi

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

if [ "\$rc" -ne 0 ] &&
   [ -f "\$BACKUP_ROOT/network.before" ]
then
  cp -a \
    "\$BACKUP_ROOT/network.before" \
    /etc/config/network ||
    rc=1

  /etc/init.d/network reload >/dev/null 2>&1 || rc=1
  sleep 8

  [ ! -x "\$SLOTS_INIT" ] ||
    "\$SLOTS_INIT" restart >/dev/null 2>&1 ||
    rc=1

  [ ! -x "\$MAPPER_INIT" ] ||
    "\$MAPPER_INIT" restart >/dev/null 2>&1 ||
    rc=1
fi

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

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

echo "STEP_050M07B rollback rc=\$rc"
exit "\$rc"
EOF

chmod 700 "$ROLLBACK"
ROLLBACK_READY=true

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

trace isolate_services

"$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
)"

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

fact emergency_log "$EMERGENCY_LOG"
fact emergency_log_size_pre "$LOG_SIZE_PRE"

trace execute_real_runner

RUNNER_STDERR="/tmp/step050m07b-runner-${RUN_ID}.stderr"

set +e

RUNNER_COMMIT="$(
  "$RUNNER" \
    --commit \
    --confirm "$CONFIRM_TOKEN" \
    2> "$RUNNER_STDERR"
)"

RUNNER_RC="$?"

set -e

fact runner_commit_rc "$RUNNER_RC"
json_block runner_commit "$RUNNER_COMMIT"

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

block runner_commit_stderr "$RUNNER_STDERR_TEXT"
rm -f "$RUNNER_STDERR"

if printf '%s\n' "$RUNNER_COMMIT" |
  grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_ok_rebalance_ok"'
then
  fact refresh_ran true
  fact rebalance_apply_ran true
else
  if printf '%s\n' "$RUNNER_COMMIT" |
    grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_'
  then
    fact refresh_ran true
  else
    fact refresh_ran false
  fi

  if printf '%s\n' "$RUNNER_COMMIT" |
    grep -q 'rebalance'
  then
    fact rebalance_apply_ran true
  else
    fact rebalance_apply_ran false
  fi
fi

trace 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

sleep 2

fact hook_restored "$(bool_cmd "$HOOK_INIT" running)"
fact watcher_restored "$(bool_cmd "$WATCHER_INIT" running)"
fact hook_enabled "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_enabled "$(bool_cmd "$WATCHER_INIT" enabled)"

trace post_state

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

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

POOL_MTIME_POST="$(
  date -r "$POOL" +%s
)"

POOL_ROWS_POST="$(
  wc -l < "$POOL" |
    tr -d ' '
)"

NETWORK_HASH_POST="$(
  uci export network |
    sha256sum |
    sed 's/[[:space:]].*$//'
)"

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
)"

fact repair_counter_post "$COUNTER_POST"
fact pool_hash_post "$POOL_HASH_POST"
fact pool_mtime_post "$POOL_MTIME_POST"
fact pool_rows_post "$POOL_ROWS_POST"
fact network_hash_post "$NETWORK_HASH_POST"
fact state_mode_post "$STATE_MODE_POST"
fact state_status_post "$STATE_STATUS_POST"
fact state_epoch_post "$STATE_EPOCH_POST"

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

collect_slots post

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

fact emergency_log_size_post "$LOG_SIZE_POST"

LOG_TAIL="$(
  tail -n 240 "$EMERGENCY_LOG" 2>/dev/null ||
  true
)"

block emergency_log_tail "$LOG_TAIL"

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

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

fact direct_failopen_changed false
fact mutation_started true

if [ "$RUNNER_RC" -ne 0 ]; then
  echo "__ERROR__ runner_commit_rc=$RUNNER_RC"
  exit 31
fi

if ! printf '%s\n' "$RUNNER_COMMIT" |
  grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_ok_rebalance_ok"'
then
  echo "__ERROR__ runner_decision_not_success"
  exit 32
fi

trace complete

trap - EXIT
exit 0
VM101

chmod 600 "$REPORT_DIR/vm101.sh"
sh -n "$REPORT_DIR/vm101.sh"

cp -a \
  "$M06A_DIR/vm101.sh" \
  "$REPORT_DIR/vm101-tool-hash-check.sh"

chmod 600 "$REPORT_DIR/vm101-tool-hash-check.sh"
sh -n "$REPORT_DIR/vm101-tool-hash-check.sh"

mark_success "remote_scripts_published_and_syntax_checked"

stage "04/10" "Повторно проверяю frozen toolchain перед real refresh"

set +e

ssh pve-mgts \
  "ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
    root@10.71.100.2 \
    'sh -s'" \
  < "$REPORT_DIR/vm101-tool-hash-check.sh" \
  > "$REPORT_DIR/vm101-tool-hash-check.txt" \
  2> "$REPORT_DIR/vm101-tool-hash-check.stderr"

HASH_CHECK_RC=$?

set -e

echo "hash_check_rc=$HASH_CHECK_RC" |
  tee -a "$PROGRESS_LOG"

[ "$HASH_CHECK_RC" -eq 0 ] ||
  fatal "FROZEN_TOOLCHAIN_REMOTE_CHECK_FAILED" "$HASH_CHECK_RC" "$LINENO"

grep -Fq \
  "__SUMMARY__ checked=10 errors=0" \
  "$REPORT_DIR/vm101-tool-hash-check.txt" ||
  fatal "LIVE_TOOLCHAIN_DRIFT_DETECTED" 5 "$LINENO"

mark_success "frozen_toolchain_verified"

stage "05/10" "Запускаю контролируемый real full HMN refresh на VM101"

set +e

ssh pve-mgts \
  "ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
    root@10.71.100.2 \
    'sh -s'" \
  < "$REPORT_DIR/vm101.sh" \
  > "$REPORT_DIR/vm101.txt" \
  2> "$REPORT_DIR/vm101.stderr"

VM101_RC=$?

set -e

echo "vm101_rc=$VM101_RC" |
  tee -a "$PROGRESS_LOG"

VM101_ROLLBACK="$(
  grep '^__FACT__ rollback=' \
    "$REPORT_DIR/vm101.txt" |
    tail -n1 |
    cut -d= -f2- ||
    true
)"

if grep -Fq \
  '__FACT__ mutation_started=true' \
  "$REPORT_DIR/vm101.txt"
then
  VM101_CHANGED=true
  PRODUCTION_MODIFIED=true
fi

if grep -Fq \
  '__FACT__ auto_rollback=true' \
  "$REPORT_DIR/vm101.txt"
then
  VM101_CHANGED=false
  PRODUCTION_MODIFIED=false
  ROLLBACK_RC="REMOTE_AUTO_ROLLBACK_OK"
fi

[ "$VM101_RC" -eq 0 ] ||
  fatal "VM101_CONTROLLED_REFRESH_FAILED" "$VM101_RC" "$LINENO"

[ -n "$VM101_ROLLBACK" ] ||
  fatal "ROLLBACK_PATH_MISSING" 6 "$LINENO"

mark_success "real_runner_completed"

stage "06/10" "Оцениваю refresh, rebalance, cooldown и convergence"

python3 - \
  "$REPORT_DIR/vm101.txt" \
  "$REPORT_DIR/vm101.stderr" \
  "$M07A2_DIR/assessment.json" \
  "$REPORT_DIR/assessment.json" <<'PY'
import json
import re
import sys
from pathlib import Path

(
    runtime_path,
    stderr_path,
    previous_path,
    output_path,
) = sys.argv[1:]

previous = json.loads(
    Path(previous_path).read_text(
        encoding="utf-8",
    )
)

runtime_text = Path(runtime_path).read_text(
    encoding="utf-8",
    errors="replace",
)

stderr_text = Path(stderr_path).read_text(
    encoding="utf-8",
    errors="replace",
).strip()

facts = {}
json_blocks = {}
text_blocks = {}
traces = []
errors = []

current_json = None
current_text = None
lines = []

for line in runtime_text.splitlines():
    if line.startswith("__TRACE__ "):
        traces.append(line)

    elif line.startswith("__ERROR__ "):
        errors.append(line)

    elif line.startswith("__FACT__ "):
        payload = line[len("__FACT__ "):]

        if "=" in payload:
            key, value = payload.split("=", 1)
            facts[key] = value

    elif line.startswith("__JSON_BEGIN__ "):
        current_json = line[len("__JSON_BEGIN__ "):]
        current_text = None
        lines = []

    elif line.startswith("__JSON_END__ "):
        name = line[len("__JSON_END__ "):]

        if name == current_json:
            json_blocks[name] = json.loads(
                "\n".join(lines)
            )

        current_json = None
        lines = []

    elif line.startswith("__BLOCK_BEGIN__ "):
        current_text = line[len("__BLOCK_BEGIN__ "):]
        current_json = None
        lines = []

    elif line.startswith("__BLOCK_END__ "):
        name = line[len("__BLOCK_END__ "):]

        if name == current_text:
            text_blocks[name] = "\n".join(lines)

        current_text = None
        lines = []

    elif current_json is not None or current_text is not None:
        lines.append(line)

allowed_stderr = [
    re.compile(
        r"^Warning: Permanently added .+ "
        r"to the list of known hosts\.$"
    ),
    re.compile(
        r"^Pseudo-terminal will not be allocated "
        r"because stdin is not a terminal\.$"
    ),
]

stderr_lines = [
    line.strip()
    for line in stderr_text.splitlines()
    if line.strip()
]

unexpected_stderr = [
    line
    for line in stderr_lines
    if not any(pattern.match(line) for pattern in allowed_stderr)
]

runner_pre = json_blocks.get("runner_pre", {})
runner_commit = json_blocks.get("runner_commit", {})
runner_post = json_blocks.get("runner_post", {})

hook_pre = json_blocks.get("hook_pre", {})
hook_post = json_blocks.get("hook_post", {})

planner_pre = json_blocks.get("planner_pre", {})
planner_post = json_blocks.get("planner_post", {})

counter_pre = int(facts.get("repair_counter_pre", "-1"))
counter_post = int(facts.get("repair_counter_post", "-1"))

state_epoch_pre = int(facts.get("state_epoch_pre", "0"))
state_epoch_post = int(facts.get("state_epoch_post", "0"))

pool_rows_pre = int(facts.get("pool_rows_pre", "0"))
pool_rows_post = int(facts.get("pool_rows_post", "0"))

log_size_pre = int(facts.get("emergency_log_size_pre", "0"))
log_size_post = int(facts.get("emergency_log_size_post", "0"))

pre_plan = planner_pre.get("plan", [])
post_plan = planner_post.get("plan", [])

pre_currents = {
    row.get("iface"): row.get("current")
    for row in pre_plan
}

post_currents = {
    row.get("iface"): row.get("current")
    for row in post_plan
}

changed_interfaces = [
    interface
    for interface in sorted(pre_currents)
    if (
        interface in post_currents
        and pre_currents[interface] != post_currents[interface]
    )
]

pre_strict = {
    interface:
        facts.get(f"pre.strict.{interface}") == "true"
    for interface in ("vpn1", "vpn2", "vpn3", "vpn4", "vpn5")
}

post_strict = {
    interface:
        facts.get(f"post.strict.{interface}") == "true"
    for interface in ("vpn1", "vpn2", "vpn3", "vpn4", "vpn5")
}

pre_routes = {
    table:
        facts.get(f"pre.route.{table}") == "true"
    for table in ("201", "202", "203", "204", "205")
}

post_routes = {
    table:
        facts.get(f"post.route.{table}") == "true"
    for table in ("201", "202", "203", "204", "205")
}

post_endpoints_proven = (
    len(post_plan) == 5
    and all(
        row.get("current")
        and ":" in row["current"]
        for row in post_plan
    )
)

pre_endpoints_proven = (
    len(pre_plan) == 5
    and all(
        row.get("current")
        and ":" in row["current"]
        for row in pre_plan
    )
)

checks = {
    "remote_complete":
        "__TRACE__ stage=complete" in traces
        and not errors,

    "stderr_benign":
        not unexpected_stderr,

    "previous_source_contract_ready":
        previous["source_contract_ready"] is True,

    "previous_only_endpoint_parser_gap":
        previous["blocking_gaps"] == ["runtime:five_endpoints"],

    "planner_pre_proves_five_endpoints":
        pre_endpoints_proven,

    "commit_and_direct_contract":
        facts.get("commit_enabled") == "true"
        and facts.get("direct_failopen_enabled") == "false",

    "services_isolated":
        facts.get("watcher_stopped") == "true"
        and facts.get("hook_stopped") == "true",

    "services_restored":
        facts.get("watcher_restored") == "true"
        and facts.get("hook_restored") == "true"
        and facts.get("watcher_enabled") == "true"
        and facts.get("hook_enabled") == "true",

    "runner_pre_ready":
        runner_pre.get("mode") == "dry-run"
        and runner_pre.get("decision")
            == "would_run_emergency_refresh"
        and runner_pre.get("threshold_reached") is True
        and runner_pre.get("commit_enabled") is True
        and runner_pre.get("direct_failopen_enabled") is False,

    "hook_pre_safe":
        hook_pre.get("mode") == "dry-run"
        and hook_pre.get("decision")
            == "would_run_emergency_refresh"
        and hook_pre.get("commit_enabled") is True
        and hook_pre.get("direct_failopen_enabled") is False,

    "real_runner_success":
        facts.get("runner_commit_rc") == "0"
        and runner_commit.get("mode") == "commit"
        and runner_commit.get("decision")
            == "refresh_ok_rebalance_ok",

    "refresh_and_rebalance_ran":
        facts.get("refresh_ran") == "true"
        and facts.get("rebalance_apply_ran") == "true",

    "state_normal_success":
        facts.get("state_mode_post") == "NORMAL"
        and facts.get("state_status_post")
            == "refresh_ok_rebalance_ok"
        and state_epoch_post > state_epoch_pre,

    "cooldown_suppresses_repeat":
        runner_post.get("mode") == "dry-run"
        and runner_post.get("decision") == "cooldown_active"
        and runner_post.get("cooldown_remaining", 0) > 0
        and runner_post.get("last_emergency_refresh_status")
            == "refresh_ok_rebalance_ok"
        and runner_post.get("commit_enabled") is True
        and runner_post.get("direct_failopen_enabled") is False,

    "automatic_hook_safe_after":
        hook_post.get("mode") == "dry-run"
        and hook_post.get("decision") == "cooldown_active"
        and hook_post.get("direct_failopen_enabled") is False,

    "counter_preserved":
        counter_pre >= 0
        and counter_pre == counter_post,

    "pool_valid":
        pool_rows_pre >= 5
        and pool_rows_post >= 5,

    "planner_converged":
        planner_post.get("decision") == "plan_ok"
        and planner_post.get("changes_count") == 0
        and planner_post.get("quarantine_enabled") is True,

    "planner_post_proves_five_endpoints":
        post_endpoints_proven,

    "strict_before":
        all(pre_strict.values())
        and facts.get("pre.strict_all") == "true",

    "strict_after":
        all(post_strict.values())
        and facts.get("post.strict_all") == "true",

    "routes_before":
        all(pre_routes.values())
        and facts.get("pre.routes_all") == "true",

    "routes_after":
        all(post_routes.values())
        and facts.get("post.routes_all") == "true",

    "log_records_real_operation":
        log_size_post > log_size_pre
        and "action=emergency_refresh_start"
            in text_blocks.get("emergency_log_tail", ""),

    "locks_released":
        facts.get("emergency_lock_present") == "false"
        and facts.get("hmn_refresh_lock_present") == "false",

    "rollback_available":
        facts.get("rollback_exists") == "true"
        and bool(facts.get("rollback")),

    "direct_unchanged":
        facts.get("direct_failopen_changed") == "false",
}

all_ok = all(checks.values())

assessment = {
    "all_ok": all_ok,
    "decision": (
        "PASS_STEP_050M07B_CONTROLLED_REAL_FULL_HMN_REFRESH"
        if all_ok
        else
        "STOP_STEP_050M07B_CONTROLLED_REAL_FULL_HMN_REFRESH"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, value in checks.items()
        if not value
    ],
    "classification": {
        "m07a2_endpoint_gap":
            "PARSER_FALSE_NEGATIVE_PROVEN_BY_PLANNER_AND_STRICT_TRAFFIC",
    },
    "operation": {
        "runner_decision": runner_commit.get("decision"),
        "refresh_ran": facts.get("refresh_ran") == "true",
        "rebalance_apply_ran":
            facts.get("rebalance_apply_ran") == "true",
        "repair_counter_before": counter_pre,
        "repair_counter_after": counter_post,
        "pool_hash_before": facts.get("pool_hash_pre"),
        "pool_hash_after": facts.get("pool_hash_post"),
        "pool_mtime_before": facts.get("pool_mtime_pre"),
        "pool_mtime_after": facts.get("pool_mtime_post"),
        "network_hash_before": facts.get("network_hash_pre"),
        "network_hash_after": facts.get("network_hash_post"),
        "state_epoch_before": state_epoch_pre,
        "state_epoch_after": state_epoch_post,
        "state_mode_after": facts.get("state_mode_post"),
        "state_status_after": facts.get("state_status_post"),
        "cooldown_remaining":
            runner_post.get("cooldown_remaining"),
        "changed_interfaces": changed_interfaces,
        "endpoints_before": pre_currents,
        "endpoints_after": post_currents,
        "strict_after": post_strict,
        "routes_after": post_routes,
        "rollback": facts.get("rollback"),
        "backup_root": facts.get("backup_root"),
    },
    "safety": {
        "services_temporarily_isolated": True,
        "services_restored": checks["services_restored"],
        "direct_failopen_changed": False,
        "rollback_available": checks["rollback_available"],
    },
    "plan_transition": {
        "completed": "M07",
        "next": "M08",
    },
    "stderr": {
        "lines": stderr_lines,
        "unexpected_lines": unexpected_stderr,
    },
}

Path(output_path).write_text(
    json.dumps(
        assessment,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)
PY

ALL_OK="$(
  python3 - "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

print("true" if data["all_ok"] else "false")
PY
)"

FAILED_CHECKS="$(
  python3 - "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

failed = data.get("failed_checks", [])
print(",".join(failed) if failed else "NONE")
PY
)"

[ "$ALL_OK" = true ] ||
  fatal "POSTVALIDATION_FAILED_${FAILED_CHECKS}" 7 "$LINENO"

mark_success "real_refresh_postvalidation_passed"

stage "07/10" "Проверяю persisted state отдельным read-only вызовом"

cat > "$REPORT_DIR/vm101-postcheck.sh" <<'VM101POST'
#!/bin/sh
set -eu
umask 077

CONF="/etc/router-egress-emergency-refresh.conf"
RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
PLANNER="/usr/local/sbin/router-egress-hmn-plan-top5.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"

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

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

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"
  )
}

RUNNER_JSON="$("$RUNNER" --dry-run)"
PLANNER_JSON="$("$PLANNER")"

fact hook_running "$(bool_cmd "$HOOK_INIT" running)"
fact hook_enabled "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_running "$(bool_cmd "$WATCHER_INIT" running)"
fact watcher_enabled "$(bool_cmd "$WATCHER_INIT" enabled)"

fact state_mode "$(state_value mode UNKNOWN)"
fact state_status "$(
  state_value last_emergency_refresh_status UNKNOWN
)"
fact state_epoch "$(
  state_value last_emergency_refresh_epoch 0
)"

json_block runner "$RUNNER_JSON"
json_block planner "$PLANNER_JSON"

fact production_modified false
VM101POST

chmod 600 "$REPORT_DIR/vm101-postcheck.sh"
sh -n "$REPORT_DIR/vm101-postcheck.sh"

set +e

ssh pve-mgts \
  "ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
    root@10.71.100.2 \
    'sh -s'" \
  < "$REPORT_DIR/vm101-postcheck.sh" \
  > "$REPORT_DIR/vm101-postcheck.txt" \
  2> "$REPORT_DIR/vm101-postcheck.stderr"

POSTCHECK_RC=$?

set -e

[ "$POSTCHECK_RC" -eq 0 ] ||
  fatal "PERSISTED_POSTCHECK_REMOTE_FAILED" "$POSTCHECK_RC" "$LINENO"

python3 - \
  "$REPORT_DIR/vm101-postcheck.txt" <<'PY'
import json
import sys
from pathlib import Path

facts = {}
blocks = {}
current = None
lines = []

for line in Path(sys.argv[1]).read_text(
    encoding="utf-8",
    errors="replace",
).splitlines():
    if line.startswith("__FACT__ "):
        payload = line[len("__FACT__ "):]

        if "=" in payload:
            key, value = payload.split("=", 1)
            facts[key] = value

    elif line.startswith("__JSON_BEGIN__ "):
        current = line[len("__JSON_BEGIN__ "):]
        lines = []

    elif line.startswith("__JSON_END__ "):
        name = line[len("__JSON_END__ "):]

        if name == current:
            blocks[name] = json.loads("\n".join(lines))

        current = None
        lines = []

    elif current is not None:
        lines.append(line)

runner = blocks["runner"]
planner = blocks["planner"]

assert facts["hook_running"] == "true"
assert facts["hook_enabled"] == "true"
assert facts["watcher_running"] == "true"
assert facts["watcher_enabled"] == "true"

assert facts["state_mode"] == "NORMAL"
assert facts["state_status"] == "refresh_ok_rebalance_ok"
assert int(facts["state_epoch"]) > 0

assert runner["mode"] == "dry-run"
assert runner["decision"] == "cooldown_active"
assert runner["cooldown_remaining"] > 0
assert runner["last_emergency_refresh_status"] == "refresh_ok_rebalance_ok"
assert runner["direct_failopen_enabled"] is False

assert planner["decision"] == "plan_ok"
assert planner["changes_count"] == 0
assert len(planner["plan"]) == 5

assert facts["production_modified"] == "false"
PY

mark_success "persisted_postcheck_passed"

stage "08/10" "Отмечаю M07 выполненным и переиздаю план"

"$STATUS_TOOL" \
  M07 done \
  --evidence "$TRYCF_REPORT" \
  --note "Контролируемый реальный full HMN refresh выполнен на VM101: refresh и top-5 rebalance завершились успешно, planner converged, пять strict-слотов и маршруты 201–205 подтверждены, state NORMAL, повтор подавлен cooldown, Direct не использовался." \
  > "$REPORT_DIR/plan-status-m07.txt" \
  2> "$REPORT_DIR/plan-status-m07.stderr"

PLAN_UPDATED=true

REPUBLISH_OUTPUT="$(
  "$REPUBLISH_TOOL" \
    "$STEP" \
    "$TRYCF_REPORT"
)"

printf '%s\n' \
  "$REPUBLISH_OUTPUT" \
  > "$REPORT_DIR/plan-republish.txt"

NEW_ARCHITECTURE_PLAN="$(
  printf '%s\n' "$REPUBLISH_OUTPUT" |
    sed -n 's/^ARCHITECTURE_PLAN=//p' |
    tail -n1
)"

[ -n "$NEW_ARCHITECTURE_PLAN" ] ||
  fatal "PLAN_REPUBLISH_URL_MISSING" 8 "$LINENO"

ARCHITECTURE_PLAN="$NEW_ARCHITECTURE_PLAN"

python3 - \
  "$PLAN_STATE_DIR/milestones.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

items = {
    item["id"]: item
    for item in data["milestones"]
}

assert items["M07"]["status"] == "done"
assert items["M08"]["status"] == "in_progress"
assert data["current_milestone"] == "M08"
PY

mark_success "plan_updated_to_m08"

stage "09/10" "Публикую PASS report и facts"

REPAIR_COUNTER="$(
  python3 - "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

print(data["operation"]["repair_counter_after"])
PY
)"

COOLDOWN_REMAINING="$(
  python3 - "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

print(data["operation"]["cooldown_remaining"])
PY
)"

CHANGED_INTERFACES="$(
  python3 - "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as source:
    data = json.load(source)

values = data["operation"]["changed_interfaces"]
print(",".join(values) if values else "NONE")
PY
)"

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${PASS_DECISION}
all_ok=true
mode=M07_CONTROLLED_REAL_FULL_REFRESH

scope:
  blocking_host=VM101
  vm100_checked=false
  vm121_checked=false

classification:
  previous_runtime_gap=runtime:five_endpoints
  classification=PARSER_FALSE_NEGATIVE_PROVEN_BY_PLANNER_AND_STRICT_TRAFFIC

operation:
  runner_decision=refresh_ok_rebalance_ok
  real_refresh_ran=true
  rebalance_apply_ran=true
  planner_converged=true
  changed_interfaces=${CHANGED_INTERFACES}
  repair_counter_after=${REPAIR_COUNTER}
  post_success_suppression=COOLDOWN_STATE
  cooldown_remaining=${COOLDOWN_REMAINING}
  state_mode=NORMAL
  state_status=refresh_ok_rebalance_ok

validation:
  five_endpoints_proven_by_planner=true
  five_strict_slots=true
  routes_201_205=true
  services_restored=true
  locks_released=true
  persisted_postcheck=true

safety:
  production_modified=true
  direct_failopen_changed=false
  rollback_vm101=${VM101_ROLLBACK}
  rollback_available=true

inline_diagnostics:
  enabled=true
  command_rc_captured=true
  stdout_and_stderr_captured=true
  failed_stage_captured=true
  automatic_classification_present=true
  rollback_on_stop=true
  separate_diagnostic_step_required=false

plan:
  milestone_completed=M07
  current_milestone=M08
  plan_republished=true

next_step:
  M08_DEGRADED_POOL_CONTROLLED_SIMULATION

artifacts:
  step_script=step.sh
  vm101_script=vm101.sh
  postcheck_script=vm101-postcheck.sh
  tool_hash_script=vm101-tool-hash-check.sh
  assessment=assessment.json
  vm101_output=vm101.txt
  vm101_stderr=vm101.stderr
  plan_status=plan-status-m07.txt
  plan_republish=plan-republish.txt

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
EOF

python3 - \
  "$REPORT_DIR/assessment.json" \
  "$STEP" \
  "$TS" \
  "$VM101_ROLLBACK" \
  "$PREVIOUS_REPORT" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" \
  "$ARCHITECTURE_PLAN" \
  "$XS_MAP" \
  "$GLOBAL_PROJECT_PLAN" \
  > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    assessment_path,
    step,
    timestamp,
    rollback,
    previous_report,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

with open(assessment_path, encoding="utf-8") as source:
    assessment = json.load(source)

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "generated_at_utc": timestamp,
    "assessment": assessment,
    "scope": {
        "blocking_hosts": ["VM101"],
        "vm100_checked": False,
        "vm121_checked": False,
    },
    "classification": {
        "previous_runtime_gap": "runtime:five_endpoints",
        "result":
            "PARSER_FALSE_NEGATIVE_PROVEN_BY_PLANNER_AND_STRICT_TRAFFIC",
    },
    "operation": {
        "real_refresh_ran": True,
        "rebalance_apply_ran": True,
        "planner_converged": True,
        "state_mode": "NORMAL",
        "state_status": "refresh_ok_rebalance_ok",
        "post_success_suppression": "COOLDOWN_STATE",
    },
    "safety": {
        "production_modified": True,
        "direct_failopen_changed": False,
        "rollback_vm101": rollback,
        "rollback_available": True,
    },
    "inline_diagnostics": {
        "enabled": True,
        "command_rc_captured": True,
        "stdout_and_stderr_captured": True,
        "failed_stage_captured": True,
        "automatic_classification_present": True,
        "rollback_on_stop": True,
        "separate_diagnostic_step_required": False,
    },
    "plan": {
        "milestone_completed": "M07",
        "current_milestone": "M08",
        "plan_republished": True,
    },
    "next_step": "M08_DEGRADED_POOL_CONTROLLED_SIMULATION",
    "source": {
        "step050m07a2": previous_report,
    },
    "artifacts": {
        "step_script": "step.sh",
        "vm101_script": "vm101.sh",
        "postcheck_script": "vm101-postcheck.sh",
        "tool_hash_script": "vm101-tool-hash-check.sh",
        "assessment": "assessment.json",
        "vm101_output": "vm101.txt",
        "vm101_stderr": "vm101.stderr",
        "plan_status": "plan-status-m07.txt",
        "plan_republish": "plan-republish.txt",
    },
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
    },
}, ensure_ascii=False, indent=2))
PY

cat > "$REPORT_DIR/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto;padding:0 20px;line-height:1.5">

<h1>${STEP}</h1>

<h2>Исполнявшиеся скрипты</h2>
<ul>
<li><a href="step.sh">step.sh</a></li>
<li><a href="vm101-tool-hash-check.sh">vm101-tool-hash-check.sh</a></li>
<li><a href="vm101.sh">vm101.sh</a></li>
<li><a href="vm101-postcheck.sh">vm101-postcheck.sh</a></li>
</ul>

<h2>Результаты</h2>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="assessment.json">assessment.json</a></li>
<li><a href="vm101.txt">VM101 controlled refresh</a></li>
<li><a href="vm101.stderr">VM101 stderr</a></li>
<li><a href="vm101-postcheck.txt">Persisted postcheck</a></li>
<li><a href="plan-status-m07.txt">Plan status</a></li>
<li><a href="plan-republish.txt">Plan republish</a></li>
</ul>

<h2>Точные исходники</h2>
<ul>
<li><a href="sources/router-egress-emergency-refresh.sh">emergency runner</a></li>
<li><a href="sources/hmn-refresh-pool-safe.sh">HMN refresh</a></li>
<li><a href="sources/router-egress-hmn-rebalance-top5-apply.sh">top-5 apply</a></li>
<li><a href="sources/router-egress-hmn-plan-top5.sh">planner</a></li>
<li><a href="sources/router-egress-recovery-state.sh">state helper</a></li>
</ul>

<h2>Планы</h2>
<ul>
<li><a href="${ARCHITECTURE_PLAN}">Local architecture plan</a></li>
<li><a href="${XS_MAP}">XS Map</a></li>
<li><a href="${GLOBAL_PROJECT_PLAN}">Global project plan</a></li>
</ul>

</body>
</html>
EOF

find "$REPORT_DIR" \
  -type f \
  ! -name SHA256SUMS \
  -print0 |
  sort -z |
  xargs -0 sha256sum \
  > "$REPORT_DIR/SHA256SUMS"

cat > "$STATE_ROOT/current-project-links.env" <<EOF
UPDATED_AT_UTC=${TS}
SOURCE_STEP=${STEP}
CURRENT_REPORT=${TRYCF_REPORT}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
EOF

stage "10/10" "Завершаю M07"

VM101_CHANGED=false
PLAN_UPDATED=false

mark_success "report_published"
trap - ERR

echo "decision=$PASS_DECISION" |
  tee -a "$PROGRESS_LOG"

echo "milestone_completed=M07" |
  tee -a "$PROGRESS_LOG"

echo "current_milestone=M08" |
  tee -a "$PROGRESS_LOG"

echo "runner_decision=refresh_ok_rebalance_ok" |
  tee -a "$PROGRESS_LOG"

echo "real_refresh_ran=true" |
  tee -a "$PROGRESS_LOG"

echo "rebalance_apply_ran=true" |
  tee -a "$PROGRESS_LOG"

echo "planner_converged=true" |
  tee -a "$PROGRESS_LOG"

echo "changed_interfaces=$CHANGED_INTERFACES" |
  tee -a "$PROGRESS_LOG"

echo "repair_counter=$REPAIR_COUNTER" |
  tee -a "$PROGRESS_LOG"

echo "cooldown_remaining=$COOLDOWN_REMAINING" |
  tee -a "$PROGRESS_LOG"

echo "state_mode=NORMAL" |
  tee -a "$PROGRESS_LOG"

echo "direct_failopen_changed=false" |
  tee -a "$PROGRESS_LOG"

echo "inline_diagnostics=true" |
  tee -a "$PROGRESS_LOG"

print_links
