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

STEP_ID="STEP_050M07R15B2_GENERATION_BUILDER_R01"
STEP_LABEL="step050m07r15b2_generation_builder_r01"
MODEL_LABEL="vm101_model_generation_builder_r15b2"
METHODS_LABEL="vm101_methods_generation_builder_r15b2"
PLAN_LABEL="local_architecture_plan_vm101_autonomous_hmn_recovery_r15b2"
EXPECTED_PUBLISHER_SHA256="e78430b57afbd0ae27fc575fc4de78bb267c76caf04ec935b01e9b2f89840e32"
EXPECTED_MACHINE_CLOSE_SHA256="e52012b347340cd66b17fcb6a02b6d46be5a3630abe2c3184eac864826767885"
EXPECTED_MACHINE_CLOSE_LIB_SHA256="bebf52157e16228c612b7bd998769eb26fca90fe37d237555b484c8ba2a7d594"
EXPECTED_MACHINE_CLOSE_SELFTEST_SHA256="2d518a23058ecc76f52b2e17acdaf8fb7ddfadc0a38ef8312745b3d7f2b3f4d9"
EXPECTED_VM101_COMMIT_BEFORE="66a24ee69f186d27cab4ae73169fab6fab636d56"
EXPECTED_VM101_FILE_COUNT_AFTER="33"
TARGET_TRANSFER_ROOT="/tmp/r15b2-r01"
TARGET_BASE="/var/lib/router-egress-recovery/steps/STEP_050M07R15B2_GENERATION_BUILDER_R01"

BUNDLE_DIR="$(cd "$(dirname "$0")" && pwd -P)"
SELF="$(readlink -f "$0")"
BUNDLE_ZIP="${BUNDLE_ZIP:?BUNDLE_ZIP is required}"
EXPECTED_BUNDLE_SHA256="${EXPECTED_BUNDLE_SHA256:?EXPECTED_BUNDLE_SHA256 is required}"
EXPECTED_INSTALLER_SHA256="${EXPECTED_INSTALLER_SHA256:?EXPECTED_INSTALLER_SHA256 is required}"
source "$BUNDLE_DIR/plans/current-links.env"
LATEST_ROOT_URL="https://reports.secret-studio.ru/latest/"
OLD_ROOT_URL="https://reports.secret-studio.ru/old/"

TS="$(date -u +%Y%m%d-%H%M%S)"
BASE="/opt/router-ops"
STEP_DIR="$BASE/steps/${TS}_${STEP_ID}"
PUBLISHER="$BASE/bin/router-publish-report"
MACHINE_CLOSE="$BASE/bin/router-machine-close"
MACHINE_CLOSE_LIB="$BASE/lib/router-machine-close-contract.sh"
MACHINE_CLOSE_SELFTEST="$BASE/bin/router-machine-close-contract-selftest"
PUBLIC_TOKEN_FILE="$BASE/public/.router-public-token"
STATE_DIR="$BASE/state"

mkdir -p "$STEP_DIR"
chmod 700 "$STEP_DIR"
STEP_FILE="$STEP_DIR/step-code.sh"
cp -a "$SELF" "$STEP_FILE"
chmod 700 "$STEP_FILE"
BUNDLE_COPY="$STEP_DIR/$(basename "$BUNDLE_ZIP")"
cp -a "$BUNDLE_ZIP" "$BUNDLE_COPY"

REPORT="$STEP_DIR/report.txt"
FACTS="$STEP_DIR/facts.json"
VERIFY="$STEP_DIR/verify.txt"
ROLLBACK="$STEP_DIR/rollback.sh"
MAIN_LOG="$STEP_DIR/main.log"
PROGRESS_LOG="$STEP_DIR/progress.log"
TRANSFER_VERIFY="$STEP_DIR/transfer-verify.txt"
SYSTEM_MODEL="$STEP_DIR/system-model.txt"
METHODS_LIMITATIONS="$STEP_DIR/methods-limitations.txt"
ACCESS_ROUTES="$STEP_DIR/access-routes.txt"
PHASES="$STEP_DIR/phases.txt"
HTTP_LOG="$STEP_DIR/http-verify.log"
: > "$HTTP_LOG"
STEP_PUBLISH_LOG="$STEP_DIR/step-publish.log"
MACHINE_CLOSE_LOG="$STEP_DIR/router-machine-close.log"
POST_GIT_LOG="$STEP_DIR/vm101-post-machine-git.txt"
TARGET_PREFLIGHT_LOG="$STEP_DIR/target-preflight.log"
TARGET_TEST_LOG="$STEP_DIR/target-test-and-rank.log"
TARGET_BASELINE_LOG="$STEP_DIR/target-baseline.log"
TARGET_APPLY_LOG="$STEP_DIR/target-apply.log"
TARGET_POSTCHECK_LOG="$STEP_DIR/target-postcheck.log"
TARGET_TRANSFER_LOG="$STEP_DIR/target-transfer-verify.log"
TARGET_PACKAGE_DIR="$STEP_DIR/target-package"
TARGET_ARTIFACT_TAR="$STEP_DIR/target-artifacts.tar.gz"
TARGET_ARTIFACTS="$STEP_DIR/target-artifacts"
EXACT_CLONE_DIR="$STEP_DIR/vm101-exact-clone"
MODEL_BUILD="$STEP_DIR/model-build"
METHODS_BUILD="$STEP_DIR/methods-build"
PLAN_BUILD="$STEP_DIR/plan-build"
SAFE_PUBLISH="$STEP_DIR/publish-safe"
STATE_BACKUP="$STEP_DIR/state-backup"
mkdir -p "$TARGET_PACKAGE_DIR" "$TARGET_ARTIFACTS" "$MODEL_BUILD" "$METHODS_BUILD" "$PLAN_BUILD" "$SAFE_PUBLISH" "$STATE_BACKUP"
chmod 700 "$TARGET_PACKAGE_DIR" "$TARGET_ARTIFACTS" "$MODEL_BUILD" "$METHODS_BUILD" "$PLAN_BUILD" "$SAFE_PUBLISH" "$STATE_BACKUP"

RESULT="STOP_NOT_STARTED"
STOP_REASON="none"
CURRENT_PHASE="initialization"
COPY_VERIFY="FAIL"
STEP_WAS_NOT_STARTED="true"
CORE_CHANGE_COMPLETE="false"
CORE_CHANGE_REPEATED="false"
TARGET_INSTALLER_RERUN="false"
MACHINE_GIT_COMPLETE="false"
ROUTER_MACHINE_CLOSE_RERUN="false"
SOURCE_PUBLICATION_COMPLETE="false"
MODEL_PUBLICATION_COMPLETE="false"
METHODS_PUBLICATION_COMPLETE="false"
PLAN_PUBLICATION_COMPLETE="false"
STEP_REPORT_PUBLICATION_COMPLETE="false"
HTTP_VERIFY_PASS="false"
RUNTIME_IMPACT="false"
TRAFFIC_INTERRUPTION="false"
GENERATION_VALIDATOR="unknown"
STAGING_GENERATION_COUNT="0"
ACTIVE_GENERATION_CREATED="false"
ACTIVE_SLOTS_CHANGED="false"
ENDPOINTS_UNCHANGED="unknown"
TABLES_201_205="unknown"
RECOVERY_SERVICES="unknown"
SERVICE_RESTARTS="none"
TARGET_CORE_RESULT="not_run"
VM101_GIT_COMMIT=""
VM101_GIT_TREE=""
VM101_GIT_FILE_COUNT=""
VM130_EXACT_CLONE_STATUS="not_run"
MODEL_ARCHIVE_SHA256=""
GENERATION_ID=""
GENERATION_DIR=""
BUILDER_SHA256=""
CANDIDATE_POOL=""
SAFE_TEST_OK="0"
ELIGIBLE_CANDIDATES="0"
TEST_INTERFACE="r15b2t"
NETWORK_RELOAD="false"
UCI_CHANGED="false"
ACTIVE_ENDPOINTS_PROBED="false"
QUARANTINED_ENDPOINTS_PROBED="false"
PUBLIC_OBJECT_KIND=""
PREVIOUS_MOVED_COUNT=""
STEP_REPORT_URL=""
FACTS_URL=""
VERIFY_URL=""
ROLLBACK_URL=""
STEP_PUBLISHED_DIR=""
MODEL_PUBLISHED_DIR=""
METHODS_PUBLISHED_DIR=""
PLAN_PUBLISHED_DIR=""

exec 3>&1
exec >>"$MAIN_LOG" 2>&1

