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

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

STEP="STEP_050M06A_COMPARE_LIVE_TOOLCHAIN_TO_FROZEN_MAP"
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}"

LP2_DIR="${PUBROOT}/20260711-151610_step050lp2_readonly_inventory_vm101_recovery_toolchain"
LP2A_DIR="${PUBROOT}/20260711-172535_step050lp2a_classify_inventory_stop_and_freeze_tool_map"
SOURCE_ROOT="${LP2_DIR}/sources/vm101-root"
FROZEN_MAP="${LP2A_DIR}/source-map.txt"

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

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

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

chmod 700 "$WORK"

# Точный copy-paste script публикуется до любых проверок.
cp -a "$0" "$REPORT_DIR/step.sh"
chmod 600 "$REPORT_DIR/step.sh"

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

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

  date -u '+%Y-%m-%dT%H:%M:%SZ' |
    sed 's/^/    utc=/' |
    tee -a "$PROGRESS_LOG"
}

print_links() {
  echo
  echo "TRYCF_REPORT=$TRYCF_REPORT"
  echo "REPORT_TXT=$REPORT_TXT"
  echo "FACTS_JSON=$FACTS_JSON"
  echo "ARCHITECTURE_PLAN=$ARCHITECTURE_PLAN"
  echo "XS_MAP=$XS_MAP"
  echo "GLOBAL_PROJECT_PLAN=$GLOBAL_PROJECT_PLAN"
}

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

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=STOP_${STEP}_${reason}
all_ok=false
mode=READ_ONLY_FROZEN_MAP_COMPARISON
error_rc=${rc}
error_line=${line}
production_modified=false
vm101_modified=false
real_refresh_ran=false
rebalance_apply_ran=false
direct_failopen_changed=false

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
EOF

  python3 - \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$TRYCF_REPORT" \
    "$REPORT_TXT" \
    "$FACTS_JSON" \
    "$ARCHITECTURE_PLAN" \
    "$XS_MAP" \
    "$GLOBAL_PROJECT_PLAN" \
    > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

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

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "assessment": {
        "decision": f"STOP_{step}_{reason}",
        "all_ok": False,
        "error_rc": int(rc),
        "error_line": int(line),
    },
    "mode": "READ_ONLY_FROZEN_MAP_COMPARISON",
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
    },
}, ensure_ascii=False, indent=2))
PY
}

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

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

trap 'on_error "$LINENO"' ERR

stage "01/07" "Проверяю опубликованные frozen-артефакты"

for required in \
  "$FROZEN_MAP" \
  "$LP2A_DIR/report.txt" \
  "$LP2A_DIR/facts.json" \
  "$LP2A_DIR/diagnosis.json" \
  "$LP2A_DIR/step.sh" \
  "$LP2_DIR/report.txt" \
  "$LP2_DIR/facts.json" \
  "$LP2_DIR/source-publication-audit.json"
do
  [ -s "$required" ] || {
    echo "MISSING_REQUIRED=$required"
    exit 1
  }
done

grep -Fq \
  "decision=PASS_STEP_050LP2A_CLASSIFY_INVENTORY_STOP_AND_FREEZE_TOOL_MAP" \
  "$LP2A_DIR/report.txt"

grep -Fq \
  "source_map_complete=true" \
  "$LP2A_DIR/report.txt"

cp -a \
  "$FROZEN_MAP" \
  "$REPORT_DIR/sources/frozen-source-map.txt"

cp -a \
  "$LP2A_DIR/diagnosis.json" \
  "$REPORT_DIR/sources/step050lp2a-diagnosis.json"

cp -a \
  "$LP2A_DIR/step.sh" \
  "$REPORT_DIR/sources/step050lp2a-step.sh"

stage "02/07" "Разбираю frozen-карту и проверяю архивированные исходники"

python3 - \
  "$FROZEN_MAP" \
  "$SOURCE_ROOT" \
  "$REPORT_DIR/expected-map.json" \
  "$REPORT_DIR/expected-map.tsv" \
  "$REPORT_DIR/frozen-map-audit.json" <<'PY'
import hashlib
import json
import re
import sys
from pathlib import Path

map_path = Path(sys.argv[1])
source_root = Path(sys.argv[2])
json_path = Path(sys.argv[3])
tsv_path = Path(sys.argv[4])
audit_path = Path(sys.argv[5])

pattern = re.compile(
    r"^present=(true|false)"
    r"(?:\s+sha256=([0-9a-fA-F]{64}))?"
    r"\s+path=(.+)$"
)

records = []
parse_errors = []

