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

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

STEP="STEP_050M06E_ENABLE_COMMIT_PROVEN_PROCD_SCHEDULER"
PASS_DECISION="PASS_STEP_050M06E_ENABLE_COMMIT_PROVEN_PROCD_SCHEDULER"
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-180804_step050m06d1_readonly_audit_scheduler_contract_vm101/"
PREVIOUS_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"
PLAN_STATE_DIR="${STATE_ROOT}/local-plans/${PLAN_ID}"
PUBROOT="${ROOT}/public/r/${TOKEN}"

M06A_DIR="${PUBROOT}/20260711-173308_step050m06a_compare_live_toolchain_to_frozen_map"
M06D_DIR="${PUBROOT}/20260711-180336_step050m06d_enable_commit_proven_openwrt_commands"
M06D1_DIR="${PUBROOT}/20260711-180804_step050m06d1_readonly_audit_scheduler_contract_vm101"

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

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

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

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

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

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

VM101_CHANGED=false
VM101_ROLLBACK=""
PLAN_UPDATED=false
PRODUCTION_MODIFIED=false
ROLLBACK_RC="NOT_NEEDED"
PLAN_RESTORE_RC="NOT_NEEDED"

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

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

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

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

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

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

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

  set +e

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

  "$STATUS_TOOL" \
    M06 in_progress \
    --note "Изменение VM101 откачено; M06 снова в работе." \
    > "$REPORT_DIR/plan-restore-m06.txt" \
    2> "$REPORT_DIR/plan-restore-m06.stderr"
  rc_m06=$?

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

  set -e

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

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

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

  PLAN_RESTORE_RC="M07:${rc_m07},M06:${rc_m06},PUBLISH:${rc_publish}"
  return 1
}

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=M06_VM101_PROCD_CONTRACT
error_rc=${rc}
error_line=${line}

scope:
  blocking_host=VM101
  vm100_checked=false
  vm121_checked=false

safety:
  production_modified=${PRODUCTION_MODIFIED}
  rollback_rc=${ROLLBACK_RC}
  plan_restore_rc=${PLAN_RESTORE_RC}
  watcher_invoked=false
  real_refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false

plan:
  milestone=M06
  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 - \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$PRODUCTION_MODIFIED" \
    "$ROLLBACK_RC" \
    "$PLAN_RESTORE_RC" \
    "$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,
    production_modified,
    rollback_rc,
    plan_restore_rc,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

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

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "assessment": {
        "decision": f"STOP_{step}_{reason}",
        "all_ok": False,
        "error_rc": int(rc),
        "error_line": int(line),
    },
    "scope": {
        "blocking_hosts": ["VM101"],
        "vm100_checked": False,
        "vm121_checked": False,
    },
    "safety": {
        "production_modified": modified,
        "rollback_rc": rollback_rc,
        "plan_restore_rc": plan_restore_rc,
        "watcher_invoked": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "plan": {
        "milestone": "M06",
        "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
}

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

  trap - ERR

  rollback_vm101 || true
  restore_plan_state || true

  write_stop "$reason" "$rc" "$line"
  print_links
  exit "$rc"
}

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

  fail_step "UNEXPECTED_ERROR" "$rc" "$line"
}

trap 'on_error "$LINENO"' ERR

stage "01/09" "Классифицирую scheduler audit и проверяю текущий план"

for required in \
  "$M06D1_DIR/report.txt" \
  "$M06D1_DIR/facts.json" \
  "$M06D1_DIR/assessment.json" \
  "$M06D1_DIR/vm101.txt" \
  "$M06D1_DIR/vm101.stderr" \
  "$M06D1_DIR/step.sh" \
  "$M06D1_DIR/sources/router-egress-emergency-decision.init" \
  "$M06D1_DIR/sources/router-egress-emergency-decision-hook.sh" \
  "$M06D1_DIR/sources/router-egress-emergency-refresh.sh" \
  "$M06D_DIR/vm101.sh" \
  "$M06D_DIR/assessment.json" \
  "$M06D_DIR/step.sh" \
  "$M06A_DIR/vm101.sh" \
  "$M06A_DIR/comparison.json" \
  "$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" \
  "$STATUS_TOOL" \
  "$REPUBLISH_TOOL" \
  "$STATE_ROOT/current-local-architecture-plan-url.txt"
