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

BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
ROOT="${BUNDLE_ROOT:?}"
RUN_DIR="${ROUTER_DETACHED_RUN_DIR:?}"
CHILD_RESULT="${ROUTER_DETACHED_CHILD_RESULT:?}"
INCOMING="${ROUTER_STEP_INCOMING:-/home/ops/incoming}"
SELFTEST="${ROUTER_EXACT_WORKER_SELFTEST:-false}"
SELFTEST_COUNT=0
SELFTEST_FAIL=none
if [[ "$SELFTEST" == true ]]; then
  case "${ROUTER_PUBLIC_ROOT:-}" in
    */one/public) SELFTEST_COUNT=1 ;;
    */fail/public) SELFTEST_FAIL=reconcile1 ;;
  esac
fi
STEP=STEP_050M07R20QZA_R04_VM121_VM100_RESTART_RECONCILE_PROOF_SEMANTIC_GIT_CARDINALITY_FIX

CURRENT="$RUN_DIR/workflow-current.txt"
EVENTS="$RUN_DIR/workflow-events.tsv"
EVIDENCE="$RUN_DIR/evidence"
REPORT="$RUN_DIR/report"
PAYLOAD="$RUN_DIR/public-payload"
FINAL_ENV="$RUN_DIR/final-publication.env"
TARGET_DIR="$RUN_DIR/target"
mkdir -p "$EVIDENCE" "$REPORT" "$PAYLOAD" "$INCOMING"
chmod 700 "$EVIDENCE" "$REPORT" "$PAYLOAD"

source "$BASE/lib/router-step-workflow.sh"
router_step_workflow_init "$CURRENT" "$EVENTS" preflight quiesce restart reconcile restore postcheck report_publish archive

CORE_CHANGE_COMPLETE=false
CORE_CHANGE_REPEATED=false
ROLLBACK_STATUS=not_required
STOP_DETAIL=unknown
STOP_HANDLED=false
LATEST_ARCHIVE=not_created
LATEST_ARCHIVE_SHA256=unknown
STEP_REPORT_URL=unknown
PRE_RUNTIME_COUNT=unknown
PRE_REGISTRY_COUNT=unknown
PRE_DESIRED_COUNT=unknown
POST_RUNTIME_COUNT=unknown
POST_REGISTRY_COUNT=unknown
POST_DESIRED_COUNT=unknown
RECONCILE_RUN_1=unknown
RECONCILE_RUN_2=unknown
VM100_ALLOCATOR_PID_BEFORE=unknown
VM100_ALLOCATOR_PID_AFTER=unknown
VM100_ALLOCATOR_PROCESS_BEFORE=unknown
VM100_ALLOCATOR_PROCESS_AFTER=unknown
VM121_GUARDS_QUIESCED=false
RUNTIME_RECOVERY_STATUS=not_required
VM100_ALLOCATOR_RESTART_ATTEMPTED=false
TRIGGER_WATCH_PRE=unknown
RECONCILE_TIMER_PRE=unknown
AGENT_PATH_PRE=unknown
AGENT_TIMER_PRE=unknown

write_child() {
  local result="$1" rc="$2" tmp="${CHILD_RESULT}.tmp.$$"
  {
    echo RESULT="$result"
    echo WORKER_RC="$rc"
    echo CORE_CHANGE_COMPLETE="$CORE_CHANGE_COMPLETE"
    echo CORE_CHANGE_REPEATED="$CORE_CHANGE_REPEATED"
    echo ROLLBACK_STATUS="$ROLLBACK_STATUS"
    echo STOP_DETAIL="$STOP_DETAIL"
    echo RUNTIME_RECOVERY_STATUS="$RUNTIME_RECOVERY_STATUS"
    echo PRE_RUNTIME_COUNT="$PRE_RUNTIME_COUNT"
    echo PRE_REGISTRY_COUNT="$PRE_REGISTRY_COUNT"
    echo PRE_DESIRED_COUNT="$PRE_DESIRED_COUNT"
    echo POST_RUNTIME_COUNT="$POST_RUNTIME_COUNT"
    echo POST_REGISTRY_COUNT="$POST_REGISTRY_COUNT"
    echo POST_DESIRED_COUNT="$POST_DESIRED_COUNT"
    echo RECONCILE_RUN_1="$RECONCILE_RUN_1"
    echo RECONCILE_RUN_2="$RECONCILE_RUN_2"
    echo VM100_ALLOCATOR_PROCESS_BEFORE="$VM100_ALLOCATOR_PROCESS_BEFORE"
    echo VM100_ALLOCATOR_PROCESS_AFTER="$VM100_ALLOCATOR_PROCESS_AFTER"
    echo STEP_REPORT_URL="$STEP_REPORT_URL"
    echo LATEST_ARCHIVE="$LATEST_ARCHIVE"
    echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"
  } > "$tmp"
  chmod 600 "$tmp"
  mv "$tmp" "$CHILD_RESULT"
}

archive_latest() {
  local log="$EVIDENCE/archive.log"
  router_step_archive_latest_python "$BASE" "$INCOMING" "$(date -u +%Y%m%d-%H%M%S)" | tee "$log"
  LATEST_ARCHIVE="$(awk -F= '$1=="LATEST_ARCHIVE"{print substr($0,index($0,"=")+1)}' "$log" | tail -n1)"
  LATEST_ARCHIVE_SHA256="$(awk -F= '$1=="LATEST_ARCHIVE_SHA256"{print $2}' "$log" | tail -n1)"
}