for number, raw in enumerate(
    map_path.read_text(
        encoding="utf-8",
        errors="replace",
    ).splitlines(),
    start=1,
):
    line = raw.strip()

    if not line:
        continue

    match = pattern.match(line)

    if not match:
        parse_errors.append({
            "line": number,
            "content": raw,
        })
        continue

    present = match.group(1) == "true"
    frozen_hash = (
        match.group(2).lower()
        if match.group(2)
        else None
    )
    relative = match.group(3).lstrip("/")
    archived_path = source_root / relative

    archived_exists = archived_path.is_file()

    archived_hash = None

    if archived_exists:
        archived_hash = hashlib.sha256(
            archived_path.read_bytes()
        ).hexdigest()

    records.append({
        "path": "/" + relative,
        "relative_path": relative,
        "present_in_frozen_map": present,
        "frozen_sha256": frozen_hash,
        "archived_source_exists": archived_exists,
        "archived_source_sha256": archived_hash,
        "frozen_matches_archive": (
            present
            and archived_exists
            and frozen_hash == archived_hash
        ),
    })

if parse_errors:
    raise SystemExit(
        "STOP: source-map содержит нераспознанные строки: "
        + json.dumps(parse_errors, ensure_ascii=False)
    )

present_records = [
    record
    for record in records
    if record["present_in_frozen_map"]
]

if not present_records:
    raise SystemExit(
        "STOP: frozen source-map не содержит present=true"
    )

self_consistent = all(
    record["frozen_matches_archive"]
    for record in present_records
)

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

with tsv_path.open("w", encoding="utf-8") as output:
    for record in present_records:
        output.write(
            record["frozen_sha256"]
            + "\t"
            + record["path"]
            + "\n"
        )

audit = {
    "record_count": len(present_records),
    "parse_error_count": len(parse_errors),
    "frozen_map_self_consistent": self_consistent,
    "records": present_records,
}

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

if not self_consistent:
    raise SystemExit(
        "STOP: frozen source-map не совпадает "
        "с опубликованными архивными исходниками"
    )
PY

EXPECTED_COUNT="$(
  wc -l < "$REPORT_DIR/expected-map.tsv" |
    tr -d ' '
)"

echo "expected_tool_count=$EXPECTED_COUNT" |
  tee -a "$PROGRESS_LOG"

stage "03/07" "Создаю точный remote checker из реальной frozen-карты"

python3 - \
  "$REPORT_DIR/expected-map.tsv" \
  "$REPORT_DIR/vm101.sh" <<'PY'
import shlex
import sys
from pathlib import Path

tsv_path = Path(sys.argv[1])
output_path = Path(sys.argv[2])

records = []

for raw in tsv_path.read_text(
    encoding="utf-8",
).splitlines():
    if not raw.strip():
        continue

    expected, path = raw.split("\t", 1)
    records.append((expected, path))

lines = [
    "#!/bin/sh",
    "set -u",
    "umask 077",
    "",
    "ERRORS=0",
    "CHECKED=0",
    "",
    "check_file() {",
    '  expected="$1"',
    '  path="$2"',
    "",
    "  CHECKED=$((CHECKED + 1))",
    "",
    '  if [ ! -f "$path" ]; then',
    "    printf '__HASH__ match=false missing=true "
    "expected=%s actual=MISSING path=%s\\n' "
    '"$expected" "$path"',
    "    ERRORS=$((ERRORS + 1))",
    "    return",
    "  fi",
    "",
    '  actual="$(sha256sum "$path" | '
    "sed 's/[[:space:]].*$//')\"",
    "",
    '  if [ "$actual" = "$expected" ]; then',
    "    match=true",
    "  else",
    "    match=false",
    "    ERRORS=$((ERRORS + 1))",
    "  fi",
    "",
    "  printf '__HASH__ match=%s missing=false "
    "expected=%s actual=%s path=%s\\n' "
    '"$match" "$expected" "$actual" "$path"',
    "}",
    "",
]

for expected, path in records:
    lines.append(
        "check_file "
        + shlex.quote(expected)
        + " "
        + shlex.quote(path)
    )

lines.extend([
    "",
    "printf '__SUMMARY__ checked=%s errors=%s\\n' "
    '"$CHECKED" "$ERRORS"',
    "",
    "exit 0",
    "",
])

output_path.write_text(
    "\n".join(lines),
    encoding="utf-8",
)
PY

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

stage "04/07" "Сравниваю frozen-карту с live 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-comparison.txt" \
  2> "$REPORT_DIR/vm101-comparison.stderr"
then
  VM101_RC=0
else
  VM101_RC=$?
fi

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

