#!/usr/bin/env bash

set -Eeuo pipefail
umask 077
export LANG=C

EXPECTED_HOSTNAME="sqjcnginx01"
EXPECTED_IPV4="10.2.203.102"
EXPECTED_SOURCE_COMMIT="47584918681b8aad36fee3c9eade5cd919c702ab"
EXPECTED_FRAMEWORK_SHA256="f74941f6328c752eeefb5fe082c4366dfed5c91ecad36d31ba7e11b7d2cac2d5"
EXPECTED_RPM_MANIFEST_SHA256="0ef1b62af801d782e8331b608b4b0580e0721a7553e913ab17686d4dc32dee3d"
EXPECTED_ROLLBACK_SHA256="7febaeed2bd23dbc98985bd8d03709a2a69f473a49c86a3372b8759c5297d1dc"
BUNDLE_ROOT="/opt/zhct-deploy"
RUN_ID=${1:-}
EVIDENCE_DIR="/var/backups/zhct-q041/evidence/${RUN_ID}"
Q040_BACKUP="/var/backups/zhct-q040/20260722T203557+0800"
FINAL_EVIDENCE_INDEX="${EVIDENCE_DIR}/q041-final-evidence-index.sha256"
FINAL_STATE_INDEX="${EVIDENCE_DIR}/q041-final-state-index.sha256"
SENSITIVE_SCAN="${EVIDENCE_DIR}/q041-sensitive-scan.txt"
VIP_RE='10\.2\.203\.(115|116|117)'
ALL_PORTS='8081|8443|1883|8083|3306|3308|6379|6387|26379|26387|3000|10008|18081|18443|26390|26391'
MIN_FREE_BYTES=$((9 * 1024 * 1024 * 1024))
UNITS=(nginx.service keepalived.service haproxy.service orchestrator.service sentinel5.service sentinel7.service)

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "${RUN_ID}" =~ ^q041-[0-9]{8}T[0-9]{6}[+]0800$ ]] || die "invalid Q041 run id"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname mismatch"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 | \
  grep -Fxq "${EXPECTED_IPV4}" || die "expected IPv4 is not configured"

[[ "$(tr -d '\r\n' <"${BUNDLE_ROOT}/.source-commit")" == "${EXPECTED_SOURCE_COMMIT}" ]] || \
  die "framework source commit mismatch"
[[ "$(tr -d '\r\n' <"${BUNDLE_ROOT}/.framework-bundle-sha256")" == "${EXPECTED_FRAMEWORK_SHA256}" ]] || \
  die "framework bundle marker mismatch"
[[ "$(sha256sum "${BUNDLE_ROOT}/packages/rpms.sha256" | awk '{print $1}')" == "${EXPECTED_RPM_MANIFEST_SHA256}" ]] || \
  die "RPM manifest SHA256 mismatch"

required_evidence=(
  preinstall-check.log
  nginx-packages-sha256-check.txt
  rpms-sha256-check.txt
  path-state-before.tsv
  account-state-before.tsv
  dnf-history-before.txt
  rpm-list-before.txt
  target-listeners-before.txt
  target-processes-before.txt
  target-units-before.txt
  ipv4-before.txt
  disk-before.txt
  orchestrator-rpm-file-list.txt
  sysctl-state-before.tsv
  dnf-install-assumeno.log
  role-install-dry-run.log
  preinstall-result.env
  install-started.marker
  install-role.log
  dnf-history-id-before.txt
  dnf-history-id-after.txt
  dnf-history-install.txt
  rpm-list-after-install.txt
  unit-files-after-install.txt
  install-result.env
  rpm-nevra-verify.tsv
  orchestrator-rpms.txt
  orchestrator-version.txt
  postinstall-static-result.env
  nginx-temp-config-test.txt
  haproxy-temp-config-test.txt
  keepalived-temp-config-test.txt
  orchestrator-self-check.txt
  nginx-temp-runtime.log
  haproxy-temp-runtime.log
  sentinel5-temp-runtime.log
  sentinel7-temp-runtime.log
  temp-health-result.env
)
for evidence in "${required_evidence[@]}"; do
  [[ -e "${EVIDENCE_DIR}/${evidence}" ]] || die "required evidence is missing: ${evidence}"
