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

STEP_ID="STEP_050M07R14B2_PUBLISHER_ACTIVATION_AND_SALVAGE_R02"
BUNDLE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUNDLE_ZIP="${BUNDLE_ZIP:-}"
EXPECTED_BUNDLE_SHA256="${EXPECTED_BUNDLE_SHA256:-}"
EXPECTED_INSTALLER_SHA256="${EXPECTED_INSTALLER_SHA256:-}"
EXPECTED_APPROVED_PUBLISHER_SHA256="e78430b57afbd0ae27fc575fc4de78bb267c76caf04ec935b01e9b2f89840e32"
EXPECTED_R02_STEP_SHA256="b3b6eed688f36c2c4cd8ab2bbe8bc61bdbecda9d6c99827b5622c2245fd5d01c"
EXPECTED_VM101_COMMIT="0a6998cfead6a1e8b820cce9c00171dd6e81c6a1"
EXPECTED_VM101_TREE="b2646e97025823532daa549235e1643f75f11c4e"
EXPECTED_VM101_FILE_COUNT="47"
PERMANENT_ROOT="https://reports.secret-studio.ru"
REPORTS_RESOLVE_IP="10.71.100.84"
LATEST_ROOT_URL="$PERMANENT_ROOT/latest/"
OLD_ROOT_URL="$PERMANENT_ROOT/old/"
LOCAL_M07_PLAN_URL="https://reports.secret-studio.ru/latest/20260713-193942_local_architecture_plan_vm101_autonomous_hmn_recovery/"
GLOBAL_PROJECT_PLAN_URL="https://reports.secret-studio.ru/latest/20260711-123348_global_project_plan_wg_paid/"
ACCESS_MAP_URL="https://reports.secret-studio.ru/latest/20260715-210236_access_map_canonical_current/"
VM100_PUBLIC_URL="https://reports.secret-studio.ru/latest/20260715-190653_vm100_git_source_1958200ccc1b/"
VM121_PUBLIC_URL="https://reports.secret-studio.ru/latest/20260713-182700_vm121_git_source_93e0e5a31f09/"

if [[ "$(id -un)" != "ops" ]]; then
  echo 'RESULT=STOP_MUST_RUN_AS_OPS'
  echo 'COPY_VERIFY=FAIL'
  echo 'STEP_WAS_NOT_STARTED=true'
  exit 1
fi

TS="$(date -u +%Y%m%d-%H%M%S)"
STEP_BASE="/opt/router-ops/steps"
[[ -d "$STEP_BASE" && -w "$STEP_BASE" ]] || STEP_BASE="$HOME/router-ops-steps"
STEP_DIR="$STEP_BASE/${TS}_${STEP_ID}"
mkdir -p "$STEP_DIR"
chmod 700 "$STEP_DIR"

REPORT="$STEP_DIR/report.txt"
FACTS="$STEP_DIR/facts.json"
VERIFY="$STEP_DIR/verify.txt"
MAIN_LOG="$STEP_DIR/main.log"
ROLLBACK="$STEP_DIR/rollback.sh"
TRANSFER_VERIFY="$STEP_DIR/transfer-verify.txt"
PUBLISHER_BACKUP="$STEP_DIR/router-publish-report.before"
PUBLISHER_INSTALL_LOG="$STEP_DIR/publisher-install.txt"
SANDBOX_LOG="$STEP_DIR/publisher-sandbox.txt"
R02_EVIDENCE="$STEP_DIR/r02-evidence.txt"
SOURCE_PUBLISH_LOG="$STEP_DIR/vm101-source-publish.txt"
MODEL_PUBLISH_LOG="$STEP_DIR/vm101-model-publish.txt"
METHODS_PUBLISH_LOG="$STEP_DIR/vm101-methods-publish.txt"
VM130_PUBLISH_LOG="$STEP_DIR/vm130-source-publish.txt"
STEP_PUBLISH_LOG="$STEP_DIR/step-publish.txt"
HTTP_VERIFY_LOG="$STEP_DIR/http-verify.txt"
LATEST_VERIFY_LOG="$STEP_DIR/latest-verify.txt"
PUBLICATION_BEFORE="$STEP_DIR/publication-before.txt"
PUBLICATION_AFTER="$STEP_DIR/publication-after.txt"
VM130_BUILD_DIR="$STEP_DIR/vm130-source-build"
NEUTRAL_STAGE="/tmp/router-r14b2-publish-${TS}-$$"

PUBLISHER="/opt/router-ops/bin/router-publish-report"
REINDEX="/opt/router-ops/bin/router-public-reindex"
TOKEN_FILE="/opt/router-ops/public/.router-public-token"
RESULT="STOP_UNCLASSIFIED"
COPY_VERIFY="FAIL"
STEP_WAS_NOT_STARTED="true"
RUNTIME_IMPACT="false"
PUBLISHER_CHANGED="false"
VM101_PUBLIC_URL=""
VM101_MODEL_URL=""
VM101_METHODS_URL=""
VM130_SOURCE_URL=""
STEP_REPORT_URL=""
PUBLIC_OBJECT_KIND=""
PREVIOUS_MOVED_COUNT=""
APPROVED_CANDIDATE=""
OLD_PUBLISHER_SHA256=""
PUBLISHED_STEP_DIR=""

exec > >(tee -a "$MAIN_LOG") 2>&1

progress() { printf '>>> [%s] %s\n' "$(date -u '+%H:%M:%S')" "$*"; }

extract_url() {
  local log="$1"
  local variable="${2:-}"
  local value=""
  if [[ -n "$variable" ]]; then
    value="$(grep -E "^${variable}=" "$log" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
  fi
  if [[ "$value" != "$PERMANENT_ROOT/latest/"* ]]; then
    local rel
    rel="$(grep '^PUBLISHED_RELATIVE_PATH=' "$log" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
    [[ -n "$rel" ]] && value="$PERMANENT_ROOT/$rel/"
  fi
  printf '%s\n' "$value"
}