do
  [ -e "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    fail_step "REQUIRED_ARTIFACT_MISSING" 2 "$LINENO"
  }
done

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

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

assessment = json.loads(
    Path(assessment_path).read_text(
        encoding="utf-8",
    )
)

assert assessment["all_ok"] is False
assert assessment["failed_checks"] == ["stderr_empty"]

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

contract = assessment["scheduler_contract"]
automatic = assessment["automatic_path"]

assert contract["mechanism"] == "PROCD_HOOK_INTERNAL_LOOP"
assert contract["mechanism_proven"] is True
assert contract["uses_procd"] is True
assert contract["procd_open_instance"] is True
assert contract["procd_command"] is True
assert contract["procd_respawn"] is True
assert contract["init_hook_reference"] is True
assert contract["hook_has_loop"] is True
assert contract["hook_has_sleep"] is True
assert contract["process_seen"] is True
assert contract["ubus_seen"] is True
assert contract["cron_reference_count"] == 0
assert contract["service_running"] is True
assert contract["service_enabled"] is True

assert automatic["hook_has_runner_reference"] is True
assert automatic["hook_explicit_dry_run"] is True
assert automatic["hook_commit_refs"] is False
assert automatic["runtime_mode"] == "dry-run"
assert automatic["runtime_decision"] == "would_run_emergency_refresh"
assert automatic["safe"] is True

assert assessment["runtime"]["commit_enabled"] == "false"
assert assessment["runtime"]["threshold_reached"] is True
assert assessment["safety"]["production_modified"] is False

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

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

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

unexpected = [
    line
    for line in stderr_lines
    if not any(pattern.match(line) for pattern in allowed_patterns)
]

if unexpected:
    raise SystemExit(
        "STOP: неожиданный stderr scheduler audit: "
        + json.dumps(unexpected, ensure_ascii=False)
    )

result = {
    "salvage_pass": True,
    "formal_stop_reason": "stderr_empty",
    "stderr_lines": stderr_lines,
    "unexpected_stderr_lines": unexpected,
    "scheduler_mechanism": "PROCD_HOOK_INTERNAL_LOOP",
    "scheduler_mechanism_proven": True,
    "automatic_path_safe": True,
    "cron_required": False,
    "commit_currently_enabled": False,
    "next_step":
        "STEP_050M06E_ENABLE_COMMIT_PROVEN_PROCD_SCHEDULER",
}

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

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

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

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

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

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

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

cp -a \
  "$M06D1_DIR/assessment.json" \
  "$REPORT_DIR/sources/step050m06d1-assessment.json"

cp -a \
  "$M06D1_DIR/vm101.txt" \
  "$REPORT_DIR/sources/step050m06d1-vm101.txt"

cp -a \
  "$M06D1_DIR/step.sh" \
  "$REPORT_DIR/sources/step050m06d1-step.sh"

cp -a \
  "$M06D1_DIR/sources/router-egress-emergency-decision.init" \
  "$REPORT_DIR/sources/router-egress-emergency-decision.init"

cp -a \
  "$M06D1_DIR/sources/router-egress-emergency-decision-hook.sh" \
  "$REPORT_DIR/sources/router-egress-emergency-decision-hook.sh"

cp -a \
  "$M06D1_DIR/sources/router-egress-emergency-refresh.sh" \
  "$REPORT_DIR/sources/router-egress-emergency-refresh.sh"

cp -a \
  "$M06D_DIR/assessment.json" \
  "$REPORT_DIR/sources/step050m06d-assessment.json"

stage "02/09" "Публикую точные переиспользуемые VM101-скрипты"

# Точная копия успешно исполнившегося remote script M06D.
# Его единственный отказ был во внешнем assessment scheduler_safe.
cp -a \
  "$M06D_DIR/vm101.sh" \
  "$REPORT_DIR/vm101.sh"