done
for result_file in preinstall-result.env install-result.env \
  postinstall-static-result.env temp-health-result.env; do
  [[ -s "${EVIDENCE_DIR}/${result_file}" ]] || die "required result is empty: ${result_file}"
done
required_nonempty=(
  preinstall-check.log
  nginx-packages-sha256-check.txt
  rpms-sha256-check.txt
  path-state-before.tsv
  account-state-before.tsv
  dnf-history-before.txt
  rpm-list-before.txt
  target-listeners-before.txt
  target-processes-before.txt
  target-units-before.txt
  ipv4-before.txt
  disk-before.txt
  orchestrator-rpm-file-list.txt
  sysctl-state-before.tsv
  dnf-install-assumeno.log
  role-install-dry-run.log
  install-role.log
  dnf-history-id-before.txt
  dnf-history-id-after.txt
  dnf-history-install.txt
  rpm-list-after-install.txt
  unit-files-after-install.txt
  rpm-nevra-verify.tsv
  orchestrator-rpms.txt
  orchestrator-version.txt
  postinstall-static-result.env
  nginx-temp-config-test.txt
  haproxy-temp-config-test.txt
  orchestrator-self-check.txt
  sentinel5-temp-runtime.log
  sentinel7-temp-runtime.log
  temp-health-result.env
)
for evidence in "${required_nonempty[@]}"; do
  [[ -s "${EVIDENCE_DIR}/${evidence}" ]] || die "required evidence is empty: ${evidence}"
done

grep -Fq 'preinstall_check=PASS' "${EVIDENCE_DIR}/preinstall-result.env" || \
  die "preinstall gate is not PASS"
grep -Fq 'install_execution=PASS' "${EVIDENCE_DIR}/install-result.env" || \
  die "install execution is not PASS"
grep -Fq 'postinstall_static_check=PASS' "${EVIDENCE_DIR}/postinstall-static-result.env" || \
  die "postinstall static gate is not PASS"
grep -Fq 'temporary_health_check=PASS' "${EVIDENCE_DIR}/temp-health-result.env" || \
  die "temporary health gate is not PASS"

dnf_history_id_before=$(tr -d '[:space:]' <"${EVIDENCE_DIR}/dnf-history-id-before.txt")
dnf_history_id_after=$(tr -d '[:space:]' <"${EVIDENCE_DIR}/dnf-history-id-after.txt")
[[ "${dnf_history_id_before}" =~ ^[0-9]+$ && "${dnf_history_id_after}" =~ ^[0-9]+$ ]] || \
  die "invalid DNF history boundary"
((dnf_history_id_after > dnf_history_id_before)) || die "DNF history did not advance"
grep -Fq '/opt/zhct-deploy/packages/rpms/' "${EVIDENCE_DIR}/dnf-history-install.txt" || \
  die "DNF transaction is not the Q041 local RPM transaction"
for package in percona-orchestrator percona-orchestrator-cli percona-orchestrator-client; do
  grep -Fq "${package}" "${EVIDENCE_DIR}/dnf-history-install.txt" || \
    die "Q041 DNF transaction does not contain ${package}"
done
if grep -Eiq '^[[:space:]]*(Upgrade|Downgrade|Erase|Remove|Reinstall|Obsolete)[[:space:]]' \
  "${EVIDENCE_DIR}/dnf-history-install.txt"; then
  die "Q041 DNF transaction contains a non-install action"
fi

: >"${EVIDENCE_DIR}/final-unit-state.tsv"
: >"${EVIDENCE_DIR}/final-unit-journal.txt"
preinstall_captured_at=$(awk -F= '$1 == "captured_at" {print $2}' \
  "${EVIDENCE_DIR}/preinstall-result.env")
