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

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

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

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"
PUBROOT="${ROOT}/public/r/${TOKEN}"

M06D_DIR="${PUBROOT}/20260711-180336_step050m06d_enable_commit_proven_openwrt_commands"
M06A_DIR="${PUBROOT}/20260711-173308_step050m06a_compare_live_toolchain_to_frozen_map"
LP2_DIR="${PUBROOT}/20260711-151610_step050lp2_readonly_inventory_vm101_recovery_toolchain"

SOURCE_ROOT="${LP2_DIR}/sources/vm101-root"
INIT_SOURCE="${SOURCE_ROOT}/etc/init.d/router-egress-emergency-decision"
HOOK_SOURCE="${SOURCE_ROOT}/usr/local/sbin/router-egress-emergency-decision-hook.sh"
RUNNER_SOURCE="${SOURCE_ROOT}/usr/local/sbin/router-egress-emergency-refresh.sh"

TS="$(date -u +%Y%m%d-%H%M%S)"
REPORT_SLUG="${TS}_step050m06d1_readonly_audit_scheduler_contract_vm101"
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"

# Точный 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_SCHEDULER_CONTRACT_AUDIT
error_rc=${rc}
error_line=${line}

safety:
  production_modified=false
  vm101_modified=false
  commit_flag_changed=false
  watcher_invoked=false
  refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false

plan:
  current_milestone=M06
  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 - \
    "$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_SCHEDULER_CONTRACT_AUDIT",
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "commit_flag_changed": False,
        "watcher_invoked": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "plan": {
        "current_milestone": "M06",
        "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
}

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

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

trap 'on_error "$LINENO"' ERR

stage "01/07" "Проверяю M06D, frozen toolchain и точные исходники"

for required in \
  "$M06D_DIR/report.txt" \
  "$M06D_DIR/facts.json" \
  "$M06D_DIR/assessment.json" \
  "$M06D_DIR/vm101.txt" \
  "$M06D_DIR/vm101-rollback.txt" \
  "$M06D_DIR/step.sh" \
  "$M06A_DIR/report.txt" \
  "$M06A_DIR/comparison.json" \
  "$M06A_DIR/vm101.sh" \
  "$INIT_SOURCE" \
  "$HOOK_SOURCE" \
  "$RUNNER_SOURCE"
do
  [ -s "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    exit 1
  }
done

grep -Fq \
  "decision=STOP_STEP_050M06D_ENABLE_COMMIT_PROVEN_OPENWRT_COMMANDS_VALIDATION_FAILED_scheduler_safe" \
  "$M06D_DIR/report.txt"

grep -Fq \
  "production_modified=false" \
  "$M06D_DIR/report.txt"

grep -Fq \
  "rollback_rc=0" \
  "$M06D_DIR/report.txt"

grep -Fq \
  "toolchain_exact_match=true" \
  "$M06A_DIR/report.txt"

python3 - \
  "$M06D_DIR/assessment.json" <<'PY'
import json
import sys

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

assert data["all_ok"] is False
assert data["failed_checks"] == ["scheduler_safe"]

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

assert data["runtime"]["repair_counter"] == 6
assert data["runtime"]["daily_fail_threshold"] == 5
assert data["runtime"]["commit_enabled_before"] is False
assert data["runtime"]["commit_enabled_after"] is True
PY

cp -a \
  "$M06D_DIR/report.txt" \
  "$REPORT_DIR/sources/step050m06d-report.txt"

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

cp -a \
  "$M06D_DIR/vm101.txt" \
  "$REPORT_DIR/sources/step050m06d-vm101.txt"

cp -a \
  "$M06D_DIR/step.sh" \
  "$REPORT_DIR/sources/step050m06d-step.sh"

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

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

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

stage "02/07" "Повторно проверяю frozen toolchain перед аудитом"

cp -a \
  "$M06A_DIR/vm101.sh" \
  "$REPORT_DIR/vm101-tool-hash-check.sh"

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

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 ]

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

stage "03/07" "Публикую точный read-only VM101 scheduler audit"

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

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

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

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

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

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

echo "__TRACE__ stage=required_files"

for path in \
  "$INIT" \
  "$HOOK" \
  "$RUNNER" \
  "$CONF" \
  "$HELPER" \
  "$CRON"
do
  [ -e "$path" ] || {
    echo "__ERROR__ missing=$path"
    exit 21
  }
done

echo "__TRACE__ stage=live_hashes"

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

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

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

echo "__TRACE__ stage=service_state"