[ "$VM101_RC" -eq 0 ] || {
  echo "STOP: remote hash inventory завершился rc=$VM101_RC"
  exit 1
}

stage "05/07" "Классифицирую все совпадения и расхождения"

python3 - \
  "$REPORT_DIR/expected-map.json" \
  "$REPORT_DIR/frozen-map-audit.json" \
  "$REPORT_DIR/vm101-comparison.txt" \
  "$REPORT_DIR/vm101-comparison.stderr" \
  "$REPORT_DIR/comparison.json" <<'PY'
import json
import re
import sys
from pathlib import Path

(
    expected_path,
    frozen_audit_path,
    live_path,
    stderr_path,
    output_path,
) = sys.argv[1:]

expected_records = json.loads(
    Path(expected_path).read_text(
        encoding="utf-8",
    )
)

frozen_audit = json.loads(
    Path(frozen_audit_path).read_text(
        encoding="utf-8",
    )
)

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

pattern = re.compile(
    r"^__HASH__ "
    r"match=(true|false) "
    r"missing=(true|false) "
    r"expected=([0-9a-f]{64}) "
    r"actual=([0-9a-f]{64}|MISSING) "
    r"path=(.+)$"
)

live_records = {}
summary = None
parse_errors = []

for number, raw in enumerate(
    Path(live_path).read_text(
        encoding="utf-8",
        errors="replace",
    ).splitlines(),
    start=1,
):
    if raw.startswith("__HASH__ "):
        match = pattern.match(raw)

        if not match:
            parse_errors.append({
                "line": number,
                "content": raw,
            })
            continue

        path = match.group(5)

        live_records[path] = {
            "path": path,
            "match": match.group(1) == "true",
            "missing": match.group(2) == "true",
            "expected_sha256": match.group(3),
            "actual_sha256": match.group(4),
        }

    elif raw.startswith("__SUMMARY__ "):
        values = {}

        for token in raw[len("__SUMMARY__ "):].split():
            key, value = token.split("=", 1)
            values[key] = int(value)

        summary = values

expected_by_path = {
    record["path"]: record
    for record in expected_records
}

combined = []

for path, expected in expected_by_path.items():
    live = live_records.get(path)

    combined.append({
        "path": path,
        "frozen_sha256": expected["frozen_sha256"],
        "archive_sha256":
            expected["archived_source_sha256"],
        "frozen_matches_archive":
            expected["frozen_matches_archive"],
        "live_seen": live is not None,
        "live_missing":
            live["missing"] if live else True,
        "live_sha256":
            live["actual_sha256"] if live else None,
        "live_matches_frozen":
            live["match"] if live else False,
    })

missing_live = [
    item["path"]
    for item in combined
    if item["live_missing"]
]

drifted = [
    {
        "path": item["path"],
        "frozen_sha256": item["frozen_sha256"],
        "live_sha256": item["live_sha256"],
    }
    for item in combined
    if (
        not item["live_missing"]
        and not item["live_matches_frozen"]
    )
]

exact = (
    frozen_audit["frozen_map_self_consistent"]
    and not parse_errors
    and len(live_records) == len(expected_by_path)
    and not missing_live
    and not drifted
)

if exact:
    classification = "LIVE_TOOLCHAIN_EXACTLY_MATCHES_FROZEN_MAP"
    next_step = (
        "M06_ENABLE_EMERGENCY_COMMIT_"
        "USING_DYNAMIC_FROZEN_MAP"
    )
elif missing_live:
    classification = "LIVE_TOOLCHAIN_FILES_MISSING"
    next_step = "RESEARCH_MISSING_LIVE_TOOLS_BEFORE_M06"
else:
    classification = "LIVE_TOOLCHAIN_DRIFT_DETECTED"
    next_step = "READONLY_REVIEW_DRIFTED_TOOLS_BEFORE_M06"

result = {
    "audit_completed": True,
    "toolchain_exact_match": exact,
    "classification": classification,
    "expected_count": len(expected_by_path),
    "live_count": len(live_records),
    "remote_summary": summary,
    "stderr": stderr,
    "parse_errors": parse_errors,
    "missing_live": missing_live,
    "drifted": drifted,
    "records": combined,
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "next_step": next_step,
}

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

TOOLCHAIN_MATCH="$(
  python3 - "$REPORT_DIR/comparison.json" <<'PY'
import json
import sys

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

print(
    "true"
    if data["toolchain_exact_match"]
    else "false"
)
PY
)"

CLASSIFICATION="$(
  python3 - "$REPORT_DIR/comparison.json" <<'PY'
import json
import sys

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

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