vm100_stdin() {
  local out="$1"
  ssh -T -o BatchMode=yes -o ConnectTimeout=20 pve-mgts \
    "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=20 root@10.71.100.1 /bin/sh -s" \
    >"$out" 2>&1
}

vm121_stdin() {
  local out="$1"
  ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /bin/bash -s >"$out" 2>&1
}

restore_runtime() {
  local failures=0 rc=0
  set +e
  if [[ "$SELFTEST" == true ]]; then
    {
      echo selftest_restore_guards=true
      echo selftest_allocator_recovery=true
      echo RESULT=PASS_R20QZA_R04_RUNTIME_RECOVERY
    } > "$EVIDENCE/runtime-recovery.txt"
    VM121_GUARDS_QUIESCED=false
    RUNTIME_RECOVERY_STATUS=PASS
    set -e
    return 0
  fi

  : > "$EVIDENCE/runtime-recovery.txt"
  if [[ "$VM121_GUARDS_QUIESCED" == true ]]; then
    ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /bin/bash -s >>"$EVIDENCE/runtime-recovery.txt" 2>&1 <<EOF
set +e
restore_one() {
  unit="\$1"; wanted="\$2"
  if [ "\$wanted" = active ]; then systemctl start "\$unit"; else systemctl stop "\$unit"; fi
}
restore_one wg-access-trigger-watch.service "$TRIGGER_WATCH_PRE"
restore_one wg-access-reconcile.timer "$RECONCILE_TIMER_PRE"
restore_one wg-access-agent.path "$AGENT_PATH_PRE"
restore_one wg-access-agent.timer "$AGENT_TIMER_PRE"
echo trigger_watch_active=\$(systemctl is-active wg-access-trigger-watch.service || true)
echo reconcile_timer_active=\$(systemctl is-active wg-access-reconcile.timer || true)
echo agent_path_active=\$(systemctl is-active wg-access-agent.path || true)
echo agent_timer_active=\$(systemctl is-active wg-access-agent.timer || true)
EOF
    rc=$?
    [[ $rc -eq 0 ]] || failures=$((failures+1))
    VM121_GUARDS_QUIESCED=false
  fi

  if [[ "$VM100_ALLOCATOR_RESTART_ATTEMPTED" == true ]]; then
    ssh -T -o BatchMode=yes -o ConnectTimeout=20 pve-mgts \
      "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=20 root@10.71.100.1 /bin/sh -s" \
      >>"$EVIDENCE/runtime-recovery.txt" 2>&1 <<'VM100REC'
set +e
/etc/init.d/router-wgpay-egress-local start >/dev/null 2>&1 || true
sleep 2
if ps w | grep '[r]outer-wgpay-egress-local-loop.sh' >/dev/null 2>&1; then
  echo allocator_recovery_process_present=true
else
  echo allocator_recovery_process_present=false
  false
fi
VM100REC
    rc=$?
    [[ $rc -eq 0 ]] || failures=$((failures+1))
  fi

  if [[ $failures -eq 0 ]]; then
    echo RESULT=PASS_R20QZA_R04_RUNTIME_RECOVERY >> "$EVIDENCE/runtime-recovery.txt"
    RUNTIME_RECOVERY_STATUS=PASS
  else
    echo RESULT=STOP_R20QZA_R04_RUNTIME_RECOVERY >> "$EVIDENCE/runtime-recovery.txt"
    RUNTIME_RECOVERY_STATUS=FAIL
  fi
  set -e
  [[ $failures -eq 0 ]]
}

stop_handler() {
  local rc="$1" line="$2" cmd="$3" original_detail="$STOP_DETAIL" recovery_rc=0
  trap - ERR
  [[ "$STOP_HANDLED" == false ]] || exit "$rc"
  STOP_HANDLED=true
  set +e
  restore_runtime
  recovery_rc=$?
  set -e
  STOP_DETAIL="$original_detail"
  {
    echo RESULT=STOP_R20QZA_VM121_VM100_RESTART_RECONCILE_PROOF
    echo STOP_RC="$rc"
    echo STOP_LINE="$line"
    printf 'STOP_COMMAND=%q\n' "$cmd"
    echo CORE_CHANGE_COMPLETE="$CORE_CHANGE_COMPLETE"
    echo CORE_CHANGE_REPEATED="$CORE_CHANGE_REPEATED"
    echo ROLLBACK_STATUS="$ROLLBACK_STATUS"
    echo STOP_DETAIL="$STOP_DETAIL"
    echo RUNTIME_RECOVERY_STATUS="$RUNTIME_RECOVERY_STATUS"
    echo RUNTIME_RECOVERY_RC="$recovery_rc"
  } | tee "$REPORT/stop.txt" "$EVIDENCE/stop.txt"
  set +e
  archive_latest >/dev/null 2>&1
  set -e
  write_child STOP_R20QZA_VM121_VM100_RESTART_RECONCILE_PROOF "$rc"
  exit "$rc"
}
trap 'rc=$?; stop_handler "$rc" "$LINENO" "$BASH_COMMAND"' ERR