[[ -n "${preinstall_captured_at}" ]] || die "preinstall capture time is missing"
preinstall_pid1_lstart=$(awk '$1 == "1" && $2 == "0" {
  print $5 " " $6 " " $7 " " $8 " " $9
  exit
}' "${EVIDENCE_DIR}/target-processes-before.txt")
current_pid1_lstart=$(ps -p 1 -o lstart= | awk '{$1=$1; print}')
[[ -n "${preinstall_pid1_lstart}" ]] || die "preinstall PID 1 start time is missing"
[[ "${current_pid1_lstart}" == "${preinstall_pid1_lstart}" ]] || \
  die "host rebooted after the Q041 preinstall capture"
{
  printf 'preinstall_pid1_lstart=%s\n' "${preinstall_pid1_lstart}"
  printf 'current_pid1_lstart=%s\n' "${current_pid1_lstart}"
  printf 'current_boot_id=%s\n' "$(cat /proc/sys/kernel/random/boot_id)"
  printf 'boot_continuity=PASS\n'
} >"${EVIDENCE_DIR}/boot-continuity-result.env"
for unit in "${UNITS[@]}"; do
  enabled_state=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
  active_state=$(systemctl show "${unit}" -p ActiveState --value)
  sub_state=$(systemctl show "${unit}" -p SubState --value)
  start_mono=$(systemctl show "${unit}" -p ExecMainStartTimestampMonotonic --value)
  printf '%s\t%s\t%s\t%s\t%s\n' \
    "${unit}" "${enabled_state}" "${active_state}" "${sub_state}" "${start_mono}" \
    >>"${EVIDENCE_DIR}/final-unit-state.tsv"
  [[ "${enabled_state}" == "disabled" || "${enabled_state}" == "masked" ]] || \
    die "unit is not disabled: ${unit}"
  [[ "${active_state}" == "inactive" && "${sub_state}" == "dead" ]] || \
    die "unit is not inactive/dead: ${unit} (${active_state}/${sub_state})"
  [[ "${start_mono}" == "0" ]] || die "unit has a recorded start timestamp: ${unit}"
  {
    printf '### %s\n' "${unit}"
    journalctl -u "${unit}" --since "${preinstall_captured_at}" --no-pager -o short-iso
  } >>"${EVIDENCE_DIR}/final-unit-journal.txt"
done
if grep -Eiq \
  'systemd.*(Starting|Started|Stopping|Stopped|Failed).*(nginx|keepalived|haproxy|orchestrator|sentinel5|sentinel7)' \
  "${EVIDENCE_DIR}/final-unit-journal.txt"; then
  die "a target unit has a lifecycle event after the Q041 preinstall capture"
fi

ss -lntupH >"${EVIDENCE_DIR}/final-listeners.txt"
ip -4 -br addr >"${EVIDENCE_DIR}/final-ipv4.txt"
df -hT / >"${EVIDENCE_DIR}/final-disk.txt"
target_listeners=$(grep -E ":(${ALL_PORTS})([[:space:]]|$)" \
  "${EVIDENCE_DIR}/final-listeners.txt" || true)
target_processes=$(ps -eo pid,cmd | \
  grep -Ei '[n]ginx|[k]eepalived|[h]aproxy|[o]rchestrator|[r]edis-(server|sentinel)' || true)
vip_addresses=$(ip -o -4 addr show | grep -E "${VIP_RE}" || true)
[[ -z "${target_listeners}" ]] || die "a target or temporary port is listening"
[[ -z "${target_processes}" ]] || die "a target process is running"
[[ -z "${vip_addresses}" ]] || die "a project VIP is announced"

[[ "$(sysctl -n net.ipv4.ip_nonlocal_bind)" == "1" ]] || die "ip_nonlocal_bind mismatch"
[[ "$(sysctl -n net.ipv4.conf.all.arp_ignore)" == "1" ]] || die "arp_ignore mismatch"
[[ "$(sysctl -n net.ipv4.conf.all.arp_announce)" == "2" ]] || die "arp_announce mismatch"

for forbidden_path in /usr/local/php73 /usr/local/php74 /usr/local/mysql /usr/lib/emqx; do
  [[ ! -e "${forbidden_path}" ]] || die "forbidden role path exists: ${forbidden_path}"
done
for forbidden_unit in php-fpm73.service php-fpm74.service mysql56.service mysql80.service \
  redis5.service redis7.service emqx.service; do
  if systemctl cat "${forbidden_unit}" >/dev/null 2>&1; then
    die "forbidden role unit exists: ${forbidden_unit}"
  fi
done
[[ ! -e /opt/redis/5.0.14/bin/redis-server ]] || die "Redis5 server binary exists"
[[ ! -e /opt/redis/7.2.14/bin/redis-server ]] || die "Redis7 server binary exists"

