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

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

STEP="STEP_050M07B2_NORMALIZE_COMMIT_AND_RUN_FULL_REFRESH"
PASS_DECISION="PASS_STEP_050M07B2_NORMALIZE_COMMIT_AND_RUN_FULL_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-182853_step050m07b_controlled_real_full_hmn_refresh/"
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}"

M07B_DIR="${PUBROOT}/20260711-182853_step050m07b_controlled_real_full_hmn_refresh"
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}_step050m07b2_normalize_commit_and_run_full_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"
PRECHECK_RC="NOT_RUN"
VM101_RC="NOT_RUN"
POSTCHECK_RC="NOT_RUN"
ROLLBACK_RC="NOT_NEEDED"
PLAN_RESTORE_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

  if remote_vm101 \
    "sh '$VM101_ROLLBACK'" \
    > "$REPORT_DIR/vm101-rollback.txt" \
    2> "$REPORT_DIR/vm101-rollback.stderr"
  then
    ROLLBACK_RC=0
    VM101_CHANGED=false
    PRODUCTION_MODIFIED=false
    return 0
  fi

  ROLLBACK_RC=$?
  PRODUCTION_MODIFIED=unknown
  return "$ROLLBACK_RC"
}

restore_plan_state() {
  if [ "$PLAN_UPDATED" != true ]; then
    PLAN_RESTORE_RC="NOT_NEEDED"
    return 0
  fi

  set +e

  "$STATUS_TOOL" \
    M08 pending \
    --note "Ожидает успешного завершения M07 после rollback." \
    > "$REPORT_DIR/plan-restore-m08.txt" \
    2> "$REPORT_DIR/plan-restore-m08.stderr"
  rc_m08=$?

  "$STATUS_TOOL" \
    M07 in_progress \
    --note "Результат M07 откачен; этап снова выполняется." \
    > "$REPORT_DIR/plan-restore-m07.txt" \
    2> "$REPORT_DIR/plan-restore-m07.stderr"
  rc_m07=$?

  restore_output="$(
    "$REPUBLISH_TOOL" \
      "ROLLBACK_${STEP}" \
      "$TRYCF_REPORT" \
      2> "$REPORT_DIR/plan-restore-republish.stderr"
  )"
  rc_publish=$?

  set -e

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

  [ -z "$restored_plan" ] ||
    ARCHITECTURE_PLAN="$restored_plan"

  if [ "$rc_m08" -eq 0 ] &&
     [ "$rc_m07" -eq 0 ] &&
     [ "$rc_publish" -eq 0 ]
  then
    PLAN_UPDATED=false
    PLAN_RESTORE_RC=0
    return 0
  fi

  PLAN_RESTORE_RC="M08:${rc_m08},M07:${rc_m07},PUBLISH:${rc_publish}"
  return 1
}

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" \
    "$PRECHECK_RC" \
    "$VM101_RC" \
    "$POSTCHECK_RC" \
    "$ROLLBACK_RC" \
    "$PLAN_RESTORE_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,
    precheck_rc,
    vm101_rc,
    postcheck_rc,
    rollback_rc,
    plan_restore_rc,
    production_modified,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

root = Path(report_dir)

def read_tail(path: Path, limit=40000):
    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": read_tail(path),
        }

combined = "\n".join(
    item["tail"] or ""
    for item in streams.values()
)

if "commit_disabled_in_step050f" in combined:
    classification = "COMMIT_BOOLEAN_REPRESENTATION_MISMATCH"
elif "refresh_failed_degraded_no_direct" in combined:
    classification = "HMN_REFRESH_FAILED"
elif "refresh_ok_rebalance_failed" in combined:
    classification = "REFRESH_OK_REBALANCE_FAILED"
elif "lock_busy" in combined:
    classification = "EMERGENCY_REFRESH_LOCK_BUSY"
elif "cooldown_active" in combined and stage.startswith("06/"):
    classification = "POST_SUCCESS_COOLDOWN_ACTIVE"
elif "missing_or_wrong_confirm_token" in combined:
    classification = "CONFIRM_TOKEN_MISMATCH"
elif rollback_rc not in {"NOT_NEEDED", "0"}:
    classification = "ROLLBACK_FAILED"
elif vm101_rc not in {"NOT_RUN", "0"}:
    classification = "VM101_CONTROLLED_REFRESH_FAILED"