write_selftest_vm100_source() {
  cat <<'EOF'
MODE=--check
MACHINE_ID=vm100
LOCAL_HEAD=e865282f4837747b1babfac4ac199680c6b43d04
REMOTE_HEAD=e865282f4837747b1babfac4ac199680c6b43d04
STAGED_CHANGE_COUNT=0
SIMULATED_TREE_SHA=8f04afbbc77006fe117bf1228b1f4fc977be9a47
SIMULATED_TREE_FILE_COUNT=33
RESULT=PASS_vm100_GIT_PUBLISH_CHECK
GIT_COMMIT=e865282f4837747b1babfac4ac199680c6b43d04
GIT_TREE=8f04afbbc77006fe117bf1228b1f4fc977be9a47
GIT_FILE_COUNT=33
COMMIT_CREATED=false
PUSH_PERFORMED=false
EOF
}

write_selftest_vm121_source() {
  cat <<'EOF'
MODE=--check
MACHINE_ID=vm121
LOCAL_HEAD=25d1656c1ea023b526c7aee0a75327447359de21
REMOTE_HEAD=25d1656c1ea023b526c7aee0a75327447359de21
STAGED_CHANGE_COUNT=0
SIMULATED_TREE_SHA=0ad9296bfc5f2543335cf1d3e082814fc09fcd4a
SIMULATED_TREE_FILE_COUNT=41
RESULT=PASS_VM121_GIT_PUBLISH_CHECK
GIT_COMMIT=25d1656c1ea023b526c7aee0a75327447359de21
GIT_TREE=0ad9296bfc5f2543335cf1d3e082814fc09fcd4a
GIT_FILE_COUNT=41
COMMIT_CREATED=false
PUSH_PERFORMED=false
EOF
}

collect_source_checks() {
  local stage="$1"
  if [[ "$SELFTEST" == true ]]; then
    write_selftest_vm100_source > "$EVIDENCE/${stage}-vm100-source.txt"
    write_selftest_vm121_source > "$EVIDENCE/${stage}-vm121-source.txt"
  else
    vm100_stdin "$EVIDENCE/${stage}-vm100-source.txt" <<'VM100SRC'
set -eu
/usr/local/sbin/router-vm100-git-publish.sh --check
VM100SRC
    vm121_stdin "$EVIDENCE/${stage}-vm121-source.txt" <<'VM121SRC'
set -euo pipefail
/usr/local/sbin/router-vm121-git-publish.sh --check
VM121SRC
  fi

  if ! "$ROOT/scripts/source-check-validate.sh" vm100 "$EVIDENCE/${stage}-vm100-source.txt" \
      e865282f4837747b1babfac4ac199680c6b43d04 \
      8f04afbbc77006fe117bf1228b1f4fc977be9a47 33 \
      > "$EVIDENCE/${stage}-vm100-source-validation.txt" 2>&1; then
    STOP_DETAIL="${stage}_vm100_git_semantics"; false
  fi

  if ! "$ROOT/scripts/source-check-validate.sh" vm121 "$EVIDENCE/${stage}-vm121-source.txt" \
      25d1656c1ea023b526c7aee0a75327447359de21 \
      0ad9296bfc5f2543335cf1d3e082814fc09fcd4a 41 \
      > "$EVIDENCE/${stage}-vm121-source-validation.txt" 2>&1; then
    STOP_DETAIL="${stage}_vm121_git_semantics"; false
  fi
}

write_selftest_vm100_facts() {
  cat <<EOF
schema=router-wgpay-peer-lifecycle-status-v1
config=/etc/router-wgpay-peer-lifecycle.conf
initialized=true
registry_valid=true
profile_count=$SELFTEST_COUNT
wireguard_count=$SELFTEST_COUNT
amneziawg_count=0
selector_registry_consistent=true
lifecycle_lib_sha256=64fb2a010d7efc75a6a38415e2b136c10151483334b953147a7fd9e7236b9951
lifecycle_lib_mode=775
EOF
  if [[ "$SELFTEST_COUNT" == 1 ]]; then
    printf 'runtime_pair=10.253.1.14\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n'
    printf 'registry_pair=10.253.1.14\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n'
    echo selector_ip=10.253.1.14
    echo canonical_ip=10.253.1.14
  fi
  echo selector_count="$SELFTEST_COUNT"
  echo canonical_count="$SELFTEST_COUNT"
  echo allocator_enabled=true
  echo allocator_process_present=true
  echo allocator_pid=100
  echo RESULT=PASS_R20QZA_R04_VM100_FACTS
}

write_selftest_vm121_facts() {
  local stage="$1"
  if [[ "$SELFTEST_COUNT" == 1 ]]; then
    printf 'desired_pair=10.253.1.14\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n'
  fi
  echo pending_job_count=0
  echo agent_state_secret_keys=0
  echo legacy_direct_wg_write_present=false
  if [[ "$stage" == controlled ]]; then
    echo trigger_watch_active=inactive
    echo reconcile_timer_active=inactive
    echo agent_path_active=inactive
    echo agent_timer_active=inactive
  else
    echo trigger_watch_active=active
    echo reconcile_timer_active=active
    echo agent_path_active=active
    echo agent_timer_active=inactive
  fi
  echo agent_service_active=inactive
  echo RESULT=PASS_R20QZA_R04_VM121_FACTS
}

