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

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

STEP="STEP_050M07D_CLASSIFY_FALSE_SUCCESS_AND_EGRESS1_FAILURE"
PASS_DECISION="PASS_${STEP}"

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

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

C5_DIR="${PUBROOT}/20260711-205604_step050m07c5_tmpfs_surgical_backup_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/"

TS="$(date -u +%Y%m%d-%H%M%S)"
REPORT_SLUG="${TS}_step050m07d_classify_false_success_and_egress1_failure"
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 read_tail(path: Path, limit: int = 80000):
    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": read_tail(path),
        }

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

if "baseline_strict_all=false" in combined:
    classification = "POST_ROLLBACK_VPN_BASELINE_INCOMPLETE"
elif "baseline_routes_all=false" in combined:
    classification = "POST_ROLLBACK_ROUTE_BASELINE_INCOMPLETE"
elif "table200_baseline_match=false" in combined:
    classification = "POST_ROLLBACK_TABLE200_NOT_RESTORED"
elif "source_missing=" in combined:
    classification = "REQUIRED_LIVE_SOURCE_MISSING"
elif vm101_rc not in {"NOT_RUN", "0"}:
    classification = "VM101_READONLY_EXTRACTION_FAILED"
else:
    classification = "LOCAL_PARSER_OR_PUBLICATION_FAILURE"