MISSING_COUNT="$(
  python3 - "$REPORT_DIR/comparison.json" <<'PY'
import json
import sys

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

print(len(data["missing_live"]))
PY
)"

DRIFT_COUNT="$(
  python3 - "$REPORT_DIR/comparison.json" <<'PY'
import json
import sys

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

print(len(data["drifted"]))
PY
)"

NEXT_STEP="$(
  python3 - "$REPORT_DIR/comparison.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" "Публикую точный результат read-only сравнения"

DECISION="PASS_${STEP}_AUDIT_COMPLETED"

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${DECISION}
all_ok=true
mode=READ_ONLY_FROZEN_MAP_COMPARISON
production_modified=false
vm101_modified=false
real_refresh_ran=false
rebalance_apply_ran=false
direct_failopen_changed=false

classification:
  previous_m06_attempt_reached_vm101_change=false
  previous_m06_attempt_failure=MANUALLY_HARDCODED_HASHES
  frozen_map_source=${FROZEN_MAP}
  expected_tool_count=${EXPECTED_COUNT}
  toolchain_exact_match=${TOOLCHAIN_MATCH}
  missing_live_count=${MISSING_COUNT}
  drifted_count=${DRIFT_COUNT}
  result=${CLASSIFICATION}

plan:
  current_milestone=M06
  milestone_changed=false

next_step:
  ${NEXT_STEP}

artifacts:
  step_script=step.sh
  vm101_script=vm101.sh
  frozen_source_map=sources/frozen-source-map.txt
  expected_map=expected-map.json
  frozen_map_audit=frozen-map-audit.json
  live_comparison=vm101-comparison.txt
  comparison=comparison.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 - \
  "$STEP" \
  "$TS" \
  "$DECISION" \
  "$TOOLCHAIN_MATCH" \
  "$CLASSIFICATION" \
  "$EXPECTED_COUNT" \
  "$MISSING_COUNT" \
  "$DRIFT_COUNT" \
  "$NEXT_STEP" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" \
  "$ARCHITECTURE_PLAN" \
  "$XS_MAP" \
  "$GLOBAL_PROJECT_PLAN" \
  > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    step,
    timestamp,
    decision,
    toolchain_match,
    classification,
    expected_count,
    missing_count,
    drift_count,
    next_step,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
) = sys.argv[1:]

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "generated_at_utc": timestamp,
    "assessment": {
        "decision": decision,
        "all_ok": True,
        "audit_completed": True,
        "classification": classification,
    },
    "comparison": {
        "toolchain_exact_match":
            toolchain_match == "true",
        "expected_tool_count":
            int(expected_count),
        "missing_live_count":
            int(missing_count),
        "drifted_count":
            int(drift_count),
    },
    "previous_attempt": {
        "vm101_change_reached": False,
        "failure":
            "MANUALLY_HARDCODED_HASHES",
    },
    "plan": {
        "current_milestone": "M06",
        "milestone_changed": False,
        "next_step": next_step,
    },
    "safety": {
        "production_modified": False,
        "vm101_modified": False,
        "real_refresh_ran": False,
        "rebalance_apply_ran": False,
        "direct_failopen_changed": False,
    },
    "artifacts": {
        "step_script": "step.sh",
        "vm101_script": "vm101.sh",
        "frozen_source_map":
            "sources/frozen-source-map.txt",
        "expected_map": "expected-map.json",
        "frozen_map_audit":
            "frozen-map-audit.json",
        "live_comparison":
            "vm101-comparison.txt",
        "comparison":
            "comparison.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:1050px;margin:40px auto;padding:0 20px;line-height:1.5">

<h1>${STEP}</h1>

<h2>Исполнявшиеся скрипты</h2>
<ul>
<li><a href="step.sh">step.sh</a></li>
<li><a href="vm101.sh">vm101.sh</a></li>
</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="comparison.json">comparison.json</a></li>
<li><a href="vm101-comparison.txt">vm101-comparison.txt</a></li>
<li><a href="frozen-map-audit.json">frozen-map-audit.json</a></li>
<li><a href="expected-map.json">expected-map.json</a></li>
<li><a href="sources/frozen-source-map.txt">frozen-source-map.txt</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 "classification=$CLASSIFICATION" |
  tee -a "$PROGRESS_LOG"

echo "toolchain_exact_match=$TOOLCHAIN_MATCH" |
  tee -a "$PROGRESS_LOG"

echo "missing_live_count=$MISSING_COUNT" |
  tee -a "$PROGRESS_LOG"

echo "drifted_count=$DRIFT_COUNT" |
  tee -a "$PROGRESS_LOG"

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

print_links
