#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
FINAL_ROOT="${BUNDLE_ROOT:?}"
INCOMING="${ROUTER_STEP_INCOMING:-/home/ops/incoming}"
RUN_DIR="${ROUTER_DETACHED_RUN_DIR:?}"
CHILD_RESULT="${ROUTER_DETACHED_CHILD_RESULT:?}"
SELFTEST="${ROUTER_EXACT_WORKER_SELFTEST:-false}"
STEP='STEP_050M07R20QV_R02_VM100_WG_REMOTE_DEFAULT_CS4'; REVISION=R02
EXPECTED_PACKAGE_SHA="$(python3 - "$FINAL_ROOT/release-info.json" <<'PYINFO'
import json,sys
print(json.load(open(sys.argv[1],encoding='utf-8'))['target_package_sha256'])
PYINFO
)"
EXPECTED_FILE_SHA='d4bc25c4b9aa995799f6734a8c12bd260cf54e7450161f17edf5c156c60bd5da'
PACKAGE="$FINAL_ROOT/payload/vm100-wg-remote-default-cs4-r02-target-package.tar.gz"
WORK="$RUN_DIR/work"; EVIDENCE="$RUN_DIR/evidence"; REPORT="$RUN_DIR/report"; PAYLOAD="$RUN_DIR/public-payload"
CURRENT="$RUN_DIR/workflow-current.txt"; EVENTS="$RUN_DIR/workflow-events.tsv"; FINAL_ENV="$RUN_DIR/final-publication.env"
SOURCE_ENV="$RUN_DIR/vm100-source.txt"; PROJECT_ENV="$RUN_DIR/project-source.txt"; APPLY_ENV="$RUN_DIR/apply-result.txt"
CORE_CHANGE_COMPLETE=false; ROLLBACK_STATUS=not_required; CURRENT_PHASE=initialization; STOP_HANDLED=false; LATEST_ARCHIVE=not_created; LATEST_ARCHIVE_SHA256=unknown
STOP_MACHINE=VM130; STOP_DETAIL=unclassified_failure; NEXT_DIAGNOSTIC_ACTION=inspect_retained_run_evidence
mkdir -p "$WORK" "$EVIDENCE" "$REPORT" "$PAYLOAD" "$INCOMING"; chmod 700 "$WORK" "$EVIDENCE" "$REPORT" "$PAYLOAD"
source "$BASE/lib/router-step-workflow.sh"
router_step_workflow_init "$CURRENT" "$EVENTS" preflight backup apply postcheck source project_source report_build report_publish archive

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 "ROLLBACK_STATUS=$ROLLBACK_STATUS"; echo "LATEST_ARCHIVE=$LATEST_ARCHIVE"; echo "LATEST_ARCHIVE_SHA256=$LATEST_ARCHIVE_SHA256"; } > "$tmp"
  chmod 600 "$tmp"; mv -f "$tmp" "$CHILD_RESULT"
}
archive_latest() {
  local log="$EVIDENCE/archive-latest.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)"
}
selftest_publish_dir() {
  local label="$1" src="$2" dir
  dir="${ROUTER_PUBLIC_ROOT:?}/latest/$label"
  rm -rf "$dir"; mkdir -p "$dir"; cp -a "$src"/. "$dir"/; chmod -R u+rwX,go+rX "$dir"
  printf 'selftest://%s/
' "$label"
}
PUBLIC_EVIDENCE_FILES=(
  evidence-summary.txt
  vm100-preflight.stdout.txt vm100-preflight.stderr.txt vm100-preflight.rc.txt
  vm101-preflight.stdout.txt vm101-preflight.stderr.txt vm101-preflight.rc.txt
  vm100-apply.stdout.txt vm100-apply.stderr.txt vm100-apply.rc.txt
  vm100-postcheck.stdout.txt vm100-postcheck.stderr.txt vm100-postcheck.rc.txt
  vm101-postcheck.stdout.txt vm101-postcheck.stderr.txt vm101-postcheck.rc.txt
  vm100-source-precheck.stdout.txt vm100-source-precheck.stderr.txt vm100-source-precheck.rc.txt
)
prepare_public_evidence() {
  local name file
  : > "$REPORT/evidence-summary.txt"
  while IFS= read -r file; do
    [[ -f "$file" ]] || continue
    printf '===== %s =====\n' "$(basename "$file")" >> "$REPORT/evidence-summary.txt"
    cat "$file" >> "$REPORT/evidence-summary.txt" 2>/dev/null || printf '[unreadable evidence]\n' >> "$REPORT/evidence-summary.txt"
    printf '\n' >> "$REPORT/evidence-summary.txt"
  done < <(find "$EVIDENCE" -maxdepth 1 -type f -print | sort)
  for name in "${PUBLIC_EVIDENCE_FILES[@]:1}"; do
    if [[ -f "$EVIDENCE/$name" ]]; then cp "$EVIDENCE/$name" "$REPORT/$name"; else printf 'NOT_CREATED=true\n' > "$REPORT/$name"; fi
  done
}
build_stop_report() {
  local result="$1" line="$2" rc="$3" command="$4"
  mkdir -p "$REPORT"
  { echo "RESULT=$result"; echo "REVISION=$REVISION"; echo "STOP_PHASE=$CURRENT_PHASE"; echo "STOP_LINE=$line"; echo "STOP_RC=$rc"; printf 'STOP_COMMAND=%q\n' "$command"; echo "STOP_MACHINE=$STOP_MACHINE"; echo "STOP_DETAIL=$STOP_DETAIL"; echo "NEXT_DIAGNOSTIC_ACTION=$NEXT_DIAGNOSTIC_ACTION"; echo "RETAINED_RUN_DIR=$RUN_DIR"; echo "CORE_CHANGE_COMPLETE=$CORE_CHANGE_COMPLETE"; echo "ROLLBACK_STATUS=$ROLLBACK_STATUS"; echo "RUNTIME_IMPACT=$CORE_CHANGE_COMPLETE"; } > "$REPORT/report.txt"
  cp "$CURRENT" "$REPORT/workflow-current.txt"; cp "$EVENTS" "$REPORT/workflow-events.tsv"; cp "$FINAL_ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"
  python3 - "$REPORT/facts.json" "$result" "$CURRENT_PHASE" "$CORE_CHANGE_COMPLETE" "$STOP_MACHINE" "$STOP_DETAIL" "$RUN_DIR" <<'PYSTOP'
import json,sys
p,result,phase,core,machine,detail,run_dir=sys.argv[1:]
json.dump({"result":result,"phase":phase,"core_change_complete":core=="true","machine":machine,"detail":detail,"retained_run_dir":run_dir},open(p,"w"),indent=2); open(p,"a").write("\n")
PYSTOP
  cp "$REPORT/report.txt" "$REPORT/verify.txt"; printf 'Rollback status: %s\n' "$ROLLBACK_STATUS" > "$REPORT/rollback.txt"
  prepare_public_evidence
}
publish_stop() {
  local result="$1" name
  if [[ "$SELFTEST" == true ]]; then
    selftest_publish_dir r20qv-r02-stop "$REPORT" >/dev/null
  else
    local args=("$REPORT/report.txt" report.txt "$REPORT/facts.json" facts.json "$REPORT/verify.txt" verify.txt "$REPORT/rollback.txt" rollback.txt "$REPORT/executed-step.sh" executed-step.sh "$REPORT/workflow-current.txt" workflow-current.txt "$REPORT/workflow-events.tsv" workflow-events.tsv)
    for name in "${PUBLIC_EVIDENCE_FILES[@]}"; do args+=("$REPORT/$name" "$name"); done
    set +e
    router_step_publish_payload step050m07r20qv_r02_vm100_wg_remote_default_cs4_stop "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV" "${args[@]}"
    set -e
  fi
  set +e; archive_latest; set -e
}
stop_handler() {
  local rc="$1" line="$2" command="$3"
  trap - ERR
  [[ "$STOP_HANDLED" == false ]] || exit "$rc"; STOP_HANDLED=true
  set +e; router_step_phase_fail "$CURRENT_PHASE" "rc=$rc" 2>/dev/null || true; set -e
  local result=STOP_R20QV_VM100_WG_REMOTE_DEFAULT_CS4
  build_stop_report "$result" "$line" "$rc" "$command"
  publish_stop "$result"
  write_child "$result" "$rc"
  cat "$REPORT/report.txt"
  exit "$rc"
}
trap 'rc=$?; stop_handler "$rc" "$LINENO" "$BASH_COMMAND"' ERR