progress() {
    printf '>>> [%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "$PROGRESS_LOG" >&3
}
phase() {
    CURRENT_PHASE="$1"
    printf '%s=started\n' "$CURRENT_PHASE" >> "$PHASES"
    progress "$CURRENT_PHASE"
}
value_from_log() {
    local key="$1" file="$2"
    awk -F= -v k="$key" '$1==k {print substr($0,index($0,"=")+1)}' "$file" | tail -n1
}
vm101_run_script() {
    local script="$1"
    ssh -o BatchMode=yes -o ConnectTimeout=20 pve-mgts \
        "ssh -o BatchMode=yes -o ConnectTimeout=20 -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.2 'sh -s'" \
        < "$script"
}
vm101_run_checked() {
    local script="$1"
    sh -n "$script"
    vm101_run_script "$script"
}
vm101_receive_tar() {
    local archive="$1"
    ssh -o BatchMode=yes -o ConnectTimeout=20 pve-mgts \
        "ssh -o BatchMode=yes -o ConnectTimeout=20 -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.2 'rm -rf /tmp/r15b2-r01 && mkdir -p /tmp/r15b2-r01 && tar -xzf - -C /tmp/r15b2-r01'" \
        < "$archive"
}
write_atomic() {
    local dst="$1" src="$2"
    mkdir -p "$(dirname "$dst")"
    cp "$src" "${dst}.tmp.$$"
    chmod 600 "${dst}.tmp.$$"
    mv -f "${dst}.tmp.$$" "$dst"
}
public_url_from_dir() {
    local dir="$1"
    printf 'https://reports.secret-studio.ru/latest/%s/\n' "$(basename "$dir")"
}
write_rollback_script() {
    cat > "$ROLLBACK" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail
BASE='$BASE'
STEP_DIR='$STEP_DIR'
TARGET_BASE='$TARGET_BASE'
MACHINE_CLOSE='$MACHINE_CLOSE'
TOKEN_FILE='$PUBLIC_TOKEN_FILE'
STATE_DIR='$STATE_DIR'

LAUNCHER="\$(mktemp)"
trap 'rm -f "\$LAUNCHER"' EXIT
cat > "\$LAUNCHER" <<'REMOTE'
#!/bin/sh
set -eu
TARGET_BASE='$TARGET_BASE'
if [ -x "\$TARGET_BASE/rollback-target.sh" ]; then
    exec /bin/sh "\$TARGET_BASE/rollback-target.sh"
fi
echo RESULT=PASS_R15B2_TARGET_ROLLBACK_NOT_REQUIRED
REMOTE
sh -n "\$LAUNCHER"
ssh -o BatchMode=yes pve-mgts "ssh -o BatchMode=yes -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.2 'sh -s'" < "\$LAUNCHER"

if grep -qx 'RESULT=PASS_ROUTER_MACHINE_CLOSE' "\$STEP_DIR/router-machine-close.log" 2>/dev/null; then
    "\$MACHINE_CLOSE" vm101 'ROLLBACK_R15B2: remove staged generation builder and restore source'
else
    echo MACHINE_GIT_ROLLBACK=NOT_REQUIRED
fi

TOKEN="\$(cat "\$TOKEN_FILE")"
LATEST="\$BASE/public/r/\$TOKEN/latest"
OLD="\$BASE/public/r/\$TOKEN/old"
for kind in model methods plan; do
    NEW_FILE="\$STEP_DIR/new-\$kind-folder.txt"
    PREV_FILE="\$STEP_DIR/previous-\$kind-folder.txt"
    [[ -s "\$NEW_FILE" && -s "\$PREV_FILE" ]] || continue
    NEW="\$(cat "\$NEW_FILE")"
    PREV="\$(cat "\$PREV_FILE")"
    if [[ -d "\$LATEST/\$NEW" ]]; then
        mv "\$LATEST/\$NEW" "\$OLD/\$NEW"
        [[ -d "\$OLD/\$PREV" && ! -e "\$LATEST/\$PREV" ]] && mv "\$OLD/\$PREV" "\$LATEST/\$PREV"
    else
        echo "PUBLICATION_ROLLBACK_\$kind=SKIPPED_NOT_CURRENT"
    fi
done
"\$BASE/bin/router-public-reindex"

for f in current-vm101-model.env current-vm101-methods.env current-vm101-local-plan.env; do
    [[ ! -f "\$STEP_DIR/state-backup/\$f" ]] || cp -a "\$STEP_DIR/state-backup/\$f" "\$STATE_DIR/\$f"
done

echo RESULT=PASS_R15B2_ROLLBACK
echo RUNTIME_STATE_RESTORED=true
echo PUBLICATION_STATE_RESTORED=true
EOF
    chmod 700 "$ROLLBACK"
}

write_docs() {
    cat > "$SYSTEM_MODEL" <<EOF
STEP_ID=$STEP_ID
SCOPE=VM101 generation builder, staging only
VM101_COMMIT_BEFORE=$EXPECTED_VM101_COMMIT_BEFORE
VM101_COMMIT_AFTER=${VM101_GIT_COMMIT:-pending}
MANAGED_FILE_COUNT_AFTER=${VM101_GIT_FILE_COUNT:-pending}
VM130_EXACT_CLONE_STATUS=$VM130_EXACT_CLONE_STATUS
MODEL_ARCHIVE_SHA256=${MODEL_ARCHIVE_SHA256:-pending}
GENERATION_ID=${GENERATION_ID:-pending}
GENERATION_DIR=${GENERATION_DIR:-pending}
STAGING_GENERATION_COUNT=$STAGING_GENERATION_COUNT
GENERATION_VALIDATOR=$GENERATION_VALIDATOR
ACTIVE_GENERATION_CREATED=$ACTIVE_GENERATION_CREATED
ACTIVE_SLOTS_CHANGED=$ACTIVE_SLOTS_CHANGED
ENDPOINTS_UNCHANGED=$ENDPOINTS_UNCHANGED
TABLES_201_205=$TABLES_201_205
RECOVERY_SERVICES=$RECOVERY_SERVICES
SERVICE_RESTARTS=$SERVICE_RESTARTS
TRAFFIC_INTERRUPTION=$TRAFFIC_INTERRUPTION
CANDIDATE_POOL=$CANDIDATE_POOL
SAFE_TEST_OK=$SAFE_TEST_OK
ELIGIBLE_CANDIDATES=$ELIGIBLE_CANDIDATES
ACTIVE_ENDPOINTS_PROBED=$ACTIVE_ENDPOINTS_PROBED
QUARANTINED_ENDPOINTS_PROBED=$QUARANTINED_ENDPOINTS_PROBED
TEST_INTERFACE=$TEST_INTERFACE
NETWORK_RELOAD=$NETWORK_RELOAD
UCI_CHANGED=$UCI_CHANGED
RUNTIME_IMPACT=$RUNTIME_IMPACT
EOF
    cat > "$METHODS_LIMITATIONS" <<EOF
VM101 is OpenWrt with BusyBox ash. All target scripts use /bin/sh and perform command capability preflight.
Transport route: VM130 -> pve-mgts -> root@10.71.100.2, standalone scripts through stdin and tar/gzip for binary transfer.
Active, quarantined and duplicate endpoints are excluded before probing. Remaining candidates are freshly tested through a dedicated temporary AmneziaWG interface created directly with ip and amneziawg setconf, then ranked by hmn-rank-awg.sh and validated by router-egress-generation-validate.
The old HMN tester/loader path is intentionally not used because it changes UCI state and reloads the network. R15B2 performs no UCI write, network reload, ifup/ifdown of production interfaces, service restart or production policy-table change.
No active symlink is created. No vpn1..vpn5 configuration, production route, service or repair counter is changed.
The safe test phase uses only the temporary interface r15b2t and temporary host routes to 9.9.9.9 and 1.1.1.1, with cleanup traps.
EOF
    cat > "$ACCESS_ROUTES" <<EOF
VM101 access: ops@router-ops -> ssh pve-mgts -> ssh -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.2
Direct VM130 -> VM101 SSH is not used.
Access routes, aliases, keys, IP addresses and jump hosts are unchanged.
ACCESS_MAP_URL=$ACCESS_MAP_URL
EOF
}