formal_runtime_paths=(
  /etc/keepalived/keepalived.conf
  /etc/haproxy/haproxy.cfg
  /etc/orchestrator.conf.json
  /var/lib/redis-sentinel5/sentinel.conf
  /var/lib/redis-sentinel7/sentinel.conf
  /var/lib/orchestrator
  /var/log/orchestrator
  /etc/zhct/node.env
  /etc/zhct/secrets
)
: >"${EVIDENCE_DIR}/formal-runtime-path-state.tsv"
for path in "${formal_runtime_paths[@]}"; do
  if [[ -e "${path}" || -L "${path}" ]]; then
    die "formal runtime path exists before activation: ${path}"
  fi
  printf '%s\tABSENT\n' "${path}" >>"${EVIDENCE_DIR}/formal-runtime-path-state.tsv"
done

: >"${EVIDENCE_DIR}/file-permission-check.tsv"
while IFS=$'\t' read -r path expected_mode; do
  actual=$(stat -c '%U:%G %a' "${path}")
  printf '%s\t%s\t%s\n' "${path}" "${expected_mode}" "${actual}" \
    >>"${EVIDENCE_DIR}/file-permission-check.tsv"
  [[ "${actual}" == "${expected_mode}" ]] || die "file permission mismatch: ${path}"
done <<'EOF'
/etc/systemd/system/nginx.service	root:root 644
/etc/systemd/system/keepalived.service	root:root 644
/etc/systemd/system/haproxy.service	root:root 644
/etc/systemd/system/sentinel5.service	root:root 644
/etc/systemd/system/sentinel7.service	root:root 644
/usr/lib/systemd/system/orchestrator.service	root:root 644
/etc/sysctl.d/99-zhct-edge.conf	root:root 644
/usr/local/sbin/check-front-vip.sh	root:root 750
/usr/local/sbin/check-mqtt-vip.sh	root:root 750
/usr/local/sbin/check-db-proxy-vip.sh	root:root 750
/usr/local/sbin/vip-control.sh	root:root 750
/usr/local/sbin/provision-edge-secrets.sh	root:root 750
EOF

: >"${EVIDENCE_DIR}/directory-permission-check.tsv"
while IFS=$'\t' read -r path expected_mode; do
  actual=$(stat -c '%U:%G %a' "${path}")
  printf '%s\t%s\t%s\n' "${path}" "${expected_mode}" "${actual}" \
    >>"${EVIDENCE_DIR}/directory-permission-check.tsv"
  [[ "${actual}" == "${expected_mode}" ]] || die "directory permission mismatch: ${path}"
done <<'EOF'
/usr/local/nginx	root:root 750
/usr/local/haproxy	root:root 755
/usr/local/orchestrator	root:root 755
/opt/redis/5.0.14	root:root 755
/opt/redis/7.2.14	root:root 755
/etc/zhct/templates/nginx	root:root 755
/var/log/nginx	www:www 750
/var/log/haproxy	haproxy:haproxy 750
/var/log/redis-sentinel5	redis:redis 750
/var/log/redis-sentinel7	redis:redis 750
/var/lib/redis-sentinel5	redis:redis 750
/var/lib/redis-sentinel7	redis:redis 750
/var/tmp/zhct-build/nginx	root:root 750
EOF

temp_directory_count=$(find /var/tmp -maxdepth 1 -type d \
  -name 'zhct-q041-health.*' -print | wc -l)
[[ "${temp_directory_count}" -eq 0 ]] || die "temporary Q041 health directory remains"

template_roots=(
  "${BUNDLE_ROOT}/roles/nginx/templates"
  /etc/zhct/templates/nginx
)
secret_key_lines=$(grep -RInE \
  'MySQLTopologyPassword|auth-pass|auth_pass|tcp-check[[:space:]]+send[[:space:]]+AUTH' \
  "${template_roots[@]}" || true)
[[ -n "${secret_key_lines}" ]] || die "expected secret placeholder lines are missing"
unresolved_plaintext=$(printf '%s\n' "${secret_key_lines}" | grep -Ev '__[A-Z0-9_]+__' || true)
[[ -z "${unresolved_plaintext}" ]] || die "a secret-bearing template line lacks a placeholder"
private_key_markers=$(grep -RIlE \
  'BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY' \
  "${BUNDLE_ROOT}/common" "${BUNDLE_ROOT}/roles/nginx" /etc/zhct/templates/nginx \
  /usr/local/sbin/check-*-vip.sh \
  /usr/local/sbin/vip-control.sh /usr/local/sbin/provision-edge-secrets.sh || true)