# Точная динамическая frozen-map проверка из M06A.
cp -a \
  "$M06A_DIR/vm101.sh" \
  "$REPORT_DIR/vm101-tool-hash-check.sh"

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

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

VM101_SCRIPT_SHA="$(
  sha256sum "$REPORT_DIR/vm101.sh" |
    cut -d' ' -f1
)"

HASH_CHECK_SCRIPT_SHA="$(
  sha256sum "$REPORT_DIR/vm101-tool-hash-check.sh" |
    cut -d' ' -f1
)"

echo "vm101_script_sha256=$VM101_SCRIPT_SHA" |
  tee -a "$PROGRESS_LOG"

echo "tool_hash_script_sha256=$HASH_CHECK_SCRIPT_SHA" |
  tee -a "$PROGRESS_LOG"

stage "03/09" "Повторно проверяю frozen toolchain непосредственно перед изменением"

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

[ "$HASH_RC" -eq 0 ] ||
  fail_step "FROZEN_TOOLCHAIN_REMOTE_CHECK_FAILED" "$HASH_RC" "$LINENO"

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

stage "04/09" "Включаю emergency commit на 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.txt" \
  2> "$REPORT_DIR/vm101.stderr"
then
  VM101_RC=0
else
  VM101_RC=$?
fi

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

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

if [ "$VM101_RC" -ne 0 ]; then
  if grep -Fq \
    '__AUTO_ROLLBACK__ restored=true' \
    "$REPORT_DIR/vm101.txt"
  then
    PRODUCTION_MODIFIED=false
    ROLLBACK_RC="REMOTE_AUTO_ROLLBACK_OK"
  elif grep -Fq \
    '__TRACE__ stage=post_state' \
    "$REPORT_DIR/vm101.txt"
  then
    VM101_CHANGED=true
    PRODUCTION_MODIFIED=true
  fi

  fail_step "VM101_REMOTE_FAILED" "$VM101_RC" "$LINENO"
fi

[ -n "$VM101_ROLLBACK" ] ||
  fail_step "VM101_ROLLBACK_PATH_MISSING" 5 "$LINENO"

VM101_CHANGED=true
PRODUCTION_MODIFIED=true

stage "05/09" "Оцениваю VM101-инварианты с доказанным procd-контрактом"

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

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

scheduler = json.loads(
    Path(scheduler_path).read_text(
        encoding="utf-8",
    )
)

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

current = None
lines = []

for line in Path(runtime_path).read_text(
    encoding="utf-8",
    errors="replace",
).splitlines():
    if line.startswith("__TRACE__ "):
        traces.append(line)

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

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

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

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

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

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

        current = None
        lines = []

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

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

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

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

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

runner_pre = blocks.get("runner_pre", {})
runner_post = blocks.get("runner_post", {})
hook_pre = blocks.get("hook_pre", {})
hook_post = blocks.get("hook_post", {})

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

endpoints_pre = {
    interface: facts.get(f"endpoint_pre.{interface}")
    for interface in ("vpn1", "vpn2", "vpn3", "vpn4", "vpn5")
}

endpoints_post = {
    interface: facts.get(f"endpoint_post.{interface}")
    for interface in ("vpn1", "vpn2", "vpn3", "vpn4", "vpn5")
}

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

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

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

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