write_facts() {
    local step_sha
    step_sha="$(sha256sum "$STEP_FILE" 2>/dev/null | awk '{print $1}' || true)"
    export EXPECTED_INSTALLER_SHA256 EXPECTED_BUNDLE_SHA256
    export RESULT STOP_REASON CURRENT_PHASE COPY_VERIFY STEP_WAS_NOT_STARTED CORE_CHANGE_COMPLETE CORE_CHANGE_REPEATED TARGET_INSTALLER_RERUN MACHINE_GIT_COMPLETE ROUTER_MACHINE_CLOSE_RERUN SOURCE_PUBLICATION_COMPLETE MODEL_PUBLICATION_COMPLETE METHODS_PUBLICATION_COMPLETE PLAN_PUBLICATION_COMPLETE STEP_REPORT_PUBLICATION_COMPLETE HTTP_VERIFY_PASS RUNTIME_IMPACT TRAFFIC_INTERRUPTION GENERATION_VALIDATOR STAGING_GENERATION_COUNT ACTIVE_GENERATION_CREATED ACTIVE_SLOTS_CHANGED ENDPOINTS_UNCHANGED TABLES_201_205 RECOVERY_SERVICES SERVICE_RESTARTS TARGET_CORE_RESULT VM101_GIT_COMMIT VM101_GIT_TREE VM101_GIT_FILE_COUNT VM130_EXACT_CLONE_STATUS MODEL_ARCHIVE_SHA256 GENERATION_ID GENERATION_DIR BUILDER_SHA256 CANDIDATE_POOL SAFE_TEST_OK ELIGIBLE_CANDIDATES ACTIVE_ENDPOINTS_PROBED QUARANTINED_ENDPOINTS_PROBED TEST_INTERFACE NETWORK_RELOAD UCI_CHANGED STEP_REPORT_URL LOCAL_M07_PLAN_URL GLOBAL_PROJECT_PLAN_URL ACCESS_MAP_URL VM100_PUBLIC_URL VM101_PUBLIC_URL VM121_PUBLIC_URL VM101_MODEL_URL VM101_METHODS_URL VM130_SOURCE_URL
    STEP_SHA_VALUE="$step_sha" python3 - "$FACTS" <<'PY'
import json,os,sys
keys=['RESULT','STOP_REASON','CURRENT_PHASE','COPY_VERIFY','STEP_WAS_NOT_STARTED','CORE_CHANGE_COMPLETE','CORE_CHANGE_REPEATED','TARGET_INSTALLER_RERUN','MACHINE_GIT_COMPLETE','ROUTER_MACHINE_CLOSE_RERUN','SOURCE_PUBLICATION_COMPLETE','MODEL_PUBLICATION_COMPLETE','METHODS_PUBLICATION_COMPLETE','PLAN_PUBLICATION_COMPLETE','STEP_REPORT_PUBLICATION_COMPLETE','HTTP_VERIFY_PASS','RUNTIME_IMPACT','TRAFFIC_INTERRUPTION','GENERATION_VALIDATOR','STAGING_GENERATION_COUNT','ACTIVE_GENERATION_CREATED','ACTIVE_SLOTS_CHANGED','ENDPOINTS_UNCHANGED','TABLES_201_205','RECOVERY_SERVICES','SERVICE_RESTARTS','TARGET_CORE_RESULT','VM101_GIT_COMMIT','VM101_GIT_TREE','VM101_GIT_FILE_COUNT','VM130_EXACT_CLONE_STATUS','MODEL_ARCHIVE_SHA256','GENERATION_ID','GENERATION_DIR','BUILDER_SHA256','CANDIDATE_POOL','SAFE_TEST_OK','ELIGIBLE_CANDIDATES','ACTIVE_ENDPOINTS_PROBED','QUARANTINED_ENDPOINTS_PROBED','TEST_INTERFACE','NETWORK_RELOAD','UCI_CHANGED','STEP_REPORT_URL','LOCAL_M07_PLAN_URL','GLOBAL_PROJECT_PLAN_URL','ACCESS_MAP_URL','VM100_PUBLIC_URL','VM101_PUBLIC_URL','VM121_PUBLIC_URL','VM101_MODEL_URL','VM101_METHODS_URL','VM130_SOURCE_URL']
data={k.lower():os.environ.get(k,'') for k in keys}
data['step_id']='STEP_050M07R15B2_GENERATION_BUILDER_R01'
data['step_sha256']=os.environ.get('STEP_SHA_VALUE','')
data['expected_installer_sha256']=os.environ.get('EXPECTED_INSTALLER_SHA256','')
with open(sys.argv[1],'w',encoding='utf-8') as f: json.dump(data,f,ensure_ascii=False,indent=2,sort_keys=True); f.write('\n')
PY
}
write_verify() {
    {
        echo "RESULT=$RESULT"
        echo "CURRENT_PHASE=$CURRENT_PHASE"
        echo "COPY_VERIFY=$COPY_VERIFY"
        echo "STEP_SHA256=$(sha256sum "$STEP_FILE" | awk '{print $1}')"
        echo "CORE_CHANGE_COMPLETE=$CORE_CHANGE_COMPLETE"
        echo "MACHINE_GIT_COMPLETE=$MACHINE_GIT_COMPLETE"
        echo "VM101_GIT_COMMIT=$VM101_GIT_COMMIT"
        echo "VM101_GIT_TREE=$VM101_GIT_TREE"
        echo "VM101_GIT_FILE_COUNT=$VM101_GIT_FILE_COUNT"
        echo "VM130_EXACT_CLONE_STATUS=$VM130_EXACT_CLONE_STATUS"
        echo "MODEL_ARCHIVE_SHA256=$MODEL_ARCHIVE_SHA256"
        echo "BUILDER_SHA256=$BUILDER_SHA256"
        echo "CANDIDATE_POOL=$CANDIDATE_POOL"
        echo "SAFE_TEST_OK=$SAFE_TEST_OK"
        echo "ELIGIBLE_CANDIDATES=$ELIGIBLE_CANDIDATES"
        echo "ACTIVE_ENDPOINTS_PROBED=$ACTIVE_ENDPOINTS_PROBED"
        echo "QUARANTINED_ENDPOINTS_PROBED=$QUARANTINED_ENDPOINTS_PROBED"
        echo "TEST_INTERFACE=$TEST_INTERFACE"
        echo "NETWORK_RELOAD=$NETWORK_RELOAD"
        echo "UCI_CHANGED=$UCI_CHANGED"
        echo "GENERATION_ID=$GENERATION_ID"
        echo "GENERATION_DIR=$GENERATION_DIR"
        echo "STAGING_GENERATION_COUNT=$STAGING_GENERATION_COUNT"
        echo "GENERATION_VALIDATOR=$GENERATION_VALIDATOR"
        echo "ACTIVE_GENERATION_CREATED=$ACTIVE_GENERATION_CREATED"
        echo "ACTIVE_SLOTS_CHANGED=$ACTIVE_SLOTS_CHANGED"
        echo "ENDPOINTS_UNCHANGED=$ENDPOINTS_UNCHANGED"
        echo "TABLES_201_205=$TABLES_201_205"
        echo "RECOVERY_SERVICES=$RECOVERY_SERVICES"
        echo "SERVICE_RESTARTS=$SERVICE_RESTARTS"
        echo "TRAFFIC_INTERRUPTION=$TRAFFIC_INTERRUPTION"
        echo "HTTP_VERIFY_PASS=$HTTP_VERIFY_PASS"
        echo "RUNTIME_IMPACT=$RUNTIME_IMPACT"
    } > "$VERIFY"
}
write_report() {
    cat > "$REPORT" <<EOF
RESULT=$RESULT
STEP_ID=$STEP_ID
STOP_REASON=$STOP_REASON
CURRENT_PHASE=$CURRENT_PHASE
CORE_CHANGE_COMPLETE=$CORE_CHANGE_COMPLETE
CORE_CHANGE_REPEATED=$CORE_CHANGE_REPEATED
TARGET_INSTALLER_RERUN=$TARGET_INSTALLER_RERUN
MACHINE_GIT_COMPLETE=$MACHINE_GIT_COMPLETE
ROUTER_MACHINE_CLOSE_RERUN=$ROUTER_MACHINE_CLOSE_RERUN
SOURCE_PUBLICATION_COMPLETE=$SOURCE_PUBLICATION_COMPLETE
MODEL_PUBLICATION_COMPLETE=$MODEL_PUBLICATION_COMPLETE
METHODS_PUBLICATION_COMPLETE=$METHODS_PUBLICATION_COMPLETE
PLAN_PUBLICATION_COMPLETE=$PLAN_PUBLICATION_COMPLETE
STEP_REPORT_PUBLICATION_COMPLETE=$STEP_REPORT_PUBLICATION_COMPLETE
VM101_GIT_COMMIT=$VM101_GIT_COMMIT
VM101_GIT_TREE=$VM101_GIT_TREE
VM101_GIT_FILE_COUNT=$VM101_GIT_FILE_COUNT
VM130_EXACT_CLONE_STATUS=$VM130_EXACT_CLONE_STATUS
MODEL_ARCHIVE_SHA256=$MODEL_ARCHIVE_SHA256
BUILDER_SHA256=$BUILDER_SHA256
CANDIDATE_POOL=$CANDIDATE_POOL
SAFE_TEST_OK=$SAFE_TEST_OK
ELIGIBLE_CANDIDATES=$ELIGIBLE_CANDIDATES
ACTIVE_ENDPOINTS_PROBED=$ACTIVE_ENDPOINTS_PROBED
QUARANTINED_ENDPOINTS_PROBED=$QUARANTINED_ENDPOINTS_PROBED
TEST_INTERFACE=$TEST_INTERFACE
NETWORK_RELOAD=$NETWORK_RELOAD
UCI_CHANGED=$UCI_CHANGED
GENERATION_ID=$GENERATION_ID
GENERATION_DIR=$GENERATION_DIR
STAGING_GENERATION_COUNT=$STAGING_GENERATION_COUNT
GENERATION_VALIDATOR=$GENERATION_VALIDATOR
ACTIVE_GENERATION_CREATED=$ACTIVE_GENERATION_CREATED
ACTIVE_SLOTS_CHANGED=$ACTIVE_SLOTS_CHANGED
ENDPOINTS_UNCHANGED=$ENDPOINTS_UNCHANGED
TABLES_201_205=$TABLES_201_205
RECOVERY_SERVICES=$RECOVERY_SERVICES
SERVICE_RESTARTS=$SERVICE_RESTARTS
TRAFFIC_INTERRUPTION=$TRAFFIC_INTERRUPTION
HTTP_VERIFY_PASS=$HTTP_VERIFY_PASS
RUNTIME_IMPACT=$RUNTIME_IMPACT
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=$FACTS_URL
VERIFY_URL=$VERIFY_URL
ROLLBACK_URL=$ROLLBACK_URL
PUBLIC_OBJECT_KIND=$PUBLIC_OBJECT_KIND
PREVIOUS_MOVED_COUNT=$PREVIOUS_MOVED_COUNT
COPY_VERIFY=$COPY_VERIFY
STEP_SHA256=$(sha256sum "$STEP_FILE" | awk '{print $1}')
EOF
}
print_final() {
    write_docs; write_facts; write_verify; write_report
    {
        echo "RESULT=$RESULT"
        echo "STEP_REPORT_URL=${STEP_REPORT_URL:-PENDING_NOT_PUBLISHED}"
        echo "LOCAL_M07_PLAN_URL=$LOCAL_M07_PLAN_URL"
        echo "GLOBAL_PROJECT_PLAN_URL=$GLOBAL_PROJECT_PLAN_URL"
        echo "ACCESS_MAP_URL=$ACCESS_MAP_URL"
        echo "VM100_PUBLIC_URL=$VM100_PUBLIC_URL"
        echo "VM101_PUBLIC_URL=$VM101_PUBLIC_URL"
        echo "VM121_PUBLIC_URL=$VM121_PUBLIC_URL"
        echo "VM101_MODEL_URL=$VM101_MODEL_URL"
        echo "VM101_METHODS_URL=$VM101_METHODS_URL"
        echo "VM130_SOURCE_URL=$VM130_SOURCE_URL"
        echo "LATEST_ROOT_URL=$LATEST_ROOT_URL"
        echo "OLD_ROOT_URL=$OLD_ROOT_URL"
        echo "FACTS_URL=${FACTS_URL:-PENDING_NOT_PUBLISHED}"
        echo "VERIFY_URL=${VERIFY_URL:-PENDING_NOT_PUBLISHED}"
        echo "ROLLBACK_URL=${ROLLBACK_URL:-PENDING_NOT_PUBLISHED}"
        echo "PUBLIC_OBJECT_KIND=${PUBLIC_OBJECT_KIND:-step-report}"
        echo "PREVIOUS_MOVED_COUNT=${PREVIOUS_MOVED_COUNT:-unknown}"
        echo "COPY_VERIFY=$COPY_VERIFY"
        echo "STEP_SHA256=$(sha256sum "$STEP_FILE" | awk '{print $1}')"
        echo "HTTP_VERIFY_PASS=$HTTP_VERIFY_PASS"
        echo "RUNTIME_IMPACT=$RUNTIME_IMPACT"
    } >&3
}

