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

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

STEP="STEP_050M07C4S_SALVAGE_DISK_AND_RESTORE_BASELINE"
PASS_DECISION="PASS_${STEP}"
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-203955_step050m07c4_temporary_table200_bootstrap_fresh_refresh_live/"
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"
PUBROOT="${ROOT}/public/r/${TOKEN}"
PLAN_STATE_DIR="${STATE_ROOT}/local-plans/${PLAN_ID}"

C4_DIR="${PUBROOT}/20260711-203955_step050m07c4_temporary_table200_bootstrap_fresh_refresh_live"

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

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

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

# Точный верхнеуровневый STEP сохраняется первым.
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"
VM101_RC="NOT_RUN"

stage() {
  CURRENT_STAGE="$1"

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

  date -u '+    utc=%Y-%m-%dT%H:%M:%SZ' |
    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"
}

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

  python3 - \
    "$REPORT_DIR" \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$CURRENT_STAGE" \
    "$LAST_SUCCESS" \
    "$VM101_RC" \
    > "$REPORT_DIR/diagnostic.json" <<'PY'
import json
import sys
from pathlib import Path

(
    report_dir,
    step,
    reason,
    rc,
    line,
    stage,
    last_success,
    vm101_rc,
) = sys.argv[1:]

root = Path(report_dir)


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

    return path.read_text(
        encoding="utf-8",
        errors="replace",
    )[-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 = "\n".join(
    item["tail"] or ""
    for item in streams.values()
)

if "free_space_below_minimum" in combined:
    classification = "VM101_FREE_SPACE_STILL_TOO_LOW"
elif "table200_hash_mismatch" in combined:
    classification = "TEMPORARY_TABLE200_ROUTE_NOT_FULLY_REMOVED"
elif "hook_not_running" in combined:
    classification = "EMERGENCY_HOOK_NOT_RESTORED"
elif "watcher_not_running" in combined:
    classification = "HEALTH_WATCHER_NOT_RESTORED"
elif "strict_not_all" in combined:
    classification = "VPN_BASELINE_NOT_FIVE_OF_FIVE"
elif "routes_not_all" in combined:
    classification = "EGRESS_TABLE_BASELINE_INCOMPLETE"
elif "config_raw_unexpected" in combined:
    classification = "COMMIT_CONFIG_CHANGED_UNEXPECTEDLY"
elif vm101_rc not in {"NOT_RUN", "0"}:
    classification = "VM101_BASELINE_SALVAGE_FAILED"
else:
    classification = "LOCAL_VALIDATION_OR_PUBLICATION_FAILURE"

print(json.dumps({
    "schema": "router-step-inline-diagnostic-v8",
    "step": step,
    "failure": {
        "reason": reason,
        "rc": int(rc),
        "line": int(line),
        "stage": stage,
        "last_success": last_success,
    },
    "command_results": {
        "vm101_rc": vm101_rc,
    },
    "automatic_classification": classification,
    "captured_streams": streams,
    "safety": {
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
        "plan_changed": False,
    },
    "recommended_next_step": (
        "Использовать уже опубликованные disk/du, service, table-200, "
        "strict и route факты. Отдельный диагностический STEP не нужен."
    ),
}, 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_C4_DISK_AND_BASELINE_SALVAGE
error_rc=${rc}
error_line=${line}
failed_stage=${CURRENT_STAGE}
last_success=${LAST_SUCCESS}

command_results:
  vm101_rc=${VM101_RC}

safety:
  refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false
  plan_changed=false

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

plan:
  current_milestone=M07
  milestone_completed=false

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

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": diagnostic["safety"],
    "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">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto">
<h1>${STEP}</h1>
<ul>
<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="diagnostic.json">diagnostic.json</a></li>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</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
  write_stop "$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/06" "Проверяю доказательства C4 и текущий M07"

for required in \
  "$C4_DIR/report.txt" \
  "$C4_DIR/facts.json" \
  "$C4_DIR/diagnostic.json" \
  "$C4_DIR/vm101.txt" \
  "$C4_DIR/vm101.stderr" \
  "$C4_DIR/step.sh" \
  "$PLAN_STATE_DIR/milestones.json"
do
  [ -s "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    fatal "REQUIRED_ARTIFACT_MISSING" 2 "$LINENO"
  }
done

grep -Fq \
  "No space left on device" \
  "$C4_DIR/vm101.stderr" ||
  fatal "C4_ENOSPC_NOT_PROVEN" 3 "$LINENO"

grep -Fq \
  "__FACT__ code_test_rc=0" \
  "$C4_DIR/vm101.txt" ||
  fatal "C4_CODE_TEST_PASS_NOT_PROVEN" 4 "$LINENO"

grep -Fq \
  "__FACT__ temporary_table200_bootstrap_installed=true" \
  "$C4_DIR/vm101.txt" ||
  fatal "C4_TEMPORARY_ROUTE_NOT_PROVEN" 5 "$LINENO"

grep -Fq \
  "__FACT__ rollback_exists=false" \
  "$C4_DIR/vm101.txt" ||
  fatal "C4_MISSING_ROLLBACK_NOT_PROVEN" 6 "$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["current_milestone"] == "M07"
assert items["M06"]["status"] == "done"
assert items["M07"]["status"] == "in_progress"
assert items["M08"]["status"] == "pending"
PY

cp -a \
  "$C4_DIR/diagnostic.json" \
  "$REPORT_DIR/sources/step050m07c4-diagnostic.json"

cp -a \
  "$C4_DIR/vm101.txt" \
  "$REPORT_DIR/sources/step050m07c4-vm101.txt"

cp -a \
  "$C4_DIR/vm101.stderr" \
  "$REPORT_DIR/sources/step050m07c4-vm101.stderr"

mark_success "c4_failure_and_plan_verified"

stage "02/06" "Публикую точный VM101 salvage script"

cat > "$REPORT_DIR/vm101.sh" <<'VM101'
#!/bin/sh
set -u
umask 077

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

FAILED_BACKUP="/root/step050m07c4-backup-20260711-203956-6486"
FAILED_ROLLBACK="/root/rollback-step050m07c4-20260711-203956-6486.sh"

EXPECTED_TABLE200_HASH="a03a7a5738c031c280e4a3147e9f7c5feb4af524f4d7ea427871b3b6cafd4691"
MIN_FREE_KB=131072

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

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

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

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

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

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

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

  return 1
}

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

  return 0
}

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

  return 0
}