fact service_running "$(bool_cmd "$INIT" running)"
fact service_enabled "$(bool_cmd "$INIT" enabled)"

if command -v ubus >/dev/null 2>&1; then
  UBUS_OUTPUT="$(
    ubus call service list \
      '{"name":"router-egress-emergency-decision"}' \
      2>&1 ||
    true
  )"

  block ubus_service "$UBUS_OUTPUT"
else
  block ubus_service "UBUS_MISSING"
fi

PS_OUTPUT="$(
  ps w 2>/dev/null |
    grep -E \
      'router-egress-emergency-decision|router-egress-emergency-refresh' |
    grep -v grep ||
  true
)"

block relevant_processes "$PS_OUTPUT"

echo "__TRACE__ stage=cron_state"

CRON_OUTPUT="$(
  grep -nE \
    'router-egress-emergency-decision|router-egress-emergency-refresh' \
    "$CRON" \
    2>/dev/null ||
  true
)"

block relevant_cron "$CRON_OUTPUT"

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

fact cron_reference_count "$CRON_REFS"

echo "__TRACE__ stage=current_state"

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"

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

echo "__TRACE__ stage=safety"

fact production_modified false
fact vm101_modified false
fact commit_flag_changed false
fact watcher_invoked false
fact refresh_ran false
fact rebalance_apply_ran false
fact direct_failopen_changed false

echo "__TRACE__ stage=complete"
VM101

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

stage "04/07" "Выполняю read-only scheduler audit на 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_RC" -eq 0 ]

grep -Fq \
  "__TRACE__ stage=complete" \
  "$REPORT_DIR/vm101.txt"

grep -Fq \
  "__FACT__ production_modified=false" \
  "$REPORT_DIR/vm101.txt"

stage "05/07" "Классифицирую реальный scheduler contract"

python3 - \
  "$REPORT_DIR/sources/router-egress-emergency-decision.init" \
  "$REPORT_DIR/sources/router-egress-emergency-decision-hook.sh" \
  "$REPORT_DIR/sources/router-egress-emergency-refresh.sh" \
  "$REPORT_DIR/vm101.txt" \
  "$REPORT_DIR/vm101.stderr" \
  "$REPORT_DIR/assessment.json" <<'PY'
import hashlib
import json
import re
import sys
from pathlib import Path

(
    init_path,
    hook_path,
    runner_path,
    runtime_path,
    stderr_path,
    output_path,
) = sys.argv[1:]

init_bytes = Path(init_path).read_bytes()
hook_bytes = Path(hook_path).read_bytes()
runner_bytes = Path(runner_path).read_bytes()

init_text = init_bytes.decode("utf-8", errors="replace")
hook_text = hook_bytes.decode("utf-8", errors="replace")
runner_text = runner_bytes.decode("utf-8", errors="replace")

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

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

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

current_block = None
current_json = None
lines = []

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

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

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

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

    elif line.startswith("__BLOCK_BEGIN__ "):
        current_block = line[len("__BLOCK_BEGIN__ "):]
        current_json = None
        lines = []

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

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

        current_block = None
        lines = []

    elif line.startswith("__JSON_BEGIN__ "):
        current_json = line[len("__JSON_BEGIN__ "):]
        current_block = None
        lines = []

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

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

        current_json = None
        lines = []

    elif current_block is not None or current_json is not None:
        lines.append(line)

init_sha = hashlib.sha256(init_bytes).hexdigest()
hook_sha = hashlib.sha256(hook_bytes).hexdigest()
runner_sha = hashlib.sha256(runner_bytes).hexdigest()

live_hashes_match = (
    facts.get("init_sha256") == init_sha
    and facts.get("hook_sha256") == hook_sha
    and facts.get("runner_sha256") == runner_sha
)

uses_procd = bool(
    re.search(
        r"(?m)^[ \t]*USE_PROCD[ \t]*=[ \t]*1",
        init_text,
    )
)

procd_open_instance = "procd_open_instance" in init_text
procd_command = "procd_set_param command" in init_text
procd_respawn = "procd_set_param respawn" in init_text

init_hook_reference = (
    "router-egress-emergency-decision-hook.sh"
    in init_text
)

init_runner_reference = (
    "router-egress-emergency-refresh.sh"
    in init_text
)

init_has_loop = bool(
    re.search(r"\bwhile\b|\buntil\b", init_text)
)

init_has_sleep = bool(
    re.search(r"\bsleep\b", init_text)
)

hook_has_loop = bool(
    re.search(r"\bwhile\b|\buntil\b", hook_text)
)