print(json.dumps({
    "schema": "router-step-inline-diagnostic-v9",
    "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,
    "safety": {
        "read_only": True,
        "refresh_ran": False,
        "rebalance_ran": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "plan_changed": False,
        "direct_failopen_changed": False,
    },
    "captured_streams": streams,
    "recommended_next_step": (
        "Использовать source-contract.json, failure-artifacts.txt "
        "и current-baseline.json для точечного исправления exit-contract "
        "и slot_apply_failed_egress1."
    ),
}, 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_READONLY_FALSE_SUCCESS_CLASSIFICATION
error_rc=${rc}
error_line=${line}
failed_stage=${CURRENT_STAGE}
last_success=${LAST_SUCCESS}

command_results:
  vm101_rc=${VM101_RC}

safety:
  read_only=true
  refresh_ran=false
  rebalance_ran=false
  network_changed=false
  services_changed=false
  state_changed=false
  plan_changed=false
  direct_failopen_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
  milestone_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 - \
    "$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,
        "milestone_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

  create_index

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

create_index() {
  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>

<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="source-contract.json">source-contract.json</a></li>
<li><a href="current-baseline.json">current-baseline.json</a></li>
<li><a href="failure-artifacts.txt">failure-artifacts.txt</a></li>
</ul>

<h2>Исходники VM101</h2>
<ul>
<li><a href="runner-source.txt">runner-source.txt</a></li>
<li><a href="rebalance-apply-source.txt">rebalance-apply-source.txt</a></li>
<li><a href="planner-source.txt">planner-source.txt</a></li>
<li><a href="adapter-sources.txt">adapter-sources.txt</a></li>
</ul>

<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.txt">vm101.txt</a></li>
<li><a href="vm101.stderr">vm101.stderr</a></li>
</ul>
</body>
</html>
EOF
}

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" "Проверяю C5 и доказанный remote rollback"

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

grep -Fq \
  "decision=STOP_STEP_050M07C5_TMPFS_SURGICAL_BACKUP_FRESH_REFRESH_LIVE_VM101_CONTROLLED_REFRESH_FAILED" \
  "$C5_DIR/report.txt" ||
  fatal "C5_STOP_NOT_PROVEN" 3 "$LINENO"

grep -Fq \
  '"automatic_classification": "POST_REFRESH_PLANNER_NOT_CONVERGED"' \
  "$C5_DIR/facts.json" ||
  fatal "C5_CLASSIFICATION_NOT_PROVEN" 4 "$LINENO"

grep -Fq \
  '"decision": "refresh_ok_rebalance_ok"' \
  "$C5_DIR/vm101.txt" ||
  fatal "RUNNER_FALSE_SUCCESS_NOT_PROVEN" 5 "$LINENO"

grep -Fq \
  '"decision": "commit_failed"' \
  "$C5_DIR/vm101.txt" ||
  fatal "UNDERLYING_COMMIT_FAILURE_NOT_PROVEN" 6 "$LINENO"

grep -Fq \
  '"reason": "slot_apply_failed_egress1"' \
  "$C5_DIR/vm101.txt" ||
  fatal "EGRESS1_FAILURE_NOT_PROVEN" 7 "$LINENO"

grep -Fq \
  '__FACT__ fresh_download_proven=true' \
  "$C5_DIR/vm101.txt" ||
  fatal "FRESH_DOWNLOAD_NOT_PROVEN" 8 "$LINENO"

grep -Fq \
  '__FACT__ auto_rollback=true' \
  "$C5_DIR/vm101.txt" ||
  fatal "REMOTE_AUTO_ROLLBACK_NOT_PROVEN" 9 "$LINENO"

grep -Fq \
  "production_modified=false" \
  "$C5_DIR/report.txt" ||
  fatal "POST_ROLLBACK_SAFETY_NOT_PROVEN" 10 "$LINENO"

cp -a \
  "$C5_DIR/diagnostic.json" \
  "$REPORT_DIR/sources/step050m07c5-diagnostic.json"

cp -a \
  "$C5_DIR/vm101.txt" \
  "$REPORT_DIR/sources/step050m07c5-vm101.txt"

cp -a \
  "$C5_DIR/vm101.stderr" \
  "$REPORT_DIR/sources/step050m07c5-vm101.stderr"

mark_success "c5_false_success_and_rollback_verified"

stage "02/06" "Публикую точный read-only VM101 extractor"

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

RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
APPLY="/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh"
PLANNER="/usr/local/sbin/router-egress-hmn-plan-top5.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"
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"

EXPECTED_TABLE200_HASH="a03a7a5738c031c280e4a3147e9f7c5feb4af524f4d7ea427871b3b6cafd4691"

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

block_file() {
  name="$1"
  path="$2"

  echo "__FILE_BEGIN__ $name path=$path"

  if [ -f "$path" ]; then
    cat "$path"
  else
    echo "__ERROR__ source_missing=$path"
  fi

  echo "__FILE_END__ $name"
}

block_text() {
  name="$1"
  shift

  echo "__BLOCK_BEGIN__ $name"
  "$@" 2>&1 || true
  echo "__BLOCK_END__ $name"
}

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

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
}

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

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

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

for required in \
  "$RUNNER" \
  "$APPLY" \
  "$PLANNER" \
  "$HELPER" \
  "$CONF"
do
  [ -f "$required" ] || {
    echo "__ERROR__ source_missing=$required"
    exit 21
  }
done

echo "__TRACE__ stage=current_baseline"

fact read_only true
fact refresh_ran false
fact rebalance_ran false
fact direct_failopen_changed false
fact plan_changed false

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

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

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

fact helper_sha256 "$(
  sha256sum "$HELPER" |
    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)"

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

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

fact baseline_strict_all "$(bool_cmd strict_all)"
fact baseline_routes_all "$(bool_cmd routes_all)"

TABLE200_HASH="$(table200_hash)"
fact table200_hash "$TABLE200_HASH"

if [ "$TABLE200_HASH" = "$EXPECTED_TABLE200_HASH" ]; then
  fact table200_baseline_match true
else
  fact table200_baseline_match false
fi

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

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

block_text table200 \
  ip route show table 200

block_text routes_201_205 sh -c '
  for table in 201 202 203 204 205; do
    echo "=== table $table ==="
    ip route show table "$table"
  done
'

block_text wireguard_endpoints sh -c '
  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    echo "=== $interface ==="
    wg show "$interface" endpoints 2>/dev/null || true
  done
'

block_text current_planner "$PLANNER"
block_text current_runner "$RUNNER" --dry-run

echo "__TRACE__ stage=exact_sources"

block_file runner "$RUNNER"
block_file rebalance_apply "$APPLY"
block_file planner "$PLANNER"

echo "__TRACE__ stage=adapter_discovery"

ADAPTER_PATHS="$(
  grep -Eo \
    '/[^"'"'"'[:space:]]*(adapter|replace|egress)[^"'"'"'[:space:]]*\.sh' \
    "$APPLY" |
  sort -u ||
  true
)"