free_kb() {
  df -Pk / |
    awk 'NR == 2 {print $4}'
}

echo "__TRACE__ stage=before"

DF_BEFORE="$(
  df -Pk / 2>&1 ||
  true
)"

block df_before "$DF_BEFORE"

DU_BEFORE="$(
  {
    du -sk \
      /root/step050m07* \
      /var/lib/router-egress-recovery/step050m07* \
      2>/dev/null ||
    true
  } |
  sort -n
)"

block candidate_usage_before "$DU_BEFORE"

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

block table200_before "$TABLE200_BEFORE"

fact table200_hash_before "$(table200_hash)"
fact hook_running_before "$(bool_cmd "$HOOK_INIT" running)"
fact hook_enabled_before "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_running_before "$(bool_cmd "$WATCHER_INIT" running)"
fact watcher_enabled_before "$(bool_cmd "$WATCHER_INIT" enabled)"

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

fact commit_raw_before "$CONFIG_RAW_BEFORE"

PROCESSES_BEFORE="$(
  ps w 2>/dev/null |
    grep -E \
      'hmn-refresh-pool-safe|router-egress-emergency-refresh|rebalance-top5' |
    grep -v grep ||
  true
)"

block relevant_processes_before "$PROCESSES_BEFORE"

if [ -n "$PROCESSES_BEFORE" ]; then
  echo "__ERROR__ refresh_or_rebalance_process_present"
  exit 21
fi

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

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

echo "__TRACE__ stage=remove_incomplete_c4_artifacts"

FAILED_BACKUP_SIZE_KB=0