collect_facts() {
  local stage="$1"
  if [[ "$SELFTEST" == true ]]; then
    write_selftest_vm100_facts > "$EVIDENCE/${stage}-vm100.txt"
    write_selftest_vm121_facts "$stage" > "$EVIDENCE/${stage}-vm121.txt"
  else
    vm100_stdin "$EVIDENCE/${stage}-vm100.txt" <<'VM100FACTS'
set -eu
/usr/local/sbin/router-wgpay-peer-lifecycle.sh --status
sha256sum /usr/local/lib/router-wgpay-peer-lifecycle-lib.sh | awk '{print "lifecycle_lib_sha256=" $1}'
mode="$(stat -c %a /usr/local/lib/router-wgpay-peer-lifecycle-lib.sh)"
echo lifecycle_lib_mode="$mode"
wg show wg_paid allowed-ips 2>/dev/null | while read -r key ips; do
  oldifs=$IFS; IFS=,
  for ip in $ips; do ip=${ip%/32}; [ -n "$ip" ] && printf 'runtime_pair=%s\t%s\n' "$ip" "$key"; done
  IFS=$oldifs
done
while IFS="$(printf '\t')" read -r profile protocol iface key ip normal active generation created updated; do
  case "$profile" in ""|\#*) continue;; esac
  [ "$protocol" = wireguard ] && printf 'registry_pair=%s\t%s\n' "$ip" "$key"
done < /etc/router-wgpay-peer-state/registry.tsv
awk 'NF && $1 !~ /^#/ {print "selector_ip=" $1; c++} END{print "selector_count=" c+0}' /etc/router-wgpay-peer-state/peers.conf
awk 'NF && $1 !~ /^#/ {print "canonical_ip=" $1; c++} END{print "canonical_count=" c+0}' /etc/router-wgpay-peer-state/canonical.conf
/etc/init.d/router-wgpay-egress-local enabled >/dev/null 2>&1 && echo allocator_enabled=true || echo allocator_enabled=false
pid="$(ps w | awk '/[r]outer-wgpay-egress-local-loop.sh/{print $1; exit}')"
if [ -n "$pid" ]; then echo allocator_process_present=true; echo allocator_pid="$pid"; else echo allocator_process_present=false; echo allocator_pid=none; fi
echo RESULT=PASS_R20QZA_R04_VM100_FACTS
VM100FACTS

    vm121_stdin "$EVIDENCE/${stage}-vm121.txt" <<'VM121FACTS'
set -euo pipefail
docker exec -i wgaccess-postgres sh -lc 'exec psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At' <<'SQL'
SELECT 'desired_pair=' || tunnel_ip || E'\t' || public_key FROM peers WHERE enabled IS TRUE ORDER BY tunnel_ip, public_key;
SELECT 'pending_job_count=' || count(*) FROM provisioning_jobs WHERE status IN ('pending','running');
SQL
python3 - <<'PY'
import json
from pathlib import Path
env={}
for raw in Path('/opt/wg-access/agent/agent.env').read_text(encoding='utf-8').splitlines():
    raw=raw.strip()
    if raw and not raw.startswith('#') and '=' in raw:
        k,v=raw.split('=',1); env[k.strip()]=v.strip()
state=Path(env['STATE_FILE'])
data=json.loads(state.read_text(encoding='utf-8')) if state.exists() else {}
forbidden={'preshared_key','private_key','client_config'}
def bad(x):
    if isinstance(x,dict): return sum(1 for k in x if k in forbidden)+sum(bad(v) for v in x.values())
    if isinstance(x,list): return sum(bad(v) for v in x)
    return 0
print('agent_state_secret_keys='+str(bad(data)))
PY
a='wg set '
if grep -F "$a" /opt/wg-access/agent/wg_access_agent.py >/dev/null; then echo legacy_direct_wg_write_present=true; else echo legacy_direct_wg_write_present=false; fi
echo trigger_watch_active="$(systemctl is-active wg-access-trigger-watch.service || true)"
echo reconcile_timer_active="$(systemctl is-active wg-access-reconcile.timer || true)"
echo agent_path_active="$(systemctl is-active wg-access-agent.path || true)"
echo agent_timer_active="$(systemctl is-active wg-access-agent.timer || true)"
echo agent_service_active="$(systemctl is-active wg-access-agent.service || true)"
echo RESULT=PASS_R20QZA_R04_VM121_FACTS
VM121FACTS
  fi

  grep -Fqx 'registry_valid=true' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_registry_invalid"; false; }
  grep -Fqx 'selector_registry_consistent=true' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_selector_registry"; false; }
  grep -Fqx 'amneziawg_count=0' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_awg_count"; false; }
  grep -Fqx 'lifecycle_lib_sha256=64fb2a010d7efc75a6a38415e2b136c10151483334b953147a7fd9e7236b9951' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_lifecycle_sha"; false; }
  grep -Fqx 'lifecycle_lib_mode=775' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_lifecycle_mode"; false; }
  grep -Fqx 'allocator_enabled=true' "$EVIDENCE/${stage}-vm100.txt" || { STOP_DETAIL="${stage}_allocator_disabled"; false; }

  grep -Fqx 'pending_job_count=0' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_pending_jobs"; false; }
  grep -Fqx 'agent_state_secret_keys=0' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_agent_state_secret"; false; }
  grep -Fqx 'legacy_direct_wg_write_present=false' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_legacy_direct_wg"; false; }
  if [[ "$stage" == controlled ]]; then
    grep -Fqx 'trigger_watch_active=inactive' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_trigger_watch_not_quiesced"; false; }
    grep -Fqx 'reconcile_timer_active=inactive' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_reconcile_timer_not_quiesced"; false; }
    grep -Fqx 'agent_path_active=inactive' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_agent_path_not_quiesced"; false; }
    grep -Fqx 'agent_timer_active=inactive' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_agent_timer_not_quiesced"; false; }
    grep -Fqx 'agent_service_active=inactive' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_agent_service_not_quiesced"; false; }
  else
    grep -Fqx 'trigger_watch_active=active' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_trigger_watch"; false; }
    grep -Fqx 'reconcile_timer_active=active' "$EVIDENCE/${stage}-vm121.txt" || { STOP_DETAIL="${stage}_reconcile_timer"; false; }
  fi

  awk -F= '$1=="runtime_pair"{print substr($0,index($0,"=")+1)}' "$EVIDENCE/${stage}-vm100.txt" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-runtime.set"
  awk -F= '$1=="registry_pair"{print substr($0,index($0,"=")+1)}' "$EVIDENCE/${stage}-vm100.txt" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-registry.set"
  awk -F= '$1=="desired_pair"{print substr($0,index($0,"=")+1)}' "$EVIDENCE/${stage}-vm121.txt" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-desired.set"
  awk -F= '$1=="selector_ip"{print $2}' "$EVIDENCE/${stage}-vm100.txt" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-selector-ip.set"
  awk -F= '$1=="canonical_ip"{print $2}' "$EVIDENCE/${stage}-vm100.txt" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-canonical-ip.set"
  cut -f1 "$EVIDENCE/${stage}-desired.set" | LC_ALL=C sort -u > "$EVIDENCE/${stage}-desired-ip.set"

  cmp -s "$EVIDENCE/${stage}-runtime.set" "$EVIDENCE/${stage}-desired.set" || { STOP_DETAIL="${stage}_runtime_desired"; false; }
  cmp -s "$EVIDENCE/${stage}-registry.set" "$EVIDENCE/${stage}-desired.set" || { STOP_DETAIL="${stage}_registry_desired"; false; }
  cmp -s "$EVIDENCE/${stage}-selector-ip.set" "$EVIDENCE/${stage}-desired-ip.set" || { STOP_DETAIL="${stage}_selector_desired"; false; }
  cmp -s "$EVIDENCE/${stage}-canonical-ip.set" "$EVIDENCE/${stage}-desired-ip.set" || { STOP_DETAIL="${stage}_canonical_desired"; false; }
}