[[ -z "${private_key_markers}" ]] || die "private key material exists in the installed role"
credential_urls=$(grep -RInE \
  '://[^/[:space:]:]+:[^/@[:space:]]+@' \
  "${BUNDLE_ROOT}/common" "${BUNDLE_ROOT}/roles/nginx" /etc/zhct/templates/nginx \
  /usr/local/sbin/check-*-vip.sh \
  /usr/local/sbin/vip-control.sh /usr/local/sbin/provision-edge-secrets.sh || true)
[[ -z "${credential_urls}" ]] || die "credential-bearing URL exists in the installed role"
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'template_secret_lines=%s\n' "$(printf '%s\n' "${secret_key_lines}" | wc -l)"
  printf 'template_secret_lines_without_placeholder=0\n'
  printf 'private_key_file_count=0\n'
  printf 'credential_url_count=0\n'
  printf 'runtime_secret_directory=ABSENT\n'
  printf 'sensitive_scan=PASS\n'
} >"${SENSITIVE_SCAN}"

[[ -d "${Q040_BACKUP}" ]] || die "Q040 baseline backup is missing"
for q040_item in result.env sha256sums.txt config/system-config-before-q040.tar.gz \
  v10-result.env; do
  [[ -s "${Q040_BACKUP}/${q040_item}" ]] || die "Q040 backup evidence is missing: ${q040_item}"
done
grep -Fq 'sensitive_scan=PASS' "${Q040_BACKUP}/v10-result.env" || \
  die "Q040 final audit is not PASS"
(
  cd "${Q040_BACKUP}"
  sha256sum -c sha256sums.txt
) >"${EVIDENCE_DIR}/q040-baseline-sha256-recheck.txt"

expected_path_baseline=(
  /usr/local/nginx
  /usr/local/haproxy
  /usr/local/sbin/keepalived
  /opt/redis/5.0.14
  /opt/redis/7.2.14
  /etc/zhct/templates/nginx
  /usr/local/sbin/check-front-vip.sh
  /usr/local/sbin/check-mqtt-vip.sh
  /usr/local/sbin/check-db-proxy-vip.sh
  /usr/local/sbin/vip-control.sh
  /usr/local/sbin/provision-edge-secrets.sh
  /etc/sysctl.d/99-zhct-edge.conf
  /etc/systemd/system/nginx.service
  /etc/systemd/system/keepalived.service
  /etc/systemd/system/haproxy.service
  /etc/systemd/system/sentinel5.service
  /etc/systemd/system/sentinel7.service
  /usr/lib/systemd/system/orchestrator.service
  /var/log/nginx
  /var/log/haproxy
  /var/log/redis-sentinel5
  /var/log/redis-sentinel7
  /var/lib/redis-sentinel5
  /var/lib/redis-sentinel7
  /var/lib/orchestrator
  /var/log/orchestrator
  /etc/orchestrator.conf.json
  /var/tmp/zhct-build/nginx
)
printf '%s\n' "${expected_path_baseline[@]}" | sort \
  >"${EVIDENCE_DIR}/expected-path-baseline.txt"
awk -F'\t' '{print $1}' "${EVIDENCE_DIR}/path-state-before.tsv" | sort \
  >"${EVIDENCE_DIR}/actual-path-baseline.txt"
cmp -s "${EVIDENCE_DIR}/expected-path-baseline.txt" \
  "${EVIDENCE_DIR}/actual-path-baseline.txt" || die "path baseline set mismatch"
path_baseline_count=$(wc -l <"${EVIDENCE_DIR}/path-state-before.tsv")
[[ "${path_baseline_count}" -eq "${#expected_path_baseline[@]}" ]] || \
  die "path baseline row count mismatch"
awk -F'\t' 'NF != 2 || ($2 != "ABSENT" && $2 != "PRESENT") {bad=1} END {exit bad}' \
  "${EVIDENCE_DIR}/path-state-before.tsv" || die "path baseline is malformed"
present_path_count=$(awk -F'\t' '$2 == "PRESENT" {count++} END {print count + 0}' \
  "${EVIDENCE_DIR}/path-state-before.tsv")
[[ "${present_path_count}" -eq 0 ]] || die "preinstall target path baseline was not clean"
role_backup_state=NOT_REQUIRED_BASELINE_ABSENT