vm100_script() { ssh -o BatchMode=yes -o ConnectTimeout=15 pve-mgts "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=15 root@10.71.100.1 'sh -s'"; }
vm101_script() { ssh -o BatchMode=yes -o ConnectTimeout=15 pve-mgts "ssh -T -i /root/.ssh/pve_to_openwrt_mgts_ed25519 -o BatchMode=yes -o ConnectTimeout=15 root@10.71.100.2 'sh -s'"; }
capture_remote() {
  local machine="$1" input="$2" out="$3" err="$4" rcfile="$5" rc
  if [[ "$machine" == vm100 ]]; then
    if vm100_script < "$input" > "$out" 2> "$err"; then rc=0; else rc=$?; fi
  else
    if vm101_script < "$input" > "$out" 2> "$err"; then rc=0; else rc=$?; fi
  fi
  printf 'REMOTE_RC=%s\nMACHINE=%s\nINPUT=%s\n' "$rc" "$machine" "$input" > "$rcfile"
  return "$rc"
}
capture_local() {
  local out="$1" err="$2" rcfile="$3"; shift 3
  local rc
  if "$@" > "$out" 2> "$err"; then rc=0; else rc=$?; fi
  printf 'LOCAL_RC=%s\nCOMMAND=%q\n' "$rc" "$*" > "$rcfile"
  return "$rc"
}
expand_apply() { python3 - "$FINAL_ROOT/scripts/apply-vm100.sh" "$PACKAGE" "$WORK/apply-expanded.sh" <<'PYEXPAND'
from pathlib import Path
import base64,sys
src,pkg,out=map(Path,sys.argv[1:])
t=src.read_text(); marker='__ROUTER_TARGET_PACKAGE_B64__'; assert t.count(marker)==1
t=t.replace(marker,base64.b64encode(pkg.read_bytes()).decode())
out.write_text(t); out.chmod(0o700)
PYEXPAND
}
setup_selftest_target() {
  FAKE="$WORK/fake"; FROOT="$FAKE/root"; FBIN="$FAKE/bin"; mkdir -p "$FROOT/etc/nftables.d" "$FROOT/etc/pbr.d" "$FROOT/etc/config" "$FROOT/etc/router-wgpay-selector.d" "$FBIN"
  cp "$FINAL_ROOT/tests/fixtures/default_lan_to_vpn.sh" "$FROOT/etc/pbr.d/default_lan_to_vpn.sh"; cp "$FINAL_ROOT/tests/fixtures/pbr.config" "$FROOT/etc/config/pbr"; cp "$FINAL_ROOT/tests/fixtures/selector-canonical.conf" "$FROOT/etc/router-wgpay-selector.d/peers.conf"; cp "$FINAL_ROOT/tests/fixtures/selector-canonical.conf" "$FROOT/etc/router-wgpay-selector.d/canonical.conf"
  cat > "$FBIN/ip" <<'EOF_IP'
#!/bin/sh
case "$*" in 'link show dev wg_remote') exit 0;; 'route show table 201') echo 'default dev vpn1'; exit 0;; esac
exit 0
EOF_IP
  cat > "$FBIN/fw4" <<'EOF_FW4'