publish_step_best_effort() {
    set +e
    write_docs; write_facts; write_verify; write_report
    rm -rf "$SAFE_PUBLISH"; mkdir -p "$SAFE_PUBLISH"
    for f in "$STEP_FILE" "$REPORT" "$FACTS" "$VERIFY" "$ROLLBACK" "$MAIN_LOG" "$PROGRESS_LOG" "$TRANSFER_VERIFY" "$SYSTEM_MODEL" "$METHODS_LIMITATIONS" "$ACCESS_ROUTES" "$PHASES" "$TARGET_PREFLIGHT_LOG" "$TARGET_TEST_LOG" "$TARGET_BASELINE_LOG" "$TARGET_APPLY_LOG" "$TARGET_POSTCHECK_LOG" "$TARGET_TRANSFER_LOG" "$MACHINE_CLOSE_LOG" "$POST_GIT_LOG"; do [[ -f "$f" ]] && cp -a "$f" "$SAFE_PUBLISH/$(basename "$f")"; done
    if [[ -x "$PUBLISHER" ]]; then
        mapfile -d '' files < <(find "$SAFE_PUBLISH" -maxdepth 1 -type f -print0 | sort -z)
        "$PUBLISHER" "${STEP_LABEL}_stop" "${files[@]}" > "$STEP_PUBLISH_LOG" 2>&1
        if [[ $? -eq 0 ]]; then
            STEP_PUBLISHED_DIR="$(value_from_log PUBLISHED_DIR "$STEP_PUBLISH_LOG")"
            STEP_REPORT_URL="$(value_from_log STEP_REPORT_URL "$STEP_PUBLISH_LOG")"
            [[ -n "$STEP_REPORT_URL" ]] || STEP_REPORT_URL="$(public_url_from_dir "$STEP_PUBLISHED_DIR")"
            PUBLIC_OBJECT_KIND="$(value_from_log PUBLIC_OBJECT_KIND "$STEP_PUBLISH_LOG")"
            PREVIOUS_MOVED_COUNT="$(value_from_log PREVIOUS_MOVED_COUNT "$STEP_PUBLISH_LOG")"
            FACTS_URL="${STEP_REPORT_URL}facts.json"; VERIFY_URL="${STEP_REPORT_URL}verify.txt"; ROLLBACK_URL="${STEP_REPORT_URL}rollback.sh"
            write_docs; write_facts; write_verify; write_report
            cp -a "$REPORT" "$FACTS" "$VERIFY" "$SYSTEM_MODEL" "$METHODS_LIMITATIONS" "$ACCESS_ROUTES" "$STEP_PUBLISHED_DIR/" 2>/dev/null || true
            chmod 644 "$STEP_PUBLISHED_DIR/report.txt" "$STEP_PUBLISHED_DIR/facts.json" "$STEP_PUBLISHED_DIR/verify.txt" "$STEP_PUBLISHED_DIR/system-model.txt" "$STEP_PUBLISHED_DIR/methods-limitations.txt" "$STEP_PUBLISHED_DIR/access-routes.txt" 2>/dev/null || true
        fi
    fi
    set -e
}
stop_step() {
    local result="$1" reason="$2"
    trap - ERR
    RESULT="$result"; STOP_REASON="$reason"
    publish_step_best_effort
    print_final
    exit 1
}
trap 'stop_step "STOP_R15B2_UNEXPECTED_ERROR" "phase=${CURRENT_PHASE};line=${LINENO};rc=$?"' ERR

write_rollback_script
write_docs

phase transfer_verification
ACTUAL_BUNDLE_SHA256="$(sha256sum "$BUNDLE_COPY" | awk '{print $1}')"
ACTUAL_INSTALLER_SHA256="$(sha256sum "$STEP_FILE" | awk '{print $1}')"
[[ "$ACTUAL_BUNDLE_SHA256" == "$EXPECTED_BUNDLE_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR bundle_sha256_mismatch
[[ "$ACTUAL_INSTALLER_SHA256" == "$EXPECTED_INSTALLER_SHA256" ]] || stop_step STOP_COPY_OR_TRANSFER_ERROR installer_sha256_mismatch
( cd "$BUNDLE_DIR" && sha256sum -c manifest.sha256 ) > "$STEP_DIR/manifest-check.log" 2>&1 || stop_step STOP_COPY_OR_TRANSFER_ERROR manifest_failed
bash -n "$STEP_FILE" || stop_step STOP_COPY_OR_TRANSFER_ERROR installer_syntax
FIXTURE_SUMMARY="$STEP_DIR/local-fixtures.log"
: > "$FIXTURE_SUMMARY"
for fixture in "$BUNDLE_DIR"/tests/test-*.sh; do
    name="$(basename "$fixture")"
    echo ">>> fixture $name" | tee -a "$FIXTURE_SUMMARY"
    bash "$fixture" >> "$FIXTURE_SUMMARY" 2>&1 || stop_step STOP_COPY_OR_TRANSFER_ERROR "fixture:$name"
done
COPY_VERIFY="PASS"; STEP_WAS_NOT_STARTED="false"
cat > "$TRANSFER_VERIFY" <<EOF
EXPECTED_BUNDLE_SHA256=$EXPECTED_BUNDLE_SHA256
ACTUAL_BUNDLE_SHA256=$ACTUAL_BUNDLE_SHA256
EXPECTED_INSTALLER_SHA256=$EXPECTED_INSTALLER_SHA256
ACTUAL_INSTALLER_SHA256=$ACTUAL_INSTALLER_SHA256
MANIFEST_VERIFY=PASS
SYNTAX_VERIFY=PASS
FIXTURE_VERIFY=PASS
COPY_VERIFY=PASS
STEP_SHA256=$ACTUAL_INSTALLER_SHA256
EOF

phase vm130_preflight
for cmd in bash sh awk sed grep find tar gzip sha256sum cmp diff mktemp stat curl python3 tee sort xargs readlink dirname basename cp mv rm chmod mkdir date hostname install ssh git; do command -v "$cmd" >/dev/null 2>&1 || stop_step STOP_PREFLIGHT_REQUIRED_COMMAND_MISSING "missing_command=$cmd"; done
[[ "$(hostname)" == router-ops ]] || stop_step STOP_PREFLIGHT_WRONG_HOST "hostname=$(hostname)"
[[ -x "$PUBLISHER" && "$(sha256sum "$PUBLISHER" | awk '{print $1}')" == "$EXPECTED_PUBLISHER_SHA256" ]] || stop_step STOP_PREFLIGHT_PUBLISHER_MISMATCH publisher
[[ -x "$MACHINE_CLOSE" && "$(sha256sum "$MACHINE_CLOSE" | awk '{print $1}')" == "$EXPECTED_MACHINE_CLOSE_SHA256" ]] || stop_step STOP_PREFLIGHT_MACHINE_CLOSE_MISMATCH wrapper
[[ -r "$MACHINE_CLOSE_LIB" && "$(sha256sum "$MACHINE_CLOSE_LIB" | awk '{print $1}')" == "$EXPECTED_MACHINE_CLOSE_LIB_SHA256" ]] || stop_step STOP_PREFLIGHT_MACHINE_CLOSE_MISMATCH library
[[ -x "$MACHINE_CLOSE_SELFTEST" && "$(sha256sum "$MACHINE_CLOSE_SELFTEST" | awk '{print $1}')" == "$EXPECTED_MACHINE_CLOSE_SELFTEST_SHA256" ]] || stop_step STOP_PREFLIGHT_MACHINE_CLOSE_MISMATCH selftest
[[ -r "$PUBLIC_TOKEN_FILE" ]] || stop_step STOP_PREFLIGHT_PUBLIC_TOKEN_MISSING token
[[ -w "$STATE_DIR" ]] || stop_step STOP_PREFLIGHT_STATE_NOT_WRITABLE "$STATE_DIR"
"$MACHINE_CLOSE_SELFTEST" > "$STEP_DIR/machine-close-selftest.log" 2>&1 || stop_step STOP_PREFLIGHT_MACHINE_CLOSE_SELFTEST_FAILED selftest
ssh -o BatchMode=yes -o ConnectTimeout=20 pve-mgts "ssh -o BatchMode=yes -o ConnectTimeout=20 -i /root/.ssh/pve_to_openwrt_mgts_ed25519 root@10.71.100.2 true" || stop_step STOP_PREFLIGHT_VM101_ACCESS_FAILED nested_ssh

phase target_package_build
PKG_ROOT="$TARGET_PACKAGE_DIR/root"
mkdir -p "$PKG_ROOT/payload/vm101/rootfs/usr/local/sbin" "$PKG_ROOT/scripts/vm101"
cp -a "$BUNDLE_DIR/payload/vm101/rootfs/usr/local/sbin/router-egress-generation-build" "$PKG_ROOT/payload/vm101/rootfs/usr/local/sbin/"
cp -a "$BUNDLE_DIR/scripts/vm101/"*.sh "$PKG_ROOT/scripts/vm101/"
( cd "$PKG_ROOT" && find . -type f -print0 | sort -z | xargs -0 sha256sum ) > "$PKG_ROOT/target-package-manifest.sha256"
TARGET_PACKAGE_TAR="$TARGET_PACKAGE_DIR/r15b2-target-package.tar.gz"
tar -C "$PKG_ROOT" -czf "$TARGET_PACKAGE_TAR" .
gzip -t "$TARGET_PACKAGE_TAR"
ROUND="$TARGET_PACKAGE_DIR/roundtrip"; mkdir -p "$ROUND"; tar -xzf "$TARGET_PACKAGE_TAR" -C "$ROUND"; ( cd "$ROUND" && sha256sum -c target-package-manifest.sha256 ) > "$TARGET_PACKAGE_DIR/roundtrip.log" 2>&1
while IFS= read -r f; do case "$(head -n1 "$f" 2>/dev/null || true)" in '#!/bin/sh') sh -n "$f";; '#!/usr/bin/env bash'|'#!/bin/bash') bash -n "$f";; esac; done < <(find "$ROUND" -type f | sort)

