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

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

STEP="STEP_050LP2_READONLY_INVENTORY_VM101_RECOVERY_TOOLCHAIN"
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-150958_step050lp1_publish_canonical_vm101_recovery_plan/"
ARCHITECTURE_PLAN="${PUBLIC_BASE}/20260711-150958_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"
BIN_DIR="${ROOT}/bin"
PLAN_STATE_DIR="${STATE_ROOT}/local-plans/${PLAN_ID}"
PUBROOT="${ROOT}/public/r/${TOKEN}"

PREVIOUS_REPORT_DIR="${PUBROOT}/20260711-150958_step050lp1_publish_canonical_vm101_recovery_plan"
PLAN_PUBLIC_DIR="${PUBROOT}/20260711-150958_local_architecture_plan_vm101_autonomous_hmn_recovery"

TS="$(date -u +%Y%m%d-%H%M%S)"
REPORT_SLUG="${TS}_step050lp2_readonly_inventory_vm101_recovery_toolchain"
REPORT_DIR="${PUBROOT}/${REPORT_SLUG}"
WORK="/tmp/${STEP}_${TS}"

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" \
  "$REPORT_DIR/sources/plan-toolchain" \
  "$REPORT_DIR/sources/vm101-root" \
  "$WORK" \
  "$STATE_ROOT"

chmod 700 "$WORK"

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

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

stage() {
  echo
  echo ">>> [$1] $2" | tee -a "$PROGRESS_LOG"

  date -u '+%Y-%m-%dT%H:%M:%SZ' |
    sed 's/^/    utc=/' |
    tee -a "$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"

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=STOP_${STEP}_${reason}
all_ok=false
mode=READ_ONLY_INVENTORY
error_rc=${rc}
error_line=${line}
production_modified=false
vm100_modified=false
vm101_modified=false
vm121_modified=false
refresh_ran=false
rebalance_apply_ran=false
direct_failopen_changed=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 - \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$TRYCF_REPORT" \
    "$REPORT_TXT" \
    "$FACTS_JSON" \
    "$ARCHITECTURE_PLAN" \
    "$XS_MAP" \
    "$GLOBAL_PROJECT_PLAN" \
    > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

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

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),
    },
    "mode": "READ_ONLY_INVENTORY",
    "safety": {
        "production_modified": False,
        "vm100_modified": False,
        "vm101_modified": False,
        "vm121_modified": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": 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
}

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

  trap - ERR
  write_stop "UNEXPECTED_ERROR" "$rc" "$line"
  print_links
  exit "$rc"
}

trap 'on_error "$LINENO"' ERR

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

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

safe_name() {
  printf '%s' "$1" |
    sed 's#[^A-Za-z0-9_.-]#_#g'
}

file_fact() {
  path="$1"
  key="$(safe_name "$path")"

  if [ -e "$path" ]; then
    fact "file.${key}.exists" true

    if [ -f "$path" ]; then
      size="$(
        wc -c < "$path" |
          tr -d ' '
      )"

      hash="$(
        sha256sum "$path" 2>/dev/null |
          sed 's/[[:space:]].*$//' ||
        true
      )"

      fact "file.${key}.size" "${size:-UNKNOWN}"
      fact "file.${key}.sha256" "${hash:-UNKNOWN}"
    fi
  else
    fact "file.${key}.exists" false
  fi
}

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

redact_config() {
  file="$1"

  awk '
    /^[[:space:]]*#/ {
      print
      next
    }

    /^[[:space:]]*$/ {
      print
      next
    }

    /=/ {
      line=$0
      key=line
      sub(/=.*/, "", key)
      upper=toupper(key)

      if (
        upper ~ /PRIVATE/ ||
        upper ~ /PRESHARED/ ||
        upper ~ /PASSWORD/ ||
        upper ~ /PASSWD/ ||
        upper ~ /SECRET/ ||
        upper ~ /TOKEN/ ||
        upper ~ /ACCESS.*CODE/ ||
        upper ~ /API.*KEY/
      ) {
        print key "=REDACTED"
      } else {
        print line
      }

      next
    }

    {
      print
    }
  ' "$file"
}

trace known_tool_inventory