scheduler_contract = scheduler["scheduler_contract"]
automatic_path = scheduler["automatic_path"]

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

    "stderr_benign":
        not unexpected_stderr,

    "openwrt_command_contract":
        facts.get("stat_command_present") == "false"
        and facts.get("pool_mtime_method") == "date_r",

    "services_running_enabled":
        facts.get("hook_running") == "true"
        and facts.get("hook_enabled") == "true"
        and facts.get("watcher_running") == "true"
        and facts.get("watcher_enabled") == "true",

    "procd_scheduler_contract":
        scheduler_contract["mechanism"]
            == "PROCD_HOOK_INTERNAL_LOOP"
        and scheduler_contract["mechanism_proven"] is True
        and scheduler_contract["uses_procd"] is True
        and scheduler_contract["procd_open_instance"] is True
        and scheduler_contract["procd_command"] is True
        and scheduler_contract["procd_respawn"] is True
        and scheduler_contract["hook_has_loop"] is True
        and scheduler_contract["hook_has_sleep"] is True
        and scheduler_contract["process_seen"] is True
        and scheduler_contract["ubus_seen"] is True
        and scheduler_contract["cron_reference_count"] == 0,

    "automatic_hook_safe":
        automatic_path["hook_explicit_dry_run"] is True
        and automatic_path["hook_commit_refs"] is False
        and automatic_path["safe"] is True,

    "commit_transition":
        facts.get("commit_enabled_pre") == "false"
        and facts.get("commit_enabled_post") == "true",

    "threshold_reached":
        isinstance(threshold, int)
        and counter_pre >= threshold
        and runner_pre.get("threshold_reached") is True
        and runner_post.get("threshold_reached") is True,

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

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

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

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

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

    "endpoints_preserved":
        endpoints_pre == endpoints_post
        and all(endpoints_pre.values()),

    "strict_all":
        all(strict_pre.values())
        and all(strict_post.values())
        and facts.get("strict_pre_all") == "true"
        and facts.get("strict_post_all") == "true",

    "routes_all":
        all(routes_pre.values())
        and all(routes_post.values())
        and facts.get("routes_pre_all") == "true"
        and facts.get("routes_post_all") == "true",

    "network_unchanged":
        facts.get("network_hash_pre")
            == facts.get("network_hash_post"),

    "runner_unchanged":
        facts.get("runner_hash_pre")
            == facts.get("runner_hash_post"),

    "hook_unchanged":
        facts.get("hook_hash_pre")
            == facts.get("hook_hash_post"),

    "pool_unchanged":
        facts.get("pool_hash_pre")
            == facts.get("pool_hash_post")
        and facts.get("pool_mtime_pre")
            == facts.get("pool_mtime_post"),

    "config_changed":
        facts.get("config_hash_pre")
            != facts.get("config_hash_post"),

    "watcher_not_invoked":
        facts.get("watcher_invoked") == "false",

    "no_real_actions":
        facts.get("commit_invoked") == "false"
        and facts.get("refresh_ran") == "false"
        and facts.get("rebalance_apply_ran") == "false"
        and facts.get("direct_failopen_changed") == "false",

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

all_ok = all(checks.values())

assessment = {
    "all_ok": all_ok,
    "decision": (
        "PASS_STEP_050M06E_ENABLE_COMMIT_PROVEN_PROCD_SCHEDULER"
        if all_ok
        else
        "STOP_STEP_050M06E_ENABLE_COMMIT_PROVEN_PROCD_SCHEDULER"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, value in checks.items()
        if not value
    ],
    "stderr": {
        "lines": stderr_lines,
        "unexpected_lines": unexpected_stderr,
    },
    "scheduler_contract": {
        "mechanism": "PROCD_HOOK_INTERNAL_LOOP",
        "cron_required": False,
        "automatic_hook_mode": "dry-run",
        "automatic_hook_commit_path": False,
    },
    "runtime": {
        "repair_counter": counter_pre,
        "daily_fail_threshold": threshold,
        "commit_enabled_before": False,
        "commit_enabled_after": True,
        "endpoints": endpoints_post,
        "strict": strict_post,
        "routes": routes_post,
        "rollback": facts.get("rollback"),
    },
    "safety": {
        "watcher_invoked": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "plan_transition": {
        "completed": "M06",
        "next": "M07",
    },
}

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

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

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

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

[ "$ALL_OK" = true ] ||
  fail_step "VALIDATION_FAILED_${FAILED_CHECKS}" 6 "$LINENO"

stage "06/09" "Проверяю persisted commit и procd hook отдельным read-only скриптом"

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

CONF="/etc/router-egress-emergency-refresh.conf"
RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
HOOK="/usr/local/sbin/router-egress-emergency-decision-hook.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"
INIT="/etc/init.d/router-egress-emergency-decision"

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

json_block() {
  echo "__JSON_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__JSON_END__ $1"
}

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

COMMIT="$(
  (
    unset EMERGENCY_COMMIT_ENABLED
    . "$CONF"

    case "${EMERGENCY_COMMIT_ENABLED:-0}" in
      1|true|TRUE|yes|YES|on|ON)
        printf true
        ;;
      *)
        printf false
        ;;
    esac
  )
)"