elif postcheck_rc not in {"NOT_RUN", "0"}:
    classification = "PERSISTED_POSTCHECK_FAILED"
else:
    classification = "LOCAL_VALIDATION_OR_PLAN_UPDATE_FAILURE"

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

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

print(json.dumps({
    "schema": "router-step-inline-diagnostic-v4",
    "step": step,
    "failure": {
        "reason": reason,
        "rc": int(rc),
        "line": int(line),
        "stage": stage,
        "last_success": last_success,
    },
    "command_results": {
        "hash_check_rc": hash_rc,
        "precheck_rc": precheck_rc,
        "vm101_rc": vm101_rc,
        "postcheck_rc": postcheck_rc,
        "rollback_rc": rollback_rc,
        "plan_restore_rc": plan_restore_rc,
    },
    "automatic_classification": classification,
    "production_modified_after_rollback": modified,
    "captured_streams": streams,
    "artifacts_present": artifacts,
    "recommended_next_step": (
        "Использовать automatic_classification, assessment.json, "
        "vm101-precheck.*, vm101.*, vm101-postcheck.* и 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_BOOLEAN_NORMALIZATION_AND_REAL_REFRESH
error_rc=${rc}
error_line=${line}
failed_stage=${CURRENT_STAGE}
last_success=${LAST_SUCCESS}

command_results:
  hash_check_rc=${HASH_CHECK_RC}
  precheck_rc=${PRECHECK_RC}
  vm101_rc=${VM101_RC}
  postcheck_rc=${POSTCHECK_RC}
  rollback_rc=${ROLLBACK_RC}
  plan_restore_rc=${PLAN_RESTORE_RC}

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

inline_diagnostics:
  enabled=true
  diagnostic=diagnostic.json
  stdout_and_stderr_captured=true
  failed_stage_captured=true
  automatic_classification_present=true
  rollback_on_stop=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-precheck.sh">vm101-precheck.sh</a></li>
<li><a href="vm101-precheck.txt">vm101-precheck.txt</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-postcheck.sh">vm101-postcheck.sh</a></li>
<li><a href="vm101-postcheck.txt">vm101-postcheck.txt</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
  restore_plan_state || 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" "Классифицирую M07B STOP и проверяю текущий M07"

for required in \
  "$M07B_DIR/report.txt" \
  "$M07B_DIR/facts.json" \
  "$M07B_DIR/diagnostic.json" \
  "$M07B_DIR/vm101.txt" \
  "$M07B_DIR/vm101.sh" \
  "$M07B_DIR/step.sh" \
  "$M07A2_DIR/assessment.json" \
  "$M07A2_DIR/exact-source-contract.json" \
  "$M07A2_DIR/exact-source-excerpts.txt" \
  "$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

grep -Fq \
  "decision=STOP_STEP_050M07B_CONTROLLED_REAL_FULL_HMN_REFRESH" \
  "$M07B_DIR/report.txt" ||
  fatal "PREVIOUS_M07B_NOT_STOP" 3 "$LINENO"

grep -Fq \
  "production_modified=false" \
  "$M07B_DIR/report.txt" ||
  fatal "PREVIOUS_ROLLBACK_NOT_PROVEN" 4 "$LINENO"

grep -Fq \
  '"decision": "commit_disabled_in_step050f"' \
  "$M07B_DIR/vm101.txt" ||
  fatal "PREVIOUS_COMMIT_GATE_FAILURE_NOT_PROVEN" 5 "$LINENO"

grep -Fq \
  '__FACT__ auto_rollback=true' \
  "$M07B_DIR/vm101.txt" ||
  fatal "PREVIOUS_REMOTE_ROLLBACK_NOT_PROVEN" 6 "$LINENO"

grep -Fq \
  '__FACT__ refresh_ran=false' \
  "$M07B_DIR/vm101.txt" ||
  fatal "PREVIOUS_REFRESH_SAFETY_NOT_PROVEN" 7 "$LINENO"

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

[ "$CURRENT_PLAN" = "$PREVIOUS_ARCHITECTURE_PLAN" ] ||
  fatal "CURRENT_PLAN_POINTER_MISMATCH" 8 "$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 \
  "$M07B_DIR/diagnostic.json" \
  "$REPORT_DIR/sources/step050m07b-diagnostic.json"

cp -a \
  "$M07B_DIR/vm101.txt" \
  "$REPORT_DIR/sources/step050m07b-vm101.txt"

cp -a \
  "$M07B_DIR/step.sh" \
  "$REPORT_DIR/sources/step050m07b-step.sh"

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

mark_success "previous_stop_and_plan_verified"

stage "02/10" "Публикую live precheck boolean-контракта"

cat > "$REPORT_DIR/vm101-precheck.sh" <<'VM101PRE'
#!/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"
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
}

RAW_LINE="$(
  grep -E \
    '^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=' \
    "$CONF"
)"

RAW_VALUE="$(
  printf '%s\n' "$RAW_LINE" |
    sed 's/^[^=]*=//;s/[[:space:]]//g'
)"

fact commit_config_line "$RAW_LINE"
fact commit_raw_value "$RAW_VALUE"

fact runner_sha256 "$(
  sha256sum "$RUNNER" |
    sed 's/[[:space:]].*$//'
)"

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