hook_has_sleep = bool(
    re.search(r"\bsleep\b", hook_text)
)

hook_has_runner_reference = (
    "router-egress-emergency-refresh.sh"
    in hook_text
    or re.search(r"\bRUNNER\b", hook_text) is not None
)

hook_explicit_dry_run = (
    "--dry-run" in hook_text
    and hook_has_runner_reference
)

hook_commit_refs = (
    "--commit" in hook_text
    or "EMERGENCY_COMMIT_ENABLED" in hook_text
)

service_running = facts.get("service_running") == "true"
service_enabled = facts.get("service_enabled") == "true"

cron_count = int(
    facts.get("cron_reference_count", "0")
)

process_text = blocks.get("relevant_processes", "")

process_seen = bool(
    re.search(
        r"router-egress-emergency-decision|"
        r"router-egress-emergency-refresh",
        process_text,
    )
)

ubus_text = blocks.get("ubus_service", "")

ubus_seen = (
    "router-egress-emergency-decision" in ubus_text
)

if cron_count > 0:
    scheduler_mechanism = "CRON"
    mechanism_proven = True

elif (
    uses_procd
    and procd_open_instance
    and procd_command
    and init_hook_reference
    and init_has_loop
    and init_has_sleep
):
    scheduler_mechanism = "PROCD_INIT_WRAPPER_LOOP"
    mechanism_proven = True

elif (
    uses_procd
    and procd_open_instance
    and procd_command
    and init_hook_reference
    and hook_has_loop
    and hook_has_sleep
):
    scheduler_mechanism = "PROCD_HOOK_INTERNAL_LOOP"
    mechanism_proven = True

elif (
    uses_procd
    and procd_open_instance
    and procd_command
    and init_hook_reference
    and procd_respawn
):
    scheduler_mechanism = "PROCD_RESPAWN"
    mechanism_proven = True

elif (
    uses_procd
    and procd_open_instance
    and procd_command
    and init_hook_reference
    and service_running
    and process_seen
):
    scheduler_mechanism = "PROCD_LONG_RUNNING_PROCESS"
    mechanism_proven = True

else:
    scheduler_mechanism = "UNPROVEN"
    mechanism_proven = False

runner = json_blocks.get("runner", {})
hook = json_blocks.get("hook", {})

counter = int(facts.get("repair_counter", "-1"))
threshold = runner.get("daily_fail_threshold")

automatic_path_safe = (
    hook_explicit_dry_run
    and not hook_commit_refs
    and hook.get("mode") == "dry-run"
    and hook.get("decision") == "would_run_emergency_refresh"
    and hook.get("commit_enabled") is False
    and hook.get("direct_failopen_enabled") is False
)

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

    "stderr_empty":
        not stderr_text,

    "live_sources_match_archive":
        live_hashes_match,

    "service_running":
        service_running,

    "service_enabled":
        service_enabled,

    "scheduler_mechanism_proven":
        mechanism_proven,

    "automatic_hook_explicit_dry_run":
        hook_explicit_dry_run,

    "automatic_hook_has_no_commit_path":
        not hook_commit_refs,

    "automatic_runtime_path_safe":
        automatic_path_safe,

    "commit_still_disabled_after_rollback":
        facts.get("commit_enabled") == "false",

    "threshold_state_consistent":
        isinstance(threshold, int)
        and counter >= threshold
        and runner.get("threshold_reached") is True
        and hook.get("threshold_reached") is True,

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

    "no_changes":
        facts.get("production_modified") == "false"
        and facts.get("vm101_modified") == "false"
        and facts.get("commit_flag_changed") == "false"
        and facts.get("watcher_invoked") == "false"
        and facts.get("refresh_ran") == "false"
        and facts.get("rebalance_apply_ran") == "false"
        and facts.get("direct_failopen_changed") == "false",
}

all_ok = all(checks.values())