#!/bin/sh
[ "$1" = check ]
EOF_FW4
  cat > "$FBIN/firewall" <<'EOF_FIREWALL'
#!/bin/sh
[ "$1" = reload ] || exit 2
exit 0
EOF_FIREWALL
  cat > "$FBIN/nft" <<'EOF_NFT'
#!/bin/sh
root="${ROUTER_TARGET_ROOT:?}"
if [ "$1 $2 $3 $4 $5 $6" = 'list chain inet fw4 wg_remote_default_selector ' ] || [ "$1 $2 $3 $4 $5" = 'list chain inet fw4 wg_remote_default_selector' ]; then
  [ -f "$root/etc/nftables.d/91-wg-remote-default-selector.nft" ] || exit 1
  echo 'chain wg_remote_default_selector {'
  echo ' type filter hook prerouting priority mangle; policy accept;'
  echo ' iifname "wg_remote" ip dscp set cs4 counter packets 0 bytes 0 comment "WG_REMOTE_DEFAULT_SELECTOR_CS4"'
  echo '}'; exit 0
fi
if [ "$1 $2" = 'list ruleset' ]; then echo 'table inet fw4 { }'; exit 0; fi
exit 1
EOF_NFT
  cat > "$FBIN/git100" <<'EOF_G100'
#!/bin/sh
root="${ROUTER_TARGET_ROOT:-}"
echo 'RESULT=PASS_vm100_GIT_PUBLISH_CHECK'
echo 'GIT_COMMIT=2973959bbb942b23cf7fe3d68474111aa063d4f2'
if [ -f "$root/etc/nftables.d/91-wg-remote-default-selector.nft" ]; then
  echo 'STAGED_CHANGE_COUNT=1'
  echo '=== STAGED PATHS ==='
  echo 'etc/nftables.d/91-wg-remote-default-selector.nft'
  echo '>>> [vm100-git] validate simulated staged paths'
  echo 'GIT_TREE=3333333333333333333333333333333333333333'
  echo 'GIT_FILE_COUNT=30'