if grep -Eq '^percona-orchestrator($|-)' "${EVIDENCE_DIR}/rpm-list-before.txt"; then
  die "an Orchestrator RPM existed before Q041"
fi
: >"${EVIDENCE_DIR}/orchestrator-package-owned-paths.tsv"
while IFS=$'\t' read -r path expected_package; do
  actual_package=$(rpm -qf --qf '%{NAME}' "${path}")
  printf '%s\t%s\t%s\n' "${path}" "${expected_package}" "${actual_package}" \
    >>"${EVIDENCE_DIR}/orchestrator-package-owned-paths.tsv"
  [[ "${actual_package}" == "${expected_package}" ]] || \
    die "Orchestrator package ownership mismatch: ${path}"
done <<'EOF'
/usr/local/orchestrator/orchestrator	percona-orchestrator
/usr/bin/orchestrator	percona-orchestrator-cli
/usr/bin/orchestrator-client	percona-orchestrator-client
EOF

[[ $(wc -l <"${EVIDENCE_DIR}/account-state-before.tsv") -eq 4 ]] || \
  die "account baseline row count mismatch"
for account in www haproxy redis orchestrator; do
  awk -F'\t' -v expected="${account}" \
    '$1 == expected && ($2 == "ABSENT" || $2 == "PRESENT") {found=1} END {exit !found}' \
    "${EVIDENCE_DIR}/account-state-before.tsv" || die "account baseline is missing: ${account}"
done
[[ $(wc -l <"${EVIDENCE_DIR}/sysctl-state-before.tsv") -eq 3 ]] || \
  die "sysctl baseline row count mismatch"
for key in net.ipv4.ip_nonlocal_bind net.ipv4.conf.all.arp_ignore \
  net.ipv4.conf.all.arp_announce; do
  awk -F'\t' -v expected="${key}" '$1 == expected && $2 ~ /^[0-9]+$/ {found=1} END {exit !found}' \
    "${EVIDENCE_DIR}/sysctl-state-before.tsv" || die "sysctl baseline is missing: ${key}"
done
[[ "$(sha256sum /root/q041-rollback.sh | awk '{print $1}')" == "${EXPECTED_ROLLBACK_SHA256}" ]] || \
  die "rollback script SHA256 mismatch"
bash -n /root/q041-rollback.sh
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'q040_baseline_backup=PASS\n'
  printf 'q041_role_backup=%s\n' "${role_backup_state}"
  printf 'path_baseline_count=%s\n' "${path_baseline_count}"
  printf 'preexisting_target_path_count=%s\n' "${present_path_count}"
  printf 'path_baseline=PASS\n'
  printf 'orchestrator_preinstall_rpm_state=ABSENT\n'
  printf 'orchestrator_installed_paths=PACKAGE_OWNED\n'
  printf 'orchestrator_rollback_scope=DNF_HISTORY_UNDO\n'
  printf 'account_baseline=PASS\n'
  printf 'sysctl_baseline=PASS\n'
  printf 'dnf_transaction_boundary=PASS\n'
  printf 'rollback_script_sha256=%s\n' "${EXPECTED_ROLLBACK_SHA256}"
  printf 'rollback_script_syntax=PASS\n'
  printf 'rollback_readiness=PASS\n'
  printf 'rollback_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/rollback-readiness-result.env"

root_available_bytes=$(df -B1 --output=avail / | tail -n 1 | tr -d ' ')
root_total_bytes=$(df -B1 --output=size / | tail -n 1 | tr -d ' ')
minimum_required_bytes=${MIN_FREE_BYTES}
if ((root_total_bytes / 5 > minimum_required_bytes)); then
  minimum_required_bytes=$((root_total_bytes / 5))
fi
((root_available_bytes >= minimum_required_bytes)) || die "root free space is below the gate"