assessment = {
    "all_ok": all_ok,
    "decision": (
        "PASS_STEP_050M06D1_READONLY_AUDIT_SCHEDULER_CONTRACT_VM101"
        if all_ok
        else
        "STOP_STEP_050M06D1_READONLY_AUDIT_SCHEDULER_CONTRACT_VM101"
    ),
    "checks": checks,
    "failed_checks": [
        name
        for name, value in checks.items()
        if not value
    ],
    "scheduler_contract": {
        "mechanism": scheduler_mechanism,
        "mechanism_proven": mechanism_proven,
        "uses_procd": uses_procd,
        "procd_open_instance": procd_open_instance,
        "procd_command": procd_command,
        "procd_respawn": procd_respawn,
        "init_hook_reference": init_hook_reference,
        "init_runner_reference": init_runner_reference,
        "init_has_loop": init_has_loop,
        "init_has_sleep": init_has_sleep,
        "hook_has_loop": hook_has_loop,
        "hook_has_sleep": hook_has_sleep,
        "process_seen": process_seen,
        "ubus_seen": ubus_seen,
        "cron_reference_count": cron_count,
        "service_running": service_running,
        "service_enabled": service_enabled,
    },
    "automatic_path": {
        "hook_has_runner_reference":
            hook_has_runner_reference,
        "hook_explicit_dry_run":
            hook_explicit_dry_run,
        "hook_commit_refs":
            hook_commit_refs,
        "runtime_mode":
            hook.get("mode"),
        "runtime_decision":
            hook.get("decision"),
        "safe":
            automatic_path_safe,
    },
    "runtime": {
        "commit_enabled":
            facts.get("commit_enabled"),
        "repair_counter":
            counter,
        "daily_fail_threshold":
            threshold,
        "threshold_reached":
            runner.get("threshold_reached"),
    },
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "commit_flag_changed": False,
        "watcher_invoked": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "next_step": (
        "STEP_050M06E_ENABLE_COMMIT_WITH_PROVEN_SCHEDULER_CONTRACT"
        if all_ok
        else
        "REPAIR_OR_CLARIFY_SCHEDULER_CONTRACT_BEFORE_M06"
    ),
}

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

MECHANISM="$(
  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["scheduler_contract"]["mechanism"])
PY
)"

AUTO_SAFE="$(
  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["automatic_path"]["safe"]
    else "false"
)
PY
)"

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

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

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

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

stage "06/07" "Публикую scheduler contract report"

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${DECISION}
all_ok=${ALL_OK}
mode=READ_ONLY_SCHEDULER_CONTRACT_AUDIT

previous_stop:
  step=STEP_050M06D_ENABLE_COMMIT_PROVEN_OPENWRT_COMMANDS
  only_failed_check=scheduler_safe
  production_modified=false
  rollback_rc=0

scheduler_contract:
  mechanism=${MECHANISM}
  mechanism_proven=${ALL_OK}
  automatic_path_safe=${AUTO_SAFE}
  cron_required=false
  cron_zero_is_not_automatically_failure=true

safety:
  production_modified=false
  vm101_modified=false
  commit_flag_changed=false
  watcher_invoked=false
  refresh_ran=false
  rebalance_apply_ran=false
  direct_failopen_changed=false

plan:
  current_milestone=M06
  milestone_changed=false

failed_checks:
  ${FAILED_CHECKS}

next_step:
  ${NEXT_STEP}

artifacts:
  step_script=step.sh
  vm101_script=vm101.sh
  tool_hash_script=vm101-tool-hash-check.sh
  assessment=assessment.json
  init_source=sources/router-egress-emergency-decision.init
  hook_source=sources/router-egress-emergency-decision-hook.sh
  runner_source=sources/router-egress-emergency-refresh.sh
  vm101_output=vm101.txt

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,
    "mode": "READ_ONLY_SCHEDULER_CONTRACT_AUDIT",
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "commit_flag_changed": False,
        "watcher_invoked": False,
        "refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "plan": {
        "current_milestone": "M06",
        "milestone_changed": False,
    },
    "artifacts": {
        "step_script": "step.sh",
        "vm101_script": "vm101.sh",
        "tool_hash_script":
            "vm101-tool-hash-check.sh",
        "assessment": "assessment.json",
        "init_source":
            "sources/router-egress-emergency-decision.init",
        "hook_source":
            "sources/router-egress-emergency-decision-hook.sh",
        "runner_source":
            "sources/router-egress-emergency-refresh.sh",
        "vm101_output": "vm101.txt",
    },
    "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>
</ul>

<h2>Результаты</h2>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="assessment.json">assessment.json</a></li>
<li><a href="vm101.txt">vm101.txt</a></li>
<li><a href="vm101.stderr">vm101.stderr</a></li>
<li><a href="vm101-tool-hash-check.txt">tool hash check</a></li>
</ul>

<h2>Точные исходники scheduler</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"

stage "07/07" "Завершаю без изменений VM101"

trap - ERR

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

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

echo "automatic_path_safe=$AUTO_SAFE" |
  tee -a "$PROGRESS_LOG"

echo "failed_checks=$FAILED_CHECKS" |
  tee -a "$PROGRESS_LOG"

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

print_links

[ "$ALL_OK" = true ]
