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

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

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

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

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

PRIVATE_ROOT="${STATE_ROOT}/private-mirrors/vm101"
PUBLIC_REFERENCE_ROOT="${PUBROOT}/vm101-reference"

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

BUILD_TS="$(date -u +%Y%m%d-%H%M%S)"
REFERENCE_ID="${BUILD_TS}_vm101_reference_v1b"

REPORT_SLUG="${BUILD_TS}_step050m07r01b_salvage_build_vm101_reference_v1"
REPORT_DIR="${PUBROOT}/${REPORT_SLUG}"

PRIVATE_SNAPSHOT="${PRIVATE_ROOT}/snapshots/${REFERENCE_ID}"
PUBLIC_STAGING="${PRIVATE_ROOT}/public-staging/${REFERENCE_ID}"

PUBLIC_SNAPSHOT="${PUBLIC_REFERENCE_ROOT}/snapshots/${REFERENCE_ID}"
PUBLIC_CURRENT="${PUBLIC_REFERENCE_ROOT}/current"

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

VM101_REFERENCE="${PUBLIC_BASE}/vm101-reference/current/"
VM101_REFERENCE_SNAPSHOT="${PUBLIC_BASE}/vm101-reference/snapshots/${REFERENCE_ID}/"

mkdir -p \
  "$REPORT_DIR" \
  "$PRIVATE_SNAPSHOT/managed-files" \
  "$PRIVATE_SNAPSHOT/runtime" \
  "$PRIVATE_SNAPSHOT/metadata" \
  "$PUBLIC_STAGING"

chmod -R go-rwx "$PRIVATE_ROOT" 2>/dev/null || true

# Сохраняем точный STEP до любых проверок и SSH.
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"

FILES_DISCOVERED=0
FILES_FETCHED=0
REQUIRED_MISSING=0
PUBLIC_FILES=0
REDACTIONS=0
SECRET_FINDINGS=0
SYNTAX_FAILURES=0
VM101_INVENTORY_RC="NOT_RUN"

PRIVATE_SNAPSHOT_COMPLETE=false
PUBLIC_SNAPSHOT_PUBLISHED=false
PUBLIC_CURRENT_UPDATED=false
VM101_MODIFIED=false

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"
  echo "VM101_REFERENCE=$VM101_REFERENCE"
}

remote_vm101_script() {
  ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o ServerAliveInterval=20 \
    -o ServerAliveCountMax=6 \
    -o LogLevel=ERROR \
    pve-mgts \
    "ssh \
      -o BatchMode=yes \
      -o ConnectTimeout=8 \
      -o ServerAliveInterval=20 \
      -o ServerAliveCountMax=6 \
      -o StrictHostKeyChecking=no \
      -o UserKnownHostsFile=/dev/null \
      -o LogLevel=ERROR \
      -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
      root@10.71.100.2 \
      'sh -s'"
}

remote_vm101_cmd() {
  local command="$1"
  local encoded

  encoded="$(
    printf '%s' "$command" |
      base64 -w0
  )"

  ssh -n \
    -o BatchMode=yes \
    -o ConnectTimeout=8 \
    -o ServerAliveInterval=20 \
    -o ServerAliveCountMax=6 \
    -o LogLevel=ERROR \
    pve-mgts \
    "printf '%s' '$encoded' |
     base64 -d |
     ssh \
       -o BatchMode=yes \
       -o ConnectTimeout=8 \
       -o ServerAliveInterval=20 \
       -o ServerAliveCountMax=6 \
       -o StrictHostKeyChecking=no \
       -o UserKnownHostsFile=/dev/null \
       -o LogLevel=ERROR \
       -i /root/.ssh/pve_to_openwrt_mgts_ed25519 \
       root@10.71.100.2 \
       'sh -s'"
}

create_report_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;padding:0 20px">
<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="build-assessment.json">build-assessment.json</a></li>
<li><a href="file-inventory.tsv">file-inventory.tsv</a></li>
<li><a href="fetch-results.tsv">fetch-results.tsv</a></li>
<li><a href="secret-scan.json">secret-scan.json</a></li>
<li><a href="syntax-checks.txt">syntax-checks.txt</a></li>
</ul>

<h2>VM101 Reference</h2>
<ul>
<li><a href="${VM101_REFERENCE}">current</a></li>
<li><a href="${VM101_REFERENCE_SNAPSHOT}">immutable snapshot</a></li>
</ul>

<h2>Исполнение</h2>
<ul>
<li><a href="inventory-raw.txt">inventory-raw.txt</a></li>
<li><a href="inventory.stderr">inventory.stderr</a></li>
<li><a href="step.sh">step.sh</a></li>
</ul>
</body>
</html>
EOF
}

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

  python3 - \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$CURRENT_STAGE" \
    "$LAST_SUCCESS" \
    "$FILES_DISCOVERED" \
    "$FILES_FETCHED" \
    "$REQUIRED_MISSING" \
    "$PUBLIC_FILES" \
    "$REDACTIONS" \
    "$SECRET_FINDINGS" \
    "$SYNTAX_FAILURES" \
    "$PRIVATE_SNAPSHOT_COMPLETE" \
    "$PUBLIC_SNAPSHOT_PUBLISHED" \
    "$PUBLIC_CURRENT_UPDATED" \
    "$REFERENCE_ID" \
    "$PRIVATE_SNAPSHOT" \
    "$VM101_REFERENCE" \
    "$VM101_REFERENCE_SNAPSHOT" \
    > "$REPORT_DIR/diagnostic.json" <<'PY'
import json
import sys

(
    step,
    reason,
    rc,
    line,
    stage,
    last_success,
    files_discovered,
    files_fetched,
    required_missing,
    public_files,
    redactions,
    secret_findings,
    syntax_failures,
    private_complete,
    snapshot_published,
    current_updated,
    reference_id,
    private_snapshot,
    current_url,
    snapshot_url,
) = sys.argv[1:]

classification = {
    "REQUIRED_VM101_FILE_MISSING":
        "REQUIRED_MANAGED_FILE_MISSING",

    "MANAGED_FILE_FETCH_INCOMPLETE":
        "SSH_STDIN_DRAIN_OR_FETCH_INCOMPLETE",
    "VM101_INVENTORY_FAILED":
        "VM101_READONLY_INVENTORY_FAILED",
    "SECRET_SCAN_FAILED":
        "PUBLIC_REFERENCE_SECRET_SCAN_FAILED",
    "PUBLIC_SYNTAX_CHECK_FAILED":
        "SANITIZED_REFERENCE_SYNTAX_FAILED",
    "PUBLICATION_FAILED":
        "REFERENCE_PUBLICATION_FAILED",
}.get(reason, "REFERENCE_BUILD_FAILURE")

print(json.dumps({
    "schema": "vm101-reference-build-diagnostic-v1",
    "step": step,
    "failure": {
        "reason": reason,
        "rc": int(rc),
        "line": int(line),
        "stage": stage,
        "last_success": last_success,
    },
    "automatic_classification": classification,
    "counts": {
        "files_discovered": int(files_discovered),
        "files_fetched": int(files_fetched),
        "required_missing": int(required_missing),
        "public_files": int(public_files),
        "redactions": int(redactions),
        "secret_findings": int(secret_findings),
        "syntax_failures": int(syntax_failures),
    },
    "state": {
        "private_snapshot_complete":
            private_complete == "true",
        "public_snapshot_published":
            snapshot_published == "true",
        "public_current_updated":
            current_updated == "true",
        "vm101_modified": False,
    },
    "reference": {
        "id": reference_id,
        "private_snapshot": private_snapshot,
        "current_url": current_url,
        "snapshot_url": snapshot_url,
    },
}, ensure_ascii=False, indent=2))
PY

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=STOP_${STEP}_${reason}
step_execution=STOP
operation_result=REFERENCE_NOT_PUBLISHED
production_health=UNCHANGED
milestone_status=M07_IN_PROGRESS
all_ok=false

failure:
  rc=${rc}
  line=${line}
  stage=${CURRENT_STAGE}
  last_success=${LAST_SUCCESS}

counts:
  files_discovered=${FILES_DISCOVERED}
  files_fetched=${FILES_FETCHED}
  required_missing=${REQUIRED_MISSING}
  public_files=${PUBLIC_FILES}
  redactions=${REDACTIONS}
  secret_findings=${SECRET_FINDINGS}
  syntax_failures=${SYNTAX_FAILURES}