if [ -e "$FAILED_BACKUP" ]; then
  FAILED_BACKUP_SIZE_KB="$(
    du -sk "$FAILED_BACKUP" 2>/dev/null |
      awk '{print $1}' ||
    echo 0
  )"

  rm -rf -- "$FAILED_BACKUP"
fi

rm -f -- "$FAILED_ROLLBACK"

# Удаляются только временные файлы конкретного неуспешного C4.
rm -f \
  /etc/router-egress-emergency-refresh.conf.step050m07c4.*.tmp \
  /tmp/step050m07c4-* \
  2>/dev/null ||
true

fact incomplete_c4_backup_size_kb "$FAILED_BACKUP_SIZE_KB"
fact incomplete_c4_backup_removed "$(
  if [ -e "$FAILED_BACKUP" ]; then
    echo false
  else
    echo true
  fi
)"

echo "__TRACE__ stage=restore_table200"

# До C4 table 200 не содержала default route.
# Удаляем только exact temporary default, установленный C4.
while ip route show table 200 2>/dev/null |
  grep -q '^default dev vpn1\([[:space:]]\|$\)'
do
  ip route del table 200 default dev vpn1 ||
    break
done

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

block table200_after_route_restore "$TABLE200_AFTER_ROUTE"

TABLE200_HASH_AFTER="$(table200_hash)"
fact table200_hash_after "$TABLE200_HASH_AFTER"

if [ "$TABLE200_HASH_AFTER" != "$EXPECTED_TABLE200_HASH" ]; then
  echo "__ERROR__ table200_hash_mismatch"
  echo "__EXPECTED__ table200_hash=$EXPECTED_TABLE200_HASH"
  echo "__OBSERVED__ table200_hash=$TABLE200_HASH_AFTER"
  exit 22
fi

echo "__TRACE__ stage=restore_services"

"$HOOK_INIT" running >/dev/null 2>&1 ||
  "$HOOK_INIT" start >/dev/null 2>&1 ||
{
  echo "__ERROR__ hook_start_failed"
  exit 23
}

"$WATCHER_INIT" running >/dev/null 2>&1 ||
  "$WATCHER_INIT" start >/dev/null 2>&1 ||
{
  echo "__ERROR__ watcher_start_failed"
  exit 24
}

sleep 2

HOOK_RUNNING="$(bool_cmd "$HOOK_INIT" running)"
HOOK_ENABLED="$(bool_cmd "$HOOK_INIT" enabled)"
WATCHER_RUNNING="$(bool_cmd "$WATCHER_INIT" running)"
WATCHER_ENABLED="$(bool_cmd "$WATCHER_INIT" enabled)"

fact hook_running_after "$HOOK_RUNNING"
fact hook_enabled_after "$HOOK_ENABLED"
fact watcher_running_after "$WATCHER_RUNNING"
fact watcher_enabled_after "$WATCHER_ENABLED"

[ "$HOOK_RUNNING" = true ] || {
  echo "__ERROR__ hook_not_running"
  exit 25
}

[ "$WATCHER_RUNNING" = true ] || {
  echo "__ERROR__ watcher_not_running"
  exit 26
}

echo "__TRACE__ stage=verify_baseline"

STRICT_ALL="$(bool_cmd strict_all)"
ROUTES_ALL="$(bool_cmd routes_all)"

fact strict_all "$STRICT_ALL"
fact routes_all "$ROUTES_ALL"

[ "$STRICT_ALL" = true ] || {
  echo "__ERROR__ strict_not_all"
  exit 27
}

[ "$ROUTES_ALL" = true ] || {
  echo "__ERROR__ routes_not_all"
  exit 28
}

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

fact commit_raw_after "$CONFIG_RAW_AFTER"

[ "$CONFIG_RAW_AFTER" = "1" ] || {
  echo "__ERROR__ config_raw_unexpected"
  echo "__EXPECTED__ commit_raw=1"
  echo "__OBSERVED__ commit_raw=$CONFIG_RAW_AFTER"
  exit 29
}

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

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