GUARD="$(
  sed -n '228,237p' "$RUNNER"
)"

echo "__BLOCK_BEGIN__ runner_commit_guard"
printf '%s\n' "$GUARD"
echo "__BLOCK_END__ runner_commit_guard"

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

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

fact production_modified false
VM101PRE

chmod 600 "$REPORT_DIR/vm101-precheck.sh"
sh -n "$REPORT_DIR/vm101-precheck.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 "precheck_scripts_published"

stage "03/10" "Проверяю frozen toolchain и live boolean mismatch"

if 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"
then
  HASH_CHECK_RC=0
else
  HASH_CHECK_RC=$?
fi

[ "$HASH_CHECK_RC" -eq 0 ] ||
  fatal "FROZEN_TOOLCHAIN_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" 9 "$LINENO"

if 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-precheck.sh" \
  > "$REPORT_DIR/vm101-precheck.txt" \
  2> "$REPORT_DIR/vm101-precheck.stderr"
then
  PRECHECK_RC=0
else
  PRECHECK_RC=$?
fi

[ "$PRECHECK_RC" -eq 0 ] ||
  fatal "VM101_PRECHECK_FAILED" "$PRECHECK_RC" "$LINENO"

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

text = Path(sys.argv[1]).read_text(
    encoding="utf-8",
    errors="replace",
)

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

for line in text.splitlines():
    if line.startswith("__FACT__ "):
        key, value = line[len("__FACT__ "):].split("=", 1)
        facts[key] = value

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

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

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

        current = None

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

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

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

        current = None

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

runner = blocks["json:runner"]
planner = blocks["json:planner"]
guard = blocks["runner_commit_guard"]

assert facts["commit_raw_value"] == "1"
assert '[ "$EMERGENCY_COMMIT_ENABLED" != "true" ]' in guard

assert runner["mode"] == "dry-run"
assert runner["commit_enabled"] is True
assert runner["decision"] == "would_run_emergency_refresh"
assert runner["threshold_reached"] is True

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

assert facts["hook_running"] == "true"
assert facts["hook_enabled"] == "true"
assert facts["watcher_running"] == "true"
assert facts["watcher_enabled"] == "true"
assert facts["production_modified"] == "false"
PY

mark_success "boolean_representation_mismatch_proven"

stage "04/10" "Публикую controlled normalize-and-refresh VM101 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"
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"
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/step050m07b2-backup-${RUN_ID}"
ROLLBACK="/root/rollback-step050m07b2-${RUN_ID}.sh"

ROLLBACK_READY=false
ROLLBACK_DONE=false
MUTATION_STARTED=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_raw() {
  (
    . "$CONF"
    printf '%s' "${EMERGENCY_COMMIT_ENABLED:-UNSET}"
  )
}

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
  routes_all=true

  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    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"

  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() {
  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
    fact auto_rollback false
  fi

  exit "$rc"
}

trap auto_rollback_on_exit EXIT

trace required_contract

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

RAW_PRE="$(config_raw)"
fact commit_raw_pre "$RAW_PRE"

[ "$RAW_PRE" = "1" ] || {
  echo "__ERROR__ expected_commit_raw_1_actual=$RAW_PRE"
  exit 22
}

CONFIRM_TOKEN="$(
  config_value EMERGENCY_CONFIRM_TOKEN ''
)"

EMERGENCY_LOG="$(
  config_value \
    EMERGENCY_LOG \
    /var/log/router-egress-emergency-refresh.log
)"

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