router_step_phase_begin preflight
"$ROOT/scripts/target-apply.sh" "$(awk '{print $1}' "$ROOT/reference/target-package.sha256")" "$TARGET_DIR" > "$EVIDENCE/target-extract.txt"
collect_source_checks pre
collect_facts pre
TRIGGER_WATCH_PRE="$(awk -F= '$1=="trigger_watch_active"{print $2}' "$EVIDENCE/pre-vm121.txt" | tail -n1)"
RECONCILE_TIMER_PRE="$(awk -F= '$1=="reconcile_timer_active"{print $2}' "$EVIDENCE/pre-vm121.txt" | tail -n1)"
AGENT_PATH_PRE="$(awk -F= '$1=="agent_path_active"{print $2}' "$EVIDENCE/pre-vm121.txt" | tail -n1)"
AGENT_TIMER_PRE="$(awk -F= '$1=="agent_timer_active"{print $2}' "$EVIDENCE/pre-vm121.txt" | tail -n1)"
VM100_ALLOCATOR_PROCESS_BEFORE="$(awk -F= '$1=="allocator_process_present"{print $2}' "$EVIDENCE/pre-vm100.txt" | tail -n1)"
VM100_ALLOCATOR_PID_BEFORE="$(awk -F= '$1=="allocator_pid"{print $2}' "$EVIDENCE/pre-vm100.txt" | tail -n1)"
router_step_phase_complete preflight exact_sources_and_membership_aligned

router_step_phase_begin quiesce
if [[ "$SELFTEST" == true ]]; then
  cat > "$EVIDENCE/vm121-quiesce.txt" <<'EOF'
trigger_watch_active=inactive
reconcile_timer_active=inactive
agent_path_active=inactive
agent_timer_active=inactive
agent_service_active=inactive
RESULT=PASS_R20QZA_R04_VM121_QUIESCE
EOF
else
  vm121_stdin "$EVIDENCE/vm121-quiesce.txt" <<'VM121QUIESCE'
set -euo pipefail
systemctl stop wg-access-trigger-watch.service wg-access-reconcile.timer wg-access-agent.path wg-access-agent.timer
for i in $(seq 1 30); do
  state="$(systemctl is-active wg-access-agent.service || true)"
  [ "$state" != active ] && break
  sleep 1