reference:
  id=${REFERENCE_ID}
  private_snapshot_complete=${PRIVATE_SNAPSHOT_COMPLETE}
  public_snapshot_published=${PUBLIC_SNAPSHOT_PUBLISHED}
  public_current_updated=${PUBLIC_CURRENT_UPDATED}

safety:
  vm101_read_only=true
  vm101_modified=false
  network_changed=false
  services_changed=false
  state_changed=false
  refresh_ran=false
  rebalance_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}
VM101_REFERENCE=${VM101_REFERENCE}
EOF

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

(
    diagnostic_path,
    step,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
    vm101_reference,
) = 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}_{diagnostic['failure']['reason']}",
        "step_execution": "STOP",
        "operation_result": "REFERENCE_NOT_PUBLISHED",
        "production_health": "UNCHANGED",
        "milestone_status": "M07_IN_PROGRESS",
        "all_ok": False,
    },
    "diagnostic": diagnostic,
    "safety": {
        "vm101_read_only": True,
        "vm101_modified": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "refresh_ran": False,
        "rebalance_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,
        "vm101_reference": vm101_reference,
    },
}, ensure_ascii=False, indent=2))
PY

  create_report_index

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

  print_links
  exit "$rc"
}

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

  trap - ERR
  write_stop "$reason" "$rc" "$line"
}

trap 'rc=$?; fatal "UNEXPECTED_ERROR" "$rc" "$LINENO"' ERR

stage "01/09" "Проверяю prerequisites и создаю private workspace"

for command in \
  python3 \
  ssh \
  base64 \
  sha256sum \
  find \
  sort \
  cp \
  mv \
  mkdir \
  chmod \
  sh
do
  command -v "$command" >/dev/null 2>&1 || {
    echo "MISSING_LOCAL_COMMAND=$command"
    fatal "LOCAL_PREREQUISITE_MISSING" 2 "$LINENO"
  }
done

cat > "$REPORT_DIR/required-files.txt" <<'EOF'
/root/hmn/hmn-code-test.sh
/root/hmn/hmn-download-all-awg.sh
/root/hmn/hmn-refresh-pool-safe.sh
/root/hmn/hmn-validate-current-pool.sh
/usr/local/sbin/router-egress-emergency-refresh.sh
/usr/local/sbin/router-egress-hmn-plan-top5.sh
/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh
/usr/local/lib/router-egress-recovery-state.sh
/etc/init.d/router-egress-emergency-decision
/etc/init.d/router-egress-health-repair
/etc/crontabs/root
/etc/config/network
EOF

mark_success "private_workspace_created"

stage "02/09" "Обнаруживаю managed toolchain VM101"

cat > "$REPORT_DIR/discover-remote.sh" <<'VM101'
#!/bin/sh
set -u