KNOWN_PATHS="
/usr/local/sbin/router-egress-health-repair-watch.sh
/usr/local/sbin/router-egress-emergency-decision-hook.sh
/usr/local/sbin/router-egress-emergency-refresh.sh
/usr/local/sbin/router-egress-hmn-plan-top5.sh
/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh
/usr/local/lib/router-egress-recovery-state.sh
/root/hmn/hmn-refresh-pool-safe.sh
/etc/router-egress-emergency-refresh.conf
/etc/init.d/router-egress-health-repair
/etc/init.d/router-egress-emergency-decision
/etc/init.d/router-egress-slots
/etc/init.d/router-egress-mapper
/etc/crontabs/root
/root/hmn/cache/ok-awg1-strict-foreign-latest.tsv
/var/lib/router-egress-recovery
"

for path in $KNOWN_PATHS; do
  file_fact "$path"
done

trace syntax_inventory

SCRIPT_PATHS="
/usr/local/sbin/router-egress-health-repair-watch.sh
/usr/local/sbin/router-egress-emergency-decision-hook.sh
/usr/local/sbin/router-egress-emergency-refresh.sh
/usr/local/sbin/router-egress-hmn-plan-top5.sh
/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh
/usr/local/lib/router-egress-recovery-state.sh
/root/hmn/hmn-refresh-pool-safe.sh
/etc/init.d/router-egress-health-repair
/etc/init.d/router-egress-emergency-decision
/etc/init.d/router-egress-slots
/etc/init.d/router-egress-mapper
"

for path in $SCRIPT_PATHS; do
  key="$(safe_name "$path")"

  if [ -f "$path" ]; then
    if sh -n "$path"; then
      fact "syntax.${key}" true
    else
      fact "syntax.${key}" false
    fi
  else
    fact "syntax.${key}" missing
  fi
done

trace service_inventory

for service in \
  router-egress-health-repair \
  router-egress-emergency-decision \
  router-egress-slots \
  router-egress-mapper
do
  init="/etc/init.d/$service"

  if [ -x "$init" ]; then
    running="$(bool_cmd "$init" running)"
    enabled="$(bool_cmd "$init" enabled)"
  else
    running=false
    enabled=false
  fi

  fact "service.${service}.running" "$running"
  fact "service.${service}.enabled" "$enabled"
done

trace dependency_inventory

for command_name in \
  sh \
  awk \
  sed \
  grep \
  cut \
  sort \
  uniq \
  head \
  tail \
  wc \
  sha256sum \
  date \
  logger \
  flock \
  lock \
  uci \
  ubus \
  wg \
  ip \
  ping \
  nft \
  curl \
  wget
do
  if command -v "$command_name" >/dev/null 2>&1; then
    fact "command.${command_name}.exists" true
    fact "command.${command_name}.path" "$(
      command -v "$command_name"
    )"
  else
    fact "command.${command_name}.exists" false
  fi
done

trace safe_config_inventory

if [ -f /etc/router-egress-emergency-refresh.conf ]; then
  SAFE_CONFIG="$(
    redact_config /etc/router-egress-emergency-refresh.conf
  )"

  block emergency_refresh_config_redacted "$SAFE_CONFIG"

  COMMIT_VALUE="$(
    awk -F= '
      /^[[:space:]]*EMERGENCY_COMMIT_ENABLED[[:space:]]*=/ {
        value=$2
        sub(/[[:space:]#].*$/, "", value)
        gsub(/[[:space:]"\047]/, "", value)
        print value
        exit
      }
    ' /etc/router-egress-emergency-refresh.conf
  )"

  fact emergency_commit_enabled_raw "${COMMIT_VALUE:-UNSET}"
fi

trace cron_inventory

if [ -f /etc/crontabs/root ]; then
  RELEVANT_CRON="$(
    grep -nE \
      'router-egress|hmn|vpn|recovery|repair|refresh|rebalance|failopen|bootstrap' \
      /etc/crontabs/root \
      2>/dev/null ||
      true
  )"

  block relevant_cron "$RELEVANT_CRON"
fi

trace discovered_tool_inventory

DISCOVERED="$(
  {
    find /usr/local/sbin \
      /usr/local/lib \
      /etc/init.d \
      /root/hmn \
      -maxdepth 3 \
      -type f \
      2>/dev/null ||
      true
  } |
    grep -Ei \
      '(router-egress|hmn|health|repair|recovery|refresh|rebalance|failopen|bootstrap|quarantine|vpn)' |
    sort -u
)"

block discovered_tools "$DISCOVERED"

DISCOVERED_COUNT="$(
  printf '%s\n' "$DISCOVERED" |
    sed '/^$/d' |
    wc -l |
    tr -d ' '
)"