done
echo trigger_watch_active="$(systemctl is-active wg-access-trigger-watch.service || true)"
echo reconcile_timer_active="$(systemctl is-active wg-access-reconcile.timer || true)"
echo agent_path_active="$(systemctl is-active wg-access-agent.path || true)"
echo agent_timer_active="$(systemctl is-active wg-access-agent.timer || true)"
echo agent_service_active="$(systemctl is-active wg-access-agent.service || true)"
[ "$(systemctl is-active wg-access-trigger-watch.service || true)" = inactive ]
[ "$(systemctl is-active wg-access-reconcile.timer || true)" = inactive ]
[ "$(systemctl is-active wg-access-agent.path || true)" = inactive ]
[ "$(systemctl is-active wg-access-agent.timer || true)" = inactive ]
[ "$(systemctl is-active wg-access-agent.service || true)" != active ]
echo RESULT=PASS_R20QZA_R04_VM121_QUIESCE
VM121QUIESCE
fi
VM121_GUARDS_QUIESCED=true
grep -Fqx 'RESULT=PASS_R20QZA_R04_VM121_QUIESCE' "$EVIDENCE/vm121-quiesce.txt" || { STOP_DETAIL=vm121_quiesce; false; }
collect_facts controlled
PRE_RUNTIME_COUNT="$(wc -l < "$EVIDENCE/controlled-runtime.set" | tr -d ' ')"
PRE_REGISTRY_COUNT="$(wc -l < "$EVIDENCE/controlled-registry.set" | tr -d ' ')"
PRE_DESIRED_COUNT="$(wc -l < "$EVIDENCE/controlled-desired.set" | tr -d ' ')"
router_step_phase_complete quiesce automatic_triggers_quiesced_and_controlled_baseline_captured

router_step_phase_begin restart
VM100_ALLOCATOR_RESTART_ATTEMPTED=true
if [[ "$SELFTEST" == true ]]; then
  cat > "$EVIDENCE/vm100-restart.txt" <<'EOF'
allocator_pid_before=100
allocator_pid_after=101
allocator_process_present=true
registry_valid=true
RESULT=PASS_R20QZA_R04_VM100_ALLOCATOR_RESTART
EOF
else
  vm100_stdin "$EVIDENCE/vm100-restart.txt" <<'VM100RESTART'
set -eu
before="$(ps w | awk '/[r]outer-wgpay-egress-local-loop.sh/{print $1; exit}')"
/etc/init.d/router-wgpay-egress-local restart
found=""
for i in 1 2 3 4 5 6 7 8 9 10; do
  after="$(ps w | awk '/[r]outer-wgpay-egress-local-loop.sh/{print $1; exit}')"
  if [ -n "$after" ]; then found="$after"; break; fi
  sleep 1
done
echo allocator_pid_before="${before:-none}"
echo allocator_pid_after="${found:-none}"
[ -n "$found" ]
echo allocator_process_present=true
/usr/local/sbin/router-wgpay-peer-lifecycle.sh --status | grep -F 'registry_valid=true'
echo registry_valid=true
echo RESULT=PASS_R20QZA_R04_VM100_ALLOCATOR_RESTART
VM100RESTART
fi
grep -Fqx 'RESULT=PASS_R20QZA_R04_VM100_ALLOCATOR_RESTART' "$EVIDENCE/vm100-restart.txt" || { STOP_DETAIL=vm100_allocator_restart; false; }
VM100_ALLOCATOR_PROCESS_AFTER=true
VM100_ALLOCATOR_PID_AFTER="$(awk -F= '$1=="allocator_pid_after"{print $2}' "$EVIDENCE/vm100-restart.txt" | tail -n1)"
router_step_phase_complete restart vm100_allocator_restarted_without_wg_interface_restart

router_step_phase_begin reconcile
if [[ "$SELFTEST" == true ]]; then
  if [[ "$SELFTEST_FAIL" == reconcile1 ]]; then STOP_DETAIL=selftest_reconcile1_injected; false; fi
  cat > "$EVIDENCE/vm121-reconcile-1.txt" <<'EOF'
Result=success
ExecMainStatus=0
agent_Result=success
agent_ExecMainStatus=0
RESULT=PASS_R20QZA_R04_RECONCILE_RUN_1
EOF
  if [[ "$SELFTEST_FAIL" == reconcile2 ]]; then STOP_DETAIL=selftest_reconcile2_injected; false; fi
  cat > "$EVIDENCE/vm121-reconcile-2.txt" <<'EOF'
Result=success
ExecMainStatus=0
agent_Result=success
agent_ExecMainStatus=0
RESULT=PASS_R20QZA_R04_RECONCILE_RUN_2
EOF
else
  for n in 1 2; do
    vm121_stdin "$EVIDENCE/vm121-reconcile-${n}.txt" <<VM121RECON