COUNTER="$(
  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_daily_repair_get
  )
)"

RUNNER_JSON="$("$RUNNER" --dry-run)"
HOOK_JSON="$("$HOOK")"

fact commit_enabled "$COMMIT"
fact repair_counter "$COUNTER"
fact service_running "$(bool_cmd "$INIT" running)"
fact service_enabled "$(bool_cmd "$INIT" enabled)"

json_block runner "$RUNNER_JSON"
json_block hook "$HOOK_JSON"

fact watcher_invoked false
fact real_refresh_ran false
fact rebalance_apply_ran false
fact direct_failopen_changed false
VM101POST

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

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

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

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

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

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

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

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

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

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

        current = None
        lines = []

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

runner = blocks["runner"]
hook = blocks["hook"]

assert facts["commit_enabled"] == "true"
assert facts["service_running"] == "true"
assert facts["service_enabled"] == "true"

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

assert hook["mode"] == "dry-run"
assert hook["decision"] == "would_run_emergency_refresh"
assert hook["threshold_reached"] is True
assert hook["commit_enabled"] is True
assert hook["direct_failopen_enabled"] is False

assert facts["watcher_invoked"] == "false"
assert facts["real_refresh_ran"] == "false"
assert facts["rebalance_apply_ran"] == "false"
assert facts["direct_failopen_changed"] == "false"
PY

stage "07/09" "Отмечаю M06 выполненным и переиздаю локальный план"

"$STATUS_TOOL" \
  M06 done \
  --evidence "$TRYCF_REPORT" \
  --note "Emergency commit включён на VM101. Scheduler доказан как PROCD_HOOK_INTERNAL_LOOP; автоматический hook вызывает runner только в dry-run и не имеет commit-пути. Refresh, rebalance и Direct не запускались." \
  > "$REPORT_DIR/plan-status-m06.txt" \
  2> "$REPORT_DIR/plan-status-m06.stderr"

PLAN_UPDATED=true

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

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

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

[ -n "$NEW_ARCHITECTURE_PLAN" ] ||
  fail_step "PLAN_REPUBLISH_URL_MISSING" 7 "$LINENO"

ARCHITECTURE_PLAN="$NEW_ARCHITECTURE_PLAN"

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

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

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

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

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

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

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

print(data["runtime"]["repair_counter"])
PY
)"

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

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

scope:
  blocking_host=VM101
  vm100_checked=false
  vm121_checked=false

implementation:
  frozen_toolchain_rechecked=true
  reused_vm101_script=STEP_050M06D/vm101.sh
  vm101_script_sha256=${VM101_SCRIPT_SHA}
  repair_counter=${REPAIR_COUNTER}
  daily_fail_threshold=${FAIL_THRESHOLD}
  threshold_reached=true
  emergency_commit_enabled_before=false
  emergency_commit_enabled_after=true
  persisted_postcheck=true

scheduler_contract:
  mechanism=PROCD_HOOK_INTERNAL_LOOP
  service_running=true
  service_enabled=true
  cron_reference_count=0
  cron_required=false
  automatic_hook_mode=dry-run
  automatic_hook_commit_path=false
  automatic_path_safe=true

safety:
  production_modified=true
  endpoints_changed=false
  network_config_changed=false
  runner_changed=false
  hook_changed=false
  pool_changed=false
  repair_counter_changed=false
  watcher_invoked=false
  real_refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false
  rollback_vm101=${VM101_ROLLBACK}

plan:
  milestone_completed=M06
  current_milestone=M07
  plan_republished=true

next_step:
  M07_READONLY_REVIEW_REAL_FULL_HMN_REFRESH_CONTRACT