for path in \
  /root/hmn/*.sh \
  /usr/local/sbin/router-egress-* \
  /usr/local/lib/router-egress-* \
  /etc/init.d/router-egress-* \
  /etc/router-egress-*
do
  if [ -f "$path" ]; then
    printf '%s\n' "$path"
  fi
done

for path in \
  /etc/crontabs/root \
  /etc/config/network \
  /etc/config/firewall \
  /etc/config/system \
  /etc/config/pbr
do
  if [ -f "$path" ]; then
    printf '%s\n' "$path"
  fi
done
VM101

chmod 600 "$REPORT_DIR/discover-remote.sh"
sh -n "$REPORT_DIR/discover-remote.sh"

remote_vm101_script \
  < "$REPORT_DIR/discover-remote.sh" \
  > "$REPORT_DIR/discovered-files.txt" \
  2> "$REPORT_DIR/discovery.stderr"

cat \
  "$REPORT_DIR/required-files.txt" \
  "$REPORT_DIR/discovered-files.txt" |
  sed '/^[[:space:]]*$/d' |
  sort -u \
  > "$REPORT_DIR/all-managed-files.txt"

python3 - \
  "$REPORT_DIR/required-files.txt" \
  "$REPORT_DIR/all-managed-files.txt" \
  > "$REPORT_DIR/file-inventory.tsv" <<'PY'
import sys
from pathlib import Path

required_path, all_path = sys.argv[1:]

required = {
    line.strip()
    for line in Path(required_path).read_text(
        encoding="utf-8"
    ).splitlines()
    if line.strip()
}

all_files = [
    line.strip()
    for line in Path(all_path).read_text(
        encoding="utf-8"
    ).splitlines()
    if line.strip()
]

print("required\tsource_path\trelative_path")

for path in all_files:
    relative = path.lstrip("/")

    print(
        f"{'true' if path in required else 'false'}"
        f"\t{path}\t{relative}"
    )
PY

FILES_DISCOVERED="$(
  tail -n +2 "$REPORT_DIR/file-inventory.tsv" |
    grep -c . ||
  true
)"

echo "files_discovered=$FILES_DISCOVERED" |
  tee -a "$PROGRESS_LOG"

[ "$FILES_DISCOVERED" -gt 0 ] ||
  fatal "NO_MANAGED_FILES_DISCOVERED" 3 "$LINENO"

mark_success "managed_files_discovered"

stage "03/09" "Копирую полный raw mirror в private storage VM130"

printf 'required\tstatus\tsource_path\trelative_path\tsha256\tsize_bytes\n' \
  > "$REPORT_DIR/fetch-results.tsv"

while IFS=$'\t' read -r required source_path relative_path; do
  [ "$required" = "required" ] && continue

  destination="${PRIVATE_SNAPSHOT}/managed-files/${relative_path}"

  mkdir -p "$(dirname "$destination")"

  if remote_vm101_cmd "test -f '$source_path'"; then
    remote_vm101_cmd "cat '$source_path'" \
      > "$destination"

    chmod 600 "$destination"

    hash="$(
      sha256sum "$destination" |
        cut -d' ' -f1
    )"

    size="$(
      wc -c < "$destination" |
        tr -d '[:space:]'
    )"

    printf '%s\tpresent\t%s\t%s\t%s\t%s\n' \
      "$required" \
      "$source_path" \
      "$relative_path" \
      "$hash" \
      "$size" \
      >> "$REPORT_DIR/fetch-results.tsv"

    FILES_FETCHED=$((FILES_FETCHED + 1))
  else
    printf '%s\tmissing\t%s\t%s\t-\t0\n' \
      "$required" \
      "$source_path" \
      "$relative_path" \
      >> "$REPORT_DIR/fetch-results.tsv"

    if [ "$required" = "true" ]; then
      REQUIRED_MISSING=$((REQUIRED_MISSING + 1))
    fi
  fi
done < "$REPORT_DIR/file-inventory.tsv"

cp -a \
  "$REPORT_DIR/file-inventory.tsv" \
  "$REPORT_DIR/fetch-results.tsv" \
  "$PRIVATE_SNAPSHOT/metadata/"

echo "files_fetched=$FILES_FETCHED" |
  tee -a "$PROGRESS_LOG"

echo "required_missing=$REQUIRED_MISSING" |
  tee -a "$PROGRESS_LOG"

[ "$REQUIRED_MISSING" -eq 0 ] ||
  fatal "REQUIRED_VM101_FILE_MISSING" 4 "$LINENO"

[ "$FILES_FETCHED" -eq "$FILES_DISCOVERED" ] || {
  echo "FETCH_INCOMPLETE discovered=$FILES_DISCOVERED fetched=$FILES_FETCHED"
  fatal "MANAGED_FILE_FETCH_INCOMPLETE" 48 "$LINENO"
}

(
  cd "$PRIVATE_SNAPSHOT"

  find . \
    -type f \
    ! -name SHA256SUMS \
    -print0 |
    sort -z |
    xargs -0 sha256sum \
    > SHA256SUMS
)

mark_success "private_raw_files_fetched"

stage "04/09" "Снимаю machine profile и безопасный runtime snapshot"

cat > "$REPORT_DIR/inventory-remote.sh" <<'VM101'
#!/bin/sh
set -u

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

block() {
  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() {
  iface="$1"
  attempt=1

  while [ "$attempt" -le 3 ]; do
    if ping \
      -I "$iface" \
      -c 1 \
      -W 3 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))
    sleep 1
  done

  return 1
}

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
}

AMNEZIAWG="$(
  command -v amneziawg 2>/dev/null ||
  true
)"

WG="$(
  command -v wg 2>/dev/null ||
  true
)"

AWG="$(
  command -v awg 2>/dev/null ||
  true
)"

fact captured_epoch "$(date +%s)"
fact captured_utc "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
fact captured_local "$(date '+%Y-%m-%dT%H:%M:%S%z')"

fact hostname "$(hostname | tr ' ' '_')"
fact shell "${SHELL:-/bin/ash}"
fact kernel "$(uname -r | tr ' ' '_')"
fact machine "$(uname -m | tr ' ' '_')"

fact busybox_version "$(
  busybox 2>&1 |
    head -n1 |
    tr ' ' '_'
)"

fact openwrt_release "$(
  . /etc/openwrt_release 2>/dev/null
  printf '%s' "${DISTRIB_RELEASE:-UNKNOWN}" |
    tr ' ' '_'
)"

fact openwrt_description "$(
  . /etc/openwrt_release 2>/dev/null
  printf '%s' "${DISTRIB_DESCRIPTION:-UNKNOWN}" |
    tr ' ' '_'
)"

fact timezone_posix "$(
  value="$(
    uci -q get system.@system[0].timezone 2>/dev/null ||
    true
  )"

  [ -n "$value" ] || value=UNKNOWN
  printf '%s' "$value" | tr ' ' '_'
)"

fact timezone_name "$(
  value="$(
    uci -q get system.@system[0].zonename 2>/dev/null ||
    true
  )"

  [ -n "$value" ] || value=UNKNOWN
  printf '%s' "$value" | tr ' ' '_'
)"

fact cli_amneziawg "${AMNEZIAWG:-NOT_FOUND}"
fact cli_wg "${WG:-NOT_FOUND}"
fact cli_awg "${AWG:-NOT_FOUND}"

fact root_total_kb "$(
  df -Pk / |
    awk 'NR == 2 {print $2}'
)"

fact root_used_kb "$(
  df -Pk / |
    awk 'NR == 2 {print $3}'
)"

fact root_available_kb "$(
  df -Pk / |
    awk 'NR == 2 {print $4}'
)"

fact root_available_inodes "UNSUPPORTED_BY_BUSYBOX_DF"

fact tmp_total_kb "$(
  df -Pk /tmp |
    awk 'NR == 2 {print $2}'
)"

fact tmp_available_kb "$(
  df -Pk /tmp |
    awk 'NR == 2 {print $4}'
)"

CRON_LINE="$(
  grep -E \
    '^[[:space:]]*20[[:space:]]+4[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+' \
    /etc/crontabs/root \
    2>/dev/null |
  grep -F '/root/hmn/hmn-refresh-pool-safe.sh' |
  head -n1 ||
  true
)"

fact scheduled_refresh_0420_utc "$(
  if [ -n "$CRON_LINE" ]; then
    echo true
  else
    echo false
  fi
)"

fact hook_running "$(
  bool_cmd /etc/init.d/router-egress-emergency-decision running
)"

fact hook_enabled "$(
  bool_cmd /etc/init.d/router-egress-emergency-decision enabled
)"

fact watcher_running "$(
  bool_cmd /etc/init.d/router-egress-health-repair running
)"

fact watcher_enabled "$(
  bool_cmd /etc/init.d/router-egress-health-repair 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
)"

HEALTHY=0

for iface in vpn1 vpn2 vpn3 vpn4 vpn5; do
  if strict_iface "$iface"; then
    strict=true
    HEALTHY=$((HEALTHY + 1))
  else
    strict=false
  fi

  fact "strict.${iface}" "$strict"

  endpoint="UNRESOLVED"
  handshake=0
  rx=0
  tx=0
  public_key_hash="UNRESOLVED"

  if [ -n "$AMNEZIAWG" ]; then
    line="$(
      "$AMNEZIAWG" show "$iface" dump 2>/dev/null |
        sed -n '2p'
    )"

    if [ -n "$line" ]; then
      public_key="$(
        printf '%s\n' "$line" |
          cut -f1
      )"

      endpoint="$(
        printf '%s\n' "$line" |
          cut -f3
      )"

      handshake="$(
        printf '%s\n' "$line" |
          cut -f5
      )"

      rx="$(
        printf '%s\n' "$line" |
          cut -f6
      )"

      tx="$(
        printf '%s\n' "$line" |
          cut -f7
      )"

      public_key_hash="$(
        printf '%s' "$public_key" |
          sha256sum |
          sed 's/[[:space:]].*$//'
      )"
    fi
  fi

  fact "endpoint.${iface}" "${endpoint:-UNRESOLVED}"
  fact "handshake.${iface}" "${handshake:-0}"
  fact "rx.${iface}" "${rx:-0}"
  fact "tx.${iface}" "${tx:-0}"
  fact "peer_public_key_sha256.${iface}" "$public_key_hash"
done

fact healthy_slots "$HEALTHY"

fact routes_201_205 "$(
  if routes_all; then
    echo true
  else
    echo false
  fi
)"

fact table200_default_present "$(
  if ip route show table 200 2>/dev/null |
     grep -q '^default '
  then
    echo true
  else
    echo false
  fi
)"

block exact_cron_line sh -c '
  grep -E \
    "^[[:space:]]*20[[:space:]]+4[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+" \
    /etc/crontabs/root \
    2>/dev/null |
  grep -F "/root/hmn/hmn-refresh-pool-safe.sh" ||
  true
'

block routes_200_205 sh -c '
  for table in 200 201 202 203 204 205; do
    echo "===== table $table ====="
    ip route show table "$table" 2>/dev/null || true
  done
'

block relevant_packages sh -c '
  opkg list-installed 2>/dev/null |
    grep -Ei \
      "amnezia|wireguard|(^|-)wg($|-)|kmod.*(awg|wireguard)" ||
  true
'

block interface_links sh -c '
  for iface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    echo "===== $iface ====="
    ip -details link show dev "$iface" 2>/dev/null || true
  done
'

block filesystem_usage df -Pk
block filesystem_inodes sh -c '
  echo "UNSUPPORTED_BY_BUSYBOX_DF"
' 

echo "__TRACE__ stage=complete"
exit 0
VM101

chmod 600 "$REPORT_DIR/inventory-remote.sh"
sh -n "$REPORT_DIR/inventory-remote.sh"

if remote_vm101_script \
  < "$REPORT_DIR/inventory-remote.sh" \
  > >(tee "$REPORT_DIR/inventory-raw.txt") \
  2> >(tee "$REPORT_DIR/inventory.stderr" >&2)
then
  VM101_INVENTORY_RC=0
else
  VM101_INVENTORY_RC=$?
fi

[ "$VM101_INVENTORY_RC" -eq 0 ] ||
  fatal "VM101_INVENTORY_FAILED" "$VM101_INVENTORY_RC" "$LINENO"

grep -Fq \
  "__TRACE__ stage=complete" \
  "$REPORT_DIR/inventory-raw.txt" ||
  fatal "VM101_INVENTORY_INCOMPLETE" 5 "$LINENO"

cp -a \
  "$REPORT_DIR/inventory-raw.txt" \
  "$REPORT_DIR/inventory.stderr" \
  "$PRIVATE_SNAPSHOT/runtime/"

PRIVATE_SNAPSHOT_COMPLETE=true
mark_success "private_snapshot_complete"

stage "05/09" "Строю sanitized public reference"

python3 - \
  "$PRIVATE_SNAPSHOT" \
  "$PUBLIC_STAGING" \
  "$REPORT_DIR/fetch-results.tsv" \
  "$REPORT_DIR/inventory-raw.txt" \
  "$REFERENCE_ID" \
  "$REPORT_DIR/secret-scan.json" \
  > "$REPORT_DIR/sanitize-build.log" <<'PY'
import hashlib
import html
import json
import os
import re
import shutil
import sys
from pathlib import Path

(
    private_snapshot_arg,
    public_staging_arg,
    fetch_results_arg,
    inventory_raw_arg,
    reference_id,
    secret_scan_output_arg,
) = sys.argv[1:]

private_snapshot = Path(private_snapshot_arg)
public_staging = Path(public_staging_arg)
fetch_results_path = Path(fetch_results_arg)
inventory_raw_path = Path(inventory_raw_arg)
secret_scan_output = Path(secret_scan_output_arg)

if public_staging.exists():
    shutil.rmtree(public_staging)

public_staging.mkdir(parents=True, mode=0o700)

managed_private = private_snapshot / "managed-files"
managed_public = public_staging / "managed-files"

managed_public.mkdir(parents=True)

facts = {}
blocks = {}
current_block = None
block_lines = []

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

for line in inventory_text.splitlines():
    if 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__ "):]
        block_lines = []

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

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

        current_block = None
        block_lines = []

    elif current_block is not None:
        block_lines.append(line)


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


SENSITIVE_EXACT = {
    "private_key",
    "preshared_key",
    "password",
    "passwd",
    "token",
    "secret",
    "access_code",
    "hmn_code",
    "hmn_access_code",
    "authorization",
    "api_key",
    "client_secret",
}


def sensitive_name(name: str) -> bool:
    lowered = name.lower()

    if lowered in SENSITIVE_EXACT:
        return True

    return lowered.endswith((
        "_password",
        "_passwd",
        "_token",
        "_secret",
        "_access_code",
        "_private_key",
        "_preshared_key",
        "_api_key",
        "_client_secret",
    ))


def sanitize_text(text: str) -> tuple[str, int]:
    output = []
    redactions = 0
    inside_private_pem = False

    for original_line in text.splitlines(keepends=True):
        line = original_line
        stripped = line.strip()

        if re.search(
            r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
            stripped,
            re.IGNORECASE,
        ):
            output.append("# REDACTED PRIVATE KEY BLOCK\n")
            inside_private_pem = True
            redactions += 1
            continue

        if inside_private_pem:
            if re.search(
                r"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
                stripped,
                re.IGNORECASE,
            ):
                inside_private_pem = False
            continue

        uci = re.match(
            r"^(\s*option\s+)"
            r"([A-Za-z0-9_.-]+)"
            r"(\s+)"
            r"(.*?)(\r?\n)?$",
            line,
            re.IGNORECASE,
        )

        if uci and sensitive_name(uci.group(2)):
            newline = uci.group(5) or ""

            output.append(
                f"{uci.group(1)}{uci.group(2)}"
                f"{uci.group(3)}'REDACTED'{newline}"
            )

            redactions += 1
            continue

        ini = re.match(
            r"^(\s*)"
            r"(PrivateKey|PresharedKey|Password|Token)"
            r"(\s*=\s*)"
            r"(.*?)(\r?\n)?$",
            line,
            re.IGNORECASE,
        )

        if ini:
            newline = ini.group(5) or ""

            output.append(
                f"{ini.group(1)}{ini.group(2)}"
                f"{ini.group(3)}REDACTED{newline}"
            )

            redactions += 1
            continue

        shell = re.match(
            r"^(\s*(?:export\s+)?)"
            r"([A-Za-z_][A-Za-z0-9_]*)"
            r"(\s*=\s*)"
            r"(.*?)(\r?\n)?$",
            line,
        )

        if shell and sensitive_name(shell.group(2)):
            newline = shell.group(5) or ""

            output.append(
                f"{shell.group(1)}{shell.group(2)}"
                f"{shell.group(3)}'REDACTED'{newline}"
            )

            redactions += 1
            continue

        json_value = re.match(
            r'^(\s*"([^"]+)"\s*:\s*)'
            r'(".*?"|[^,}\r\n]+)'
            r'(,?)(\r?\n)?$',
            line,
        )

        if (
            json_value
            and sensitive_name(json_value.group(2))
        ):
            newline = json_value.group(5) or ""

            output.append(
                f'{json_value.group(1)}"REDACTED"'
                f"{json_value.group(4)}{newline}"
            )

            redactions += 1
            continue

        if re.search(
            r"Authorization\s*:\s*(?:Bearer|Basic)\s+\S+",
            line,
            re.IGNORECASE,
        ):
            line = re.sub(
                r"(Authorization\s*:\s*)"
                r"(?:Bearer|Basic)\s+\S+",
                r"\1REDACTED",
                line,
                flags=re.IGNORECASE,
            )

            redactions += 1

        credential_url = re.compile(
            r"([a-z][a-z0-9+.-]*://)"
            r"([^/\s:@]+):([^/\s@]+)@",
            re.IGNORECASE,
        )

        if credential_url.search(line):
            line = credential_url.sub(
                r"\1REDACTED:REDACTED@",
                line,
            )

            redactions += 1

        query_secret = re.compile(
            r"([?&](?:token|secret|password|passwd|"
            r"code|access_code|api_key)=)"
            r"([^&\s\"']+)",
            re.IGNORECASE,
        )

        if query_secret.search(line):
            line = query_secret.sub(
                r"\1REDACTED",
                line,
            )

            redactions += 1

        output.append(line)

    return "".join(output), redactions


fetch_lines = fetch_results_path.read_text(
    encoding="utf-8"
).splitlines()

header = fetch_lines[0].split("\t")

rows = [
    dict(zip(header, line.split("\t")))
    for line in fetch_lines[1:]
]

manifest_files = []
total_redactions = 0

for row in rows:
    if row["status"] != "present":
        continue

    relative_path = row["relative_path"]
    source_path = row["source_path"]

    source = managed_private / relative_path
    destination = managed_public / relative_path

    destination.parent.mkdir(parents=True, exist_ok=True)

    raw = source.read_bytes()

    try:
        text = raw.decode("utf-8")
    except UnicodeDecodeError:
        raise RuntimeError(
            f"non-text managed file refused: {source_path}"
        )

    sanitized, redactions = sanitize_text(text)

    destination.write_text(
        sanitized,
        encoding="utf-8",
    )

    os.chmod(destination, 0o644)

    public_bytes = sanitized.encode("utf-8")
    total_redactions += redactions

    manifest_files.append({
        "required": row["required"] == "true",
        "source_path": source_path,
        "relative_path": relative_path,
        "raw_sha256": ("PRIVATE_ONLY" if redactions else sha256_bytes(raw)),
        "public_sha256": sha256_bytes(public_bytes),
        "raw_size_bytes": len(raw),
        "public_size_bytes": len(public_bytes),
        "redactions": redactions,
    })

interfaces = ["vpn1", "vpn2", "vpn3", "vpn4", "vpn5"]

runtime = {
    "captured_at": {
        "epoch": int(facts["captured_epoch"]),
        "utc": facts["captured_utc"],
        "local": facts["captured_local"],
    },
    "protocol": "AmneziaWG",
    "runtime_cli": facts["cli_amneziawg"],
    "interfaces": {
        iface: {
            "endpoint": facts[f"endpoint.{iface}"],
            "strict":
                facts[f"strict.{iface}"] == "true",
            "latest_handshake_epoch":
                int(facts[f"handshake.{iface}"]),
            "rx_bytes":
                int(facts[f"rx.{iface}"]),
            "tx_bytes":
                int(facts[f"tx.{iface}"]),
            "peer_public_key_sha256":
                facts[f"peer_public_key_sha256.{iface}"],
        }
        for iface in interfaces
    },
    "healthy_slots": int(facts["healthy_slots"]),
    "routes_201_205":
        facts["routes_201_205"] == "true",
    "table200_default_present":
        facts["table200_default_present"] == "true",
    "services": {
        "emergency_decision": {
            "running":
                facts["hook_running"] == "true",
            "enabled":
                facts["hook_enabled"] == "true",
        },
        "health_repair": {
            "running":
                facts["watcher_running"] == "true",
            "enabled":
                facts["watcher_enabled"] == "true",
        },
    },
    "locks": {
        "emergency":
            facts["emergency_lock_present"] == "true",
        "refresh":
            facts["refresh_lock_present"] == "true",
    },
}

machine_profile = {
    "schema": "vm101-machine-profile-v1",
    "reference_id": reference_id,
    "identity": {
        "host": facts["hostname"],
        "role": "MGTS edge egress",
        "os_family": "OpenWrt",
        "openwrt_release":
            facts["openwrt_release"],
        "openwrt_description":
            facts["openwrt_description"],
        "kernel": facts["kernel"],
        "machine": facts["machine"],
        "shell": "/bin/ash",
        "userland": "BusyBox",
        "busybox_version":
            facts["busybox_version"],
    },
    "time": {
        "system_timezone": "UTC",
        "uci_timezone":
            facts["timezone_posix"],
        "uci_zonename":
            facts["timezone_name"],
        "scheduled_refresh": "20 4 * * *",
        "scheduled_refresh_0420_utc":
            facts["scheduled_refresh_0420_utc"]
            == "true",
    },
    "vpn": {
        "protocol": "AmneziaWG",
        "interfaces": interfaces,
        "runtime_cli":
            facts["cli_amneziawg"],
        "standard_wg_cli":
            facts["cli_wg"],
        "awg_cli":
            facts["cli_awg"],
        "endpoint_source_of_truth":
            "amneziawg runtime peer dump",
        "uci_hmn_endpoint":
            "descriptive and may be stale",
        "planner_current":
            "must be compared with runtime before use",
    },
    "routing": {
        "authoritative_tables":
            [201, 202, 203, 204, 205],
        "table_200":
            "optional legacy/bootstrap route table",
        "direct_failopen": False,
    },
    "storage": {
        "root_total_kb":
            int(facts["root_total_kb"]),
        "root_used_kb":
            int(facts["root_used_kb"]),
        "root_available_kb":
            int(facts["root_available_kb"]),
        "root_available_inodes":
            facts["root_available_inodes"],
        "tmp_total_kb":
            int(facts["tmp_total_kb"]),
        "tmp_available_kb":
            int(facts["tmp_available_kb"]),
        "backup_policy":
            "size preflight plus surgical manifest; "
            "temporary large backups use /tmp",
    },
    "runtime_health": {
        "healthy_slots":
            runtime["healthy_slots"],
        "routes_201_205":
            runtime["routes_201_205"],
        "services":
            runtime["services"],
        "locks":
            runtime["locks"],
    },
}

(public_staging / "runtime").mkdir()
(public_staging / "methods").mkdir()
(public_staging / "docs").mkdir()

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

(public_staging / "runtime" / "routes-200-205.txt").write_text(
    blocks.get("routes_200_205", "") + "\n",
    encoding="utf-8",
)

(public_staging / "runtime" / "packages.txt").write_text(
    blocks.get("relevant_packages", "") + "\n",
    encoding="utf-8",
)

(public_staging / "runtime" / "filesystem.txt").write_text(
    "\n".join([
        blocks.get("filesystem_usage", ""),
        "",
        blocks.get("filesystem_inodes", ""),
    ]) + "\n",
    encoding="utf-8",
)

(public_staging / "machine-profile.json").write_text(
    json.dumps(
        machine_profile,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)

profile_md = f"""# VM101 machine profile

Reference: {reference_id}

## Identity

- Role: MGTS edge egress
- OS: {machine_profile['identity']['openwrt_description']}
- Shell: BusyBox ash
- Kernel: {machine_profile['identity']['kernel']}
- System timezone: UTC
- Scheduled HMN refresh: 20 4 * * * UTC

## AmneziaWG

- Protocol: AmneziaWG
- Authoritative runtime CLI: {machine_profile['vpn']['runtime_cli']}
- Standard wg: {machine_profile['vpn']['standard_wg_cli']}
- awg: {machine_profile['vpn']['awg_cli']}
- Interfaces: vpn1 through vpn5
- Runtime endpoint source: amneziawg show IFACE dump

## Routing

- Authoritative tables: 201 through 205
- Table 200: optional legacy/bootstrap table
- Direct fail-open: disabled

## Storage

- Root total: {machine_profile['storage']['root_total_kb']} KB
- Root available: {machine_profile['storage']['root_available_kb']} KB
- /tmp available: {machine_profile['storage']['tmp_available_kb']} KB
- Large backups require a storage preflight.

## Current health

- Healthy VPN slots: {runtime['healthy_slots']}/5
- Routes 201-205: {runtime['routes_201_205']}
- Emergency decision running: {runtime['services']['emergency_decision']['running']}
- Health-repair watcher running: {runtime['services']['health_repair']['running']}
"""

(public_staging / "machine-profile.md").write_text(
    profile_md,
    encoding="utf-8",
)

source_truth = """# VM101 source-of-truth hierarchy

Use this order when different sources disagree.

1. Actual AmneziaWG runtime:
   /usr/bin/amneziawg show IFACE dump

2. Actual data-plane health:
   ping -I vpnN
   route tables 201 through 205

3. Kernel and service state:
   routes, services and lock files

4. UCI metadata:
   network.IFACE.hmn_endpoint
   This value may be stale after a failed apply or rollback.

5. Planner current fields:
   They must be checked against AmneziaWG runtime before commit.

Table 200 is optional and must not be the sole proof that a healthy
VPN bootstrap interface exists.
"""

(public_staging / "docs" / "source-of-truth.md").write_text(
    source_truth,
    encoding="utf-8",
)

compatibility = """# VM101 compatibility matrix

| Capability | Contract |
|---|---|
| Shell | BusyBox /bin/ash |
| Bash on VM101 | Do not assume |
| GNU awk, find, sed | Do not assume |
| AmneziaWG CLI | /usr/bin/amneziawg |
| wg CLI | Installed but not authoritative for vpn1-vpn5 |
| awg CLI | Absent |
| Runtime endpoint | amneziawg show IFACE dump, peer line |
| Route tables | 201-205 authoritative |
| Table 200 | Optional legacy/bootstrap |
| Timezone | UTC |
| Scheduled refresh | 04:20 UTC |
| Root filesystem | Very small; use storage preflight |
| Cross-STEP state | Never depend on /tmp from a previous STEP |
| Patches | Prefer complete canonical files over regex surgery |
"""

(public_staging / "docs" / "compatibility.md").write_text(
    compatibility,
    encoding="utf-8",
)

known_failures = """# Repeated failure patterns

## Wrong VPN CLI

Do not use wg show or assume awg. VM101 uses
/usr/bin/amneziawg for vpn1 through vpn5 runtime state.

## BusyBox versus GNU tools

Do not assume GNU-only awk, find, sed, stat or Bash syntax.

## Table 200 assumptions

A missing table-200 default does not mean all VPN slots are down.
The five-slot architecture uses tables 201 through 205.

## Root filesystem exhaustion

Do not create broad backups on the small root filesystem. Calculate
size and inode requirements before mutation. Use surgical manifests
and /tmp for temporary backup material.

## Temporary script dependencies

A subsequent STEP must not depend on a previous /tmp script. Use an
immutable report artifact, private mirror or canonical managed source.

## Regex patching of unknown live files

Do not guess a live file layout with a fragile regex. Compare its hash
with the reference, construct a complete replacement, syntax-check it,
review the diff and install it atomically.

## Ambiguous PASS

Reports must distinguish step execution, operation result, production
health and milestone status.

## Rollback readiness

Rollback is available only after a complete manifest, non-empty
backups, stored hashes, syntax validation and a tested rollback path.
"""

(public_staging / "docs" / "known-failures.md").write_text(
    known_failures,
    encoding="utf-8",
)

report_contract = """# Standard report contract

Every operational STEP reports these independently:

    step_execution=PASS or STOP
    operation_result=...
    production_health=HEALTHY, DEGRADED, FAILED or UNCHANGED
    milestone_status=...

Every modifying STEP also reports:

    backup_manifest_complete=true or false
    rollback_available=true or false
    production_modified=true or false
    full_refresh_ran=true or false
    rebalance_commit_ran=true or false

Standard published links:

    TRYCF_REPORT
    REPORT_TXT
    FACTS_JSON
    ARCHITECTURE_PLAN
    XS_MAP
    GLOBAL_PROJECT_PLAN
    VM101_REFERENCE
"""

(public_staging / "docs" / "report-contract.md").write_text(
    report_contract,
    encoding="utf-8",
)

methods_readme = """# Verified VM101 methods

vm101-lib.sh contains BusyBox-compatible read-only methods.

Typical use:

    . ./vm101-lib.sh
    vm101_detect_runtime_cli
    vm101_runtime_endpoint vpn1
    vm101_strict_all
    vm101_routes_all
    vm101_storage_facts

Rules:

- amneziawg is preferred and required by default.
- Standard wg fallback is disabled unless explicitly enabled.
- Table 200 is informational and optional.
- Methods remain read-only unless documented otherwise.
"""

(public_staging / "methods" / "README.md").write_text(
    methods_readme,
    encoding="utf-8",
)

vm101_lib = r'''#!/bin/sh

# BusyBox-compatible VM101 read-only method library.

vm101_detect_runtime_cli() {
  if command -v amneziawg >/dev/null 2>&1; then
    command -v amneziawg
    return 0
  fi

  if [ "${VM101_ALLOW_STANDARD_WG_FALLBACK:-0}" = "1" ] &&
     command -v wg >/dev/null 2>&1
  then
    command -v wg
    return 0
  fi

  return 1
}

vm101_runtime_endpoint() {
  iface="$1"

  cli="$(
    vm101_detect_runtime_cli ||
    true
  )"

  [ -n "$cli" ] || return 1

  endpoint="$(
    "$cli" show "$iface" dump 2>/dev/null |
      sed -n '2p' |
      cut -f3
  )"

  case "$endpoint" in
    ""|"(none)")
      return 1
      ;;
  esac

  printf '%s\n' "$endpoint"
}

vm101_strict_iface() {
  iface="$1"
  attempt=1

  while [ "$attempt" -le 3 ]; do
    if ping \
      -I "$iface" \
      -c 1 \
      -W 3 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))
    sleep 1
  done

  return 1
}

vm101_strict_all() {
  for iface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    vm101_strict_iface "$iface" ||
      return 1
  done

  return 0
}

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

vm101_table200_default_present() {
  ip route show table 200 2>/dev/null |
    grep -q '^default '
}

vm101_storage_facts() {
  echo "root:"
  df -Pk /
  df -Pi /

  echo
  echo "tmp:"
  df -Pk /tmp
  df -Pi /tmp
}

vm101_readonly_baseline() {
  cli="$(
    vm101_detect_runtime_cli ||
    true
  )"

  printf 'runtime_cli=%s\n' "${cli:-NOT_FOUND}"

  healthy=0

  for iface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    endpoint="$(
      vm101_runtime_endpoint "$iface" ||
      true
    )"

    if vm101_strict_iface "$iface"; then
      strict=true
      healthy=$((healthy + 1))
    else
      strict=false
    fi

    printf '%s_endpoint=%s\n' \
      "$iface" \
      "${endpoint:-UNRESOLVED}"

    printf '%s_strict=%s\n' \
      "$iface" \
      "$strict"
  done

  printf 'healthy_slots=%s\n' "$healthy"

  if vm101_routes_all; then
    echo "routes_201_205=true"
  else
    echo "routes_201_205=false"
  fi

  if vm101_table200_default_present; then
    echo "table200_default_present=true"
  else
    echo "table200_default_present=false"
  fi
}
'''

(public_staging / "methods" / "vm101-lib.sh").write_text(
    vm101_lib,
    encoding="utf-8",
)

os.chmod(
    public_staging / "methods" / "vm101-lib.sh",
    0o755,
)

preflight = r'''#!/bin/sh
set -u

SCRIPT_DIR="$(
  CDPATH= cd -- "$(dirname -- "$0")" &&
  pwd
)"

. "$SCRIPT_DIR/vm101-lib.sh"

echo "=== VM101 read-only preflight ==="
vm101_readonly_baseline

echo
echo "=== storage ==="
vm101_storage_facts
'''

(public_staging / "methods" / "preflight-readonly.sh").write_text(
    preflight,
    encoding="utf-8",
)

os.chmod(
    public_staging / "methods" / "preflight-readonly.sh",
    0o755,
)

install_contract = """# Managed-file installation contract

1. Refresh private and public VM101 references.
2. Verify the live hash against manifest.json.
3. Build a complete replacement file on VM130.
4. Run a syntax check.
5. Produce and inspect a unified diff.
6. Calculate backup size and inode requirements.
7. Create a surgical backup manifest.
8. Upload under a temporary name.
9. Verify uploaded hash and syntax on VM101.
10. Atomically replace the destination.
11. Run postchecks.
12. On PASS publish a new reference snapshot.
13. On STOP rollback and do not update current.
"""

(public_staging / "methods" / "managed-file-install-contract.md").write_text(
    install_contract,
    encoding="utf-8",
)

manifest = {
    "schema": "vm101-public-reference-manifest-v1",
    "reference_id": reference_id,
    "generated_from":
        "private raw mirror on router-ops VM130",
    "sanitization": {
        "performed": True,
        "total_redactions": total_redactions,
        "raw_files_publicly_exposed": False,
    },
    "managed_file_count": len(manifest_files),
    "managed_files": manifest_files,
}

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

(public_staging / "generated-at.txt").write_text(
    facts["captured_utc"] + "\n",
    encoding="utf-8",
)

index_items = []

for item in manifest_files:
    path = "managed-files/" + item["relative_path"]

    index_items.append(
        f'<li><a href="{html.escape(path)}">'
        f'{html.escape(item["source_path"])}</a></li>'
    )

index = f"""<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>VM101 Reference {html.escape(reference_id)}</title>
</head>
<body style="font-family:system-ui;max-width:1200px;margin:40px auto;padding:0 20px">
<h1>VM101 Reference</h1>

<p>Immutable snapshot: <code>{html.escape(reference_id)}</code></p>

<h2>Machine</h2>
<ul>
<li><a href="machine-profile.md">machine-profile.md</a></li>
<li><a href="machine-profile.json">machine-profile.json</a></li>
<li><a href="runtime/current.json">runtime/current.json</a></li>
</ul>

<h2>Contracts and known issues</h2>
<ul>
<li><a href="docs/source-of-truth.md">source-of-truth.md</a></li>
<li><a href="docs/compatibility.md">compatibility.md</a></li>
<li><a href="docs/known-failures.md">known-failures.md</a></li>
<li><a href="docs/report-contract.md">report-contract.md</a></li>
</ul>

<h2>Verified methods</h2>
<ul>
<li><a href="methods/README.md">README.md</a></li>
<li><a href="methods/vm101-lib.sh">vm101-lib.sh</a></li>
<li><a href="methods/preflight-readonly.sh">preflight-readonly.sh</a></li>
<li><a href="methods/managed-file-install-contract.md">managed-file-install-contract.md</a></li>
</ul>

<h2>Managed files</h2>
<ul>
{''.join(index_items)}
</ul>

<h2>Integrity</h2>
<ul>
<li><a href="manifest.json">manifest.json</a></li>
<li><a href="secret-scan.json">secret-scan.json</a></li>
<li><a href="SHA256SUMS">SHA256SUMS</a></li>
</ul>
</body>
</html>
"""

(public_staging / "index.html").write_text(
    index,
    encoding="utf-8",
)

scan_patterns = [
    (
        "private_pem",
        re.compile(
            r"-----BEGIN "
            r"(?:RSA |EC |OPENSSH )?"
            r"PRIVATE KEY-----",
            re.IGNORECASE,
        ),
    ),
    (
        "wireguard_private_value",
        re.compile(
            r"^\s*(?:PrivateKey|PresharedKey)"
            r"\s*=\s*(?!REDACTED\b)\S+",
            re.IGNORECASE,
        ),
    ),
    (
        "authorization_header",
        re.compile(
            r"Authorization\s*:\s*"
            r"(?:Bearer|Basic)\s+"
            r"(?!REDACTED\b)\S+",
            re.IGNORECASE,
        ),
    ),
    (
        "credential_url",
        re.compile(
            r"[a-z][a-z0-9+.-]*://"
            r"[^/\s:@]+:[^/\s@]+@",
            re.IGNORECASE,
        ),
    ),
    (
        "sensitive_query",
        re.compile(
            r"[?&](?:token|secret|password|passwd|"
            r"code|access_code|api_key)="
            r"(?!REDACTED\b)[^&\s\"']+",
            re.IGNORECASE,
        ),
    ),
]

findings = []

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

    if path.name == "secret-scan.json":
        continue

    try:
        text = path.read_text(
            encoding="utf-8",
            errors="strict",
        )
    except UnicodeDecodeError:
        findings.append({
            "file": str(path.relative_to(public_staging)),
            "line": 0,
            "type": "non_utf8_file",
            "sample": "binary or non-UTF-8 content",
        })
        continue

    for number, line in enumerate(
        text.splitlines(),
        start=1,
    ):
        for finding_type, pattern in scan_patterns:
            if pattern.search(line):
                findings.append({
                    "file":
                        str(path.relative_to(public_staging)),
                    "line": number,
                    "type": finding_type,
                    "sample": line[:240],
                })

scan_result = {
    "schema": "vm101-reference-secret-scan-v1",
    "passed": not findings,
    "finding_count": len(findings),
    "findings": findings,
}

scan_json = json.dumps(
    scan_result,
    ensure_ascii=False,
    indent=2,
) + "\n"

(public_staging / "secret-scan.json").write_text(
    scan_json,
    encoding="utf-8",
)

secret_scan_output.write_text(
    scan_json,
    encoding="utf-8",
)

print(json.dumps({
    "reference_id": reference_id,
    "managed_files": len(manifest_files),
    "redactions": total_redactions,
    "secret_findings": len(findings),
    "healthy_slots": runtime["healthy_slots"],
    "routes_201_205": runtime["routes_201_205"],
}, ensure_ascii=False))

if findings:
    raise SystemExit(40)
PY

PUBLIC_FILES="$(
  find "$PUBLIC_STAGING" \
    -type f |
    wc -l |
    tr -d '[:space:]'
)"

REDACTIONS="$(
  python3 - "$PUBLIC_STAGING/manifest.json" <<'PY'
import json
import sys

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

print(data["sanitization"]["total_redactions"])
PY
)"

SECRET_FINDINGS="$(
  python3 - "$REPORT_DIR/secret-scan.json" <<'PY'
import json
import sys

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

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

echo "public_files=$PUBLIC_FILES" |
  tee -a "$PROGRESS_LOG"

echo "redactions=$REDACTIONS" |
  tee -a "$PROGRESS_LOG"

echo "secret_findings=$SECRET_FINDINGS" |
  tee -a "$PROGRESS_LOG"

[ "$SECRET_FINDINGS" -eq 0 ] ||
  fatal "SECRET_SCAN_FAILED" 40 "$LINENO"

mark_success "sanitized_public_staging_built"

stage "06/09" "Проверяю синтаксис public shell-файлов"

: > "$REPORT_DIR/syntax-checks.txt"

while IFS= read -r path; do
  relative="${path#${PUBLIC_STAGING}/}"

  case "$relative" in
    methods/*.sh|managed-files/*.sh|managed-files/etc/init.d/*)
      if sh -n "$path" \
        >> "$REPORT_DIR/syntax-checks.txt" \
        2>&1
      then
        echo "PASS $relative" \
          >> "$REPORT_DIR/syntax-checks.txt"
      else
        echo "FAIL $relative" \
          >> "$REPORT_DIR/syntax-checks.txt"

        SYNTAX_FAILURES=$((SYNTAX_FAILURES + 1))
      fi
      ;;
  esac
done < <(
  find "$PUBLIC_STAGING" \
    -type f |
    sort
)

echo "syntax_failures=$SYNTAX_FAILURES" |
  tee -a "$PROGRESS_LOG"

[ "$SYNTAX_FAILURES" -eq 0 ] ||
  fatal "PUBLIC_SYNTAX_CHECK_FAILED" 41 "$LINENO"

(
  cd "$PUBLIC_STAGING"

  find . \
    -type f \
    ! -name SHA256SUMS \
    -print0 |
    sort -z |
    xargs -0 sha256sum \
    > SHA256SUMS
)

cp -a \
  "$REPORT_DIR/secret-scan.json" \
  "$REPORT_DIR/syntax-checks.txt" \
  "$PRIVATE_SNAPSHOT/metadata/"

mark_success "public_staging_validated"

stage "07/09" "Публикую immutable snapshot и обновляю current"

mkdir -p "$PUBLIC_REFERENCE_ROOT/snapshots"

PUBLIC_TMP="${PUBLIC_SNAPSHOT}.new.$$"

rm -rf "$PUBLIC_TMP"

cp -a "$PUBLIC_STAGING" "$PUBLIC_TMP"

chmod -R a+rX "$PUBLIC_TMP"

[ -s "$PUBLIC_TMP/manifest.json" ] ||
  fatal "PUBLICATION_FAILED" 42 "$LINENO"

[ -s "$PUBLIC_TMP/secret-scan.json" ] ||
  fatal "PUBLICATION_FAILED" 43 "$LINENO"

grep -Fq '"passed": true' \
  "$PUBLIC_TMP/secret-scan.json" ||
  fatal "PUBLICATION_FAILED" 44 "$LINENO"

[ ! -e "$PUBLIC_SNAPSHOT" ] ||
  fatal "PUBLICATION_FAILED" 45 "$LINENO"

mv "$PUBLIC_TMP" "$PUBLIC_SNAPSHOT"

PUBLIC_SNAPSHOT_PUBLISHED=true

CURRENT_NEW="${PUBLIC_REFERENCE_ROOT}/current.new.$$"
CURRENT_OLD="${PUBLIC_REFERENCE_ROOT}/current.old.$$"

rm -rf "$CURRENT_NEW" "$CURRENT_OLD"

cp -a "$PUBLIC_SNAPSHOT" "$CURRENT_NEW"

if [ -e "$PUBLIC_CURRENT" ]; then
  mv "$PUBLIC_CURRENT" "$CURRENT_OLD"
fi

mv "$CURRENT_NEW" "$PUBLIC_CURRENT"

rm -rf "$CURRENT_OLD"

PUBLIC_CURRENT_UPDATED=true

rm -rf "${PRIVATE_ROOT}/current"

ln -s \
  "snapshots/${REFERENCE_ID}" \
  "${PRIVATE_ROOT}/current"

mark_success "public_snapshot_and_current_published"

stage "08/09" "Обновляю history, root index и project links"

python3 - \
  "$PUBLIC_REFERENCE_ROOT" \
  "$REFERENCE_ID" \
  "$VM101_REFERENCE" \
  "$VM101_REFERENCE_SNAPSHOT" \
  "$BUILD_TS" <<'PY'
import html
import json
import sys
from pathlib import Path

(
    root_arg,
    reference_id,
    current_url,
    snapshot_url,
    build_ts,
) = sys.argv[1:]

root = Path(root_arg)
history_path = root / "history.json"

if history_path.exists():
    try:
        history = json.loads(
            history_path.read_text(
                encoding="utf-8"
            )
        )
    except Exception:
        history = {
            "schema": "vm101-reference-history-v1",
            "snapshots": [],
        }
else:
    history = {
        "schema": "vm101-reference-history-v1",
        "snapshots": [],
    }

history["current"] = reference_id

existing = {
    item.get("reference_id")
    for item in history["snapshots"]
}

if reference_id not in existing:
    history["snapshots"].append({
        "reference_id": reference_id,
        "generated_at_compact_utc": build_ts,
        "snapshot_url": snapshot_url,
    })

history["snapshots"] = sorted(
    history["snapshots"],
    key=lambda item: item["reference_id"],
    reverse=True,
)

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

items = []

for item in history["snapshots"]:
    items.append(
        '<li><a href="'
        + html.escape(item["snapshot_url"])
        + '">'
        + html.escape(item["reference_id"])
        + "</a></li>"
    )

index = f"""<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>VM101 Reference</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto;padding:0 20px">
<h1>VM101 Reference</h1>

<p><a href="{html.escape(current_url)}"><strong>Current reference</strong></a></p>

<h2>Immutable snapshots</h2>
<ul>
{''.join(items)}
</ul>

<p><a href="history.json">history.json</a></p>
</body>
</html>
"""

(root / "index.html").write_text(
    index,
    encoding="utf-8",
)
PY

chmod -R a+rX "$PUBLIC_REFERENCE_ROOT"

cat > "$STATE_ROOT/current-vm101-reference.env" <<EOF
VM101_REFERENCE_ID=${REFERENCE_ID}
VM101_REFERENCE=${VM101_REFERENCE}
VM101_REFERENCE_SNAPSHOT=${VM101_REFERENCE_SNAPSHOT}
VM101_PRIVATE_SNAPSHOT=${PRIVATE_SNAPSHOT}
VM101_REFERENCE_MANIFEST=${VM101_REFERENCE}manifest.json
VM101_REFERENCE_GENERATED_AT_UTC=${BUILD_TS}
EOF

chmod 600 "$STATE_ROOT/current-vm101-reference.env"

python3 - \
  "$STATE_ROOT/current-project-links.env" \
  "$VM101_REFERENCE" <<'PY'
import sys
from pathlib import Path

path = Path(sys.argv[1])
reference = sys.argv[2]

lines = []

if path.exists():
    lines = path.read_text(
        encoding="utf-8",
        errors="replace",
    ).splitlines()

lines = [
    line
    for line in lines
    if not line.startswith("VM101_REFERENCE=")
]

lines.append(
    f"VM101_REFERENCE={reference}"
)

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

chmod 600 "$STATE_ROOT/current-project-links.env"

cat > "$ROOT/bin/vm101-reference-status" <<'STATUS'
#!/usr/bin/env bash
set -Eeuo pipefail

STATE="/opt/router-ops/state/current-vm101-reference.env"

[ -s "$STATE" ] || {
  echo "VM101 reference state is not available"
  exit 1
}

# shellcheck disable=SC1090
. "$STATE"

echo "VM101_REFERENCE_ID=$VM101_REFERENCE_ID"
echo "VM101_REFERENCE=$VM101_REFERENCE"
echo "VM101_REFERENCE_SNAPSHOT=$VM101_REFERENCE_SNAPSHOT"
echo "VM101_REFERENCE_MANIFEST=$VM101_REFERENCE_MANIFEST"
echo "VM101_PRIVATE_SNAPSHOT=$VM101_PRIVATE_SNAPSHOT"
STATUS

chmod 755 "$ROOT/bin/vm101-reference-status"

mark_success "history_and_project_links_updated"

stage "09/09" "Проверяю reference и формирую PASS"

for required in \
  "$PUBLIC_CURRENT/index.html" \
  "$PUBLIC_CURRENT/machine-profile.json" \
  "$PUBLIC_CURRENT/manifest.json" \
  "$PUBLIC_CURRENT/secret-scan.json" \
  "$PUBLIC_CURRENT/methods/vm101-lib.sh" \
  "$PUBLIC_CURRENT/docs/known-failures.md" \
  "$PUBLIC_CURRENT/SHA256SUMS"
do
  [ -s "$required" ] || {
    echo "CURRENT_REQUIRED_MISSING=$required"
    fatal "PUBLICATION_FAILED" 46 "$LINENO"
  }
done

grep -Fq '"passed": true' \
  "$PUBLIC_CURRENT/secret-scan.json" ||
  fatal "PUBLICATION_FAILED" 47 "$LINENO"

MANAGED_COUNT="$(
  python3 - "$PUBLIC_CURRENT/manifest.json" <<'PY'
import json
import sys

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

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

HEALTHY_SLOTS="$(
  python3 - "$PUBLIC_CURRENT/runtime/current.json" <<'PY'
import json
import sys

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

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

ROUTES_OK="$(
  python3 - "$PUBLIC_CURRENT/runtime/current.json" <<'PY'
import json
import sys

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

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

MANIFEST_SHA="$(
  sha256sum "$PUBLIC_CURRENT/manifest.json" |
    cut -d' ' -f1
)"

PRODUCTION_HEALTH="$(
  if [ "$HEALTHY_SLOTS" = "5" ] &&
     [ "$ROUTES_OK" = "true" ]
  then
    echo HEALTHY
  else
    echo DEGRADED
  fi
)"

python3 - \
  "$REFERENCE_ID" \
  "$MANAGED_COUNT" \
  "$PUBLIC_FILES" \
  "$REDACTIONS" \
  "$SECRET_FINDINGS" \
  "$SYNTAX_FAILURES" \
  "$HEALTHY_SLOTS" \
  "$ROUTES_OK" \
  "$PRODUCTION_HEALTH" \
  "$MANIFEST_SHA" \
  "$PRIVATE_SNAPSHOT" \
  "$VM101_REFERENCE" \
  "$VM101_REFERENCE_SNAPSHOT" \
  > "$REPORT_DIR/build-assessment.json" <<'PY'
import json
import sys

(
    reference_id,
    managed_count,
    public_files,
    redactions,
    secret_findings,
    syntax_failures,
    healthy_slots,
    routes_ok,
    production_health,
    manifest_sha,
    private_snapshot,
    current_url,
    snapshot_url,
) = sys.argv[1:]

print(json.dumps({
    "schema": "vm101-reference-build-assessment-v1",
    "all_ok": True,
    "decision":
        "PASS_STEP_050M07R01B_SALVAGE_BUILD_VM101_REFERENCE_V1",
    "step_execution": "PASS",
    "operation_result": "REFERENCE_PUBLISHED",
    "production_health": production_health,
    "milestone_status": "M07_IN_PROGRESS",
    "reference": {
        "id": reference_id,
        "managed_file_count": int(managed_count),
        "public_file_count": int(public_files),
        "redactions": int(redactions),
        "secret_findings": int(secret_findings),
        "syntax_failures": int(syntax_failures),
        "manifest_sha256": manifest_sha,
        "private_snapshot": private_snapshot,
        "current_url": current_url,
        "snapshot_url": snapshot_url,
    },
    "runtime": {
        "healthy_slots": int(healthy_slots),
        "routes_201_205": routes_ok == "true",
    },
    "safety": {
        "vm101_read_only": True,
        "vm101_modified": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "refresh_ran": False,
        "rebalance_ran": False,
        "direct_failopen_changed": False,
        "raw_private_files_publicly_exposed": False,
        "public_secret_scan_passed": True,
    },
    "next_step":
        "BUILD_CANONICAL_REPLACEMENT_FILES_FROM_VM101_REFERENCE",
}, ensure_ascii=False, indent=2))
PY

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${PASS_DECISION}
step_execution=PASS
operation_result=REFERENCE_PUBLISHED
production_health=${PRODUCTION_HEALTH}
milestone_status=M07_IN_PROGRESS
all_ok=true

reference:
  id=${REFERENCE_ID}
  private_raw_mirror=${PRIVATE_SNAPSHOT}
  public_current=${VM101_REFERENCE}
  public_snapshot=${VM101_REFERENCE_SNAPSHOT}
  manifest_sha256=${MANIFEST_SHA}

contents:
  managed_files=${MANAGED_COUNT}
  public_files=${PUBLIC_FILES}
  redactions=${REDACTIONS}
  secret_findings=${SECRET_FINDINGS}
  syntax_failures=${SYNTAX_FAILURES}
  machine_profile=true
  runtime_snapshot=true
  source_of_truth_contract=true
  compatibility_matrix=true
  known_failure_catalog=true
  verified_method_library=true
  immutable_history=true

runtime:
  healthy_slots=${HEALTHY_SLOTS}
  routes_201_205=${ROUTES_OK}
  protocol=AmneziaWG
  authoritative_cli=/usr/bin/amneziawg
  system_timezone=UTC
  table200_optional=true

safety:
  vm101_read_only=true
  vm101_modified=false
  raw_private_files_publicly_exposed=false
  public_secret_scan_passed=true
  network_changed=false
  services_changed=false
  state_changed=false
  refresh_ran=false
  rebalance_ran=false
  direct_failopen_changed=false

plan:
  current_milestone=M07
  milestone_completed=false

next_step:
  BUILD_CANONICAL_REPLACEMENT_FILES_FROM_VM101_REFERENCE

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}
VM101_REFERENCE=${VM101_REFERENCE}
EOF

python3 - \
  "$REPORT_DIR/build-assessment.json" \
  "$STEP" \
  "$BUILD_TS" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" \
  "$ARCHITECTURE_PLAN" \
  "$XS_MAP" \
  "$GLOBAL_PROJECT_PLAN" \
  "$VM101_REFERENCE" \
  > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    assessment_path,
    step,
    generated_at,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
    vm101_reference,
) = 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": generated_at,
    "assessment": assessment,
    "safety": assessment["safety"],
    "plan": {
        "current_milestone": "M07",
        "milestone_completed": False,
    },
    "next_step":
        "BUILD_CANONICAL_REPLACEMENT_FILES_FROM_VM101_REFERENCE",
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
        "vm101_reference": vm101_reference,
    },
}, ensure_ascii=False, indent=2))
PY

create_report_index

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

trap - ERR

echo "decision=$PASS_DECISION"
echo "step_execution=PASS"
echo "operation_result=REFERENCE_PUBLISHED"
echo "production_health=$PRODUCTION_HEALTH"
echo "reference_id=$REFERENCE_ID"
echo "managed_files=$MANAGED_COUNT"
echo "redactions=$REDACTIONS"
echo "secret_findings=$SECRET_FINDINGS"
echo "syntax_failures=$SYNTAX_FAILURES"
echo "healthy_slots=$HEALTHY_SLOTS"
echo "routes_201_205=$ROUTES_OK"
echo "vm101_modified=false"

print_links