phase target_transfer
vm101_receive_tar "$TARGET_PACKAGE_TAR" || stop_step STOP_R15B2_TARGET_TRANSFER_FAILED ssh_tar
VERIFY_LAUNCHER="$STEP_DIR/remote-verify-package.sh"
cat > "$VERIFY_LAUNCHER" <<'REMOTE'
#!/bin/sh
set -eu
cd /tmp/r15b2-r01
sha256sum -c target-package-manifest.sha256
for f in scripts/vm101/*.sh payload/vm101/rootfs/usr/local/sbin/router-egress-generation-build; do sh -n "$f"; done
echo RESULT=PASS_R15B2_TARGET_TRANSFER_VERIFY
REMOTE
if ! vm101_run_checked "$VERIFY_LAUNCHER" | tee "$TARGET_TRANSFER_LOG"; then stop_step STOP_R15B2_TARGET_TRANSFER_VERIFY_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_TARGET_TRANSFER_VERIFY "$TARGET_TRANSFER_LOG" || stop_step STOP_R15B2_TARGET_TRANSFER_VERIFY_FAILED marker

phase target_preflight
PREFLIGHT_LAUNCHER="$STEP_DIR/remote-preflight.sh"
cat > "$PREFLIGHT_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-preflight.sh $TARGET_BASE
REMOTE
if ! vm101_run_checked "$PREFLIGHT_LAUNCHER" | tee "$TARGET_PREFLIGHT_LOG"; then stop_step STOP_R15B2_TARGET_PREFLIGHT_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_TARGET_PREFLIGHT "$TARGET_PREFLIGHT_LOG" || stop_step STOP_R15B2_TARGET_PREFLIGHT_FAILED marker

phase target_baseline
BASELINE_LAUNCHER="$STEP_DIR/remote-baseline.sh"
cat > "$BASELINE_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-capture-before.sh $TARGET_BASE
REMOTE
if ! vm101_run_checked "$BASELINE_LAUNCHER" | tee "$TARGET_BASELINE_LOG"; then stop_step STOP_R15B2_TARGET_BASELINE_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_BASELINE_CAPTURED "$TARGET_BASELINE_LOG" || stop_step STOP_R15B2_BASELINE_FAILED marker

phase candidate_testing_and_ranking
TEST_LAUNCHER="$STEP_DIR/remote-test-rank.sh"
cat > "$TEST_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-test-and-rank.sh $TARGET_BASE
REMOTE
if ! vm101_run_checked "$TEST_LAUNCHER" | tee "$TARGET_TEST_LOG"; then stop_step STOP_R15B2_CANDIDATE_TEST_OR_RANK_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_CANDIDATE_POOL_READY "$TARGET_TEST_LOG" || stop_step STOP_R15B2_CANDIDATE_TEST_OR_RANK_FAILED marker
CANDIDATE_POOL="$(value_from_log POOL "$TARGET_TEST_LOG")"
SAFE_TEST_OK="$(value_from_log SAFE_TEST_OK "$TARGET_TEST_LOG")"
ELIGIBLE_CANDIDATES="$(value_from_log ELIGIBLE "$TARGET_TEST_LOG")"
TEST_INTERFACE="$(value_from_log TEST_INTERFACE "$TARGET_TEST_LOG")"
NETWORK_RELOAD="$(value_from_log NETWORK_RELOAD "$TARGET_TEST_LOG")"
UCI_CHANGED="$(value_from_log UCI_CHANGED "$TARGET_TEST_LOG")"
ACTIVE_ENDPOINTS_PROBED="$(value_from_log ACTIVE_ENDPOINTS_PROBED "$TARGET_TEST_LOG")"
QUARANTINED_ENDPOINTS_PROBED="$(value_from_log QUARANTINED_ENDPOINTS_PROBED "$TARGET_TEST_LOG")"
[[ "$CANDIDATE_POOL" == "$TARGET_BASE/"* ]] || stop_step STOP_R15B2_CANDIDATE_TEST_OR_RANK_FAILED pool_path
[[ "$SAFE_TEST_OK" =~ ^[0-9]+$ && "$SAFE_TEST_OK" -ge 5 && "$ELIGIBLE_CANDIDATES" =~ ^[0-9]+$ && "$ELIGIBLE_CANDIDATES" -ge 5 ]] || stop_step STOP_R15B2_CANDIDATE_TEST_OR_RANK_FAILED counts
[[ "$NETWORK_RELOAD" == false && "$UCI_CHANGED" == false && "$ACTIVE_ENDPOINTS_PROBED" == false && "$QUARANTINED_ENDPOINTS_PROBED" == false && "$TEST_INTERFACE" == r15b2t ]] || stop_step STOP_R15B2_SAFE_TEST_CONTRACT_FAILED contract

phase target_core
APPLY_LAUNCHER="$STEP_DIR/remote-apply.sh"
cat > "$APPLY_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-apply.sh $TARGET_TRANSFER_ROOT $TARGET_BASE $CANDIDATE_POOL
REMOTE
if ! vm101_run_checked "$APPLY_LAUNCHER" | tee "$TARGET_APPLY_LOG"; then stop_step STOP_R15B2_TARGET_CORE_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_TARGET_CORE "$TARGET_APPLY_LOG" || stop_step STOP_R15B2_TARGET_CORE_FAILED marker
CORE_CHANGE_COMPLETE="true"; TARGET_CORE_RESULT="PASS_R15B2_TARGET_CORE"

phase target_postcheck
POSTCHECK_LAUNCHER="$STEP_DIR/remote-postcheck.sh"
cat > "$POSTCHECK_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-postcheck.sh $TARGET_BASE
REMOTE
if ! vm101_run_checked "$POSTCHECK_LAUNCHER" | tee "$TARGET_POSTCHECK_LOG"; then stop_step STOP_R15B2_TARGET_POSTCHECK_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_TARGET_POSTCHECK "$TARGET_POSTCHECK_LOG" || stop_step STOP_R15B2_TARGET_POSTCHECK_FAILED marker
ENDPOINTS_UNCHANGED="PASS"; TABLES_201_205="PASS"; STAGING_GENERATION_COUNT="1"; GENERATION_VALIDATOR="PASS"; RECOVERY_SERVICES="PASS"

phase target_artifact_export
EXPORT_LAUNCHER="$STEP_DIR/remote-export.sh"
cat > "$EXPORT_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-export.sh $TARGET_BASE
REMOTE
vm101_run_checked "$EXPORT_LAUNCHER" > "$TARGET_ARTIFACT_TAR" || stop_step STOP_R15B2_TARGET_ARTIFACT_EXPORT_FAILED remote_rc
gzip -t "$TARGET_ARTIFACT_TAR"
tar -xzf "$TARGET_ARTIFACT_TAR" -C "$TARGET_ARTIFACTS"
[[ -s "$TARGET_ARTIFACTS/facts.json" && -s "$TARGET_ARTIFACTS/verify.txt" && -s "$TARGET_ARTIFACTS/rollback-target.sh" ]] || stop_step STOP_R15B2_TARGET_ARTIFACTS_INCOMPLETE required
python3 -m json.tool "$TARGET_ARTIFACTS/facts.json" >/dev/null
BUILDER_SHA256="$(value_from_log BUILDER_SHA256 "$TARGET_ARTIFACTS/verify.txt")"
GENERATION_ID="$(value_from_log GENERATION_ID "$TARGET_ARTIFACTS/verify.txt")"
GENERATION_DIR="$(value_from_log GENERATION_DIR "$TARGET_ARTIFACTS/verify.txt")"
[[ "$BUILDER_SHA256" =~ ^[0-9a-f]{64}$ && -n "$GENERATION_ID" && -n "$GENERATION_DIR" ]] || stop_step STOP_R15B2_TARGET_ARTIFACT_PARSE_FAILED values

phase rollback_preparation
for f in "$STATE_DIR/current-vm101-model.env" "$STATE_DIR/current-vm101-methods.env" "$STATE_DIR/current-vm101-local-plan.env"; do [[ ! -f "$f" ]] || cp -a "$f" "$STATE_BACKUP/$(basename "$f")"; done
TOKEN="$(cat "$PUBLIC_TOKEN_FILE")"
LATEST_LOCAL="$BASE/public/r/$TOKEN/latest"
OLD_LOCAL="$BASE/public/r/$TOKEN/old"
write_rollback_script

phase machine_git_and_source_publication
if ! "$MACHINE_CLOSE" vm101 "$STEP_ID: generation builder staging only" | tee "$MACHINE_CLOSE_LOG"; then stop_step STOP_R15B2_MACHINE_CLOSE_FAILED outer_rc; fi
grep -qx RESULT=PASS_ROUTER_MACHINE_CLOSE "$MACHINE_CLOSE_LOG" || stop_step STOP_R15B2_MACHINE_CLOSE_FAILED marker
VM101_GIT_COMMIT="$(value_from_log COMMIT "$MACHINE_CLOSE_LOG")"
VM101_GIT_TREE="$(value_from_log TREE "$MACHINE_CLOSE_LOG")"
VM101_GIT_FILE_COUNT="$(value_from_log GIT_FILE_COUNT "$MACHINE_CLOSE_LOG")"
VM101_PUBLIC_URL="$(value_from_log VM101_PUBLIC_URL "$MACHINE_CLOSE_LOG")"
[[ "$VM101_GIT_COMMIT" =~ ^[0-9a-f]{40}$ && "$VM101_GIT_TREE" =~ ^[0-9a-f]{40}$ ]] || stop_step STOP_R15B2_MACHINE_CLOSE_PARSE_FAILED commit_tree
[[ "$VM101_GIT_COMMIT" != "$EXPECTED_VM101_COMMIT_BEFORE" ]] || stop_step STOP_R15B2_MACHINE_GIT_COMMIT_UNCHANGED commit
[[ "$VM101_GIT_FILE_COUNT" == "$EXPECTED_VM101_FILE_COUNT_AFTER" ]] || stop_step STOP_R15B2_MACHINE_GIT_FILE_COUNT "actual=$VM101_GIT_FILE_COUNT"
[[ "$VM101_PUBLIC_URL" == https://reports.secret-studio.ru/latest/*/ ]] || stop_step STOP_R15B2_MACHINE_SOURCE_URL_INVALID url
MACHINE_GIT_COMPLETE="true"; SOURCE_PUBLICATION_COMPLETE="true"