[ ! -e /var/lock/router-egress-emergency-refresh.lock ] || {
  echo "__ERROR__ emergency_lock_present"
  exit 30
}

[ ! -e /tmp/hmn-refresh-pool-safe.lock ] || {
  echo "__ERROR__ refresh_lock_present"
  exit 31
}

FREE_KB_AFTER="$(free_kb)"
fact free_kb_after "$FREE_KB_AFTER"
fact minimum_free_kb "$MIN_FREE_KB"

case "$FREE_KB_AFTER" in
  ''|*[!0-9]*)
    echo "__ERROR__ free_space_parse_failed"
    exit 32
    ;;
esac

if [ "$FREE_KB_AFTER" -lt "$MIN_FREE_KB" ]; then
  echo "__ERROR__ free_space_below_minimum"
  echo "__EXPECTED__ free_kb_at_least=$MIN_FREE_KB"
  echo "__OBSERVED__ free_kb=$FREE_KB_AFTER"
  exit 33
fi

DF_AFTER="$(
  df -Pk / 2>&1 ||
  true
)"

block df_after "$DF_AFTER"

DU_AFTER="$(
  {
    du -sk \
      /root/step050m07* \
      /var/lib/router-egress-recovery/step050m07* \
      2>/dev/null ||
    true
  } |
  sort -n
)"

block candidate_usage_after "$DU_AFTER"

fact refresh_ran false
fact rebalance_apply_ran false
fact direct_failopen_changed false
fact plan_changed false

echo "__TRACE__ stage=complete"
VM101

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

mark_success "remote_salvage_script_published"

stage "03/06" "Восстанавливаю VM101 baseline и освобождаю диск"

if ssh pve-mgts \
  "ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o ServerAliveInterval=20 \
    -o ServerAliveCountMax=6 \
    -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" \
  > >(tee "$REPORT_DIR/vm101.txt") \
  2> >(tee "$REPORT_DIR/vm101.stderr" >&2)
then
  VM101_RC=0
else
  VM101_RC=$?
fi

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

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

grep -Fq \
  "__TRACE__ stage=complete" \
  "$REPORT_DIR/vm101.txt" ||
  fatal "REMOTE_COMPLETION_MARKER_MISSING" 7 "$LINENO"

mark_success "vm101_baseline_restored"

stage "04/06" "Проверяю итоговые salvage-факты"

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

runtime_path, output_path = sys.argv[1:]

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

facts = {}

for line in text.splitlines():
    if not line.startswith("__FACT__ "):
        continue

    payload = line[len("__FACT__ "):]

    if "=" not in payload:
        continue

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

checks = {
    "incomplete_c4_backup_removed":
        facts.get("incomplete_c4_backup_removed") == "true",

    "table200_restored":
        facts.get("table200_hash_after")
            == "a03a7a5738c031c280e4a3147e9f7c5feb4af524f4d7ea427871b3b6cafd4691",

    "hook_running_enabled":
        facts.get("hook_running_after") == "true"
        and facts.get("hook_enabled_after") == "true",

    "watcher_running_enabled":
        facts.get("watcher_running_after") == "true"
        and facts.get("watcher_enabled_after") == "true",

    "five_strict_slots":
        facts.get("strict_all") == "true",

    "routes_201_205":
        facts.get("routes_all") == "true",

    "commit_config_preserved":
        facts.get("commit_raw_after") == "1",

    "locks_absent":
        facts.get("emergency_lock_after") == "false"
        and facts.get("refresh_lock_after") == "false",

    "minimum_free_space":
        int(facts.get("free_kb_after", "0"))
            >= int(facts.get("minimum_free_kb", "131072")),

    "no_refresh":
        facts.get("refresh_ran") == "false",

    "no_rebalance":
        facts.get("rebalance_apply_ran") == "false",

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

    "plan_unchanged":
        facts.get("plan_changed") == "false",
}

all_ok = all(checks.values())