else
  echo 'STAGED_CHANGE_COUNT=0'
  echo 'STAGED_PATHS=NONE'
  echo 'GIT_TREE=4b35b85779e10d5b1465b44060aef11a9c99c4aa'
  echo 'GIT_FILE_COUNT=29'
fi
EOF_G100
  chmod 700 "$FBIN"/*
}

CURRENT_PHASE=preflight; router_step_phase_begin preflight
if [[ "$SELFTEST" == true ]]; then
  setup_selftest_target
  capture_local "$EVIDENCE/vm100-preflight.stdout.txt" "$EVIDENCE/vm100-preflight.stderr.txt" "$EVIDENCE/vm100-preflight.rc.txt" env ROUTER_TARGET_ROOT="$FROOT" ROUTER_IP_BIN="$FBIN/ip" ROUTER_NFT_BIN="$FBIN/nft" ROUTER_FW4_BIN="$FBIN/fw4" ROUTER_GIT_CHECK_BIN="$FBIN/git100" sh "$FINAL_ROOT/scripts/preflight-vm100.sh" || { STOP_MACHINE=VM100; STOP_DETAIL=selftest_vm100_preflight_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm100_preflight_evidence; rc="$(sed -n 's/^LOCAL_RC=//p' "$EVIDENCE/vm100-preflight.rc.txt")"; stop_handler "${rc:-1}" "$LINENO" selftest_vm100_preflight; }
  cat > "$EVIDENCE/vm101-preflight.stdout.txt" <<'EOF_VM101'
RESULT=PASS_R20QV_VM101_READONLY_PREFLIGHT
VM101_GIT_COMMIT=ac678d1b3164e0201d221967fa131e354a211881
VM101_GIT_TREE=0d00ec84743795ff4dde671e42ac3e77241591ef
VM101_GIT_FILE_COUNT=58
VM101_GIT_CLEAN=true
HEALTHY_SLOTS=5
CS4_CANONICAL_TARGET=egress1
CS4_CANONICAL_INTERFACE=vpn1
CS4_CANONICAL_MARK=0x201
CS4_CANONICAL_TABLE=201
EOF_VM101
  : > "$EVIDENCE/vm101-preflight.stderr.txt"; printf 'LOCAL_RC=0\n' > "$EVIDENCE/vm101-preflight.rc.txt"
  if [[ -f "$INCOMING/selftest-force-stop" ]]; then STOP_MACHINE=VM130; STOP_DETAIL=forced_pre_core_stop_fixture; NEXT_DIAGNOSTIC_ACTION=none_fixture_expected; false; fi
else
  capture_remote vm100 "$FINAL_ROOT/scripts/preflight-vm100.sh" "$EVIDENCE/vm100-preflight.stdout.txt" "$EVIDENCE/vm100-preflight.stderr.txt" "$EVIDENCE/vm100-preflight.rc.txt" || { rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm100-preflight.rc.txt")"; STOP_MACHINE=VM100; STOP_DETAIL=vm100_preflight_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm100_preflight_stdout_stderr; stop_handler "${rc:-1}" "$LINENO" vm100_preflight_remote; }
  capture_remote vm101 "$FINAL_ROOT/scripts/preflight-vm101.sh" "$EVIDENCE/vm101-preflight.stdout.txt" "$EVIDENCE/vm101-preflight.stderr.txt" "$EVIDENCE/vm101-preflight.rc.txt" || { rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm101-preflight.rc.txt")"; STOP_MACHINE=VM101; STOP_DETAIL=vm101_readonly_preflight_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm101_preflight_stdout_stderr; stop_handler "${rc:-1}" "$LINENO" vm101_preflight_remote; }
fi
grep -Fqx RESULT=PASS_R20QV_VM100_PREFLIGHT "$EVIDENCE/vm100-preflight.stdout.txt"
grep -Fqx RESULT=PASS_R20QV_VM101_READONLY_PREFLIGHT "$EVIDENCE/vm101-preflight.stdout.txt"
router_step_phase_complete preflight clean_machine_git_canonical_selector_and_runtime_prerequisites_verified

CURRENT_PHASE=backup; router_step_phase_begin backup
BACKUP_DIR="/root/router-step-backups/r20qv-r02-$(date -u +%Y%m%d-%H%M%S)"
[[ "$SELFTEST" == true ]] && BACKUP_DIR="$WORK/fake/backup"
echo "VM100_BACKUP_DIR=$BACKUP_DIR" > "$EVIDENCE/backup.txt"
router_step_phase_complete backup backup_path_reserved_snapshot_skipped_not_required

CURRENT_PHASE=apply; router_step_phase_begin apply
expand_apply
if [[ "$SELFTEST" == true ]]; then
  capture_local "$EVIDENCE/vm100-apply.stdout.txt" "$EVIDENCE/vm100-apply.stderr.txt" "$EVIDENCE/vm100-apply.rc.txt" env ROUTER_TARGET_ROOT="$FROOT" ROUTER_FW4_BIN="$FBIN/fw4" ROUTER_FIREWALL_INIT="$FBIN/firewall" ROUTER_NFT_BIN="$FBIN/nft" sh "$WORK/apply-expanded.sh" "$EXPECTED_PACKAGE_SHA" "$BACKUP_DIR" || { if grep -Fqx CORE_CHANGE_COMPLETE=true "$EVIDENCE/vm100-apply.stdout.txt"; then CORE_CHANGE_COMPLETE=true; ROLLBACK_STATUS=not_run_apply_reported_core_complete; fi; rc="$(sed -n 's/^LOCAL_RC=//p' "$EVIDENCE/vm100-apply.rc.txt")"; STOP_MACHINE=VM100; STOP_DETAIL=selftest_vm100_apply_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm100_apply_evidence; stop_handler "${rc:-1}" "$LINENO" selftest_vm100_apply; }
else
  APPLY_INPUT="$WORK/vm100-apply-input.sh"
  { printf 'set -- %q %q\n' "$EXPECTED_PACKAGE_SHA" "$BACKUP_DIR"; cat "$WORK/apply-expanded.sh"; } > "$APPLY_INPUT"
  capture_remote vm100 "$APPLY_INPUT" "$EVIDENCE/vm100-apply.stdout.txt" "$EVIDENCE/vm100-apply.stderr.txt" "$EVIDENCE/vm100-apply.rc.txt" || { if grep -Fqx CORE_CHANGE_COMPLETE=true "$EVIDENCE/vm100-apply.stdout.txt"; then CORE_CHANGE_COMPLETE=true; ROLLBACK_STATUS=not_run_apply_reported_core_complete; fi; rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm100-apply.rc.txt")"; STOP_MACHINE=VM100; STOP_DETAIL=vm100_apply_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm100_apply_stdout_stderr; stop_handler "${rc:-1}" "$LINENO" vm100_apply_remote; }
fi
cp "$EVIDENCE/vm100-apply.stdout.txt" "$APPLY_ENV"
if grep -Fqx CORE_CHANGE_COMPLETE=true "$APPLY_ENV"; then CORE_CHANGE_COMPLETE=true; ROLLBACK_STATUS=not_run_apply_verified; fi
grep -Fqx RESULT=PASS_R20QV_VM100_APPLY "$APPLY_ENV"
[[ "$CORE_CHANGE_COMPLETE" == true ]]
router_step_phase_complete apply target_file_firewall_reload_and_runtime_chain_verified

CURRENT_PHASE=postcheck; router_step_phase_begin postcheck
VERIFY_INPUT="$WORK/vm100-verify-input.sh"
{ printf 'set -- %q\n' "$EXPECTED_FILE_SHA"; cat "$FINAL_ROOT/scripts/verify-vm100.sh"; } > "$VERIFY_INPUT"
if [[ "$SELFTEST" == true ]]; then
  capture_local "$EVIDENCE/vm100-postcheck.stdout.txt" "$EVIDENCE/vm100-postcheck.stderr.txt" "$EVIDENCE/vm100-postcheck.rc.txt" env ROUTER_TARGET_ROOT="$FROOT" ROUTER_NFT_BIN="$FBIN/nft" ROUTER_FW4_BIN="$FBIN/fw4" sh "$FINAL_ROOT/scripts/verify-vm100.sh" "$EXPECTED_FILE_SHA" || { STOP_MACHINE=VM100; STOP_DETAIL=selftest_vm100_postcheck_failed; NEXT_DIAGNOSTIC_ACTION=inspect_vm100_postcheck_evidence; rc="$(sed -n 's/^LOCAL_RC=//p' "$EVIDENCE/vm100-postcheck.rc.txt")"; stop_handler "${rc:-1}" "$LINENO" selftest_vm100_postcheck; }
  cp "$EVIDENCE/vm101-preflight.stdout.txt" "$EVIDENCE/vm101-postcheck.stdout.txt"; : > "$EVIDENCE/vm101-postcheck.stderr.txt"; printf 'LOCAL_RC=0\n' > "$EVIDENCE/vm101-postcheck.rc.txt"
else
  capture_remote vm100 "$VERIFY_INPUT" "$EVIDENCE/vm100-postcheck.stdout.txt" "$EVIDENCE/vm100-postcheck.stderr.txt" "$EVIDENCE/vm100-postcheck.rc.txt" || { rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm100-postcheck.rc.txt")"; STOP_MACHINE=VM100; STOP_DETAIL=vm100_postcheck_failed_after_core; NEXT_DIAGNOSTIC_ACTION=read_only_continuation_do_not_rollback; stop_handler "${rc:-1}" "$LINENO" vm100_postcheck_remote; }
  capture_remote vm101 "$FINAL_ROOT/scripts/preflight-vm101.sh" "$EVIDENCE/vm101-postcheck.stdout.txt" "$EVIDENCE/vm101-postcheck.stderr.txt" "$EVIDENCE/vm101-postcheck.rc.txt" || { rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm101-postcheck.rc.txt")"; STOP_MACHINE=VM101; STOP_DETAIL=vm101_readonly_postcheck_failed_after_core; NEXT_DIAGNOSTIC_ACTION=read_only_continuation_do_not_rollback; stop_handler "${rc:-1}" "$LINENO" vm101_postcheck_remote; }
fi
grep -Fqx RESULT=PASS_R20QV_VM100_POSTCHECK "$EVIDENCE/vm100-postcheck.stdout.txt"
router_step_phase_complete postcheck persistent_file_runtime_rule_and_vm101_mapping_verified

CURRENT_PHASE=source; router_step_phase_begin source
SOURCE_CHECK_INPUT="$WORK/vm100-source-precheck-input.sh"
{ printf 'set -- %q\n' "$EXPECTED_FILE_SHA"; cat "$FINAL_ROOT/scripts/source-precheck-vm100.sh"; } > "$SOURCE_CHECK_INPUT"
if [[ "$SELFTEST" == true ]]; then
  capture_local "$EVIDENCE/vm100-source-precheck.stdout.txt" "$EVIDENCE/vm100-source-precheck.stderr.txt" "$EVIDENCE/vm100-source-precheck.rc.txt" env ROUTER_TARGET_ROOT="$FROOT" ROUTER_GIT_CHECK_BIN="$FBIN/git100" sh "$FINAL_ROOT/scripts/source-precheck-vm100.sh" "$EXPECTED_FILE_SHA" || { STOP_MACHINE=VM100; STOP_DETAIL=selftest_source_precheck_failed_after_core; NEXT_DIAGNOSTIC_ACTION=inspect_source_precheck_evidence; rc="$(sed -n 's/^LOCAL_RC=//p' "$EVIDENCE/vm100-source-precheck.rc.txt")"; stop_handler "${rc:-1}" "$LINENO" selftest_vm100_source_precheck; }
else
  capture_remote vm100 "$SOURCE_CHECK_INPUT" "$EVIDENCE/vm100-source-precheck.stdout.txt" "$EVIDENCE/vm100-source-precheck.stderr.txt" "$EVIDENCE/vm100-source-precheck.rc.txt" || { rc="$(sed -n 's/^REMOTE_RC=//p' "$EVIDENCE/vm100-source-precheck.rc.txt")"; STOP_MACHINE=VM100; STOP_DETAIL=source_precheck_found_transient_or_unexpected_drift_after_core; NEXT_DIAGNOSTIC_ACTION=wait_for_canonical_selector_then_read_only_source_continuation; stop_handler "${rc:-1}" "$LINENO" vm100_source_precheck_remote; }
fi
grep -Fqx RESULT=PASS_R20QV_VM100_SOURCE_PRECHECK "$EVIDENCE/vm100-source-precheck.stdout.txt"
if [[ "$SELFTEST" == true ]]; then
  cat > "$SOURCE_ENV" <<'EOF_SOURCE'
RESULT=PASS_ROUTER_MACHINE_CLOSE
COMMIT=1111111111111111111111111111111111111111
TREE=2222222222222222222222222222222222222222
GIT_FILE_COUNT=30
VM100_PUBLIC_URL=selftest://vm100-source/
EOF_SOURCE
else
  if "$BASE/bin/router-machine-close-safe" vm100 "$STEP: persistent wg_remote default selector cs4" > "$EVIDENCE/vm100-machine-close.stdout.txt" 2> "$EVIDENCE/vm100-machine-close.stderr.txt"; then close_rc=0; else close_rc=$?; fi
  printf 'LOCAL_RC=%s\n' "$close_rc" > "$EVIDENCE/vm100-machine-close.rc.txt"
  cp "$EVIDENCE/vm100-machine-close.stdout.txt" "$SOURCE_ENV"
  if (( close_rc != 0 )); then STOP_MACHINE=VM100; STOP_DETAIL=machine_close_failed_after_core; NEXT_DIAGNOSTIC_ACTION=read_only_source_publication_continuation; stop_handler "$close_rc" "$LINENO" router_machine_close_safe_vm100; fi
fi
grep -Eq '^RESULT=PASS_ROUTER_MACHINE_CLOSE$' "$SOURCE_ENV"
VM100_PUBLIC_URL="$(awk -F= '$1=="VM100_PUBLIC_URL"{print substr($0,index($0,"=")+1)}' "$SOURCE_ENV" | tail -n1)"
VM100_COMMIT="$(awk -F= '$1=="COMMIT"{print $2}' "$SOURCE_ENV" | tail -n1)"
VM100_TREE="$(awk -F= '$1=="TREE"{print $2}' "$SOURCE_ENV" | tail -n1)"
VM100_COUNT="$(awk -F= '$1=="GIT_FILE_COUNT"{print $2}' "$SOURCE_ENV" | tail -n1)"
[[ -n "$VM100_PUBLIC_URL" && "$VM100_COMMIT" =~ ^[0-9a-f]{40}$ && "$VM100_TREE" =~ ^[0-9a-f]{40}$ && "$VM100_COUNT" == 30 ]]
router_step_phase_complete source only_target_path_committed_pushed_and_published

CURRENT_PHASE=project_source; router_step_phase_begin project_source
PS="$WORK/project-source"; mkdir -p "$PS"
cat > "$PS/WG_PAID_MGTS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4.md" <<EOF_PS
# WG Paid / MGTS — R20QV R02 VM100 wg_remote default selector cs4

Result: PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4 through R02.

VM100 now contains /etc/nftables.d/91-wg-remote-default-selector.nft. Every IPv4 packet entering through wg_remote receives DSCP cs4. Existing VM100 PBR remains authoritative: internal 10.71.100.0/24 is ignored by PBR, RU destinations remain Direct, and other destinations selected for transit_vpn reach VM101 with cs4. VM101 was read-only and retains canonical mapping cs4 → egress1 → vpn1 → mark 0x201 → table 201 while the normal five-slot state is active.

R01 stopped before core on a transient selector/Machine-Git baseline mismatch. R02 uses a clean-live preflight and an exact one-staged-path source gate.

No per-peer selector UI, topology/fallback change, Direct-helper change, WireGuard peer change, VM101 mutation, reboot or snapshot was performed.

VM100 commit: $VM100_COMMIT  
VM100 tree: $VM100_TREE  
VM100 managed file count: $VM100_COUNT  
VM100 source: $VM100_PUBLIC_URL
EOF_PS
if [[ "$SELFTEST" == true ]]; then
  PROJECT_SOURCE_POST_URL="$(selftest_publish_dir r20qv-project-source "$PS")"
else
  "$BASE/bin/router-publish-report" project_source_post_r20qv_vm100_wg_remote_default_cs4 "$PS/WG_PAID_MGTS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4.md" | tee "$PROJECT_ENV"
  PROJECT_SOURCE_POST_URL="$(awk -F= '$1=="PROJECT_SOURCE_POST_URL"{print substr($0,index($0,"=")+1)}' "$PROJECT_ENV" | tail -n1)"
fi
[[ -n "$PROJECT_SOURCE_POST_URL" ]]
router_step_phase_complete project_source implementation_checkpoint_published

CURRENT_PHASE=report_build; router_step_phase_begin report_build
cat > "$REPORT/report.txt" <<EOF_REPORT
RESULT=PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4
REVISION=R02
WORKFLOW_CONTRACT_VERSION=2
PREVIOUS_REVISION_RESULT=STOP_R20QV_VM100_WG_REMOTE_DEFAULT_CS4_PRE_CORE
R02_PREFLIGHT_POLICY=clean_live_machine_git_plus_exact_canonical_selector
R02_SOURCE_POLICY=only_target_nft_file_may_be_staged
CORE_CHANGE_COMPLETE=true
RUNTIME_IMPACT=true
SNAPSHOT_CREATED=false
SNAPSHOT_REASON=not_required_single_file_transactional_reload
ROLLBACK_STATUS=$ROLLBACK_STATUS
VM100_GIT_COMMIT=$VM100_COMMIT
VM100_GIT_TREE=$VM100_TREE
VM100_GIT_FILE_COUNT=$VM100_COUNT
VM100_PUBLIC_URL=$VM100_PUBLIC_URL
VM101_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260801-095331_vm101_git_source_ac678d1b3164/
VM121_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260713-182700_vm121_git_source_93e0e5a31f09/
PROJECT_SOURCE_POST_URL=$PROJECT_SOURCE_POST_URL
LOCAL_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/20260711-123348_global_project_plan_wg_paid/
ACCESS_MAP_URL=https://reports.secret-studio.ru/latest/20260715-210236_access_map_canonical_current/
XS_MAP_URL=https://reports.secret-studio.ru/latest/20260711-123348_global_project_plan_wg_paid/sources/xs-map-report.txt
VM100_TARGET_FILE=/etc/nftables.d/91-wg-remote-default-selector.nft
VM100_TARGET_FILE_SHA256=$EXPECTED_FILE_SHA
WG_REMOTE_DEFAULT_SELECTOR=cs4
VM101_CS4_CANONICAL_PATH=egress1:vpn1:0x201:table201
PER_PEER_SELECTOR_IMPLEMENTED=false
VM101_MUTATED=false
VM121_MUTATED=false
REBOOT_EXECUTED=false
ZERO_HEALTHY_TEST_REPEATED=false
EOF_REPORT
python3 - "$REPORT/facts.json" "$VM100_COMMIT" "$VM100_TREE" "$VM100_COUNT" "$VM100_PUBLIC_URL" "$PROJECT_SOURCE_POST_URL" <<'PYFACTS'
import json,sys
p,c,t,n,u,ps=sys.argv[1:]
json.dump({"result":"PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4","core_change_complete":True,"runtime_impact":True,"vm100":{"commit":c,"tree":t,"file_count":int(n),"url":u},"project_source_post_url":ps,"selector":"cs4","interface":"wg_remote"},open(p,"w"),indent=2); open(p,"a").write("\n")
PYFACTS
printf 'RESULT=PASS_R20QV_VERIFY
TARGET_FILE_SHA256=%s
RUNTIME_CHAIN=wg_remote_default_selector
SELECTOR=cs4
' "$EXPECTED_FILE_SHA" > "$REPORT/verify.txt"
printf 'Automatic rollback was not run because apply and immediate runtime verification completed. Retained backup: %s
' "$BACKUP_DIR" > "$REPORT/rollback.txt"
prepare_public_evidence; cp "$FINAL_ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"; cp "$FINAL_ROOT/release-info.json" "$REPORT/release-info.json"; cp "$FINAL_ROOT/manifest.sha256" "$REPORT/bundle-manifest.sha256"; cp "$CURRENT" "$REPORT/workflow-current.txt"; cp "$EVENTS" "$REPORT/workflow-events.tsv"; cp "$FINAL_ROOT/payload/rootfs/etc/nftables.d/91-wg-remote-default-selector.nft" "$REPORT/91-wg-remote-default-selector.nft"; cp "$EVIDENCE"/*.log "$REPORT/" 2>/dev/null || true; cp "$SOURCE_ENV" "$REPORT/vm100-source.txt"
router_step_phase_complete report_build complete_step_payload_built

CURRENT_PHASE=report_publish; router_step_phase_begin report_publish
if [[ "$SELFTEST" == true ]]; then
  STEP_REPORT_URL="$(selftest_publish_dir r20qv-r02-pass "$REPORT")"
else
  args=("$REPORT/report.txt" report.txt "$REPORT/facts.json" facts.json "$REPORT/verify.txt" verify.txt "$REPORT/rollback.txt" rollback.txt "$REPORT/executed-step.sh" executed-step.sh "$REPORT/release-info.json" release-info.json "$REPORT/bundle-manifest.sha256" bundle-manifest.sha256 "$REPORT/workflow-current.txt" workflow-current.txt "$REPORT/workflow-events.tsv" workflow-events.tsv "$REPORT/91-wg-remote-default-selector.nft" 91-wg-remote-default-selector.nft "$REPORT/vm100-source.txt" vm100-source.txt)
  for name in "${PUBLIC_EVIDENCE_FILES[@]}"; do args+=("$REPORT/$name" "$name"); done
  router_step_publish_payload step050m07r20qv_r02_vm100_wg_remote_default_cs4 "$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_and_verified

CURRENT_PHASE=archive; router_step_phase_begin archive
archive_latest
[[ -f "$LATEST_ARCHIVE" ]]
router_step_phase_complete archive one_final_latest_archive_created
router_step_workflow_mark_complete
trap - ERR
write_child PASS_R20QV_VM100_WG_REMOTE_DEFAULT_CS4 0
cat "$REPORT/report.txt"
echo STEP_REPORT_URL="$STEP_REPORT_URL"
echo LATEST_ARCHIVE="$LATEST_ARCHIVE"
echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"