evidence_temp=$(mktemp "${EVIDENCE_DIR}/.q041-final-evidence-index.XXXXXX")
state_temp=$(mktemp "${EVIDENCE_DIR}/.q041-final-state-index.XXXXXX")
trap 'rm -f "${evidence_temp}" "${state_temp}"' EXIT
(
  cd "${EVIDENCE_DIR}"
  find . -type f \
    ! -name 'q041-final-evidence-index.sha256' \
    ! -name 'q041-final-evidence-index.sha256.sha256' \
    ! -name 'q041-final-state-index.sha256' \
    ! -name 'q041-final-state-index.sha256.sha256' \
    ! -name 'final-audit-result.env' \
    ! -name '.q041-final-evidence-index.*' \
    ! -name '.q041-final-state-index.*' \
    -print0 | sort -z | xargs -0 sha256sum
) >"${evidence_temp}"
[[ -s "${evidence_temp}" ]] || die "final evidence index is empty"
mv "${evidence_temp}" "${FINAL_EVIDENCE_INDEX}"
(
  cd "${EVIDENCE_DIR}"
  sha256sum "$(basename "${FINAL_EVIDENCE_INDEX}")"
) >"${FINAL_EVIDENCE_INDEX}.sha256"

state_roots=(
  /usr/local/nginx
  /usr/local/haproxy
  /usr/local/orchestrator
  /usr/local/sbin/keepalived
  /opt/redis/5.0.14
  /opt/redis/7.2.14
  /etc/zhct/templates/nginx
  /etc/systemd/system/nginx.service
  /etc/systemd/system/keepalived.service
  /etc/systemd/system/haproxy.service
  /etc/systemd/system/sentinel5.service
  /etc/systemd/system/sentinel7.service
  /usr/lib/systemd/system/orchestrator.service
  /etc/sysctl.d/99-zhct-edge.conf
  /usr/local/sbin/check-front-vip.sh
  /usr/local/sbin/check-mqtt-vip.sh
  /usr/local/sbin/check-db-proxy-vip.sh
  /usr/local/sbin/vip-control.sh
  /usr/local/sbin/provision-edge-secrets.sh
)
find "${state_roots[@]}" -type f -print0 | sort -z | xargs -0 sha256sum >"${state_temp}"
sha256sum /usr/bin/orchestrator /usr/bin/orchestrator-client >>"${state_temp}"
[[ -s "${state_temp}" ]] || die "final state index is empty"
mv "${state_temp}" "${FINAL_STATE_INDEX}"
(
  cd "${EVIDENCE_DIR}"
  sha256sum "$(basename "${FINAL_STATE_INDEX}")"
) >"${FINAL_STATE_INDEX}.sha256"

evidence_count=$(wc -l <"${FINAL_EVIDENCE_INDEX}")
evidence_index_sha256=$(awk '{print $1}' "${FINAL_EVIDENCE_INDEX}.sha256")
state_count=$(wc -l <"${FINAL_STATE_INDEX}")
state_index_sha256=$(awk '{print $1}' "${FINAL_STATE_INDEX}.sha256")
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'run_id=%s\n' "${RUN_ID}"
  printf 'dnf_history_id_before=%s\n' "${dnf_history_id_before}"
  printf 'dnf_history_id_after=%s\n' "${dnf_history_id_after}"
  printf 'evidence_file_count=%s\n' "${evidence_count}"
  printf 'evidence_index_sha256=%s\n' "${evidence_index_sha256}"
  printf 'state_file_count=%s\n' "${state_count}"
  printf 'state_index_sha256=%s\n' "${state_index_sha256}"
  printf 'root_available_bytes=%s\n' "${root_available_bytes}"
  printf 'minimum_required_bytes=%s\n' "${minimum_required_bytes}"
  printf 'v8_cleanup_and_inactive_check=PASS\n'
  printf 'boot_continuity=PASS\n'
  printf 'unit_start_history=ABSENT\n'
  printf 'temporary_health_directory_count=%s\n' "${temp_directory_count}"
  printf 'v9_role_permission_space_check=PASS\n'
  printf 'formal_runtime_path_count=%s\n' "${#formal_runtime_paths[@]}"
  printf 'formal_runtime_paths=ABSENT\n'
  printf 'required_evidence_check=PASS\n'
  printf 'sensitive_scan=PASS\n'
  printf 'rollback_readiness=PASS\n'
  printf 'install_execution=PASS\n'
  printf 'formal_target_service_start=NOT_PERFORMED\n'
  printf 'q042_remote_runtime_state=NOT_STARTED\n'
  printf 'final_audit_check=PASS\n'
} >"${EVIDENCE_DIR}/final-audit-result.env"

chmod 0600 "${EVIDENCE_DIR}"/*
printf 'Q041_FINAL_AUDIT_OK\n'
cat "${EVIDENCE_DIR}/final-audit-result.env"