phase post_machine_git
POST_GIT_LAUNCHER="$STEP_DIR/remote-post-git.sh"
cat > "$POST_GIT_LAUNCHER" <<REMOTE
#!/bin/sh
set -eu
exec /bin/sh $TARGET_TRANSFER_ROOT/scripts/vm101/vm101-post-machine-git.sh $BUILDER_SHA256
REMOTE
if ! vm101_run_checked "$POST_GIT_LAUNCHER" | tee "$POST_GIT_LOG"; then stop_step STOP_R15B2_POST_MACHINE_GIT_FAILED remote_rc; fi
grep -qx RESULT=PASS_R15B2_POST_MACHINE_GIT "$POST_GIT_LOG" || stop_step STOP_R15B2_POST_MACHINE_GIT_FAILED marker
[[ "$(value_from_log VM101_GIT_COMMIT "$POST_GIT_LOG")" == "$VM101_GIT_COMMIT" ]] || stop_step STOP_R15B2_POST_MACHINE_GIT_COMMIT_MISMATCH commit

phase vm130_exact_clone
rm -rf "$EXACT_CLONE_DIR"
git clone --quiet https://github.com/DChuvelev/wg-paid-vm101.git "$EXACT_CLONE_DIR" || stop_step STOP_R15B2_EXACT_CLONE_FAILED clone
git -C "$EXACT_CLONE_DIR" checkout --quiet --detach "$VM101_GIT_COMMIT" || stop_step STOP_R15B2_EXACT_CLONE_FAILED checkout
CLONE_COMMIT="$(git -C "$EXACT_CLONE_DIR" rev-parse HEAD)"
CLONE_TREE="$(git -C "$EXACT_CLONE_DIR" rev-parse 'HEAD^{tree}')"
CLONE_COUNT="$(git -C "$EXACT_CLONE_DIR" ls-tree -r --name-only HEAD | wc -l | tr -d ' ')"
CLONE_STATUS="$(git -C "$EXACT_CLONE_DIR" status --porcelain=v1 --untracked-files=no)"
[[ "$CLONE_COMMIT" == "$VM101_GIT_COMMIT" && "$CLONE_TREE" == "$VM101_GIT_TREE" && "$CLONE_COUNT" == "$EXPECTED_VM101_FILE_COUNT_AFTER" && -z "$CLONE_STATUS" ]] || stop_step STOP_R15B2_EXACT_CLONE_VERIFY_FAILED "commit=$CLONE_COMMIT;tree=$CLONE_TREE;count=$CLONE_COUNT"
cat > "$STEP_DIR/vm130-exact-clone-proof.txt" <<EOF
RESULT=PASS_R15B2_VM130_EXACT_CLONE
VM101_GIT_COMMIT=$CLONE_COMMIT
VM101_GIT_TREE=$CLONE_TREE
VM101_GIT_FILE_COUNT=$CLONE_COUNT
VM130_EXACT_CLONE_STATUS=CLEAN
EOF
VM130_EXACT_CLONE_STATUS="CLEAN"

phase model_build
MODEL_ROOTFS="$MODEL_BUILD/rootfs"
mkdir -p "$MODEL_ROOTFS"
git -C "$EXACT_CLONE_DIR" archive --format=tar "$VM101_GIT_COMMIT" | tar -xf - -C "$MODEL_ROOTFS"
( cd "$MODEL_ROOTFS" && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum ) > "$MODEL_BUILD/managed-rootfs-manifest.sha256"
MODEL_ARCHIVE="$MODEL_BUILD/vm101-managed-rootfs-r15b2.tar.gz"
tar -C "$MODEL_BUILD" -czf "$MODEL_ARCHIVE" rootfs
gzip -t "$MODEL_ARCHIVE"
MODEL_ROUNDTRIP="$STEP_DIR/model-roundtrip"
rm -rf "$MODEL_ROUNDTRIP"; mkdir -p "$MODEL_ROUNDTRIP"
tar -xzf "$MODEL_ARCHIVE" -C "$MODEL_ROUNDTRIP"
( cd "$MODEL_ROUNDTRIP/rootfs" && sha256sum -c "$MODEL_BUILD/managed-rootfs-manifest.sha256" ) > "$STEP_DIR/model-roundtrip-verify.txt" 2>&1
MODEL_ARCHIVE_SHA256="$(sha256sum "$MODEL_ARCHIVE" | awk '{print $1}')"
rm -rf "$MODEL_ROUNDTRIP" "$MODEL_ROOTFS"
cp -a "$STEP_DIR/model-roundtrip-verify.txt" "$MODEL_BUILD/model-roundtrip-verify.txt"
cp -a "$STEP_DIR/vm130-exact-clone-proof.txt" "$MODEL_BUILD/vm130-exact-clone-proof.txt"
cp -a "$TARGET_ARTIFACTS/facts.json" "$MODEL_BUILD/target-facts.json"
cp -a "$TARGET_ARTIFACTS/verify.txt" "$MODEL_BUILD/target-verify.txt"
cp -a "$TARGET_ARTIFACTS/endpoints-after.tsv" "$MODEL_BUILD/live-endpoints.tsv"
cp -a "$TARGET_ARTIFACTS/routes-after.txt" "$MODEL_BUILD/tables-201-205.txt"
cp -a "$TARGET_ARTIFACTS/generation-metadata.kv" "$MODEL_BUILD/generation-metadata.kv"
cp -a "$TARGET_ARTIFACTS/generation-candidates.tsv" "$MODEL_BUILD/generation-candidates.tsv"
[[ ! -f "$TARGET_ARTIFACTS/safe-test-results.tsv" ]] || cp -a "$TARGET_ARTIFACTS/safe-test-results.tsv" "$MODEL_BUILD/safe-test-results.tsv"
[[ ! -f "$TARGET_ARTIFACTS/isolated-candidate-pool.tsv" ]] || cp -a "$TARGET_ARTIFACTS/isolated-candidate-pool.tsv" "$MODEL_BUILD/isolated-candidate-pool.tsv"
cp -a "$MACHINE_CLOSE_LOG" "$MODEL_BUILD/router-machine-close.log"
cp -a "$POST_GIT_LOG" "$MODEL_BUILD/vm101-post-machine-git.txt"
cat > "$MODEL_BUILD/vm101-model.md" <<EOF
# VM101 current model — R15B2 generation builder