trace preflight

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

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 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 backup

mkdir -p "$BACKUP_ROOT"

cp -a "$CONF" "$BACKUP_ROOT/emergency-refresh.conf.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"

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'

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

cp -a \
  "\$BACKUP_ROOT/emergency-refresh.conf.before" \
  "\$CONF" ||
rc=1

rm -rf "\$CACHE_DIR"
cp -a "\$BACKUP_ROOT/cache.before" "\$CACHE_DIR" ||
rc=1

rm -rf "\$STATE_DIR"
cp -a "\$BACKUP_ROOT/state.before" "\$STATE_DIR" ||
rc=1

cp -a \
  "\$BACKUP_ROOT/network.before" \
  /etc/config/network ||
rc=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 || 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

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_050M07B2 rollback rc=\$rc"
exit "\$rc"
EOF

chmod 700 "$ROLLBACK"
ROLLBACK_READY=true

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

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

trace normalize_boolean

TEMP="${CONF}.step050m07b2.$$.tmp"

awk '
  BEGIN {
    count=0
  }

  /^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=/ {
    print "EMERGENCY_COMMIT_ENABLED=true"
    count++
    next
  }

  {
    print
  }

  END {
    if (count != 1) {
      exit 42
    }
  }
' "$CONF" > "$TEMP"

patch_rc="$?"

[ "$patch_rc" -eq 0 ] || {
  rm -f "$TEMP"
  echo "__ERROR__ config_patch_rc=$patch_rc"
  exit 24
}

chmod 600 "$TEMP"
chown 0:0 "$TEMP"
mv "$TEMP" "$CONF"

MUTATION_STARTED=true
fact mutation_started true

RAW_POST="$(config_raw)"
fact commit_raw_post "$RAW_POST"

[ "$RAW_POST" = "true" ] || {
  echo "__ERROR__ normalized_value_not_true"
  exit 25
}

trace execute_real_runner

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"

RUNNER_ERR="/tmp/step050m07b2-runner-${RUN_ID}.stderr"

set +e

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

RUNNER_RC="$?"

set -e

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

rm -f "$RUNNER_ERR"

fact runner_commit_rc "$RUNNER_RC"
json_block runner_commit "$RUNNER_COMMIT"
block runner_commit_stderr "$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
  fact refresh_ran false
  fact rebalance_apply_ran false

  echo "__ERROR__ runner_decision_not_success"
  exit 31
fi

[ "$RUNNER_RC" -eq 0 ] || {
  echo "__ERROR__ runner_commit_rc=$RUNNER_RC"
  exit 32
}

trace restore_services

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

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

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

fact emergency_log_size_post "$LOG_SIZE_POST"
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

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

printf '%s\n' "$RUNNER_POST" |
  grep -q '"decision"[[:space:]]*:[[:space:]]*"cooldown_active"' || {
    echo "__ERROR__ cooldown_not_active"
    exit 34
  }

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

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

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

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

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

trace complete

trap - EXIT
exit 0
VM101

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

mark_success "controlled_vm101_script_published"

stage "05/10" "Нормализую boolean и запускаю real full refresh"

if 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"
then
  VM101_RC=0
else
  VM101_RC=$?
fi

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" 10 "$LINENO"

mark_success "real_refresh_remote_completed"

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

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

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

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\.$"
    ),
]

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["runner_pre"]
runner_commit = json_blocks["runner_commit"]
runner_post = json_blocks["runner_post"]
hook_post = json_blocks["hook_post"]
planner_pre = json_blocks["planner_pre"]
planner_post = json_blocks["planner_post"]

counter_pre = int(facts["repair_counter_pre"])
counter_post = int(facts["repair_counter_post"])

epoch_pre = int(facts["state_epoch_pre"])
epoch_post = int(facts["state_epoch_post"])

log_pre = int(facts["emergency_log_size_pre"])
log_post = int(facts["emergency_log_size_post"])

pre_plan = {
    row["iface"]: row["current"]
    for row in planner_pre["plan"]
}

post_plan = {
    row["iface"]: row["current"]
    for row in planner_post["plan"]
}