artifacts:
  step_script=step.sh
  executed_vm101_script=vm101.sh
  tool_hash_script=vm101-tool-hash-check.sh
  postcheck_script=vm101-postcheck.sh
  assessment=assessment.json
  scheduler_salvage=scheduler-contract-salvage.json

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" \
  "$REPORT_DIR/scheduler-contract-salvage.json" \
  "$STEP" \
  "$TS" \
  "$VM101_SCRIPT_SHA" \
  "$VM101_ROLLBACK" \
  "$PREVIOUS_REPORT" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" \
  "$ARCHITECTURE_PLAN" \
  "$XS_MAP" \
  "$GLOBAL_PROJECT_PLAN" \
  > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

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

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

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

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "generated_at_utc": timestamp,
    "assessment": assessment,
    "scheduler_audit_salvage": salvage,
    "scope": {
        "blocking_hosts": ["VM101"],
        "vm100_checked": False,
        "vm121_checked": False,
    },
    "implementation": {
        "reused_vm101_script":
            "STEP_050M06D/vm101.sh",
        "vm101_script_sha256":
            script_sha,
        "emergency_commit_enabled_before":
            False,
        "emergency_commit_enabled_after":
            True,
        "persisted_postcheck":
            True,
        "rollback_vm101":
            rollback,
    },
    "scheduler_contract": {
        "mechanism":
            "PROCD_HOOK_INTERNAL_LOOP",
        "cron_required":
            False,
        "automatic_hook_mode":
            "dry-run",
        "automatic_hook_commit_path":
            False,
        "automatic_path_safe":
            True,
    },
    "safety": {
        "production_modified": True,
        "endpoints_changed": False,
        "network_config_changed": False,
        "runner_changed": False,
        "hook_changed": False,
        "pool_changed": False,
        "repair_counter_changed": False,
        "watcher_invoked": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "plan": {
        "milestone_completed": "M06",
        "current_milestone": "M07",
        "plan_republished": True,
    },
    "next_step":
        "M07_READONLY_REVIEW_REAL_FULL_HMN_REFRESH_CONTRACT",
    "source": {
        "step050m06d1": previous_report,
    },
    "artifacts": {
        "step_script":
            "step.sh",
        "executed_vm101_script":
            "vm101.sh",
        "tool_hash_script":
            "vm101-tool-hash-check.sh",
        "postcheck_script":
            "vm101-postcheck.sh",
        "assessment":
            "assessment.json",
        "scheduler_salvage":
            "scheduler-contract-salvage.json",
    },
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
    },
}, ensure_ascii=False, indent=2))
PY

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

<h1>${STEP}</h1>

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

<h2>Результаты</h2>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="assessment.json">assessment.json</a></li>
<li><a href="scheduler-contract-salvage.json">scheduler-contract-salvage.json</a></li>
<li><a href="vm101.txt">VM101 execution</a></li>
<li><a href="vm101-postcheck.txt">Persisted postcheck</a></li>
<li><a href="plan-status-m06.txt">Plan status</a></li>
<li><a href="plan-republish.txt">Plan republish</a></li>
</ul>

<h2>Scheduler sources</h2>
<ul>
<li><a href="sources/router-egress-emergency-decision.init">init script</a></li>
<li><a href="sources/router-egress-emergency-decision-hook.sh">decision hook</a></li>
<li><a href="sources/router-egress-emergency-refresh.sh">emergency runner</a></li>
</ul>

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

</body>
</html>
EOF

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

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

stage "09/09" "Завершаю M06"

VM101_CHANGED=false
PLAN_UPDATED=false

trap - ERR

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

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

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

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

echo "daily_fail_threshold=$FAIL_THRESHOLD" |
  tee -a "$PROGRESS_LOG"

echo "scheduler_mechanism=PROCD_HOOK_INTERNAL_LOOP" |
  tee -a "$PROGRESS_LOG"

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

echo "automatic_hook_mode=dry-run" |
  tee -a "$PROGRESS_LOG"

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

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

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

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

print_links