set -euo pipefail
systemctl reset-failed wg-access-reconcile.service wg-access-agent.service >/dev/null 2>&1 || true
systemctl start wg-access-reconcile.service
systemctl show wg-access-reconcile.service -p Result -p ExecMainStatus --no-pager
systemctl show wg-access-agent.service -p Result -p ExecMainStatus --no-pager | sed 's/^Result=/agent_Result=/; s/^ExecMainStatus=/agent_ExecMainStatus=/'
echo RESULT=PASS_R20QZA_R04_RECONCILE_RUN_${n}
VM121RECON
    grep -Fqx 'Result=success' "$EVIDENCE/vm121-reconcile-${n}.txt" || { STOP_DETAIL="reconcile_run_${n}_result"; false; }
    grep -Fqx 'ExecMainStatus=0' "$EVIDENCE/vm121-reconcile-${n}.txt" || { STOP_DETAIL="reconcile_run_${n}_rc"; false; }
    grep -Fqx 'agent_Result=success' "$EVIDENCE/vm121-reconcile-${n}.txt" || { STOP_DETAIL="reconcile_run_${n}_agent_result"; false; }
    grep -Fqx 'agent_ExecMainStatus=0' "$EVIDENCE/vm121-reconcile-${n}.txt" || { STOP_DETAIL="reconcile_run_${n}_agent_rc"; false; }
  done
fi
RECONCILE_RUN_1=PASS
RECONCILE_RUN_2=PASS
router_step_phase_complete reconcile two_explicit_idempotent_reconcile_runs_passed

router_step_phase_begin restore
if [[ "$SELFTEST" == true ]]; then
  cat > "$EVIDENCE/vm121-restore.txt" <<EOF
trigger_watch_active=$TRIGGER_WATCH_PRE
reconcile_timer_active=$RECONCILE_TIMER_PRE
agent_path_active=$AGENT_PATH_PRE
agent_timer_active=$AGENT_TIMER_PRE
RESULT=PASS_R20QZA_R04_VM121_RESTORE
EOF
else
  vm121_stdin "$EVIDENCE/vm121-restore.txt" <<EOF
set -euo pipefail
restore_one() {
  unit="\$1"; wanted="\$2"
  if [ "\$wanted" = active ]; then systemctl start "\$unit"; else systemctl stop "\$unit"; fi
}
restore_one wg-access-trigger-watch.service "$TRIGGER_WATCH_PRE"
restore_one wg-access-reconcile.timer "$RECONCILE_TIMER_PRE"
restore_one wg-access-agent.path "$AGENT_PATH_PRE"
restore_one wg-access-agent.timer "$AGENT_TIMER_PRE"
echo trigger_watch_active=\$(systemctl is-active wg-access-trigger-watch.service || true)
echo reconcile_timer_active=\$(systemctl is-active wg-access-reconcile.timer || true)
echo agent_path_active=\$(systemctl is-active wg-access-agent.path || true)
echo agent_timer_active=\$(systemctl is-active wg-access-agent.timer || true)
EOF
fi
grep -Fqx "trigger_watch_active=$TRIGGER_WATCH_PRE" "$EVIDENCE/vm121-restore.txt" || { STOP_DETAIL=restore_trigger_watch; false; }
grep -Fqx "reconcile_timer_active=$RECONCILE_TIMER_PRE" "$EVIDENCE/vm121-restore.txt" || { STOP_DETAIL=restore_reconcile_timer; false; }
grep -Fqx "agent_path_active=$AGENT_PATH_PRE" "$EVIDENCE/vm121-restore.txt" || { STOP_DETAIL=restore_agent_path; false; }
grep -Fqx "agent_timer_active=$AGENT_TIMER_PRE" "$EVIDENCE/vm121-restore.txt" || { STOP_DETAIL=restore_agent_timer; false; }
if [[ "$SELFTEST" != true ]]; then echo RESULT=PASS_R20QZA_R04_VM121_RESTORE >> "$EVIDENCE/vm121-restore.txt"; fi
VM121_GUARDS_QUIESCED=false
grep -Fqx 'RESULT=PASS_R20QZA_R04_VM121_RESTORE' "$EVIDENCE/vm121-restore.txt" || { STOP_DETAIL=vm121_restore; false; }
router_step_phase_complete restore exact_preproof_trigger_states_restored

router_step_phase_begin postcheck
collect_source_checks post
collect_facts post
POST_RUNTIME_COUNT="$(wc -l < "$EVIDENCE/post-runtime.set" | tr -d ' ')"
POST_REGISTRY_COUNT="$(wc -l < "$EVIDENCE/post-registry.set" | tr -d ' ')"
POST_DESIRED_COUNT="$(wc -l < "$EVIDENCE/post-desired.set" | tr -d ' ')"
cmp -s "$EVIDENCE/controlled-runtime.set" "$EVIDENCE/post-runtime.set" || { STOP_DETAIL=membership_runtime_changed; false; }
cmp -s "$EVIDENCE/controlled-registry.set" "$EVIDENCE/post-registry.set" || { STOP_DETAIL=membership_registry_changed; false; }
cmp -s "$EVIDENCE/controlled-desired.set" "$EVIDENCE/post-desired.set" || { STOP_DETAIL=membership_desired_changed; false; }
grep -Fqx 'allocator_process_present=true' "$EVIDENCE/post-vm100.txt" || { STOP_DETAIL=post_allocator_not_running; false; }
CORE_CHANGE_COMPLETE=true
router_step_phase_complete postcheck membership_and_source_survive_targeted_restarts