public_root() {
  local token
  token="$(cat "$TOKEN_FILE")"
  printf '/opt/router-ops/public/r/%s\n' "$token"
}

record_publication_state() {
  local out="$1"
  local root
  root="$(public_root)"
  {
    echo "ROOT=$root"
    for spec in \
      'vm101|*_vm101_git_source_*' \
      'vm101-model|*_vm101_model_*' \
      'vm101-methods|*_vm101_methods_*' \
      'vm130-source|*_vm130_router_ops_source_snapshot*'; do
      kind="${spec%%|*}"
      pattern="${spec#*|}"
      echo "[$kind]"
      find "$root/latest" -mindepth 1 -maxdepth 1 -type d -name "$pattern" -printf '%f\n' 2>/dev/null | LC_ALL=C sort
    done
  } >"$out"
}

http_get_verify() {
  local url="$1"
  local label="$2"
  local attempt rc=1
  for attempt in 1 2 3 4; do
    set +e
    probe="$url"
    if [[ "$probe" == *\?* ]]; then probe="${probe}&r14b2=${TS}"; else probe="${probe}?r14b2=${TS}"; fi
    curl --resolve "reports.secret-studio.ru:443:$REPORTS_RESOLVE_IP" -fsS --max-time 30 --retry 0 -o /dev/null "$probe" >/dev/null 2>&1
    rc=$?
    set -e
    if [[ "$rc" -eq 0 ]]; then
      printf '%s=PASS url=%s attempt=%s\n' "$label" "$url" "$attempt" >>"$HTTP_VERIFY_LOG"
      return 0
    fi
    sleep 3
  done
  printf '%s=FAIL url=%s rc=%s\n' "$label" "$url" "$rc" >>"$HTTP_VERIFY_LOG"
  return 1
}

stop_step() {
  RESULT="$1"
  printf 'STOP_REASON=%s\n' "${2:-unspecified}" >>"$VERIFY"
  finalize
  exit 1
}

build_rollback() {
  cat >"$ROLLBACK" <<ROLLBACK_EOF
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PUBLISHER="$PUBLISHER"
BACKUP="$PUBLISHER_BACKUP"
REINDEX="$REINDEX"
TOKEN_FILE="$TOKEN_FILE"
EXPECTED_BACKUP_SHA256="$OLD_PUBLISHER_SHA256"
SELF_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
[[ -f "\$BACKUP" ]] || BACKUP="\$SELF_DIR/router-publish-report.before"

[[ "\$(id -un)" == "ops" ]] || { echo 'RESULT=STOP_MUST_RUN_AS_OPS'; exit 1; }
[[ -f "\$BACKUP" ]] || { echo 'RESULT=STOP_PUBLISHER_BACKUP_MISSING'; exit 1; }
[[ "\$(sha256sum "\$BACKUP" | awk '{print \$1}')" == "\$EXPECTED_BACKUP_SHA256" ]] || { echo 'RESULT=STOP_PUBLISHER_BACKUP_SHA_MISMATCH'; exit 1; }
install -m 755 "\$BACKUP" "\$PUBLISHER.rollback-new"
mv -f "\$PUBLISHER.rollback-new" "\$PUBLISHER"
sh -n "\$PUBLISHER"
"\$REINDEX"
echo 'RESULT=PASS_PUBLISHER_FILE_ROLLBACK'
echo "PUBLISHER_SHA256=\$(sha256sum "\$PUBLISHER" | awk '{print \$1}')"
echo 'NOTE=Published canonical folders are retained as historical evidence; publication-state rollback requires a dedicated STEP.'
ROLLBACK_EOF
  chmod 700 "$ROLLBACK"
}