fact discovered_tool_count "$DISCOVERED_COUNT"

trace capability_source_scan

CAPABILITY_SCAN="$(
  grep -RniE \
    'EMERGENCY_COMMIT|DIRECT_FAILOPEN|BOOTSTRAP|DEGRADED|CONSOLIDAT|SLOT_EXHAUST|quarantine|daily.*repair|full.*refresh|retry.*interval|threshold|vpn1' \
    /usr/local/sbin/router-egress-* \
    /usr/local/lib/router-egress-* \
    /root/hmn/*.sh \
    /etc/router-egress-emergency-refresh.conf \
    /etc/init.d/router-egress-* \
    2>/dev/null ||
    true
)"

block capability_scan "$CAPABILITY_SCAN"

trace state_inventory

STATE_DIR="/var/lib/router-egress-recovery"

if [ -d "$STATE_DIR" ]; then
  STATE_FILES="$(
    find "$STATE_DIR" \
      -maxdepth 3 \
      -type f \
      2>/dev/null |
      sort
  )"

  block recovery_state_files "$STATE_FILES"

  STATE_FILE_COUNT="$(
    printf '%s\n' "$STATE_FILES" |
      sed '/^$/d' |
      wc -l |
      tr -d ' '
  )"

  fact recovery_state_file_count "$STATE_FILE_COUNT"
fi

HELPER="/usr/local/lib/router-egress-recovery-state.sh"

if [ -f "$HELPER" ]; then
  COUNTER="$(
    (
      unset REG_STATE_DIR
      . "$HELPER" || exit 71
      reg_daily_repair_get
    ) 2>/dev/null ||
    true
  )"

  fact repair_counter "${COUNTER:-UNKNOWN}"
fi

trace pool_inventory

POOL="/root/hmn/cache/ok-awg1-strict-foreign-latest.tsv"

if [ -f "$POOL" ]; then
  POOL_ROWS="$(
    wc -l < "$POOL" |
      tr -d ' '
  )"

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

  POOL_MTIME="$(
    stat -c '%Y' "$POOL" 2>/dev/null ||
      echo UNKNOWN
  )"

  fact pool_rows "$POOL_ROWS"
  fact pool_sha256 "$POOL_HASH"
  fact pool_mtime_epoch "$POOL_MTIME"
fi

trace runtime_inventory

for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  exists=false
  endpoint="MISSING"
  strict=false

  if ip link show dev "$interface" >/dev/null 2>&1; then
    exists=true

    endpoint="$(
      wg show "$interface" endpoints 2>/dev/null |
        awk '
          NF >= 2 {
            print $NF
            exit
          }
        ' ||
      true
    )"

    [ -n "$endpoint" ] ||
      endpoint="UNRESOLVED"

    if ping \
      -I "$interface" \
      -c 1 \
      -W 4 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      strict=true
    fi
  fi

  fact "runtime.${interface}.exists" "$exists"
  fact "runtime.${interface}.endpoint" "$endpoint"
  fact "runtime.${interface}.strict" "$strict"
done

for table in 201 202 203 204 205; do
  route=false

  if ip route show table "$table" 2>/dev/null |
    grep -q '^default '
  then
    route=true
  fi

  fact "runtime.table.${table}.default" "$route"
done

trace safe_dryrun_inventory

RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
PLANNER="/usr/local/sbin/router-egress-hmn-plan-top5.sh"
WATCHER="/usr/local/sbin/router-egress-health-repair-watch.sh"

if [ -x "$RUNNER" ]; then
  if RUNNER_JSON="$("$RUNNER" --dry-run 2>/dev/null)"; then
    fact runner_dryrun_ok true
    block runner_dryrun "$RUNNER_JSON"
  else
    fact runner_dryrun_ok false
  fi
fi

if [ -x "$PLANNER" ]; then
  if PLANNER_JSON="$("$PLANNER" 2>/dev/null)"; then
    fact planner_readonly_ok true
    block planner "$PLANNER_JSON"
  else
    fact planner_readonly_ok false
  fi
fi

if [ -x "$WATCHER" ]; then
  if WATCHER_JSON="$("$WATCHER" --dry-run --once 2>/dev/null)"; then
    fact watcher_dryrun_ok true
    block watcher_dryrun "$WATCHER_JSON"
  else
    fact watcher_dryrun_ok false
  fi
fi

trace firewall_inventory

NFT_RELEVANT="$(
  nft list ruleset 2>/dev/null |
    grep -niE \
      'direct|failopen|vpn1|vpn2|vpn3|vpn4|vpn5|0x20[1-5]|cs[1-5]' ||
    true
)"

block nft_relevant "$NFT_RELEVANT"

trace complete

fact production_modified false
fact refresh_ran false
fact rebalance_apply_ran false
fact direct_failopen_changed false
VM101

chmod 600 "$REPORT_DIR/vm101.sh"

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

FILES="
usr/local/sbin/router-egress-health-repair-watch.sh
usr/local/sbin/router-egress-emergency-decision-hook.sh
usr/local/sbin/router-egress-emergency-refresh.sh
usr/local/sbin/router-egress-hmn-plan-top5.sh
usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh
usr/local/lib/router-egress-recovery-state.sh
root/hmn/hmn-refresh-pool-safe.sh
etc/init.d/router-egress-health-repair
etc/init.d/router-egress-emergency-decision
etc/init.d/router-egress-slots
etc/init.d/router-egress-mapper
"

EXISTING=""

for path in $FILES; do
  if [ -f "/$path" ]; then
    EXISTING="$EXISTING $path"
  fi
done

[ -n "$EXISTING" ]

tar -C / -cf - $EXISTING
VM101ARCHIVE

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

stage "01/08" "Проверяю новую публикацию и current pointers"

for required in \
  "$PREVIOUS_REPORT_DIR/report.txt" \
  "$PREVIOUS_REPORT_DIR/facts.json" \
  "$PREVIOUS_REPORT_DIR/step.sh" \
  "$PLAN_PUBLIC_DIR/index.html" \
  "$PLAN_PUBLIC_DIR/facts.json" \
  "$PLAN_PUBLIC_DIR/source/milestones.json" \
  "$PLAN_PUBLIC_DIR/source/canonical-plan.md" \
  "$PLAN_PUBLIC_DIR/source/render_plan.py" \
  "$PLAN_PUBLIC_DIR/source/set_status.py" \
  "$PLAN_PUBLIC_DIR/source/republish.sh" \
  "$PLAN_PUBLIC_DIR/source/README.md" \
  "$PLAN_STATE_DIR/canonical-plan.md" \
  "$PLAN_STATE_DIR/milestones.json" \
  "$PLAN_STATE_DIR/render_plan.py" \
  "$PLAN_STATE_DIR/set_status.py" \
  "$PLAN_STATE_DIR/republish.sh" \
  "$BIN_DIR/vm101-local-plan-status" \
  "$BIN_DIR/vm101-local-plan-republish" \
  "$STATE_ROOT/current-local-architecture-plan-url.txt" \
  "$STATE_ROOT/current-vm101-local-plan.env" \
  "$STATE_ROOT/current-project-links.env"
do
  [ -s "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    exit 1
  }
done

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

[ "$CURRENT_PLAN_URL" = "$ARCHITECTURE_PLAN" ] || {
  echo "CURRENT_PLAN_POINTER=$CURRENT_PLAN_URL"
  echo "EXPECTED_PLAN_POINTER=$ARCHITECTURE_PLAN"
  exit 1
}

grep -Fq \
  "PLAN_ID=${PLAN_ID}" \
  "$STATE_ROOT/current-vm101-local-plan.env"

grep -Fq \
  "CURRENT_MILESTONE=M06" \
  "$STATE_ROOT/current-vm101-local-plan.env"

grep -Fq \
  "ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}" \
  "$STATE_ROOT/current-vm101-local-plan.env"

grep -Fq \
  "ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}" \
  "$STATE_ROOT/current-project-links.env"

python3 -m py_compile \
  "$PLAN_STATE_DIR/render_plan.py" \
  "$PLAN_STATE_DIR/set_status.py"

bash -n \
  "$PLAN_STATE_DIR/republish.sh" \
  "$BIN_DIR/vm101-local-plan-status" \
  "$BIN_DIR/vm101-local-plan-republish"

"$BIN_DIR/vm101-local-plan-status" --help \
  > "$REPORT_DIR/plan-status-help.txt"

cat > "$REPORT_DIR/current-pointer-audit.txt" <<EOF
current_plan_pointer_ok=true
current_plan_url=${CURRENT_PLAN_URL}
plan_id=${PLAN_ID}
current_milestone=M06
status_tool=${BIN_DIR}/vm101-local-plan-status
republish_tool=${BIN_DIR}/vm101-local-plan-republish
EOF

stage "02/08" "Копирую точные инструменты ведения плана"

cp -a \
  "$PLAN_STATE_DIR/canonical-plan.md" \
  "$REPORT_DIR/sources/plan-toolchain/canonical-plan.md"

cp -a \
  "$PLAN_STATE_DIR/milestones.json" \
  "$REPORT_DIR/sources/plan-toolchain/milestones.json"

cp -a \
  "$PLAN_STATE_DIR/render_plan.py" \
  "$REPORT_DIR/sources/plan-toolchain/render_plan.py"

cp -a \
  "$PLAN_STATE_DIR/set_status.py" \
  "$REPORT_DIR/sources/plan-toolchain/set_status.py"

cp -a \
  "$PLAN_STATE_DIR/republish.sh" \
  "$REPORT_DIR/sources/plan-toolchain/republish.sh"

cp -a \
  "$PLAN_STATE_DIR/README.md" \
  "$REPORT_DIR/sources/plan-toolchain/README.md"

cp -a \
  "$PREVIOUS_REPORT_DIR/step.sh" \
  "$REPORT_DIR/sources/step050lp1-step.sh"

stage "03/08" "Запускаю read-only инвентаризацию VM101"

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

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

stage "04/08" "Получаю существующие исходники VM101 без конфигов и секретных данных"

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-archive.sh" \
  > "$WORK/vm101-toolchain.tar" \
  2> "$REPORT_DIR/vm101-archive.stderr"
then
  VM101_ARCHIVE_RC=0
else
  VM101_ARCHIVE_RC=$?
fi

echo "vm101_archive_rc=$VM101_ARCHIVE_RC" |
  tee -a "$PROGRESS_LOG"

if [ "$VM101_ARCHIVE_RC" -eq 0 ]; then
  mkdir -p "$WORK/extracted"

  tar -C "$WORK/extracted" \
    -xf "$WORK/vm101-toolchain.tar"

  tar -tf "$WORK/vm101-toolchain.tar" \
    > "$REPORT_DIR/vm101-archive-manifest.txt"

  python3 - \
    "$WORK/extracted" \
    "$REPORT_DIR/sources/vm101-root" \
    "$REPORT_DIR/source-publication-audit.json" <<'PY'
import json
import re
import shutil
import sys
from pathlib import Path

source_root = Path(sys.argv[1])
destination_root = Path(sys.argv[2])
audit_path = Path(sys.argv[3])

patterns = [
    re.compile(
        rb"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----",
        re.IGNORECASE,
    ),
    re.compile(
        rb"(?im)^[ \t]*"
        rb"(?:export[ \t]+)?"
        rb"[A-Z0-9_]*(?:PRIVATE|PRESHARED|PASSWORD|PASSWD|SECRET|TOKEN)"
        rb"[A-Z0-9_]*[ \t]*=[ \t]*"
        rb"['\"]?[A-Za-z0-9+/=_-]{12,}"
    ),
    re.compile(
        rb"(?im)^[ \t]*"
        rb"(?:export[ \t]+)?"
        rb"[A-Z0-9_]*(?:ACCESS.*CODE|API.*KEY)"
        rb"[A-Z0-9_]*[ \t]*=[ \t]*"
        rb"['\"]?[A-Za-z0-9+/=_-]{8,}"
    ),
]

published = []
omitted = []

for path in sorted(source_root.rglob("*")):
    if not path.is_file():
        continue

    relative = path.relative_to(source_root)
    data = path.read_bytes()

    suspicious = any(
        pattern.search(data)
        for pattern in patterns
    )

    if suspicious:
        omitted.append({
            "path": str(relative),
            "reason": "possible_embedded_secret",
        })
        continue

    target = destination_root / relative
    target.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(path, target)

    published.append(str(relative))

audit = {
    "published_count": len(published),
    "omitted_count": len(omitted),
    "published": published,
    "omitted": omitted,
}

audit_path.write_text(
    json.dumps(
        audit,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)
PY
else
  cat > "$REPORT_DIR/source-publication-audit.json" <<EOF
{
  "published_count": 0,
  "omitted_count": 0,
  "published": [],
  "omitted": [],
  "archive_error": true
}
EOF
fi

stage "05/08" "Формирую карту существующих инструментов и пробелов"

python3 - \
  "$REPORT_DIR/vm101-inventory.txt" \
  "$REPORT_DIR/vm101-inventory.stderr" \
  "$VM101_RC" \
  "$VM101_ARCHIVE_RC" \
  "$REPORT_DIR/source-publication-audit.json" \
  "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys
from pathlib import Path

(
    inventory_path,
    stderr_path,
    inventory_rc,
    archive_rc,
    publication_audit_path,
    output_path,
) = sys.argv[1:]

inventory_text = Path(inventory_path).read_text(
    encoding="utf-8",
    errors="replace",
)

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

publication_audit = json.loads(
    Path(publication_audit_path).read_text(
        encoding="utf-8",
        errors="replace",
    )
)

facts = {}
traces = []
blocks = {}

current_block = None
current_lines = []

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

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

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

        continue

    if line.startswith("__BLOCK_BEGIN__ "):
        current_block = line[len("__BLOCK_BEGIN__ "):]
        current_lines = []
        continue

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

        if current_block == name:
            blocks[name] = "\n".join(current_lines)

        current_block = None
        current_lines = []
        continue

    if current_block is not None:
        current_lines.append(line)


def file_exists(path):
    key = "file." + "".join(
        char if (
            char.isalnum()
            or char in "_.-"
        ) else "_"
        for char in path
    ) + ".exists"

    return facts.get(key) == "true"


m06_paths = [
    "/usr/local/sbin/router-egress-health-repair-watch.sh",
    "/usr/local/sbin/router-egress-emergency-decision-hook.sh",
    "/usr/local/sbin/router-egress-emergency-refresh.sh",
    "/usr/local/sbin/router-egress-hmn-plan-top5.sh",
    "/usr/local/lib/router-egress-recovery-state.sh",
    "/etc/router-egress-emergency-refresh.conf",
    "/etc/init.d/router-egress-health-repair",
    "/etc/init.d/router-egress-emergency-decision",
]

m07_paths = [
    "/root/hmn/hmn-refresh-pool-safe.sh",
    "/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh",
    "/root/hmn/cache/ok-awg1-strict-foreign-latest.tsv",
]

m06_presence = {
    path: file_exists(path)
    for path in m06_paths
}

m07_presence = {
    path: file_exists(path)
    for path in m07_paths
}

capability_scan = blocks.get("capability_scan", "").lower()
discovered_tools = blocks.get("discovered_tools", "")

future_capabilities = {
    "degraded_pool_explicit":
        "degraded" in capability_scan,

    "consolidation_explicit":
        "consolidat" in capability_scan
        or "slot_exhaust" in capability_scan,

    "direct_failopen_explicit":
        "direct_failopen" in capability_scan
        or "failopen" in capability_scan,

    "bootstrap_explicit":
        "bootstrap" in capability_scan,

    "cached_vpn1_strategy_explicit":
        "bootstrap" in capability_scan
        and "vpn1" in capability_scan,
}

runtime_slots = {
    interface: {
        "exists":
            facts.get(
                f"runtime.{interface}.exists"
            ),
        "endpoint":
            facts.get(
                f"runtime.{interface}.endpoint"
            ),
        "strict":
            facts.get(
                f"runtime.{interface}.strict"
            ),
    }
    for interface in (
        "vpn1",
        "vpn2",
        "vpn3",
        "vpn4",
        "vpn5",
    )
}

runtime_routes = {
    table: facts.get(
        f"runtime.table.{table}.default"
    )
    for table in (
        "201",
        "202",
        "203",
        "204",
        "205",
    )
}

checks = {
    "vm101_inventory_completed":
        int(inventory_rc) == 0
        and "__TRACE__ stage=complete" in traces,

    "vm101_inventory_stderr_empty":
        not stderr_text,

    "m06_toolchain_complete":
        all(m06_presence.values()),

    "m07_base_tools_present":
        all(m07_presence.values()),

    "runner_dryrun_available":
        facts.get("runner_dryrun_ok") == "true",

    "planner_readonly_available":
        facts.get("planner_readonly_ok") == "true",

    "watcher_dryrun_available":
        facts.get("watcher_dryrun_ok") == "true",

    "five_runtime_slots_present":
        all(
            slot["exists"] == "true"
            for slot in runtime_slots.values()
        ),

    "five_runtime_routes_present":
        all(
            value == "true"
            for value in runtime_routes.values()
        ),

    "source_archive_collected":
        int(archive_rc) == 0,

    "safe_sources_published":
        publication_audit.get(
            "published_count",
            0,
        ) >= 1,
}

inventory_ok = all(checks.values())

assessment = {
    "all_ok": inventory_ok,
    "decision": (
        "PASS_STEP_050LP2_READONLY_INVENTORY_VM101_RECOVERY_TOOLCHAIN"
        if inventory_ok
        else
        "STOP_STEP_050LP2_READONLY_INVENTORY_VM101_RECOVERY_TOOLCHAIN"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, result in checks.items()
        if not result
    ],
    "scope": {
        "implementation_host": "VM101",
        "blocking_hosts": ["VM101"],
        "non_blocking_hosts": ["VM100", "VM121"],
    },
    "current_plan_position": {
        "milestone": "M06",
        "title": "Постоянно включить emergency commit на VM101",
    },
    "m06_toolchain": {
        "complete": all(m06_presence.values()),
        "paths": m06_presence,
    },
    "m07_base_toolchain": {
        "complete": all(m07_presence.values()),
        "paths": m07_presence,
    },
    "future_capability_evidence": future_capabilities,
    "future_work_interpretation": {
        "M08_DEGRADED_POOL":
            (
                "existing_explicit_evidence"
                if future_capabilities["degraded_pool_explicit"]
                else "implementation_or_contract_still_required"
            ),
        "M09_CONSOLIDATION":
            (
                "existing_explicit_evidence"
                if future_capabilities["consolidation_explicit"]
                else "implementation_or_contract_still_required"
            ),
        "M10_DIRECT_EMERGENCY":
            (
                "existing_explicit_evidence"
                if future_capabilities["direct_failopen_explicit"]
                else "implementation_or_contract_still_required"
            ),
        "M11_CACHED_VPN1_BOOTSTRAP":
            (
                "existing_explicit_evidence"
                if future_capabilities[
                    "cached_vpn1_strategy_explicit"
                ]
                else "implementation_or_contract_still_required"
            ),
        "M12_BOOTSTRAP_CONTRACT":
            (
                "existing_explicit_evidence"
                if future_capabilities["bootstrap_explicit"]
                else "implementation_or_contract_still_required"
            ),
    },
    "runtime": {
        "repair_counter":
            facts.get("repair_counter"),
        "emergency_commit_enabled_raw":
            facts.get("emergency_commit_enabled_raw"),
        "pool_rows":
            facts.get("pool_rows"),
        "pool_sha256":
            facts.get("pool_sha256"),
        "slots":
            runtime_slots,
        "routes":
            runtime_routes,
    },
    "discovery": {
        "discovered_tool_count":
            facts.get("discovered_tool_count"),
        "discovered_tools":
            [
                line
                for line in discovered_tools.splitlines()
                if line
            ],
        "safe_source_publication":
            publication_audit,
    },
    "safety": {
        "production_modified": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "recommended_next_step": (
        "M06_ENABLE_EMERGENCY_COMMIT_REUSING_EXISTING_VM101_TOOLCHAIN"
        if all(m06_presence.values())
        else "STOP_AND_REPAIR_MISSING_M06_TOOLCHAIN"
    ),
}

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

DECISION="$(
  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["decision"])
PY
)"

M06_COMPLETE="$(
  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["m06_toolchain"]["complete"]
    else "false"
)
PY
)"

M07_COMPLETE="$(
  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["m07_base_toolchain"]["complete"]
    else "false"
)
PY
)"

CURRENT_COMMIT="$(
  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["runtime"].get(
        "emergency_commit_enabled_raw",
        "UNKNOWN",
    )
)
PY
)"

stage "06/08" "Публикую inventory report и facts"

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${DECISION}
all_ok=${ALL_OK}
mode=READ_ONLY_INVENTORY
production_modified=false
vm100_modified=false
vm101_modified=false
vm121_modified=false
refresh_ran=false
rebalance_apply_ran=false
direct_failopen_changed=false

plan_verification:
  current_plan_pointer_ok=true
  architecture_plan=${ARCHITECTURE_PLAN}
  plan_id=${PLAN_ID}
  current_milestone=M06
  publication_tools_verified=true

implementation_inventory:
  m06_toolchain_complete=${M06_COMPLETE}
  m07_base_toolchain_complete=${M07_COMPLETE}
  emergency_commit_enabled_raw=${CURRENT_COMMIT}
  future_capabilities_classified=true

artifacts:
  step_script=step.sh
  vm101_inventory_script=vm101.sh
  vm101_archive_script=vm101-archive.sh
  assessment=assessment.json
  vm101_inventory=vm101-inventory.txt
  archive_manifest=vm101-archive-manifest.txt
  source_publication_audit=source-publication-audit.json
  vm101_sources=sources/vm101-root/
  plan_tools=sources/plan-toolchain/

next_step:
  M06_ENABLE_EMERGENCY_COMMIT_REUSING_EXISTING_VM101_TOOLCHAIN

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,
    "mode": "READ_ONLY_INVENTORY",
    "safety": {
        "production_modified": False,
        "vm100_modified": False,
        "vm101_modified": False,
        "vm121_modified": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "source": {
        "step050lp1": previous_report,
    },
    "artifacts": {
        "step_script": "step.sh",
        "vm101_inventory_script": "vm101.sh",
        "vm101_archive_script": "vm101-archive.sh",
        "assessment": "assessment.json",
        "vm101_inventory": "vm101-inventory.txt",
        "archive_manifest": "vm101-archive-manifest.txt",
        "source_publication_audit":
            "source-publication-audit.json",
        "vm101_sources": "sources/vm101-root/",
        "plan_tools": "sources/plan-toolchain/",
    },
    "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

stage "07/08" "Создаю web index и checksums"

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.sh">vm101.sh</a></li>
<li><a href="vm101-archive.sh">vm101-archive.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="current-pointer-audit.txt">current-pointer-audit.txt</a></li>
<li><a href="vm101-inventory.txt">vm101-inventory.txt</a></li>
<li><a href="vm101-inventory.stderr">vm101-inventory.stderr</a></li>
<li><a href="vm101-archive-manifest.txt">vm101-archive-manifest.txt</a></li>
<li><a href="source-publication-audit.json">source-publication-audit.json</a></li>
<li><a href="sources/vm101-root/">VM101 implementation sources</a></li>
<li><a href="sources/plan-toolchain/">Plan toolchain</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"

stage "08/08" "Завершаю read-only inventory"

trap - ERR

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

echo "m06_toolchain_complete=$M06_COMPLETE" |
  tee -a "$PROGRESS_LOG"

echo "m07_base_toolchain_complete=$M07_COMPLETE" |
  tee -a "$PROGRESS_LOG"

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

print_links

[ "$ALL_OK" = true ]