echo "__BLOCK_BEGIN__ discovered_adapter_paths"
printf '%s\n' "$ADAPTER_PATHS"
echo "__BLOCK_END__ discovered_adapter_paths"

for adapter_path in $ADAPTER_PATHS; do
  case "$adapter_path" in
    /usr/local/*|/root/*|/etc/*)
      if [ -f "$adapter_path" ]; then
        echo "__ADAPTER_FILE_BEGIN__ path=$adapter_path"
        cat "$adapter_path"
        echo "__ADAPTER_FILE_END__ path=$adapter_path"
      fi
      ;;
  esac
done

echo "__TRACE__ stage=source_excerpts"

block_text runner_contract_excerpt sh -c "
  grep -nE \
    'REBALANCE_APPLY|refresh_ok_rebalance_ok|refresh_ok_rebalance_failed|commit|decision|apply_ok|exit' \
    '$RUNNER'
"

block_text apply_contract_excerpt sh -c "
  grep -nE \
    'commit_ok|commit_failed|slot_apply_failed|apply_ok|decision|adapter|rollback|strict|exit|return' \
    '$APPLY'
"

block_text planner_contract_excerpt sh -c "
  grep -nE \
    'changes_count|plan_ok|current|target|quarantine|exit|return' \
    '$PLANNER'
"

echo "__TRACE__ stage=failure_artifacts"

block_text recent_recovery_files sh -c '
  find /var/lib/router-egress-recovery \
    -maxdepth 5 \
    -type f \
    -mmin -240 \
    -exec ls -l {} \; \
    2>/dev/null |
  sort
'

block_text recent_tmp_files sh -c '
  find /tmp \
    -maxdepth 3 \
    -type f \
    -mmin -240 \
    \( \
      -name "*egress1*" \
      -o -name "*rebalance*" \
      -o -name "*step050m07c5*" \
      -o -name "*rollback*" \
    \) \
    -exec ls -l {} \; \
    2>/dev/null |
  sort
'

block_text recent_hmn_logs sh -c '
  find /root/hmn/logs \
    -maxdepth 2 \
    -type f \
    -mmin -240 \
    -exec ls -l {} \; \
    2>/dev/null |
  sort
'

block_text relevant_log_lines sh -c '
  grep -RniE \
    "slot_apply_failed_egress1|commit_failed|egress1|vpn1|strict.*fail|rollback" \
    /var/lib/router-egress-recovery \
    /root/hmn/logs \
    2>/dev/null |
  tail -n 500
'

block_text recent_small_artifact_contents sh -c '
  find /var/lib/router-egress-recovery /tmp \
    -maxdepth 5 \
    -type f \
    -mmin -240 \
    -size -128k \
    \( \
      -name "*egress1*" \
      -o -name "*rebalance*" \
      -o -name "*rollback-egress*" \
      -o -name "*step050m07c5*" \
    \) \
    2>/dev/null |
  sort |
  while IFS= read -r path; do
    echo "===== FILE $path ====="
    sed -n "1,500p" "$path" 2>/dev/null || true
  done
'

echo "__TRACE__ stage=storage"

block_text filesystem_usage df -Pk

block_text relevant_directory_sizes sh -c '
  du -sk \
    /root/hmn/cache \
    /root/hmn/configs/awg1 \
    /root/hmn/runs \
    /root/hmn/backups \
    /var/lib/router-egress-recovery \
    /tmp/step050m07c5-* \
    2>/dev/null |
  sort -n
'

block_text latest_links sh -c '
  ls -ld \
    /root/hmn/configs/awg1/latest \
    /root/hmn/cache/*latest* \
    2>/dev/null || true

  readlink -f \
    /root/hmn/configs/awg1/latest \
    2>/dev/null || true
'

echo "__TRACE__ stage=complete"

[ "$(bool_cmd strict_all)" = true ] || exit 31
[ "$(bool_cmd routes_all)" = true ] || exit 32
[ "$(table200_hash)" = "$EXPECTED_TABLE200_HASH" ] || exit 33

exit 0
VM101

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

mark_success "readonly_extractor_published"

stage "03/06" "Извлекаю точные live-источники и failure artifacts"

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_READONLY_EXTRACTION_FAILED" "$VM101_RC" "$LINENO"

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

mark_success "live_sources_and_artifacts_extracted"

stage "04/06" "Разбираю исходники и точный exit-contract"

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

c5_path, current_path, report_dir = sys.argv[1:]
root = Path(report_dir)

c5 = Path(c5_path).read_text(
    encoding="utf-8",
    errors="replace",
)

current = Path(current_path).read_text(
    encoding="utf-8",
    errors="replace",
)


def parse_facts(text):
    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

    return facts


def extract_file(text, name):
    pattern = re.compile(
        rf"^__FILE_BEGIN__ {re.escape(name)} path=(.*?)\n"
        rf"(.*?)"
        rf"^__FILE_END__ {re.escape(name)}$",
        re.MULTILINE | re.DOTALL,
    )

    match = pattern.search(text)

    if not match:
        raise RuntimeError(f"missing source block: {name}")

    return match.group(1), match.group(2)


def extract_block(text, name):
    pattern = re.compile(
        rf"^__BLOCK_BEGIN__ {re.escape(name)}\n"
        rf"(.*?)"
        rf"^__BLOCK_END__ {re.escape(name)}$",
        re.MULTILINE | re.DOTALL,
    )

    match = pattern.search(text)

    if not match:
        raise RuntimeError(f"missing block: {name}")

    return match.group(1)


facts = parse_facts(current)

runner_path, runner_source = extract_file(current, "runner")
apply_path, apply_source = extract_file(
    current,
    "rebalance_apply",
)
planner_path, planner_source = extract_file(
    current,
    "planner",
)

(root / "runner-source.txt").write_text(
    runner_source,
    encoding="utf-8",
)

(root / "rebalance-apply-source.txt").write_text(
    apply_source,
    encoding="utf-8",
)

(root / "planner-source.txt").write_text(
    planner_source,
    encoding="utf-8",
)

adapter_blocks = re.findall(
    r"^__ADAPTER_FILE_BEGIN__ path=(.*?)\n"
    r"(.*?)"
    r"^__ADAPTER_FILE_END__ path=.*?$",
    current,
    re.MULTILINE | re.DOTALL,
)

adapter_output = []

for path, content in adapter_blocks:
    adapter_output.append(
        f"===== {path} =====\n{content}"
    )

(root / "adapter-sources.txt").write_text(
    "\n\n".join(adapter_output) + "\n",
    encoding="utf-8",
)

failure_sections = []

for name in (
    "recent_recovery_files",
    "recent_tmp_files",
    "recent_hmn_logs",
    "relevant_log_lines",
    "recent_small_artifact_contents",
    "filesystem_usage",
    "relevant_directory_sizes",
    "latest_links",
):
    failure_sections.append(
        f"===== {name} =====\n"
        + extract_block(current, name)
    )

(root / "failure-artifacts.txt").write_text(
    "\n\n".join(failure_sections) + "\n",
    encoding="utf-8",
)

runner_calls_apply = (
    "EMERGENCY_REBALANCE_APPLY_CMD" in runner_source
)

runner_checks_apply_json = bool(
    re.search(
        r'apply_ok|commit_ok|commit_failed',
        runner_source,
    )
)

runner_success_literal = (
    "refresh_ok_rebalance_ok" in runner_source
)

apply_emits_commit_failed = (
    "commit_failed" in apply_source
)

apply_emits_slot_failure = (
    "slot_apply_failed_" in apply_source
)

apply_has_explicit_nonzero_after_failure = bool(
    re.search(
        r'commit_failed.*?(?:exit|return)\s+[1-9]',
        apply_source,
        re.DOTALL,
    )
)

c5_runner_success = bool(
    re.search(
        r'"decision"\s*:\s*"refresh_ok_rebalance_ok"',
        c5,
    )
)

c5_apply_failure = bool(
    re.search(
        r'"decision"\s*:\s*"commit_failed"',
        c5,
    )
)

c5_apply_reason = bool(
    re.search(
        r'"reason"\s*:\s*"slot_apply_failed_egress1"',
        c5,
    )
)

c5_planner_not_converged = bool(
    re.search(
        r'"changes_count"\s*:\s*3',
        c5,
    )
    and "__ERROR__ planner_not_converged" in c5
)

c5_auto_rollback = (
    "__FACT__ auto_rollback=true" in c5
)

baseline_checks = {
    "strict_all":
        facts.get("baseline_strict_all") == "true",

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

    "table200_restored":
        facts.get("table200_baseline_match") == "true",

    "hook_running_enabled":
        facts.get("hook_running") == "true"
        and facts.get("hook_enabled") == "true",

    "watcher_running_enabled":
        facts.get("watcher_running") == "true"
        and facts.get("watcher_enabled") == "true",

    "locks_absent":
        facts.get("emergency_lock_present") == "false"
        and facts.get("refresh_lock_present") == "false",

    "read_only":
        facts.get("read_only") == "true",
}

false_success_checks = {
    "runner_reported_success":
        c5_runner_success,

    "underlying_apply_reported_failure":
        c5_apply_failure,

    "underlying_failure_was_egress1":
        c5_apply_reason,

    "planner_remained_unconverged":
        c5_planner_not_converged,

    "external_guard_rolled_back":
        c5_auto_rollback,

    "runner_invokes_apply":
        runner_calls_apply,

    "runner_has_success_state":
        runner_success_literal,

    "apply_can_emit_commit_failed":
        apply_emits_commit_failed,

    "apply_can_emit_slot_failure":
        apply_emits_slot_failure,
}

all_ok = (
    all(baseline_checks.values())
    and all(false_success_checks.values())
)

if not runner_checks_apply_json:
    exit_contract_classification = (
        "RUNNER_TRUSTS_SHELL_RC_WITHOUT_JSON_VALIDATION"
    )

elif (
    apply_emits_commit_failed
    and not apply_has_explicit_nonzero_after_failure
):
    exit_contract_classification = (
        "APPLY_FAILURE_JSON_MAY_RETURN_ZERO"
    )

else:
    exit_contract_classification = (
        "SOURCE_REQUIRES_TARGETED_PATCH_REVIEW"
    )

source_contract = {
    "runner": {
        "path": runner_path,
        "sha256": facts.get("runner_sha256"),
        "calls_apply_command": runner_calls_apply,
        "checks_apply_json_contract":
            runner_checks_apply_json,
        "contains_success_state":
            runner_success_literal,
    },
    "apply": {
        "path": apply_path,
        "sha256": facts.get("apply_sha256"),
        "emits_commit_failed":
            apply_emits_commit_failed,
        "emits_slot_apply_failed":
            apply_emits_slot_failure,
        "explicit_nonzero_after_failure_detected":
            apply_has_explicit_nonzero_after_failure,
    },
    "planner": {
        "path": planner_path,
        "sha256": facts.get("planner_sha256"),
    },
    "classification": exit_contract_classification,
}

current_baseline = {
    "checks": baseline_checks,
    "all_ok": all(baseline_checks.values()),
    "state": {
        "mode": facts.get("state_mode"),
        "status": facts.get("state_status"),
        "epoch": facts.get("state_epoch"),
        "commit_raw": facts.get("commit_raw"),
    },
    "services": {
        "hook_running": facts.get("hook_running"),
        "hook_enabled": facts.get("hook_enabled"),
        "watcher_running": facts.get("watcher_running"),
        "watcher_enabled": facts.get("watcher_enabled"),
    },
}

assessment = {
    "all_ok": all_ok,
    "decision": (
        "PASS_STEP_050M07D_CLASSIFY_FALSE_SUCCESS_AND_EGRESS1_FAILURE"
        if all_ok
        else
        "STOP_STEP_050M07D_CLASSIFY_FALSE_SUCCESS_AND_EGRESS1_FAILURE"
    ),
    "classification": {
        "c5_core": (
            "FRESH_DOWNLOAD_PASS_REBALANCE_FAIL"
        ),
        "rebalance_failure":
            "SLOT_APPLY_FAILED_EGRESS1",
        "runner_contract":
            exit_contract_classification,
        "rollback":
            "REMOTE_AUTO_ROLLBACK_PROVEN",
    },
    "false_success_checks": false_success_checks,
    "baseline": current_baseline,
    "source_contract": source_contract,
    "next_action": (
        "Patch exact apply/runner exit contract and separately repair "
        "the egress1 apply path before repeating a full refresh."
    ),
    "safety": {
        "read_only": True,
        "refresh_ran": False,
        "rebalance_ran": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "plan_changed": False,
        "direct_failopen_changed": False,
    },
}

(root / "source-contract.json").write_text(
    json.dumps(
        source_contract,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)

(root / "current-baseline.json").write_text(
    json.dumps(
        current_baseline,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)

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

if not all_ok:
    failed = [
        name
        for name, value in {
            **baseline_checks,
            **false_success_checks,
        }.items()
        if not value
    ]

    raise SystemExit(
        "failed checks: " + ",".join(failed)
    )
PY

mark_success "false_success_contract_classified"

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

CONTRACT_CLASS="$(
  python3 - "$REPORT_DIR/source-contract.json" <<'PY'
import json
import sys

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

print(data["classification"])
PY
)"

STATE_MODE="$(
  python3 - "$REPORT_DIR/current-baseline.json" <<'PY'
import json
import sys

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

print(data["state"]["mode"])
PY
)"

STATE_STATUS="$(
  python3 - "$REPORT_DIR/current-baseline.json" <<'PY'
import json
import sys

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

print(data["state"]["status"])
PY
)"

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

c5_result:
  fresh_hmn_download=true
  downloaded_configs=35
  download_errors=0
  old_pool_fallback=false
  runner_elapsed_seconds=315
  runner_reported=refresh_ok_rebalance_ok
  underlying_apply_decision=commit_failed
  underlying_apply_reason=slot_apply_failed_egress1
  post_planner_changes_count=3
  remote_auto_rollback=true

classification:
  refresh_stage=PASS
  rebalance_stage=FAIL_EGRESS1
  runner_contract=${CONTRACT_CLASS}
  runner_false_success=true
  autonomous_cooldown_after_failed_apply=true

current_baseline:
  strict_slots=5
  routes_201_205=true
  table200_restored=true
  services_running_enabled=true
  locks_absent=true
  state_mode=${STATE_MODE}
  state_status=${STATE_STATUS}

artifacts:
  source_contract=source-contract.json
  runner_source=runner-source.txt
  rebalance_apply_source=rebalance-apply-source.txt
  planner_source=planner-source.txt
  adapter_sources=adapter-sources.txt
  failure_artifacts=failure-artifacts.txt
  current_baseline=current-baseline.json

safety:
  read_only=true
  refresh_ran=false
  rebalance_ran=false
  network_changed=false
  services_changed=false
  state_changed=false
  plan_changed=false
  direct_failopen_changed=false

plan:
  current_milestone=M07
  milestone_completed=false
  milestone_changed=false

next_step:
  M07E_PATCH_EXIT_CONTRACT_AND_REPAIR_EGRESS1_APPLY_PATH

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" \
  "$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,
    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,
    "operation": {
        "c5_fresh_download": True,
        "c5_downloaded_configs": 35,
        "c5_download_errors": 0,
        "c5_old_pool_fallback": False,
        "c5_runner_elapsed_seconds": 315,
        "c5_underlying_apply_decision": "commit_failed",
        "c5_underlying_apply_reason":
            "slot_apply_failed_egress1",
        "c5_remote_auto_rollback": True,
    },
    "safety": assessment["safety"],
    "plan": {
        "current_milestone": "M07",
        "milestone_completed": False,
        "milestone_changed": False,
    },
    "next_step":
        "M07E_PATCH_EXIT_CONTRACT_AND_REPAIR_EGRESS1_APPLY_PATH",
    "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

create_index

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

mark_success "pass_report_published"

stage "06/06" "Завершаю read-only classification"

trap - ERR

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

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

echo "c5_rebalance_failure=slot_apply_failed_egress1" |
  tee -a "$PROGRESS_LOG"

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

echo "runner_contract=$CONTRACT_CLASS" |
  tee -a "$PROGRESS_LOG"

echo "baseline_strict_slots=5" |
  tee -a "$PROGRESS_LOG"

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

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

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

print_links