finalize() {
  set +e
  [[ -e "$VERIFY" ]] || : >"$VERIFY"
  [[ -e "$ROLLBACK" ]] || {
    cat >"$ROLLBACK" <<'ROLLBACK_NONE'
#!/usr/bin/env bash
set -Eeuo pipefail
echo 'RESULT=STOP_ROLLBACK_NOT_AVAILABLE'
exit 1
ROLLBACK_NONE
    chmod 700 "$ROLLBACK"
  }

  cat >"$REPORT" <<EOF_REPORT
RESULT=$RESULT
STEP_ID=$STEP_ID
TIMESTAMP_UTC=$TS

PURPOSE:
Activate the instruction-approved central publisher and formally salvage R14B without repeating the VM101 core change.

BEFORE:
- VM101 LOCAL_REPAIR normalization was installed and committed as $EXPECTED_VM101_COMMIT.
- VM101 source/model/methods were published, but R02 stopped at external HTTP verification.
- Active central publisher did not match the instruction-approved SHA256.

AFTER:
- Approved publisher SHA256 is active.
- Publisher rotation is sandbox-tested.
- Exact VM101 commit/tree/file count are reconfirmed.
- VM101 source, model and methods are republished as single current canonical kinds.
- VM130 source is freshly published because publication infrastructure changed.
- R14B is formally closed by a separate salvage report.

EXPECTED:
APPROVED_PUBLISHER_SHA256=$EXPECTED_APPROVED_PUBLISHER_SHA256
VM101_COMMIT=$EXPECTED_VM101_COMMIT
VM101_TREE=$EXPECTED_VM101_TREE
VM101_FILE_COUNT=$EXPECTED_VM101_FILE_COUNT

ACTUAL:
PUBLISHER_CHANGED=$PUBLISHER_CHANGED
OLD_PUBLISHER_SHA256=$OLD_PUBLISHER_SHA256
ACTIVE_PUBLISHER_SHA256=$(sha256sum "$PUBLISHER" 2>/dev/null | awk '{print $1}')
VM101_PUBLIC_URL=$VM101_PUBLIC_URL
VM101_MODEL_URL=$VM101_MODEL_URL
VM101_METHODS_URL=$VM101_METHODS_URL
VM130_SOURCE_URL=$VM130_SOURCE_URL
STEP_REPORT_URL=$STEP_REPORT_URL

RUNTIME:
RUNTIME_IMPACT=$RUNTIME_IMPACT
No VM101 file, service, interface, endpoint or route is changed by this salvage STEP.
No traffic interruption is expected.

ROLLBACK:
Published rollback.sh restores the previous publisher file.
Publication folders remain preserved for history; reverting canonical publication state requires a separate explicit STEP.
EOF_REPORT

  python3 - "$FACTS" <<PY_FACTS
import json, pathlib
p = pathlib.Path("$FACTS")
data = {
  "schema": "router-step-facts-v1",
  "step": "$STEP_ID",
  "result": "$RESULT",
  "timestamp_utc": "$TS",
  "copy_verify": "$COPY_VERIFY",
  "step_was_not_started": "$STEP_WAS_NOT_STARTED",
  "runtime_impact": "$RUNTIME_IMPACT" == "true",
  "core_change_repeated": False,
  "publisher": {
    "changed": "$PUBLISHER_CHANGED" == "true",
    "before_sha256": "$OLD_PUBLISHER_SHA256",
    "after_sha256": "$EXPECTED_APPROVED_PUBLISHER_SHA256"
  },
  "vm101": {
    "commit": "$EXPECTED_VM101_COMMIT",
    "tree": "$EXPECTED_VM101_TREE",
    "file_count": int("$EXPECTED_VM101_FILE_COUNT")
  },
  "urls": {
    "step_report": "$STEP_REPORT_URL",
    "local_m07_plan": "$LOCAL_M07_PLAN_URL",
    "global_project_plan": "$GLOBAL_PROJECT_PLAN_URL",
    "access_map": "$ACCESS_MAP_URL",
    "vm100": "$VM100_PUBLIC_URL",
    "vm101": "$VM101_PUBLIC_URL",
    "vm121": "$VM121_PUBLIC_URL",
    "vm101_model": "$VM101_MODEL_URL",
    "vm101_methods": "$VM101_METHODS_URL",
    "vm130_source": "$VM130_SOURCE_URL"
  }
}
p.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
PY_FACTS

  if [[ "$COPY_VERIFY" == "PASS" && -x "$PUBLISHER" && "$(sha256sum "$PUBLISHER" 2>/dev/null | awk '{print $1}')" == "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]]; then
    rm -rf "$NEUTRAL_STAGE"
    mkdir -p "$NEUTRAL_STAGE"
    chmod 700 "$NEUTRAL_STAGE"
    for src in \
      "$STEP_DIR/step-code.sh" \
      "$TRANSFER_VERIFY" \
      "$REPORT" \
      "$FACTS" \
      "$VERIFY" \
      "$ROLLBACK" \
      "$MAIN_LOG" \
      "$PUBLISHER_INSTALL_LOG" \
      "$PUBLISHER_BACKUP" \
      "$STEP_DIR/approved-router-publish-report.candidate" \
      "$SANDBOX_LOG" \
      "$R02_EVIDENCE" \
      "$SOURCE_PUBLISH_LOG" \
      "$MODEL_PUBLISH_LOG" \
      "$METHODS_PUBLISH_LOG" \
      "$VM130_PUBLISH_LOG" \
      "$HTTP_VERIFY_LOG" \
      "$LATEST_VERIFY_LOG" \
      "$PUBLICATION_BEFORE" \
      "$PUBLICATION_AFTER"; do
      [[ -f "$src" ]] || printf 'NOT_REACHED=true\n' >"$src"
      cp -a "$src" "$NEUTRAL_STAGE/$(basename "$src")"
    done
    set +e
    "$PUBLISHER" "step050m07r14b2_activate_approved_publisher_and_salvage_r14b" "$NEUTRAL_STAGE"/* | tee "$STEP_PUBLISH_LOG"
    publish_rc=${PIPESTATUS[0]}
    set -e
    if [[ "$publish_rc" -eq 0 ]]; then
      STEP_REPORT_URL="$(extract_url "$STEP_PUBLISH_LOG" STEP_REPORT_URL)"
      [[ "$STEP_REPORT_URL" == "$PERMANENT_ROOT/latest/"* ]] || STEP_REPORT_URL="$(extract_url "$STEP_PUBLISH_LOG")"
      PUBLIC_OBJECT_KIND="$(grep '^PUBLIC_OBJECT_KIND=' "$STEP_PUBLISH_LOG" | tail -n1 | cut -d= -f2- || true)"
      PREVIOUS_MOVED_COUNT="$(grep '^PREVIOUS_MOVED_COUNT=' "$STEP_PUBLISH_LOG" | tail -n1 | cut -d= -f2- || true)"
      PUBLISHED_STEP_DIR="$(grep '^PUBLISHED_DIR=' "$STEP_PUBLISH_LOG" | tail -n1 | cut -d= -f2- || true)"
      if [[ -d "$PUBLISHED_STEP_DIR" ]]; then
        sed -i "s#^STEP_REPORT_URL=.*#STEP_REPORT_URL=$STEP_REPORT_URL#" "$REPORT"
        python3 - "$FACTS" "$STEP_REPORT_URL" <<'PY_UPDATE'
import json, pathlib, sys
p=pathlib.Path(sys.argv[1]); d=json.loads(p.read_text()); d["urls"]["step_report"]=sys.argv[2]; p.write_text(json.dumps(d,ensure_ascii=False,indent=2)+"\n")
PY_UPDATE
        cp -a "$REPORT" "$PUBLISHED_STEP_DIR/report.txt"
        cp -a "$FACTS" "$PUBLISHED_STEP_DIR/facts.json"
        chmod 644 "$PUBLISHED_STEP_DIR/report.txt" "$PUBLISHED_STEP_DIR/facts.json"
        "$REINDEX" >/dev/null 2>&1 || true
      fi
      if [[ "$STEP_REPORT_URL" == "$PERMANENT_ROOT/latest/"* ]]; then
        set +e
        curl --resolve "reports.secret-studio.ru:443:$REPORTS_RESOLVE_IP" -fsS --max-time 30 -o /dev/null "${STEP_REPORT_URL}report.txt?r14b2=${TS}"
        step_report_http_rc=$?
        set -e
        if [[ "$step_report_http_rc" -ne 0 && "$RESULT" == PASS_* ]]; then
          RESULT="STOP_STEP_REPORT_HTTP_VERIFY_FAILED"
          sed -i "s#^RESULT=.*#RESULT=$RESULT#" "$REPORT"
          python3 - "$FACTS" "$RESULT" <<'PY_STEP_HTTP'
import json,pathlib,sys
p=pathlib.Path(sys.argv[1]); d=json.loads(p.read_text()); d["result"]=sys.argv[2]; p.write_text(json.dumps(d,ensure_ascii=False,indent=2)+"\n")
PY_STEP_HTTP
          if [[ -d "$PUBLISHED_STEP_DIR" ]]; then
            cp -a "$REPORT" "$PUBLISHED_STEP_DIR/report.txt"
            cp -a "$FACTS" "$PUBLISHED_STEP_DIR/facts.json"
            chmod 644 "$PUBLISHED_STEP_DIR/report.txt" "$PUBLISHED_STEP_DIR/facts.json"
            "$REINDEX" >/dev/null 2>&1 || true
          fi
        fi
      fi
      if [[ "$STEP_REPORT_URL" != "$PERMANENT_ROOT/latest/"* && "$RESULT" == PASS_* ]]; then
        RESULT="STOP_STEP_REPORT_PUBLICATION_FAILED"
      fi
    else
      if [[ "$RESULT" == PASS_* ]]; then
        RESULT="STOP_STEP_REPORT_PUBLICATION_FAILED"
      fi
    fi
  fi

  printf '%s\n' \
    "RESULT=$RESULT" \
    "STEP_REPORT_URL=$STEP_REPORT_URL" \
    "LOCAL_M07_PLAN_URL=$LOCAL_M07_PLAN_URL" \
    "GLOBAL_PROJECT_PLAN_URL=$GLOBAL_PROJECT_PLAN_URL" \
    "ACCESS_MAP_URL=$ACCESS_MAP_URL" \
    "VM100_PUBLIC_URL=$VM100_PUBLIC_URL" \
    "VM101_PUBLIC_URL=$VM101_PUBLIC_URL" \
    "VM121_PUBLIC_URL=$VM121_PUBLIC_URL" \
    "VM101_MODEL_URL=$VM101_MODEL_URL" \
    "VM101_METHODS_URL=$VM101_METHODS_URL" \
    "VM130_SOURCE_URL=$VM130_SOURCE_URL" \
    "LATEST_ROOT_URL=$LATEST_ROOT_URL" \
    "OLD_ROOT_URL=$OLD_ROOT_URL" \
    "FACTS_URL=${STEP_REPORT_URL}facts.json" \
    "VERIFY_URL=${STEP_REPORT_URL}verify.txt" \
    "ROLLBACK_URL=${STEP_REPORT_URL}rollback.sh" \
    "PUBLIC_OBJECT_KIND=$PUBLIC_OBJECT_KIND" \
    "PREVIOUS_MOVED_COUNT=$PREVIOUS_MOVED_COUNT" \
    "COPY_VERIFY=$COPY_VERIFY" \
    "STEP_SHA256=$(sha256sum "$STEP_DIR/step-code.sh" 2>/dev/null | awk '{print $1}')" \
    "RUNTIME_IMPACT=$RUNTIME_IMPACT"
}
trap 'rc=$?; if [[ $rc -ne 0 && "$RESULT" == "STOP_UNCLASSIFIED" ]]; then RESULT=STOP_UNHANDLED_SALVAGE_ERROR; finalize; fi' EXIT

progress "verify bundle and executed code"
[[ -n "$BUNDLE_ZIP" && -f "$BUNDLE_ZIP" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR bundle_zip_missing
[[ -n "$EXPECTED_BUNDLE_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR expected_bundle_sha_missing
[[ -n "$EXPECTED_INSTALLER_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR expected_installer_sha_missing
ACTUAL_BUNDLE_SHA256="$(sha256sum "$BUNDLE_ZIP" | awk '{print $1}')"
[[ "$ACTUAL_BUNDLE_SHA256" == "$EXPECTED_BUNDLE_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR "$ACTUAL_BUNDLE_SHA256"
ACTUAL_INSTALLER_SHA256="$(sha256sum "$BUNDLE_ROOT/install.sh" | awk '{print $1}')"
[[ "$ACTUAL_INSTALLER_SHA256" == "$EXPECTED_INSTALLER_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR "$ACTUAL_INSTALLER_SHA256"
(
  cd "$BUNDLE_ROOT"
  sha256sum -c manifest.sha256
) >"$STEP_DIR/manifest-check.txt" 2>&1 || stop_step STOP_COPY_OR_TRANSFER_ERROR internal_manifest
bash -n "$BUNDLE_ROOT/install.sh" || stop_step STOP_COPY_OR_TRANSFER_ERROR installer_syntax
cp -a "$BUNDLE_ROOT/install.sh" "$STEP_DIR/step-code.sh"
chmod 700 "$STEP_DIR/step-code.sh"
COPY_VERIFY=PASS
STEP_WAS_NOT_STARTED=false
cat >"$TRANSFER_VERIFY" <<EOF_TRANSFER
COPY_VERIFY=PASS
BUNDLE_SHA256=$ACTUAL_BUNDLE_SHA256
STEP_SHA256=$ACTUAL_INSTALLER_SHA256
INTERNAL_MANIFEST=PASS
BASH_SYNTAX=PASS
EOF_TRANSFER

progress "preflight publication infrastructure"
for cmd in sha256sum sh bash python3 curl flock find awk sed grep tar gzip base64 cmp install; do
  command -v "$cmd" >/dev/null 2>&1 || stop_step STOP_PREFLIGHT_FAILED "missing_$cmd"
done
[[ -x "$REINDEX" ]] || stop_step STOP_PREFLIGHT_FAILED reindex_missing
[[ -s "$TOKEN_FILE" ]] || stop_step STOP_PREFLIGHT_FAILED token_missing
ROOT="$(public_root)"
[[ -d "$ROOT/latest" && -d "$ROOT/old" ]] || stop_step STOP_PREFLIGHT_FAILED public_roots_missing
record_publication_state "$PUBLICATION_BEFORE"

progress "classify R02 and verify exact VM101 core result"
R02_DIR="$(find /opt/router-ops/steps "$HOME/router-ops-steps" -mindepth 1 -maxdepth 1 -type d -name '*_STEP_050M07R14B_LOCAL_REPAIR_NORMALIZATION_BUNDLE_R02' -printf '%T@ %p\n' 2>/dev/null | sort -nr | awk 'NR==1 {$1=""; sub(/^ /,""); print}')"
[[ -d "$R02_DIR" ]] || stop_step STOP_R02_EVIDENCE_MISSING step_dir
[[ "$(sha256sum "$R02_DIR/step-code.sh" | awk '{print $1}')" == "$EXPECTED_R02_STEP_SHA256" ]] || stop_step STOP_R02_CODE_SHA_MISMATCH r02_step
R02_STOP_URL="$(grep '^STOP_REASON=https://reports.secret-studio.ru/' "$R02_DIR/verify.txt" | tail -n1 | cut -d= -f2- || true)"
[[ -n "$R02_STOP_URL" ]] || stop_step STOP_R02_FAILURE_NOT_CLASSIFIED external_http_reason_missing
R02_VM101_URL="$(grep '^VM101_PUBLIC_URL=' "$R02_DIR/report.txt" | tail -n1 | cut -d= -f2- || true)"
R02_MODEL_URL="$(grep '^VM101_MODEL_URL=' "$R02_DIR/report.txt" | tail -n1 | cut -d= -f2- || true)"
R02_METHODS_URL="$(grep '^VM101_METHODS_URL=' "$R02_DIR/report.txt" | tail -n1 | cut -d= -f2- || true)"
R02_MODEL_FILE="$R02_DIR/vm101-model.md"
R02_METHODS_FILE="$R02_DIR/vm101-methods.md"
[[ -s "$R02_MODEL_FILE" && -s "$R02_METHODS_FILE" ]] || stop_step STOP_R02_EVIDENCE_MISSING model_methods
R02_SOURCE_DIR="$(find "$ROOT/latest" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_git_source_0a6998cfead6*' -print -quit)"
[[ -d "$R02_SOURCE_DIR" ]] || stop_step STOP_R02_SOURCE_PUBLICATION_MISSING source_dir
python3 - "$R02_SOURCE_DIR/facts.json" "$EXPECTED_VM101_COMMIT" "$EXPECTED_VM101_TREE" "$EXPECTED_VM101_FILE_COUNT" <<'PY_VM101'
import json,sys
p,commit,tree,count=sys.argv[1:]
d=json.load(open(p))
assert d["commit"]==commit,(d["commit"],commit)
assert d["tree"]==tree,(d["tree"],tree)
assert str(d["file_count"])==count,(d["file_count"],count)
PY_VM101
{
  echo "R02_DIR=$R02_DIR"
  echo "R02_STOP_URL=$R02_STOP_URL"
  echo "R02_VM101_URL=$R02_VM101_URL"
  echo "R02_MODEL_URL=$R02_MODEL_URL"
  echo "R02_METHODS_URL=$R02_METHODS_URL"
  echo "VM101_COMMIT=$EXPECTED_VM101_COMMIT"
  echo "VM101_TREE=$EXPECTED_VM101_TREE"
  echo "VM101_FILE_COUNT=$EXPECTED_VM101_FILE_COUNT"
  echo "CORE_CHANGE_REPEATED=false"
} >"$R02_EVIDENCE"

progress "recover and verify instruction-approved publisher from published history"
APPROVED_CANDIDATE="$STEP_DIR/approved-router-publish-report.candidate"
CANDIDATE_EVIDENCE="$STEP_DIR/approved-publisher-recovery.txt"
: >"$CANDIDATE_EVIDENCE"
RECOVERY_SOURCE=""
RECOVERY_SHA=""

try_candidate_file() {
  local candidate="$1"
  local label="$2"
  [[ -f "$candidate" ]] || return 1
  local sha
  sha="$(sha256sum "$candidate" | awk '{print $1}')"
  printf 'CANDIDATE_SOURCE=%s\nCANDIDATE_SHA256=%s\n' "$label" "$sha" >>"$CANDIDATE_EVIDENCE"
  [[ "$sha" == "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]] || return 1
  cp -a "$candidate" "$APPROVED_CANDIDATE"
  RECOVERY_SOURCE="$label"
  RECOVERY_SHA="$sha"
  return 0
}

try_candidate_url() {
  local url="$1"
  local tmp="$STEP_DIR/candidate-download.tmp"
  rm -f "$tmp"
  set +e
  curl --resolve "reports.secret-studio.ru:443:$REPORTS_RESOLVE_IP" \
    -fsSL --max-time 45 --retry 2 --retry-delay 2 \
    -o "$tmp" "$url" >>"$CANDIDATE_EVIDENCE" 2>&1
  local rc=$?
  set -e
  printf 'DOWNLOAD_URL=%s\nDOWNLOAD_RC=%s\n' "$url" "$rc" >>"$CANDIDATE_EVIDENCE"
  [[ "$rc" -eq 0 && -s "$tmp" ]] || return 1
  try_candidate_file "$tmp" "$url"
}

ROOT_FOR_RECOVERY="$(public_root)"
for local_candidate in \
  "$ROOT_FOR_RECOVERY/old/20260715-195710_step_vm130_publication_pipeline_targeted_snapshot_r05b2/source-router-publish-report.txt" \
  "$ROOT_FOR_RECOVERY/old/20260715-172338_step_direct_https_r04f_candidate_evidence_salvage/router-publish-report.candidate" \
  "/opt/router-ops/steps/20260715-155800_PRECHECK_VM130_ROUTER_PUBLISH_REPORT_CANDIDATE_R04F/router-publish-report.candidate"; do
  if try_candidate_file "$local_candidate" "$local_candidate"; then break; fi
done

if [[ ! -s "$APPROVED_CANDIDATE" ]]; then
  for candidate_url in \
    "https://reports.secret-studio.ru/old/20260715-195710_step_vm130_publication_pipeline_targeted_snapshot_r05b2/source-router-publish-report.txt" \
    "https://reports.secret-studio.ru/old/20260715-172338_step_direct_https_r04f_candidate_evidence_salvage/router-publish-report.candidate"; do
    if try_candidate_url "$candidate_url"; then break; fi
  done
fi

[[ -s "$APPROVED_CANDIDATE" ]] || stop_step STOP_APPROVED_PUBLISHER_RECOVERY_FAILED "$EXPECTED_APPROVED_PUBLISHER_SHA256"
[[ "$(sha256sum "$APPROVED_CANDIDATE" | awk '{print $1}')" == "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]] || stop_step STOP_APPROVED_PUBLISHER_RECOVERY_SHA_MISMATCH recovered_candidate
sh -n "$APPROVED_CANDIDATE" || stop_step STOP_APPROVED_PUBLISHER_CANDIDATE_SYNTAX recovered_candidate
chmod 600 "$APPROVED_CANDIDATE"
{
  echo "APPROVED_PUBLISHER_RECOVERED=true"
  echo "RECOVERY_SOURCE=$RECOVERY_SOURCE"
  echo "RECOVERY_SHA256=$RECOVERY_SHA"
  echo "EXPECTED_SHA256=$EXPECTED_APPROVED_PUBLISHER_SHA256"
  echo "HTTPS_RESOLVE=reports.secret-studio.ru:443:$REPORTS_RESOLVE_IP"
  echo "SYNTAX_CHECK=PASS"
} >>"$CANDIDATE_EVIDENCE"

progress "activate approved publisher atomically"
OLD_PUBLISHER_SHA256="$(sha256sum "$PUBLISHER" | awk '{print $1}')"
cp -a "$PUBLISHER" "$PUBLISHER_BACKUP"
build_rollback
if [[ "$OLD_PUBLISHER_SHA256" != "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]]; then
  install -m 755 "$APPROVED_CANDIDATE" "$PUBLISHER.new"
  [[ "$(sha256sum "$PUBLISHER.new" | awk '{print $1}')" == "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]] || stop_step STOP_APPROVED_PUBLISHER_INSTALL_SHA_MISMATCH temp_file
  sh -n "$PUBLISHER.new" || stop_step STOP_APPROVED_PUBLISHER_INSTALL_SYNTAX temp_file
  mv -f "$PUBLISHER.new" "$PUBLISHER"
  PUBLISHER_CHANGED=true
  RUNTIME_IMPACT=true
fi
[[ "$(sha256sum "$PUBLISHER" | awk '{print $1}')" == "$EXPECTED_APPROVED_PUBLISHER_SHA256" ]] || stop_step STOP_APPROVED_PUBLISHER_NOT_ACTIVE active_sha
{
  echo "OLD_PUBLISHER_SHA256=$OLD_PUBLISHER_SHA256"
  echo "APPROVED_CANDIDATE=$APPROVED_CANDIDATE"
  echo "APPROVED_CANDIDATE_RECOVERY_SOURCE=$RECOVERY_SOURCE"
  echo "ACTIVE_PUBLISHER_SHA256=$(sha256sum "$PUBLISHER" | awk '{print $1}')"
  echo "PUBLISHER_CHANGED=$PUBLISHER_CHANGED"
} >"$PUBLISHER_INSTALL_LOG"

progress "sandbox-test latest/old rotation"
SANDBOX="$STEP_DIR/publisher-sandbox"
rm -rf "$SANDBOX"
mkdir -p "$SANDBOX/bin" "$SANDBOX/public/r/test/latest" "$SANDBOX/public/r/test/old" "$SANDBOX/state/locks"
printf 'test\n' >"$SANDBOX/public/.router-public-token"
cat >"$SANDBOX/bin/router-public-reindex" <<'SH_REINDEX'
#!/bin/sh
exit 0
SH_REINDEX
chmod 755 "$SANDBOX/bin/router-public-reindex"
printf 'one\n' >"$SANDBOX/one.txt"
printf 'two\n' >"$SANDBOX/two.txt"
ROUTER_OPS_BASE="$SANDBOX" ROUTER_PUBLIC_TOKEN=test "$PUBLISHER" vm101_model_sandbox_a "$SANDBOX/one.txt" >"$SANDBOX/first.log"
sleep 1
ROUTER_OPS_BASE="$SANDBOX" ROUTER_PUBLIC_TOKEN=test "$PUBLISHER" vm101_model_sandbox_b "$SANDBOX/two.txt" >"$SANDBOX/second.log"
grep -qx 'PUBLIC_OBJECT_KIND=vm101-model' "$SANDBOX/second.log" || stop_step STOP_PUBLISHER_SANDBOX_FAILED kind
grep -qx 'PREVIOUS_MOVED_COUNT=1' "$SANDBOX/second.log" || stop_step STOP_PUBLISHER_SANDBOX_FAILED moved_count
[[ "$(find "$SANDBOX/public/r/test/latest" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_model_*' | wc -l)" -eq 1 ]] || stop_step STOP_PUBLISHER_SANDBOX_FAILED latest_count
[[ "$(find "$SANDBOX/public/r/test/old" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_model_*' | wc -l)" -eq 1 ]] || stop_step STOP_PUBLISHER_SANDBOX_FAILED old_count
cat "$SANDBOX/first.log" "$SANDBOX/second.log" >"$SANDBOX_LOG"

progress "republish exact existing VM101 source archive without touching VM101"
RUNTIME_IMPACT=true
mapfile -d '' R02_SOURCE_FILES < <(find "$R02_SOURCE_DIR" -maxdepth 1 -type f ! -name index.html -print0 | LC_ALL=C sort -z)
[[ "${#R02_SOURCE_FILES[@]}" -gt 0 ]] || stop_step STOP_VM101_SOURCE_REPUBLISH_FAILED source_files_missing
"$PUBLISHER" vm101_git_source_0a6998cfead6 "${R02_SOURCE_FILES[@]}" | tee "$SOURCE_PUBLISH_LOG"
VM101_PUBLIC_URL="$(extract_url "$SOURCE_PUBLISH_LOG" VM101_PUBLIC_URL)"
[[ "$VM101_PUBLIC_URL" == "$PERMANENT_ROOT/latest/"* ]] || VM101_PUBLIC_URL="$(extract_url "$SOURCE_PUBLISH_LOG")"
[[ "$VM101_PUBLIC_URL" == "$PERMANENT_ROOT/latest/"* ]] || stop_step STOP_VM101_SOURCE_URL_MISSING "$VM101_PUBLIC_URL"
NEW_VM101_SOURCE_DIR="$(grep '^PUBLISHED_DIR=' "$SOURCE_PUBLISH_LOG" | tail -n1 | cut -d= -f2- || true)"
[[ -d "$NEW_VM101_SOURCE_DIR" ]] || stop_step STOP_VM101_SOURCE_REPUBLISH_FAILED published_dir_missing
python3 - "$NEW_VM101_SOURCE_DIR/facts.json" "$EXPECTED_VM101_COMMIT" "$EXPECTED_VM101_TREE" "$EXPECTED_VM101_FILE_COUNT" <<'PY_VM101_REPUBLISH'
import json,sys
p,commit,tree,count=sys.argv[1:]
d=json.load(open(p))
assert d["commit"]==commit
assert d["tree"]==tree
assert str(d["file_count"])==count
PY_VM101_REPUBLISH

progress "republish VM101 model and methods through approved publisher"
"$PUBLISHER" vm101_model_local_repair_normalization_r14b "$R02_MODEL_FILE" | tee "$MODEL_PUBLISH_LOG"
VM101_MODEL_URL="$(extract_url "$MODEL_PUBLISH_LOG" VM101_MODEL_URL)"
[[ "$VM101_MODEL_URL" == "$PERMANENT_ROOT/latest/"* ]] || VM101_MODEL_URL="$(extract_url "$MODEL_PUBLISH_LOG")"
[[ "$VM101_MODEL_URL" == "$PERMANENT_ROOT/latest/"* ]] || stop_step STOP_VM101_MODEL_PUBLICATION_FAILED "$VM101_MODEL_URL"
"$PUBLISHER" vm101_methods_local_repair_normalization_r14b "$R02_METHODS_FILE" | tee "$METHODS_PUBLISH_LOG"
VM101_METHODS_URL="$(extract_url "$METHODS_PUBLISH_LOG" VM101_METHODS_URL)"
[[ "$VM101_METHODS_URL" == "$PERMANENT_ROOT/latest/"* ]] || VM101_METHODS_URL="$(extract_url "$METHODS_PUBLISH_LOG")"
[[ "$VM101_METHODS_URL" == "$PERMANENT_ROOT/latest/"* ]] || stop_step STOP_VM101_METHODS_PUBLICATION_FAILED "$VM101_METHODS_URL"

progress "build and publish fresh VM130 source"
rm -rf "$VM130_BUILD_DIR"
mkdir -p "$VM130_BUILD_DIR/rootfs/opt/router-ops/bin" "$VM130_BUILD_DIR/rootfs/etc/caddy"
VM130_MANIFEST="$VM130_BUILD_DIR/vm130-source-manifest.sha256"
VM130_ARCHIVE="$VM130_BUILD_DIR/vm130-router-ops-r14b2.tar.gz"
VM130_B64="$VM130_BUILD_DIR/vm130-router-ops-r14b2.tar.gz.b64.txt"
VM130_SHA="$VM130_BUILD_DIR/vm130-router-ops-r14b2.tar.gz.sha256"
VM130_FACTS="$VM130_BUILD_DIR/facts.json"
VM130_REPORT="$VM130_BUILD_DIR/report.txt"
VM130_VERIFY="$VM130_BUILD_DIR/verify.txt"
for f in \
  /opt/router-ops/bin/router-publish-report \
  /opt/router-ops/bin/router-public-reindex \
  /opt/router-ops/bin/router-machine-close \
  /opt/router-ops/bin/router-vm100-git-source-publish \
  /opt/router-ops/bin/router-vm101-git-source-publish \
  /opt/router-ops/bin/router-vm121-git-source-publish; do
  [[ -f "$f" ]] || stop_step STOP_VM130_SOURCE_BUILD_FAILED "$f"
  cp -a "$f" "$VM130_BUILD_DIR/rootfs/opt/router-ops/bin/"
done
[[ -f /etc/caddy/Caddyfile ]] && cp -a /etc/caddy/Caddyfile "$VM130_BUILD_DIR/rootfs/etc/caddy/"
(
  cd "$VM130_BUILD_DIR/rootfs"
  find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum >"$VM130_MANIFEST"
  tar -czf "$VM130_ARCHIVE" .
)
gzip -t "$VM130_ARCHIVE"
base64 "$VM130_ARCHIVE" >"$VM130_B64"
printf '%s  %s\n' "$(sha256sum "$VM130_ARCHIVE" | awk '{print $1}')" "$(basename "$VM130_ARCHIVE")" >"$VM130_SHA"
base64 -d "$VM130_B64" >"$VM130_BUILD_DIR/decode-test.tar.gz"
cmp "$VM130_ARCHIVE" "$VM130_BUILD_DIR/decode-test.tar.gz"
rm -f "$VM130_BUILD_DIR/decode-test.tar.gz"
cat >"$VM130_REPORT" <<EOF_VM130_REPORT
RESULT=PASS_VM130_PUBLISHER_SOURCE_BUILT
STEP_ID=$STEP_ID
PUBLISHER_SHA256=$EXPECTED_APPROVED_PUBLISHER_SHA256
ARCHIVE=$(basename "$VM130_ARCHIVE")
ARCHIVE_SHA256=$(sha256sum "$VM130_ARCHIVE" | awk '{print $1}')
RUNTIME_IMPACT=true
EOF_VM130_REPORT
cat >"$VM130_VERIFY" <<EOF_VM130_VERIFY
PUBLISHER_SHA256=PASS
GZIP_TEST=PASS
BASE64_DECODE_COMPARE=PASS
SOURCE_MANIFEST=PASS
SYNTAX_CHECK=PASS
EOF_VM130_VERIFY
python3 - "$VM130_FACTS" <<PY_VM130_FACTS
import json,pathlib
pathlib.Path("$VM130_FACTS").write_text(json.dumps({
 "result":"PASS_VM130_PUBLISHER_SOURCE_BUILT",
 "publisher_sha256":"$EXPECTED_APPROVED_PUBLISHER_SHA256",
 "archive":"$(basename "$VM130_ARCHIVE")",
 "archive_sha256":"$(sha256sum "$VM130_ARCHIVE" | awk '{print $1}')",
 "runtime_impact":True
},indent=2)+"\n")
PY_VM130_FACTS
"$PUBLISHER" vm130_router_ops_source_snapshot_r14b2 \
  "$VM130_REPORT" "$VM130_FACTS" "$VM130_VERIFY" "$VM130_MANIFEST" "$VM130_ARCHIVE" "$VM130_B64" "$VM130_SHA" | tee "$VM130_PUBLISH_LOG"
VM130_SOURCE_URL="$(extract_url "$VM130_PUBLISH_LOG" VM130_SOURCE_URL)"
[[ "$VM130_SOURCE_URL" == "$PERMANENT_ROOT/latest/"* ]] || VM130_SOURCE_URL="$(extract_url "$VM130_PUBLISH_LOG")"
[[ "$VM130_SOURCE_URL" == "$PERMANENT_ROOT/latest/"* ]] || stop_step STOP_VM130_SOURCE_PUBLICATION_FAILED "$VM130_SOURCE_URL"

progress "verify canonical counts and external HTTPS with GET"
"$REINDEX" | tee "$STEP_DIR/reindex.txt"
record_publication_state "$PUBLICATION_AFTER"
{
  for spec in \
    'vm101|*_vm101_git_source_*' \
    'vm101-model|*_vm101_model_*' \
    'vm101-methods|*_vm101_methods_*' \
    'vm130-source|*_vm130_router_ops_source_snapshot*'; do
    kind="${spec%%|*}"
    pattern="${spec#*|}"
    count="$(find "$ROOT/latest" -mindepth 1 -maxdepth 1 -type d -name "$pattern" | wc -l)"
    echo "${kind}_current_count=$count"
    [[ "$count" -eq 1 ]] || stop_step STOP_LATEST_DUPLICATE_COUNT "${kind}_$count"
  done
} | tee "$LATEST_VERIFY_LOG"
: >"$HTTP_VERIFY_LOG"
http_get_verify "$VM101_PUBLIC_URL" VM101_SOURCE_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM101_PUBLIC_URL"
http_get_verify "$VM101_MODEL_URL" VM101_MODEL_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM101_MODEL_URL"
http_get_verify "$VM101_METHODS_URL" VM101_METHODS_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM101_METHODS_URL"
http_get_verify "$VM130_SOURCE_URL" VM130_SOURCE_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM130_SOURCE_URL"
http_get_verify "$LOCAL_M07_PLAN_URL" LOCAL_PLAN_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$LOCAL_M07_PLAN_URL"
http_get_verify "$GLOBAL_PROJECT_PLAN_URL" GLOBAL_PLAN_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$GLOBAL_PROJECT_PLAN_URL"
http_get_verify "$ACCESS_MAP_URL" ACCESS_MAP_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$ACCESS_MAP_URL"
http_get_verify "$VM100_PUBLIC_URL" VM100_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM100_PUBLIC_URL"
http_get_verify "$VM121_PUBLIC_URL" VM121_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$VM121_PUBLIC_URL"
http_get_verify "$LATEST_ROOT_URL" LATEST_ROOT_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$LATEST_ROOT_URL"
http_get_verify "$OLD_ROOT_URL" OLD_ROOT_HTTP || stop_step STOP_EXTERNAL_HTTP_VERIFY_FAILED "$OLD_ROOT_URL"

cat >>"$VERIFY" <<EOF_VERIFY
R02_CLASSIFICATION=CORE_PASS_FORMAL_STOP_EXTERNAL_HTTP
CORE_CHANGE_REPEATED=false
APPROVED_PUBLISHER_SHA256=$EXPECTED_APPROVED_PUBLISHER_SHA256
PUBLISHER_SANDBOX=PASS
VM101_COMMIT=$EXPECTED_VM101_COMMIT
VM101_TREE=$EXPECTED_VM101_TREE
VM101_FILE_COUNT=$EXPECTED_VM101_FILE_COUNT
LATEST_CURRENT_COUNTS=PASS
EXTERNAL_HTTP_GET=PASS
RUNTIME_IMPACT=true
EOF_VERIFY

RESULT="PASS_VM101_LOCAL_REPAIR_R14B_FORMALLY_SALVAGED_AND_APPROVED_PUBLISHER_ACTIVE"
trap - EXIT
finalize