- Machine Git commit: \`$VM101_GIT_COMMIT\`
- Tree: \`$VM101_GIT_TREE\`
- Managed file count: $VM101_GIT_FILE_COUNT
- VM130 exact HTTPS clone: CLEAN
- Full managed rootfs archive: \`vm101-managed-rootfs-r15b2.tar.gz\`
- Full archive SHA256: \`$MODEL_ARCHIVE_SHA256\`
- Builder: \`/usr/local/sbin/router-egress-generation-build\`
- Builder SHA256: \`$BUILDER_SHA256\`
- Staged generation: \`$GENERATION_ID\`
- Staging generation count: 1
- Validator: PASS
- Active generation: absent
- vpn1..vpn5 endpoints: unchanged
- Tables 201..205: unchanged
- Recovery services: running
- Service restarts: none
- Traffic interruption: false
- Safe-tested candidate count: $SAFE_TEST_OK
- Eligible after active/quarantine exclusion: $ELIGIBLE_CANDIDATES
- Active endpoints probed: $ACTIVE_ENDPOINTS_PROBED
- Quarantined endpoints probed: $QUARANTINED_ENDPOINTS_PROBED
- Test interface: `$TEST_INTERFACE`
- Network reload: $NETWORK_RELOAD
- UCI changed: $UCI_CHANGED
- Runtime impact: false

The staged generation contains exactly five unique freshly tested endpoints, excludes active and quarantined endpoints, and conforms to router-egress-generation-v1. It is not activated.

Candidate testing used a dedicated temporary AmneziaWG interface and two temporary probe host routes. No UCI write, network reload, production interface change, service restart or production table modification was performed.
EOF

phase model_publication
find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_model_*' -printf '%f\n' | LC_ALL=C sort | sed -n '1p' > "$STEP_DIR/previous-model-folder.txt"
mapfile -d '' MODEL_FILES < <(find "$MODEL_BUILD" -type f -print0 | sort -z)
"$PUBLISHER" "$MODEL_LABEL" "${MODEL_FILES[@]}" > "$STEP_DIR/publish-model.log" 2>&1 || stop_step STOP_R15B2_MODEL_PUBLICATION_FAILED publisher
MODEL_PUBLISHED_DIR="$(value_from_log PUBLISHED_DIR "$STEP_DIR/publish-model.log")"
VM101_MODEL_URL="$(value_from_log VM101_MODEL_URL "$STEP_DIR/publish-model.log")"
[[ -n "$VM101_MODEL_URL" ]] || VM101_MODEL_URL="$(public_url_from_dir "$MODEL_PUBLISHED_DIR")"
[[ -d "$MODEL_PUBLISHED_DIR" && -n "$VM101_MODEL_URL" && "$(value_from_log PUBLIC_OBJECT_KIND "$STEP_DIR/publish-model.log")" == vm101-model ]] || stop_step STOP_R15B2_MODEL_PUBLICATION_CONTRACT contract
basename "$MODEL_PUBLISHED_DIR" > "$STEP_DIR/new-model-folder.txt"
MODEL_PUBLICATION_COMPLETE="true"

phase methods_build
cp -a "$BUNDLE_DIR/reference/VM101_ENVIRONMENT.md" "$METHODS_BUILD/vm101-environment.md"
cp -a "$TARGET_ARTIFACTS/validator-postcheck.txt" "$METHODS_BUILD/validator-postcheck.txt"
[[ ! -f "$TARGET_ARTIFACTS/safe-test-results.tsv" ]] || cp -a "$TARGET_ARTIFACTS/safe-test-results.tsv" "$METHODS_BUILD/safe-test-results.tsv"
[[ ! -f "$TARGET_ARTIFACTS/isolated-candidate-pool.tsv" ]] || cp -a "$TARGET_ARTIFACTS/isolated-candidate-pool.tsv" "$METHODS_BUILD/isolated-candidate-pool.tsv"
cp -a "$TARGET_TRANSFER_LOG" "$METHODS_BUILD/transport-verify.txt"
cat > "$METHODS_BUILD/vm101-methods.md" <<EOF
# VM101 methods — generation builder R15B2

## Build command

\`router-egress-generation-build --source-pool <isolated-validated-candidate-pool.tsv> --generation-id ID\`

The builder is POSIX shell, verifies every required command, reads the R15B1 schema, captures active endpoints and unified quarantine, copies five selected configs into a private staging directory, creates hashes and calls the strict validator before atomic staging commit.

## Candidate testing

The old HMN test loader is not used because it writes UCI and reloads the OpenWrt network. R15B2 excludes active, quarantined and duplicate endpoints before probing. It then creates the dedicated interface `r15b2t` directly with `ip link add ... type amneziawg`, loads each remaining private config with `amneziawg setconf`, adds only two temporary probe host routes, checks zero-loss probes plus handshake/transfer, and deletes the interface after every candidate through a trap. Production vpn1..vpn5, UCI, services and tables 201..205 remain untouched.

## Access and transport

VM130 -> pve-mgts -> VM101, standalone scripts through \`sh -s\`. Binary files use tar/gzip streams. Direct VM130 -> VM101 SSH is unsupported.

## Limits

R15B2 does not activate generations. It does not change vpn1..vpn5, routes, services or repair counters. Activation remains a separate R16 transactional STEP.
EOF

phase methods_publication
find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_methods_*' -printf '%f\n' | LC_ALL=C sort | sed -n '1p' > "$STEP_DIR/previous-methods-folder.txt"
mapfile -d '' METHODS_FILES < <(find "$METHODS_BUILD" -type f -print0 | sort -z)
"$PUBLISHER" "$METHODS_LABEL" "${METHODS_FILES[@]}" > "$STEP_DIR/publish-methods.log" 2>&1 || stop_step STOP_R15B2_METHODS_PUBLICATION_FAILED publisher
METHODS_PUBLISHED_DIR="$(value_from_log PUBLISHED_DIR "$STEP_DIR/publish-methods.log")"
VM101_METHODS_URL="$(value_from_log VM101_METHODS_URL "$STEP_DIR/publish-methods.log")"
[[ -n "$VM101_METHODS_URL" ]] || VM101_METHODS_URL="$(public_url_from_dir "$METHODS_PUBLISHED_DIR")"
[[ -d "$METHODS_PUBLISHED_DIR" && -n "$VM101_METHODS_URL" && "$(value_from_log PUBLIC_OBJECT_KIND "$STEP_DIR/publish-methods.log")" == vm101-methods ]] || stop_step STOP_R15B2_METHODS_PUBLICATION_CONTRACT contract
basename "$METHODS_PUBLISHED_DIR" > "$STEP_DIR/new-methods-folder.txt"
METHODS_PUBLICATION_COMPLETE="true"

phase plan_build
cat > "$PLAN_BUILD/local-architecture-plan.md" <<EOF
# VM101 autonomous HMN recovery — local M07 plan after R15B2

## Proven state

R15B1 installed router-egress-generation-v1 schema and strict read-only validator. R15C corrected router-machine-close result propagation. R15B2 installed the generation builder and created one validated staging-only generation with five unique freshly tested candidates. Candidate testing is isolated on a temporary direct AmneziaWG interface; active and quarantined endpoints are excluded before probing, and the legacy UCI/network-reload loader is not used.

## Fixed mapping

- egress1 -> vpn1 -> table 201 -> mark 0x201 -> DSCP cs4
- egress2 -> vpn2 -> table 202 -> mark 0x202 -> DSCP cs5
- egress3 -> vpn3 -> table 203 -> mark 0x203 -> DSCP cs1
- egress4 -> vpn4 -> table 204 -> mark 0x204 -> DSCP cs2
- egress5 -> vpn5 -> table 205 -> mark 0x205 -> DSCP cs3

## Current invariants

- Active generation is absent.
- Exactly one staging generation exists: $GENERATION_ID.
- Production endpoints and tables are unchanged.
- Quarantined endpoints cannot be selected.
- Partial activation is forbidden.
- Repair counter reset is forbidden until complete activation proof.
- Direct failopen remains forbidden without a separate explicit STEP.

## Next stages

### R16 — transactional full refresh
Build and validate five slots, apply all-or-nothing, create active symlink only after all prechecks, and rollback the entire generation on any failure.

### R17 — degraded and consolidation
Handle insufficient fresh pool, unified quarantine, daily counters and emergency threshold without implicit direct routing.

### R18 — gated bootstrap/direct
Add bootstrap and direct paths only behind explicit safety gates and approval.

### Final destructive proof
Cold bootstrap, complete activation, forced rollback, five-slot checks, quarantine and counter checks, and proof of no partial state.
EOF
cp -a "$TARGET_ARTIFACTS/generation-candidates.tsv" "$PLAN_BUILD/current-staged-candidates.tsv"

phase plan_publication
find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_local_architecture_plan_vm101_autonomous_hmn_recovery*' -printf '%f\n' | LC_ALL=C sort | sed -n '1p' > "$STEP_DIR/previous-plan-folder.txt"
mapfile -d '' PLAN_FILES < <(find "$PLAN_BUILD" -type f -print0 | sort -z)
"$PUBLISHER" "$PLAN_LABEL" "${PLAN_FILES[@]}" > "$STEP_DIR/publish-plan.log" 2>&1 || stop_step STOP_R15B2_PLAN_PUBLICATION_FAILED publisher
PLAN_PUBLISHED_DIR="$(value_from_log PUBLISHED_DIR "$STEP_DIR/publish-plan.log")"
LOCAL_M07_PLAN_URL="$(value_from_log LOCAL_M07_PLAN_URL "$STEP_DIR/publish-plan.log")"
[[ -n "$LOCAL_M07_PLAN_URL" ]] || LOCAL_M07_PLAN_URL="$(public_url_from_dir "$PLAN_PUBLISHED_DIR")"
[[ -d "$PLAN_PUBLISHED_DIR" && -n "$LOCAL_M07_PLAN_URL" && "$(value_from_log PUBLIC_OBJECT_KIND "$STEP_DIR/publish-plan.log")" == local-m07-plan ]] || stop_step STOP_R15B2_PLAN_PUBLICATION_CONTRACT contract
basename "$PLAN_PUBLISHED_DIR" > "$STEP_DIR/new-plan-folder.txt"
PLAN_PUBLICATION_COMPLETE="true"

phase current_state_update
cat > "$STEP_DIR/current-vm101-model.env" <<EOF
UPDATED_AT_UTC=$TS
SOURCE_STEP=$STEP_ID
VM101_MODEL_URL=$VM101_MODEL_URL
VM101_MODEL_PUBLIC=$MODEL_PUBLISHED_DIR
EOF
cat > "$STEP_DIR/current-vm101-methods.env" <<EOF
UPDATED_AT_UTC=$TS
SOURCE_STEP=$STEP_ID
VM101_METHODS_URL=$VM101_METHODS_URL
VM101_METHODS_PUBLIC=$METHODS_PUBLISHED_DIR
EOF
cat > "$STEP_DIR/current-vm101-local-plan.env" <<EOF
UPDATED_AT_UTC=$TS
SOURCE_STEP=$STEP_ID
ARCHITECTURE_PLAN=$LOCAL_M07_PLAN_URL
LOCAL_M07_PLAN_URL=$LOCAL_M07_PLAN_URL
ACCESS_MAP=$ACCESS_MAP_URL
GLOBAL_PROJECT_PLAN=$GLOBAL_PROJECT_PLAN_URL
EOF
write_atomic "$STATE_DIR/current-vm101-model.env" "$STEP_DIR/current-vm101-model.env"
write_atomic "$STATE_DIR/current-vm101-methods.env" "$STEP_DIR/current-vm101-methods.env"
write_atomic "$STATE_DIR/current-vm101-local-plan.env" "$STEP_DIR/current-vm101-local-plan.env"

phase step_report_preparation
RESULT="PASS_R15B2_GENERATION_BUILDER_STAGED"; STOP_REASON="none"
write_docs; write_facts; write_verify; write_report
rm -rf "$SAFE_PUBLISH"; mkdir -p "$SAFE_PUBLISH"
for f in "$STEP_FILE" "$REPORT" "$FACTS" "$VERIFY" "$ROLLBACK" "$MAIN_LOG" "$PROGRESS_LOG" "$TRANSFER_VERIFY" "$SYSTEM_MODEL" "$METHODS_LIMITATIONS" "$ACCESS_ROUTES" "$PHASES" "$HTTP_LOG" "$TARGET_PREFLIGHT_LOG" "$TARGET_TEST_LOG" "$TARGET_BASELINE_LOG" "$TARGET_APPLY_LOG" "$TARGET_POSTCHECK_LOG" "$TARGET_TRANSFER_LOG" "$MACHINE_CLOSE_LOG" "$POST_GIT_LOG" "$BUNDLE_DIR/release-info.json"; do [[ -f "$f" ]] && cp -a "$f" "$SAFE_PUBLISH/$(basename "$f")"; done
for f in "$TARGET_ARTIFACTS"/*; do [[ -f "$f" ]] && cp -a "$f" "$SAFE_PUBLISH/target-$(basename "$f")"; done

phase step_report_publication
mapfile -d '' STEP_FILES < <(find "$SAFE_PUBLISH" -type f -print0 | sort -z)
"$PUBLISHER" "$STEP_LABEL" "${STEP_FILES[@]}" > "$STEP_PUBLISH_LOG" 2>&1 || stop_step STOP_R15B2_STEP_REPORT_PUBLICATION_FAILED publisher
STEP_PUBLISHED_DIR="$(value_from_log PUBLISHED_DIR "$STEP_PUBLISH_LOG")"
STEP_REPORT_URL="$(value_from_log STEP_REPORT_URL "$STEP_PUBLISH_LOG")"
[[ -n "$STEP_REPORT_URL" ]] || STEP_REPORT_URL="$(public_url_from_dir "$STEP_PUBLISHED_DIR")"
PUBLIC_OBJECT_KIND="$(value_from_log PUBLIC_OBJECT_KIND "$STEP_PUBLISH_LOG")"
PREVIOUS_MOVED_COUNT="$(value_from_log PREVIOUS_MOVED_COUNT "$STEP_PUBLISH_LOG")"
[[ -d "$STEP_PUBLISHED_DIR" && "$PUBLIC_OBJECT_KIND" == step-report && -n "$STEP_REPORT_URL" ]] || stop_step STOP_R15B2_STEP_REPORT_PUBLICATION_CONTRACT contract
FACTS_URL="${STEP_REPORT_URL}facts.json"; VERIFY_URL="${STEP_REPORT_URL}verify.txt"; ROLLBACK_URL="${STEP_REPORT_URL}rollback.sh"
STEP_REPORT_PUBLICATION_COMPLETE="true"

phase external_http_verification
check_url() {
    local name="$1" url="$2" out="$3" expected_sha="${4:-}"
    local code
    code="$(curl --resolve reports.secret-studio.ru:443:10.71.100.84 -sS -o "$out" -w '%{http_code}' "$url")"
    echo "$name=$code" >> "$HTTP_LOG"
    [[ "$code" == 200 ]]
    [[ -z "$expected_sha" || "$(sha256sum "$out" | awk '{print $1}')" == "$expected_sha" ]]
}
ROOT_CODE="$(curl --resolve reports.secret-studio.ru:443:10.71.100.84 -sS -o /dev/null -w '%{http_code}' https://reports.secret-studio.ru/)"
echo "ROOT_CODE=$ROOT_CODE" > "$HTTP_LOG"
[[ "$ROOT_CODE" == 200 || "$ROOT_CODE" == 302 ]]
check_url LATEST "$LATEST_ROOT_URL" "$STEP_DIR/http-latest.html"
check_url OLD "$OLD_ROOT_URL" "$STEP_DIR/http-old.html"
check_url VM101_SOURCE "$VM101_PUBLIC_URL" "$STEP_DIR/http-vm101-source.html"
check_url MODEL "$VM101_MODEL_URL" "$STEP_DIR/http-model.html"
check_url MODEL_ARCHIVE "${VM101_MODEL_URL}vm101-managed-rootfs-r15b2.tar.gz" "$STEP_DIR/http-model-rootfs.tar.gz" "$MODEL_ARCHIVE_SHA256"
check_url METHODS "$VM101_METHODS_URL" "$STEP_DIR/http-methods.html"
check_url PLAN "$LOCAL_M07_PLAN_URL" "$STEP_DIR/http-plan.html"
check_url STEP "$STEP_REPORT_URL" "$STEP_DIR/http-step.html"
check_url STEP_CODE "${STEP_REPORT_URL}step-code.sh" "$STEP_DIR/http-step-code.sh" "$EXPECTED_INSTALLER_SHA256"
check_url FACTS "$FACTS_URL" "$STEP_DIR/http-facts.json"
check_url VERIFY "$VERIFY_URL" "$STEP_DIR/http-verify.txt"
check_url ROLLBACK "$ROLLBACK_URL" "$STEP_DIR/http-rollback.sh"
[[ "$(find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_git_source_*' -printf '.' | wc -c)" -eq 1 ]]
[[ "$(find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_model_*' -printf '.' | wc -c)" -eq 1 ]]
[[ "$(find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_vm101_methods_*' -printf '.' | wc -c)" -eq 1 ]]
[[ "$(find "$LATEST_LOCAL" -mindepth 1 -maxdepth 1 -type d -name '*_local_architecture_plan_vm101_autonomous_hmn_recovery*' -printf '.' | wc -c)" -eq 1 ]]
HTTP_VERIFY_PASS="true"

phase final_artifact_sync
RESULT="PASS_R15B2_GENERATION_BUILDER_STAGED"
phase complete
write_docs; write_facts; write_verify; write_report
cp -a "$REPORT" "$FACTS" "$VERIFY" "$HTTP_LOG" "$SYSTEM_MODEL" "$METHODS_LIMITATIONS" "$ACCESS_ROUTES" "$STEP_PUBLISHED_DIR/"
chmod 644 "$STEP_PUBLISHED_DIR/report.txt" "$STEP_PUBLISHED_DIR/facts.json" "$STEP_PUBLISHED_DIR/verify.txt" "$STEP_PUBLISHED_DIR/http-verify.log" "$STEP_PUBLISHED_DIR/system-model.txt" "$STEP_PUBLISHED_DIR/methods-limitations.txt" "$STEP_PUBLISHED_DIR/access-routes.txt"
check_url FINAL_REPORT "$STEP_REPORT_URL" "$STEP_DIR/http-final-report.html"
check_url FINAL_FACTS "$FACTS_URL" "$STEP_DIR/http-final-facts.json" "$(sha256sum "$FACTS" | awk '{print $1}')"
check_url FINAL_VERIFY "$VERIFY_URL" "$STEP_DIR/http-final-verify.txt" "$(sha256sum "$VERIFY" | awk '{print $1}')"
cp -a "$HTTP_LOG" "$STEP_PUBLISHED_DIR/http-verify.log"
chmod 644 "$STEP_PUBLISHED_DIR/http-verify.log"
print_final