assessment = {
    "all_ok": all_ok,
    "decision": (
        "PASS_STEP_050M07C4S_SALVAGE_DISK_AND_RESTORE_BASELINE"
        if all_ok
        else
        "STOP_STEP_050M07C4S_SALVAGE_DISK_AND_RESTORE_BASELINE"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, value in checks.items()
        if not value
    ],
    "disk": {
        "incomplete_c4_backup_size_kb": int(
            facts.get(
                "incomplete_c4_backup_size_kb",
                "0",
            )
        ),
        "free_kb_after": int(
            facts.get(
                "free_kb_after",
                "0",
            )
        ),
        "minimum_free_kb": int(
            facts.get(
                "minimum_free_kb",
                "131072",
            )
        ),
    },
    "baseline": {
        "table200_restored": True,
        "hook_running": True,
        "watcher_running": True,
        "strict_slots": 5,
        "routes_201_205": True,
        "commit_raw": facts.get("commit_raw_after"),
        "locks_absent": True,
    },
    "operation": {
        "refresh_ran": False,
        "rebalance_apply_ran": False,
    },
    "plan": {
        "current_milestone": "M07",
        "milestone_changed": False,
    },
}

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

if not all_ok:
    raise SystemExit(
        "failed checks: "
        + ",".join(assessment["failed_checks"])
    )
PY

mark_success "salvage_assessment_passed"

stage "05/06" "Публикую PASS report и facts"

FREE_KB_AFTER="$(
  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["disk"]["free_kb_after"])
PY
)"

REMOVED_KB="$(
  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["disk"]["incomplete_c4_backup_size_kb"])
PY
)"

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

classification:
  c4_failure=NO_SPACE_LEFT_ON_DEVICE
  cause=RECURSIVE_COPY_OF_RECOVERY_STATE_BACKUPS
  c4_rollback_script_created=false
  real_refresh_started=false

cleanup:
  incomplete_c4_backup_removed=true
  removed_size_kb=${REMOVED_KB}
  free_kb_after=${FREE_KB_AFTER}
  minimum_required_kb=131072

baseline:
  temporary_table200_default_removed=true
  original_table200_hash_restored=true
  hook_running_enabled=true
  watcher_running_enabled=true
  five_strict_slots=true
  routes_201_205=true
  commit_raw_preserved=1
  locks_absent=true

safety:
  refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false
  plan_changed=false

inline_diagnostics:
  enabled=true
  stdout_and_stderr_captured=true
  automatic_classification_present=true
  separate_diagnostic_step_required=false

plan:
  current_milestone=M07
  milestone_completed=false
  milestone_changed=false

next_step:
  M07C5_SURGICAL_BACKUP_FRESH_HMN_REFRESH_LIVE_STREAM

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" \
  "$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,
    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": {
        "c4_failure": "NO_SPACE_LEFT_ON_DEVICE",
        "cause": "RECURSIVE_COPY_OF_RECOVERY_STATE_BACKUPS",
        "real_refresh_started": False,
    },
    "operation": {
        "incomplete_c4_backup_removed": True,
        "temporary_table200_default_removed": True,
        "services_restored": True,
        "five_strict_slots": True,
        "routes_201_205": True,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
    },
    "safety": {
        "direct_failopen_changed": False,
        "plan_changed": False,
    },
    "plan": {
        "current_milestone": "M07",
        "milestone_completed": False,
        "milestone_changed": False,
    },
    "next_step":
        "M07C5_SURGICAL_BACKUP_FRESH_HMN_REFRESH_LIVE_STREAM",
    "source": {
        "step050m07c4": 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">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto">
<h1>${STEP}</h1>
<ul>
<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="assessment.json">assessment.json</a></li>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
</ul>
</body>
</html>
EOF

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

mark_success "pass_report_published"

stage "06/06" "Завершаю baseline salvage"

trap - ERR

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

echo "removed_incomplete_backup_kb=$REMOVED_KB" |
  tee -a "$PROGRESS_LOG"

echo "free_kb_after=$FREE_KB_AFTER" |
  tee -a "$PROGRESS_LOG"

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

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

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

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

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

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

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

print_links