EMPTY_MEMBERSHIP_TESTED=false
[[ "$PRE_DESIRED_COUNT" == 0 ]] && EMPTY_MEMBERSHIP_TESTED=true
cat > "$REPORT/report.txt" <<EOF
RESULT=PASS_R20QZA_VM121_VM100_RESTART_RECONCILE_PROOF
REVISION=R04
CORE_CHANGE_COMPLETE=true
CORE_CHANGE_REPEATED=false
SOURCE_CHANGED=false
MACHINE_GIT_CLOSE_RUN=false
DATABASE_SCHEMA_CHANGED=false
VM101_CHANGED=false
REBOOT_PERFORMED=false
SNAPSHOT_CREATED=false
BACKUP_CREATED=false
LONG_RECOVERY_TEST_REPEATED=false
RUNTIME_IMPACT=true
USER_TUNNEL_INTERFACE_RESTARTED=false
VM100_ALLOCATOR_RESTARTED=true
VM121_RECONCILE_RUNS=2
EMPTY_MEMBERSHIP_SUPPORTED=true
EMPTY_MEMBERSHIP_TESTED=$EMPTY_MEMBERSHIP_TESTED
PRE_RUNTIME_COUNT=$PRE_RUNTIME_COUNT
PRE_REGISTRY_COUNT=$PRE_REGISTRY_COUNT
PRE_DESIRED_COUNT=$PRE_DESIRED_COUNT
POST_RUNTIME_COUNT=$POST_RUNTIME_COUNT
POST_REGISTRY_COUNT=$POST_REGISTRY_COUNT
POST_DESIRED_COUNT=$POST_DESIRED_COUNT
RECONCILE_RUN_1=$RECONCILE_RUN_1
RECONCILE_RUN_2=$RECONCILE_RUN_2
VM100_ALLOCATOR_PROCESS_BEFORE=$VM100_ALLOCATOR_PROCESS_BEFORE
VM100_ALLOCATOR_PROCESS_AFTER=$VM100_ALLOCATOR_PROCESS_AFTER
VM121_DIRECT_WG_WRITES=false
VM121_SELECTOR_OWNERSHIP=false
SELECTOR_OWNER=VM100
VM121_AGENT_STATE_SECRET_KEYS=0
VM100_COMMIT=e865282f4837747b1babfac4ac199680c6b43d04
VM100_TREE=8f04afbbc77006fe117bf1228b1f4fc977be9a47
VM100_FILE_COUNT=33
VM121_COMMIT=25d1656c1ea023b526c7aee0a75327447359de21
VM121_TREE=0ad9296bfc5f2543335cf1d3e082814fc09fcd4a
VM121_FILE_COUNT=41
VM100_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260809-142753_vm100_git_source_e865282f4837/
VM121_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260808-081225_vm121_git_source_25d1656c1ea0/
VM101_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260801-095331_vm101_git_source_ac678d1b3164/
PROJECT_SOURCE_POST_URL=https://reports.secret-studio.ru/latest/20260802-105248_project_source_post_r20qx_vm121_multi_protocol_architecture/
LOCAL_M07_PLAN_URL=https://reports.secret-studio.ru/latest/20260719-080444_local_architecture_plan_vm101_autonomous_hmn_recovery_r19/
GLOBAL_PLAN_URL=https://reports.secret-studio.ru/latest/20260802-105248_global_project_plan_wg_paid_after_r20qw/
ACCESS_MAP_URL=https://reports.secret-studio.ru/latest/20260715-210236_access_map_canonical_current/
COPY_VERIFY=PASS
RUNTIME_IMPACT=true
NEXT_ACTION=P2.1 VM121 bridge migration and database backup/restore foundation
EOF
cp "$REPORT/report.txt" "$REPORT/verify.txt"
cp "$ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"
cp "$ROOT/release-info.json" "$REPORT/release-info.json"
cp "$ROOT/manifest.sha256" "$REPORT/bundle-manifest.sha256"
cp "$ROOT/rollback/README.md" "$REPORT/rollback.txt"
cp "$EVIDENCE"/*.txt "$REPORT/" 2>/dev/null || true

router_step_phase_begin report_publish
if [[ "$SELFTEST" == true ]]; then
  DEST="${ROUTER_PUBLIC_ROOT:?}/latest/r20qza-r04-selftest"
  rm -rf "$DEST"; mkdir -p "$DEST"; cp -a "$REPORT"/. "$DEST"/
  STEP_REPORT_URL=selftest://r20qza-r04-selftest/
else
  args=()
  while IFS= read -r file; do args+=("$file" "$(basename "$file")"); done < <(find "$REPORT" -maxdepth 1 -type f -print | LC_ALL=C sort)
  router_step_publish_payload step050m07r20qza_r04_vm121_vm100_restart_reconcile_proof_semantic_git_check "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV" "${args[@]}"
  STEP_REPORT_URL="$(awk -F= '$1=="PUBLIC_URL"{print substr($0,index($0,"=")+1)}' "$FINAL_ENV" | tail -n1)"
fi
router_step_phase_complete report_publish step_report_published

router_step_phase_begin archive
archive_latest
router_step_phase_complete archive latest_created
router_step_workflow_mark_complete

trap - ERR
STOP_DETAIL=none
write_child PASS_R20QZA_VM121_VM100_RESTART_RECONCILE_PROOF 0
cat "$REPORT/report.txt"
echo STEP_REPORT_URL="$STEP_REPORT_URL"
echo LATEST_ARCHIVE="$LATEST_ARCHIVE"
echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"