changed_interfaces = [
    interface
    for interface in sorted(pre_plan)
    if pre_plan[interface] != post_plan.get(interface)
]

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

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

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

    "stderr_benign":
        not unexpected_stderr,

    "boolean_normalized":
        facts.get("commit_raw_pre") == "1"
        and facts.get("commit_raw_post") == "true",

    "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["mode"] == "dry-run"
        and runner_pre["decision"] == "would_run_emergency_refresh"
        and runner_pre["commit_enabled"] is True
        and runner_pre["threshold_reached"] is True,

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

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

    "state_success":
        facts.get("state_mode_post") == "NORMAL"
        and facts.get("state_status_post")
            == "refresh_ok_rebalance_ok"
        and epoch_post > epoch_pre,

    "cooldown_active":
        runner_post["mode"] == "dry-run"
        and runner_post["decision"] == "cooldown_active"
        and runner_post["cooldown_remaining"] > 0
        and runner_post["last_emergency_refresh_status"]
            == "refresh_ok_rebalance_ok",

    "hook_safe_after":
        hook_post["mode"] == "dry-run"
        and hook_post["decision"] == "cooldown_active"
        and hook_post["direct_failopen_enabled"] is False,

    "planner_converged":
        planner_post["decision"] == "plan_ok"
        and planner_post["changes_count"] == 0
        and len(planner_post["plan"]) == 5
        and planner_post["quarantine_enabled"] is True,

    "five_endpoints_after":
        all(
            value and ":" in value
            for value in post_plan.values()
        ),

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

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

    "counter_preserved":
        counter_pre == counter_post,

    "pool_present":
        bool(facts.get("pool_hash_post"))
        and int(facts.get("pool_mtime_post", "0")) > 0,

    "log_records_operation":
        log_post > log_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_050M07B2_NORMALIZE_COMMIT_AND_RUN_FULL_REFRESH"
        if all_ok
        else
        "STOP_STEP_050M07B2_NORMALIZE_COMMIT_AND_RUN_FULL_REFRESH"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, value in checks.items()
        if not value
    ],
    "classification": {
        "previous_failure":
            "COMMIT_BOOLEAN_REPRESENTATION_MISMATCH",
        "normalization":
            "EMERGENCY_COMMIT_ENABLED_1_TO_TRUE",
    },
    "operation": {
        "runner_decision": runner_commit["decision"],
        "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": epoch_pre,
        "state_epoch_after": epoch_post,
        "changed_interfaces": changed_interfaces,
        "endpoints_before": pre_plan,
        "endpoints_after": post_plan,
        "cooldown_remaining": runner_post["cooldown_remaining"],
        "rollback": facts.get("rollback"),
        "backup_root": facts.get("backup_root"),
    },
    "safety": {
        "direct_failopen_changed": False,
        "services_restored": checks["services_restored"],
        "rollback_available": checks["rollback_available"],
    },
    "stderr": {
        "lines": stderr_lines,
        "unexpected_lines": unexpected_stderr,
    },
    "plan_transition": {
        "completed": "M07",
        "next": "M08",
    },
}

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)

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

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

mark_success "controlled_refresh_validated"

stage "07/10" "Проверяю persisted NORMAL/cooldown/convergence"

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

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

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

fact commit_raw "$RAW"
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"

if 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"
then
  POSTCHECK_RC=0
else
  POSTCHECK_RC=$?
fi

[ "$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__ "):
        key, value = line[len("__FACT__ "):].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

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

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

assert facts["commit_raw"] == "true"

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["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 "Boolean commit-флага нормализован с 1 до true. Контролируемый real full HMN refresh и top-5 rebalance выполнены успешно; state NORMAL, planner converged, пять strict-слотов и маршруты 201–205 подтверждены, повтор подавлен 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" 12 "$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_BOOLEAN_NORMALIZATION_AND_REAL_REFRESH

classification:
  previous_failure=COMMIT_BOOLEAN_REPRESENTATION_MISMATCH
  commit_raw_before=1
  commit_raw_after=true

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

validation:
  five_endpoints_proven=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

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,
    "classification": {
        "previous_failure":
            "COMMIT_BOOLEAN_REPRESENTATION_MISMATCH",
        "commit_raw_before": "1",
        "commit_raw_after": "true",
    },
    "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": {
        "step050m07b": previous_report,
    },
    "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-precheck.sh">vm101-precheck.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-precheck.txt">Boolean precheck</a></li>
<li><a href="vm101.txt">Controlled refresh</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="${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 "commit_raw_before=1" |
  tee -a "$PROGRESS_LOG"

echo "commit_raw_after=true" |
  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
