#!/usr/bin/env python3
"""Score the 10-minute AWOOOI reboot auto-recovery SLO."""

from __future__ import annotations

import argparse
import json
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[2]
SCHEMA_VERSION = "awoooi_reboot_auto_recovery_slo_scorecard_v1"
REQUIRED_HOSTS = {"99", "110", "111", "112", "120", "121", "188"}
WINDOWS99_REQUIRED_VM_ALIASES = {"111", "112", "120", "121", "188"}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Fail-closed scorecard for automatic reboot recovery within 10 minutes.",
    )
    parser.add_argument("--summary-file", type=Path, help="post-reboot-readiness-summary output.")
    parser.add_argument("--host-probe-file", type=Path, help="reboot-auto-recovery-host-probe output.")
    parser.add_argument("--reboot-event-file", type=Path, help="reboot-event-detector JSON output.")
    parser.add_argument("--target-minutes", type=int, default=10)
    parser.add_argument("--min-free-gib", type=float, default=2.0)
    parser.add_argument("--disk-path", type=Path, help="Optionally check local free space.")
    parser.add_argument(
        "--stock-freshness-file",
        type=Path,
        help="Optional StockPlatform /api/v1/system/freshness JSON readback.",
    )
    parser.add_argument(
        "--stock-ingestion-file",
        type=Path,
        help="Optional StockPlatform /api/v1/system/ingestion JSON readback.",
    )
    parser.add_argument(
        "--host-pressure-file",
        type=Path,
        help="Optional host pressure JSON readback from Prometheus / node exporter.",
    )
    parser.add_argument(
        "--public-maintenance-file",
        type=Path,
        help="Optional public maintenance fallback route probe JSON readback.",
    )
    parser.add_argument(
        "--windows99-vmware-file",
        type=Path,
        help="Optional windows99-vmware-autostart.ps1 Verify output.",
    )
    parser.add_argument(
        "--windows99-management-file",
        type=Path,
        help="Optional no-secret Windows 99 management-channel JSON readback.",
    )
    parser.add_argument("--generated-at", help="Override generated_at for stable snapshots.")
    parser.add_argument(
        "--required-host",
        action="append",
        dest="required_hosts",
        help="Required host alias. May be passed more than once.",
    )
    parser.add_argument("--output", type=Path, help="Write JSON to this path.")
    return parser.parse_args()


def read_text(path: Path | None) -> str:
    if not path:
        return ""
    return path.read_text(encoding="utf-8")


def parse_kv(text: str) -> dict[str, str]:
    values: dict[str, str] = {}
    for raw_line in text.splitlines():
        line = raw_line.strip()
        if not line or "=" not in line or line.startswith("HOST_BOOT "):
            continue
        key, value = line.split("=", 1)
        values[key.strip()] = value.strip()
    return values


def truthy(value: str | None) -> bool:
    return value in {"1", "true", "True", "yes", "YES"}


def int_value(value: Any, default: int = 0) -> int:
    try:
        return int(str(value))
    except (TypeError, ValueError):
        return default


def strings(value: Any) -> list[str]:
    if not isinstance(value, list):
        return []
    return [str(item) for item in value if item not in (None, "")]


def unique_strings(values: list[str]) -> list[str]:
    seen: set[str] = set()
    unique: list[str] = []
    for value in values:
        if value in seen:
            continue
        seen.add(value)
        unique.append(value)
    return unique


def csv_strings(value: str | None) -> list[str]:
    if not value or value in {"none", "unknown"}:
        return []
    return [item.strip() for item in value.split(",") if item.strip()]


def parse_windows99_vmware_readback(text: str) -> dict[str, Any]:
    """Parse the no-secret Windows 99 VMware autostart verifier output."""
    required_aliases = sorted(WINDOWS99_REQUIRED_VM_ALIASES)
    if not text.strip():
        return {
            "readback_present": False,
            "required_vm_aliases": required_aliases,
            "vmrun_present": False,
            "config_ready": False,
            "power_ready": False,
            "windows_update_no_auto_reboot_ready": False,
            "verify_ready": False,
            "missing_vmx_aliases": required_aliases,
            "powered_off_aliases": required_aliases,
            "service_blockers": ["VMAuthdService", "VMnetDHCP"],
            "policy_blockers": ["windows_update_policy_readback_missing"],
            "blockers": ["windows99_vmware_autostart_readback_missing"],
        }

    kv = parse_kv(text)
    vmx_present: dict[str, bool] = {}
    vm_power: dict[str, dict[str, bool]] = {}
    services: dict[str, dict[str, Any]] = {}
    policies: dict[str, dict[str, Any]] = {}
    task: dict[str, Any] = {}

    for raw_line in text.splitlines():
        line = raw_line.strip()
        match = re.match(r"^VMX alias=(\S+)\s+.*\spresent=([01])$", line)
        if match:
            vmx_present[match.group(1)] = match.group(2) == "1"
            continue
        match = re.match(
            r"^VM_POWER alias=(\S+) vmx_present=([01]) running=([01])$",
            line,
        )
        if match:
            vm_power[match.group(1)] = {
                "vmx_present": match.group(2) == "1",
                "running": match.group(3) == "1",
            }
            continue
        match = re.match(
            r"^VMWARE_SERVICE name=(\S+) present=([01]) status=(\S+) "
            r"startup_type=(\S+) ok=([01])$",
            line,
        )
        if match:
            services[match.group(1)] = {
                "present": match.group(2) == "1",
                "status": match.group(3),
                "startup_type": match.group(4),
                "ok": match.group(5) == "1",
            }
            continue
        match = re.match(
            r"^VMWARE_AUTOSTART_TASK name=(\S+) present=([01]) enabled=([01]) "
            r"state=(\S+) trigger_count=(\d+) action_count=(\d+) ok=([01])$",
            line,
        )
        if match:
            task = {
                "name": match.group(1),
                "present": match.group(2) == "1",
                "enabled": match.group(3) == "1",
                "state": match.group(4),
                "trigger_count": int_value(match.group(5)),
                "action_count": int_value(match.group(6)),
                "ok": match.group(7) == "1",
            }
            continue
        match = re.match(
            r"^WINDOWS_UPDATE_POLICY name=(\S+) value=(\S+) expected=(\S+) ok=([01])$",
            line,
        )
        if match:
            policies[match.group(1)] = {
                "value": match.group(2),
                "expected": match.group(3),
                "ok": match.group(4) == "1",
            }

    missing_from_vmx = [alias for alias in required_aliases if not vmx_present.get(alias)]
    missing_from_power = [
        alias
        for alias in required_aliases
        if not vm_power.get(alias, {}).get("vmx_present")
    ]
    missing_vmx_aliases = sorted(set(missing_from_vmx + missing_from_power))
    powered_off_aliases = sorted(
        alias
        for alias in required_aliases
        if not vm_power.get(alias, {}).get("running")
    )
    service_blockers = sorted(
        name
        for name in ("VMAuthdService", "VMnetDHCP")
        if services.get(name, {}).get("ok") is not True
    )
    policy_blockers = sorted(
        name for name, policy in policies.items() if policy.get("ok") is not True
    )
    if not policies:
        policy_blockers = ["windows_update_policy_readback_missing"]

    vmrun_present = truthy(kv.get("VMRUN_PRESENT"))
    config_ready = truthy(kv.get("VMWARE_AUTOSTART_CONFIG_READY"))
    power_ready = truthy(kv.get("VMWARE_AUTOSTART_POWER_READY"))
    update_ready = truthy(kv.get("WINDOWS_UPDATE_NO_AUTO_REBOOT_READY"))
    verify_ready = truthy(kv.get("VMWARE_AUTOSTART_VERIFY_READY"))

    if "VMWARE_AUTOSTART_CONFIG_READY" not in kv:
        config_ready = (
            vmrun_present
            and not missing_vmx_aliases
            and not service_blockers
            and bool(task)
            and task.get("ok") is True
            and update_ready
        )
    if "VMWARE_AUTOSTART_POWER_READY" not in kv:
        power_ready = not powered_off_aliases
    if "VMWARE_AUTOSTART_VERIFY_READY" not in kv:
        verify_ready = config_ready and power_ready

    blockers: list[str] = []
    if not vmrun_present:
        blockers.append("windows99_vmrun_missing")
    if missing_vmx_aliases:
        blockers.append("windows99_vmware_vmx_missing")
    if service_blockers or not task.get("ok"):
        blockers.append("windows99_vmware_autostart_config_not_ready")
    if powered_off_aliases:
        blockers.append("windows99_vmware_guest_power_not_ready")
    if not update_ready:
        blockers.append("windows99_update_no_auto_reboot_policy_not_ready")
    if not verify_ready and not blockers:
        blockers.append("windows99_vmware_autostart_verify_not_ready")

    return {
        "readback_present": True,
        "required_vm_aliases": required_aliases,
        "vmrun_present": vmrun_present,
        "config_ready": config_ready,
        "power_ready": power_ready,
        "windows_update_no_auto_reboot_ready": update_ready,
        "verify_ready": verify_ready,
        "missing_vmx_aliases": missing_vmx_aliases,
        "powered_off_aliases": powered_off_aliases,
        "service_blockers": service_blockers,
        "policy_blockers": policy_blockers,
        "scheduled_task": task,
        "vmware_services": services,
        "windows_update_policy": policies,
        "vm_power": vm_power,
        "blockers": blockers,
    }


def parse_windows99_management_readback(path: Path | None) -> dict[str, Any]:
    """Parse no-secret Windows 99 management-channel readback JSON."""
    default = {
        "readback_present": False,
        "host": "192.168.0.99",
        "host_reachable": False,
        "tcp_ports": {},
        "ssh_batch": {"checked": False, "ready": False, "status": "missing"},
        "winrm_http_open": False,
        "winrm_https_open": False,
        "hyperv_vmconnect_open": False,
        "rdp_console_reachable": False,
        "local_console_channel_reachable": False,
        "console_collection_channels": [],
        "remote_execution_channel_ready": False,
        "can_collect_vmware_verify_without_secret": False,
        "blockers": [],
    }
    if not path:
        return default
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except (FileNotFoundError, json.JSONDecodeError):
        blocked = dict(default)
        blocked["blockers"] = ["windows99_management_channel_readback_invalid"]
        return blocked
    if not isinstance(payload, dict):
        blocked = dict(default)
        blocked["blockers"] = ["windows99_management_channel_readback_invalid"]
        return blocked

    tcp_ports = payload.get("tcp_ports")
    if not isinstance(tcp_ports, dict):
        tcp_ports = {}
    ssh_batch = payload.get("ssh_batch")
    if not isinstance(ssh_batch, dict):
        ssh_batch = {"checked": False, "ready": False, "status": "missing"}
    return {
        "readback_present": True,
        "schema_version": str(payload.get("schema_version") or "unknown"),
        "generated_at": str(payload.get("generated_at") or ""),
        "host": str(payload.get("host") or "192.168.0.99"),
        "host_reachable": payload.get("host_reachable") is True,
        "tcp_ports": {str(key): str(value) for key, value in tcp_ports.items()},
        "ssh_user": str(payload.get("ssh_user") or ""),
        "ssh_batch": {
            "checked": ssh_batch.get("checked") is True,
            "ready": ssh_batch.get("ready") is True,
            "status": str(ssh_batch.get("status") or "unknown"),
        },
        "winrm_http_open": payload.get("winrm_http_open") is True,
        "winrm_https_open": payload.get("winrm_https_open") is True,
        "hyperv_vmconnect_open": payload.get("hyperv_vmconnect_open") is True,
        "rdp_console_reachable": payload.get("rdp_console_reachable") is True,
        "local_console_channel_reachable": (
            payload.get("local_console_channel_reachable") is True
        ),
        "console_collection_channels": strings(
            payload.get("console_collection_channels")
        ),
        "remote_execution_channel_ready": (
            payload.get("remote_execution_channel_ready") is True
        ),
        "can_collect_vmware_verify_without_secret": (
            payload.get("can_collect_vmware_verify_without_secret") is True
        ),
        "blockers": strings(payload.get("blockers")),
        "forbidden_actions": strings(payload.get("forbidden_actions")),
    }


def read_json_object(path: Path | None) -> dict[str, Any]:
    if not path:
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return {}
    if not isinstance(payload, dict):
        return {}
    return payload


def parse_public_maintenance_readback(path: Path | None) -> dict[str, Any]:
    """Parse public route maintenance fallback probe JSON."""
    default = {
        "runtime_readback_present": False,
        "ready": False,
        "target_url_count": 0,
        "route_count": 0,
        "raw_5xx_without_fallback_count": 0,
        "route_unreachable_without_external_fallback_count": 0,
        "maintenance_fallback_route_count": 0,
        "raw_5xx_without_fallback_urls": [],
        "route_unreachable_without_external_fallback_urls": [],
        "maintenance_fallback_urls": [],
        "routes": [],
        "blockers": ["public_maintenance_fallback_runtime_readback_missing"],
    }
    payload = read_json_object(path)
    if not payload:
        return default

    routes = payload.get("routes")
    if not isinstance(routes, list):
        routes = []
    compact_routes: list[dict[str, Any]] = []
    for item in routes:
        if not isinstance(item, dict):
            continue
        compact_routes.append(
            {
                "url": str(item.get("url") or ""),
                "http_status": int_value(item.get("http_status")),
                "status": str(item.get("status") or "unknown"),
                "fallback_header": str(item.get("fallback_header") or ""),
                "maintenance_body_detected": (
                    item.get("maintenance_body_detected") is True
                ),
                "maintenance_fallback_serving": (
                    item.get("maintenance_fallback_serving") is True
                ),
                "raw_5xx_without_fallback": (
                    item.get("raw_5xx_without_fallback") is True
                ),
                "route_unreachable_without_external_fallback": (
                    item.get("route_unreachable_without_external_fallback") is True
                ),
                "error": str(item.get("error") or "")[:300],
            }
        )

    blockers = strings(payload.get("blockers"))
    raw_5xx_count = int_value(payload.get("raw_5xx_without_fallback_count"))
    unreachable_count = int_value(
        payload.get("route_unreachable_without_external_fallback_count")
    )
    if raw_5xx_count and "public_route_raw_5xx_without_maintenance_fallback" not in blockers:
        blockers.append("public_route_raw_5xx_without_maintenance_fallback")
    if (
        unreachable_count
        and "public_route_unreachable_without_external_l1_fallback" not in blockers
    ):
        blockers.append("public_route_unreachable_without_external_l1_fallback")

    return {
        "runtime_readback_present": True,
        "schema_version": str(payload.get("schema_version") or "unknown"),
        "generated_at": str(payload.get("generated_at") or ""),
        "ready": payload.get("ready") is True and not blockers,
        "target_url_count": int_value(payload.get("target_url_count")),
        "route_count": int_value(payload.get("route_count")) or len(compact_routes),
        "raw_5xx_without_fallback_count": raw_5xx_count,
        "route_unreachable_without_external_fallback_count": unreachable_count,
        "maintenance_fallback_route_count": int_value(
            payload.get("maintenance_fallback_route_count")
        ),
        "raw_5xx_without_fallback_urls": strings(
            payload.get("raw_5xx_without_fallback_urls")
        ),
        "route_unreachable_without_external_fallback_urls": strings(
            payload.get("route_unreachable_without_external_fallback_urls")
        ),
        "maintenance_fallback_urls": strings(payload.get("maintenance_fallback_urls")),
        "routes": compact_routes,
        "blockers": blockers,
        "forbidden_actions": strings(payload.get("forbidden_actions")),
    }


def parse_host_probe(text: str) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for raw_line in text.splitlines():
        line = raw_line.strip()
        if not line.startswith("HOST_BOOT "):
            continue
        row: dict[str, Any] = {}
        for token in line.split()[1:]:
            if "=" not in token:
                continue
            key, value = token.split("=", 1)
            row[key] = value
        row["reachable"] = row.get("reachable") == "1"
        row["uptime_seconds"] = int_value(row.get("uptime_seconds"), -1)
        rows.append(row)
    return rows


def source_file(path: str) -> Path:
    return ROOT / path


def file_contains(path: Path, *needles: str) -> bool:
    try:
        text = path.read_text(encoding="utf-8")
    except FileNotFoundError:
        return False
    return all(needle in text for needle in needles)


def source_controls() -> dict[str, bool]:
    return {
        "host_110_startup_unit_source_present": file_contains(
            source_file("scripts/reboot-recovery/awoooi-startup-110.service"),
            "ExecStart=/usr/local/bin/awoooi-startup-110.sh",
            "WantedBy=multi-user.target",
        )
        and source_file("scripts/reboot-recovery/awoooi-startup-110.sh").exists(),
        "host_110_startup_controlled_drain_guarded_autostart_source_present": (
            file_contains(
                source_file("scripts/reboot-recovery/awoooi-startup-110.sh"),
                'START_CONTROLLED_CD_LANE="${AWOOOI_START_CONTROLLED_CD_LANE:-1}"',
                "cd_lane_root_restore_sources_left()",
                "install_controlled_cd_lane_drain_unit",
                'systemctl enable --now "$CD_LANE_DRAIN_SERVICE"',
                "controlled cd-lane remains closed because guardrails failed or AWOOOI_START_CONTROLLED_CD_LANE=0",
            )
        ),
        "host_188_startup_unit_source_present": file_contains(
            source_file("scripts/reboot-recovery/awoooi-startup.service"),
            "ExecStart=/usr/local/bin/awoooi-startup.sh",
            "WantedBy=multi-user.target",
        )
        and source_file("scripts/reboot-recovery/awoooi-startup.sh").exists(),
        "host_boot_probe_source_present": source_file(
            "scripts/reboot-recovery/reboot-auto-recovery-host-probe.sh"
        ).exists(),
        "reboot_event_detector_source_present": source_file(
            "scripts/reboot-recovery/reboot-event-detector.py"
        ).exists(),
        "slo_exporter_source_present": source_file(
            "scripts/reboot-recovery/reboot-auto-recovery-slo-exporter.sh"
        ).exists(),
        "slo_systemd_service_source_present": file_contains(
            source_file("scripts/reboot-recovery/awoooi-reboot-auto-recovery-slo.service"),
            "ExecStart=/usr/local/bin/awoooi-reboot-auto-recovery-slo.sh",
        ),
        "slo_systemd_timer_source_present": file_contains(
            source_file("scripts/reboot-recovery/awoooi-reboot-auto-recovery-slo.timer"),
            "OnBootSec=",
            "OnUnitActiveSec=",
        ),
        "slo_installer_source_present": file_contains(
            source_file("scripts/reboot-recovery/install-reboot-auto-recovery-slo-110.sh"),
            "--dry-run",
            "--verify-only",
            "--rollback",
            "systemctl enable --now",
        ),
        "post_reboot_summary_source_present": source_file(
            "scripts/reboot-recovery/post-reboot-readiness-summary.sh"
        ).exists(),
        "cold_start_textfile_exporter_source_present": source_file(
            "scripts/reboot-recovery/cold-start-textfile-exporter.sh"
        ).exists(),
        "windows_99_vmware_autostart_source_present": file_contains(
            source_file("scripts/reboot-recovery/windows99-vmware-autostart.ps1"),
            "VMWARE_AUTOSTART_VERIFY_READY",
            "WINDOWS_UPDATE_POLICY",
            "VM_POWER",
        ),
        "windows99_management_channel_probe_source_present": file_contains(
            source_file("scripts/reboot-recovery/windows99-management-channel-probe.py"),
            "windows99_management_channel_readback_v1",
            "remote_execution_channel_ready",
        ),
        "public_maintenance_fallback_source_present": source_file(
            "ops/maintenance/maintenance.html"
        ).exists()
        and file_contains(
            source_file("ops/maintenance/nginx-502-maintenance-snippet.conf"),
            "proxy_intercept_errors on",
            "error_page 502 503 504",
            "X-AWOOOI-Fallback",
        )
        and file_contains(
            source_file("docs/runbooks/PUBLIC-MAINTENANCE-FALLBACK-RUNBOOK.md"),
            "L0",
            "L1",
            "502",
        ),
        "telegram_reboot_backup_alert_rules_source_present": file_contains(
            source_file("ops/monitoring/alerts-unified.yml"),
            "HostRebootEventDetected",
            "RebootAutoRecoverySLOMissed",
            "BackupCoverageDomainStale",
        ),
        "conversation_event_hot_path_index_migration_source_present": file_contains(
            source_file(
                "apps/api/migrations/awooop_conversation_event_hot_path_indexes_2026-07-01.sql"
            ),
            "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_conv_event_recent",
            "idx_awooop_conv_event_project_provider_recent",
            "idx_awooop_conv_event_source_refs_event_ids_gin",
        ),
    }


def disk_free_gib(path: Path | None) -> float | None:
    if path is None:
        return None
    completed = subprocess.run(
        ["df", "-k", str(path)],
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL,
    )
    if completed.returncode != 0:
        return None
    lines = completed.stdout.splitlines()
    if len(lines) < 2:
        return None
    parts = re.split(r"\s+", lines[-1].strip())
    if len(parts) < 4:
        return None
    return int_value(parts[3]) / 1024 / 1024


def compact_stock_sources(payload: dict[str, Any]) -> list[dict[str, Any]]:
    sources = payload.get("sources")
    if not isinstance(sources, list):
        return []
    rows: list[dict[str, Any]] = []
    for item in sources:
        if not isinstance(item, dict):
            continue
        rows.append(
            {
                "source": str(item.get("source") or ""),
                "latest_date": item.get("latest_date"),
                "status": str(item.get("status") or "unknown"),
                "row_count": int_value(item.get("row_count")),
                "notes": str(item.get("notes") or "")[:500],
            }
        )
    return rows


def compact_source_runs(payload: dict[str, Any]) -> list[dict[str, Any]]:
    runs = payload.get("latest_source_runs")
    if not isinstance(runs, list):
        return []
    rows: list[dict[str, Any]] = []
    for item in runs:
        if not isinstance(item, dict):
            continue
        rows.append(
            {
                "source_run_id": item.get("source_run_id") or item.get("id"),
                "source_name": str(item.get("source_name") or item.get("source") or ""),
                "target_date": item.get("target_date") or item.get("date"),
                "status": str(item.get("status") or "unknown"),
                "started_at": item.get("started_at"),
                "finished_at": item.get("finished_at"),
            }
        )
    return rows


def final_retry_window_passed(generated_at: str, final_retry_window_end: str) -> bool:
    if not generated_at or not re.match(r"^\d{2}:\d{2}$", final_retry_window_end or ""):
        return False
    try:
        observed_at = datetime.fromisoformat(generated_at)
        hour, minute = [int(part) for part in final_retry_window_end.split(":", 1)]
        final_at = observed_at.replace(hour=hour, minute=minute, second=0, microsecond=0)
    except ValueError:
        return False
    return observed_at >= final_at


def build_stockplatform_readback(
    *,
    summary: dict[str, str],
    freshness: dict[str, Any],
    ingestion: dict[str, Any],
    generated_at: str,
) -> dict[str, Any]:
    freshness_blockers = strings(freshness.get("blockers")) or csv_strings(
        summary.get("STOCK_BLOCKERS")
    )
    ingestion_blockers = strings(ingestion.get("blockers"))
    freshness_status = str(
        freshness.get("status") or summary.get("STOCK_FRESHNESS_STATUS") or "unknown"
    )
    ingestion_status = str(ingestion.get("status") or "unknown")
    latest_trading_date = str(
        freshness.get("latest_trading_date")
        or ingestion.get("latest_trading_date")
        or summary.get("STOCK_LATEST_TRADING_DATE")
        or ""
    )
    latest_source_runs = compact_source_runs(ingestion)
    margin_short_success_ids = [
        row.get("source_run_id")
        for row in latest_source_runs
        if row.get("source_name") == "official_margin_short_daily"
        and row.get("target_date") == latest_trading_date
        and row.get("status") == "succeeded"
        and row.get("source_run_id") is not None
    ]
    eod_pending = truthy(summary.get("STOCK_EOD_WINDOW_PENDING"))
    eod_final_window = str(
        summary.get("STOCK_EOD_FINAL_RETRY_WINDOW_END_LOCAL") or "unknown"
    )
    final_passed = final_retry_window_passed(generated_at, eod_final_window)
    recovery_required = (
        freshness_status == "blocked"
        and not eod_pending
        and (
            str(summary.get("STOCK_EOD_NEXT_ACTION") or "")
            == "open_stockplatform_data_recovery_gate"
            or final_passed
        )
    )
    recovery_gate_status = (
        "ready_to_open"
        if recovery_required
        else (
            "not_required_freshness_recovered"
            if freshness_status == "ok" and not freshness_blockers
            else "not_required_yet"
        )
    )
    return {
        "freshness_endpoint_readback_present": bool(freshness),
        "ingestion_endpoint_readback_present": bool(ingestion),
        "freshness_status": freshness_status,
        "ingestion_status": ingestion_status,
        "latest_trading_date": latest_trading_date,
        "freshness_blockers": freshness_blockers,
        "ingestion_blockers": ingestion_blockers,
        "blocked_sources": [
            row
            for row in compact_stock_sources(freshness)
            if row["status"] not in {"ok", "warning"}
        ],
        "latest_source_runs": latest_source_runs,
        "eod_window": {
            "pending": eod_pending,
            "classification": str(summary.get("STOCK_EOD_CLASSIFICATION") or "unknown"),
            "next_action": str(summary.get("STOCK_EOD_NEXT_ACTION") or "unknown"),
            "first_full_window_end_local": str(
                summary.get("STOCK_EOD_FIRST_FULL_WINDOW_END_LOCAL") or "unknown"
            ),
            "final_retry_window_end_local": eod_final_window,
            "final_retry_window_passed": final_passed,
        },
        "controlled_recovery_gate": {
            "required": recovery_required,
            "status": recovery_gate_status,
            "target_selector": "stockplatform-v2:system_freshness:core.margin_short_daily,ai.recommendations",
            "allowed_actions": [
                "inspect_existing_ingestion_readback",
                "prepare_source_of_truth_diff",
                "run_check_mode_or_dry_run_import",
                "run_post_recovery_freshness_verifier",
            ],
            "forbidden_actions": [
                "manual_db_update",
                "truncate_or_restore",
                "fake_freshness_marker",
                "secret_value_read",
                "reboot_or_service_restart_from_reboot_slo_lane",
            ],
        },
        "margin_short_recovery": {
            "status": (
                "recovered"
                if freshness_status == "ok"
                and "core_margin_short_daily_missing" not in freshness_blockers
                and bool(margin_short_success_ids)
                else "not_verified"
            ),
            "cleared_blocker": "core_margin_short_daily_missing",
            "cleared_ingestion_blocker": "core.margin_short_daily_incomplete",
            "successful_source_run_ids": margin_short_success_ids,
        },
        "ai_recommendations_recovery": {
            "status": (
                "recovered"
                if freshness_status == "ok"
                and "ai_recommendations_stale" not in freshness_blockers
                else "not_verified"
            ),
            "cleared_blocker": "ai_recommendations_stale",
        },
    }


def float_value(value: Any, default: float = 0.0) -> float:
    try:
        return float(str(value))
    except (TypeError, ValueError):
        return default


def normalize_top_containers(value: Any) -> list[dict[str, Any]]:
    if not isinstance(value, list):
        return []
    rows: list[dict[str, Any]] = []
    for item in value:
        if not isinstance(item, dict):
            continue
        rows.append(
            {
                "container_name": str(item.get("container_name") or item.get("name") or ""),
                "cpu_cores": round(float_value(item.get("cpu_cores")), 4),
            }
        )
    return rows


def build_host_pressure_readback(payload: dict[str, Any]) -> dict[str, Any]:
    hosts = payload.get("hosts")
    if not isinstance(hosts, list):
        hosts = []

    rows: list[dict[str, Any]] = []
    blockers: list[str] = []
    high_load_hosts: list[str] = []
    gitea_pressure_hosts: list[str] = []
    postgres_pressure_hosts: list[str] = []
    container_attribution_stale_hosts: list[str] = []

    for item in hosts:
        if not isinstance(item, dict):
            continue
        host = str(item.get("host") or item.get("alias") or "")
        if not host:
            continue
        cores = float_value(item.get("cores") or item.get("cpu_cores"), 0.0)
        load5 = float_value(item.get("load5"), 0.0)
        load5_per_core = float_value(item.get("load5_per_core"), 0.0)
        if load5_per_core <= 0 and cores > 0:
            load5_per_core = load5 / cores
        top_containers = normalize_top_containers(item.get("top_containers"))
        docker_stats = item.get("docker_stats")
        top_containers_fresh = item.get("top_containers_fresh")
        if top_containers_fresh is None and isinstance(docker_stats, dict):
            top_containers_fresh = docker_stats.get("fresh")
        container_attribution_fresh = top_containers_fresh is not False
        row = {
            "host": host,
            "load1": round(float_value(item.get("load1")), 4),
            "load5": round(load5, 4),
            "cores": round(cores, 4),
            "load5_per_core": round(load5_per_core, 4),
            "node_procs_running": int_value(item.get("node_procs_running")),
            "node_procs_blocked": int_value(item.get("node_procs_blocked")),
            "top_containers_fresh": container_attribution_fresh,
            "top_containers": top_containers[:5],
        }
        if isinstance(docker_stats, dict):
            row["docker_stats"] = docker_stats
        rows.append(row)
        if load5_per_core > 1.0:
            high_load_hosts.append(host)
        if top_containers and not container_attribution_fresh:
            container_attribution_stale_hosts.append(host)
        if host == "110" and container_attribution_fresh and any(
            container["container_name"] == "gitea" and container["cpu_cores"] >= 2.0
            for container in top_containers
        ):
            gitea_pressure_hosts.append(host)
        if host == "188" and container_attribution_fresh and any(
            container["container_name"] == "k3s-postgres-recovery"
            and container["cpu_cores"] >= 4.0
            for container in top_containers
        ):
            postgres_pressure_hosts.append(host)

    if high_load_hosts:
        blockers.append("host_pressure_high_load")
    if container_attribution_stale_hosts:
        blockers.append("host_container_cpu_attribution_stale")
    if gitea_pressure_hosts:
        blockers.append("host_110_gitea_cpu_pressure")
    if postgres_pressure_hosts:
        blockers.append("host_188_postgres_cpu_pressure")
    if postgres_pressure_hosts and payload.get("conversation_event_hot_path_indexes_present") is False:
        blockers.append("awooop_conversation_event_hot_path_index_drift_detected")

    return {
        "readback_present": bool(payload),
        "host_count": len(rows),
        "hosts": rows,
        "high_load_hosts": high_load_hosts,
        "gitea_pressure_hosts": gitea_pressure_hosts,
        "postgres_pressure_hosts": postgres_pressure_hosts,
        "container_attribution_stale_hosts": container_attribution_stale_hosts,
        "conversation_event_hot_path_indexes_present": payload.get(
            "conversation_event_hot_path_indexes_present"
        ),
        "blockers": blockers,
        "safe_actions": [
            "keep_110_legacy_runner_failclosed",
            "read_public_gitea_queue_metadata_only",
            "restore_docker_stats_textfile_exporter_before_container_cpu_attribution",
            "apply_conversation_event_hot_path_indexes_via_controlled_db_migration",
            "rerun_host_pressure_and_cold_start_scorecard_after_apply",
        ],
        "forbidden_actions": [
            "reboot_host_from_slo_lane",
            "restart_gitea_or_postgres_without_break_glass",
            "unmask_legacy_runner_or_restore_generic_labels",
            "read_secret_or_runner_token",
            "manual_db_update_or_destructive_restore",
        ],
    }


def host_row_by_alias(host_boot_detection: dict[str, Any], alias: str) -> dict[str, Any]:
    rows = host_boot_detection.get("host_rows")
    if not isinstance(rows, list):
        return {}
    for item in rows:
        if isinstance(item, dict) and str(item.get("alias") or "") == alias:
            return item
    return {}


def build_windows99_verify_collection_packet(
    *,
    windows99: dict[str, Any],
    host_boot_detection: dict[str, Any],
) -> dict[str, Any]:
    """Describe the next no-secret Windows 99 verifier collection step."""
    host99 = host_row_by_alias(host_boot_detection, "99")
    host99_reachable = host99.get("reachable") is True
    host99_uptime_known = int_value(host99.get("uptime_seconds"), -1) >= 0
    readback_present = windows99.get("readback_present") is True
    verify_ready = windows99.get("verify_ready") is True
    blockers = strings(windows99.get("blockers"))
    collection_blockers: list[str] = []
    if not host99_reachable:
        collection_blockers.append("windows99_host_not_reachable_for_verify_collection")
    if not readback_present:
        collection_blockers.append("windows99_vmware_autostart_readback_missing")
    collection_blockers.extend(
        blocker for blocker in blockers if blocker not in collection_blockers
    )
    if not host99_uptime_known:
        collection_blockers.append("windows99_uptime_unknown")

    status = "ready_windows99_vmware_verify_readback_green"
    if not verify_ready:
        status = (
            "blocked_windows99_verify_output_missing_host_reachable"
            if host99_reachable and not readback_present
            else "blocked_windows99_verify_collection_not_ready"
        )
    return {
        "schema_version": "windows99_vmware_verify_collection_packet_v1",
        "status": status,
        "target_host_alias": "99",
        "target_host": "192.168.0.99",
        "host99_reachable": host99_reachable,
        "host99_uptime_known": host99_uptime_known,
        "readback_present": readback_present,
        "verify_ready": verify_ready,
        "can_collect_no_secret_verify": host99_reachable and not verify_ready,
        "required_vm_aliases": strings(windows99.get("required_vm_aliases"))
        or sorted(WINDOWS99_REQUIRED_VM_ALIASES),
        "expected_no_secret_output_fields": [
            "VMRUN_PRESENT",
            "VMX alias=<alias> present=<0|1>",
            "VMWARE_SERVICE name=<name> ok=<0|1>",
            "VMWARE_AUTOSTART_TASK name=AWOOOI-Start-VMware-VMs ok=<0|1>",
            "WINDOWS_UPDATE_POLICY name=<name> ok=<0|1>",
            "VM_POWER alias=<alias> running=<0|1>",
            "VMWARE_AUTOSTART_CONFIG_READY",
            "VMWARE_AUTOSTART_POWER_READY",
            "WINDOWS_UPDATE_NO_AUTO_REBOOT_READY",
            "VMWARE_AUTOSTART_VERIFY_READY",
        ],
        "no_secret_verify_command": (
            "powershell -ExecutionPolicy Bypass -File "
            ".\\windows99-vmware-autostart.ps1 -Mode Verify"
        ),
        "no_secret_collector_check_command": (
            "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --check"
        ),
        "no_secret_collector_collect_command": (
            "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh --collect"
        ),
        "post_verifier": (
            "rerun_reboot_auto_recovery_slo_scorecard_with_"
            "windows99_vmware_file_no_secret_no_reboot"
        ),
        "collection_blockers": collection_blockers,
        "safe_collection_channels": [
            "authorized_windows99_console_verify_stdout_only",
            "existing_management_channel_verify_mode_only",
            "no_secret_ssh_batchmode_verify_collector",
            "committed_no_secret_artifact_file_then_scorecard_rerun",
        ],
        "forbidden_actions": [
            "windows_password_or_secret_collection",
            "host_reboot",
            "vm_power_change",
            "windows_update_policy_apply",
            "manual_registry_edit",
            "service_restart",
            "github_api",
        ],
    }


def choose_safe_next_step(
    *,
    blockers: list[str],
    stockplatform: dict[str, Any],
    host_pressure: dict[str, Any],
    public_maintenance: dict[str, Any],
) -> str:
    freshness_status = str(stockplatform.get("freshness_status") or "unknown")
    eod_window = stockplatform.get("eod_window") if isinstance(stockplatform.get("eod_window"), dict) else {}
    eod_next_action = str(eod_window.get("next_action") or "")
    recovery_gate = (
        stockplatform.get("controlled_recovery_gate")
        if isinstance(stockplatform.get("controlled_recovery_gate"), dict)
        else {}
    )
    if freshness_status == "blocked" and recovery_gate.get("required") is True:
        return (
            "open_stockplatform_controlled_data_recovery_gate_with_source_diff_"
            "check_mode_rollback_and_post_freshness_verifier_no_manual_db_write"
        )
    if freshness_status == "blocked" and eod_next_action in {
        "inspect_ingestion_logs_and_wait_retry_windows",
        "wait_for_scheduled_eod_window",
    }:
        return (
            "inspect_stockplatform_ingestion_readback_and_wait_retry_windows_then_"
            "rerun_slo_verify_only_no_reboot"
        )
    pressure_blockers = set(strings(host_pressure.get("blockers")))
    if "awooop_conversation_event_hot_path_index_drift_detected" in pressure_blockers:
        return (
            "apply_awooop_conversation_event_hot_path_indexes_via_controlled_db_"
            "migration_then_rerun_188_postgres_cpu_readback_no_reboot"
        )
    if "host_110_gitea_cpu_pressure" in pressure_blockers:
        return (
            "keep_110_runner_failclosed_read_public_gitea_queue_and_recover_awoooi_host_"
            "controlled_lane_only_after_verifier_no_generic_runner"
        )
    if "host_container_cpu_attribution_stale" in pressure_blockers:
        return (
            "restore_docker_stats_textfile_exporter_then_collect_sanitized_host_"
            "pressure_no_restart_no_secret_read"
        )
    if "windows99_remote_execution_channel_unavailable" in blockers:
        return (
            "restore_windows99_no_secret_management_channel_or_collect_local_console_"
            "verify_readback_then_rerun_reboot_scorecard_no_reboot"
        )
    if any(blocker.startswith("windows99_") for blocker in blockers):
        return (
            "collect_windows99_vmware_autostart_verify_readback_then_rerun_all_host_"
            "reboot_scorecard_no_secret_no_reboot"
        )
    public_blockers = set(strings(public_maintenance.get("blockers")))
    if "public_route_raw_5xx_without_maintenance_fallback" in public_blockers:
        return (
            "apply_public_maintenance_fallback_l0_nginx_intercept_with_nginx_t_"
            "test_staging_vhost_header_verifier_then_deploy_no_app_restart"
        )
    if "public_route_unreachable_without_external_l1_fallback" in public_blockers:
        return (
            "prepare_public_maintenance_fallback_l1_external_static_origin_or_cdn_"
            "healthcheck_decision_record_then_controlled_cutover_with_rollback"
        )
    if "public_maintenance_fallback_runtime_readback_missing" in blockers:
        return (
            "run_public_maintenance_fallback_probe_and_rerun_reboot_scorecard_"
            "verify_only_no_nginx_reload"
        )
    if blockers == ["host_boot_observation_older_than_target_window"]:
        return (
            "timer_deployed_and_services_readback_green_wait_for_next_all_host_reboot_"
            "event_or_approved_reboot_drill_to_prove_10_minute_slo"
        )
    return (
        "deploy_boot_triggered_slo_timer_and_collect_all_host_boot_probe_then_"
        "rerun_scorecard_until_status_slo_ready"
    )


def percent(value: float) -> int:
    return max(0, min(100, round(value)))


def build_required_checks(payload: dict[str, Any]) -> dict[str, bool]:
    controls = payload.get("source_controls")
    source_controls_present = (
        isinstance(controls, dict) and bool(controls) and all(controls.values())
    )
    host_boot_detection = payload.get("host_boot_detection")
    if not isinstance(host_boot_detection, dict):
        host_boot_detection = {}
    post_reboot_readiness = payload.get("post_reboot_readiness")
    if not isinstance(post_reboot_readiness, dict):
        post_reboot_readiness = {}
    stockplatform = payload.get("stockplatform_data_freshness")
    if not isinstance(stockplatform, dict):
        stockplatform = {}
    public_maintenance = payload.get("public_maintenance_fallback")
    if not isinstance(public_maintenance, dict):
        public_maintenance = {}
    windows99 = payload.get("windows99_vmware_autostart")
    if not isinstance(windows99, dict):
        windows99 = {}
    windows99_management = payload.get("windows99_management_channel")
    if not isinstance(windows99_management, dict):
        windows99_management = {}
    ssh_batch = windows99_management.get("ssh_batch")
    if not isinstance(ssh_batch, dict):
        ssh_batch = {}

    return {
        "source_controls_present": source_controls_present,
        "required_hosts_observed": not strings(host_boot_detection.get("missing_hosts")),
        "required_hosts_reachable": not strings(
            host_boot_detection.get("unreachable_hosts")
        ),
        "windows99_vmware_autostart_ready": (
            windows99.get("verify_ready") is True
        ),
        "windows_update_no_auto_reboot_ready": (
            windows99.get("windows_update_no_auto_reboot_ready") is True
        ),
        "windows99_management_channel_ready": (
            windows99.get("verify_ready") is True
            or windows99_management.get("remote_execution_channel_ready") is True
        ),
        "service_green": post_reboot_readiness.get("service_green") is True,
        "product_data_green": post_reboot_readiness.get("product_data_green") is True,
        "backup_core_green": post_reboot_readiness.get("backup_core_green") is True,
        "host_188_service_green": (
            post_reboot_readiness.get("host_188_service_green") is True
        ),
        "stockplatform_freshness_ok": (
            stockplatform.get("freshness_status") == "ok"
        ),
        "stockplatform_ingestion_ok": (
            stockplatform.get("ingestion_status") in {"ok", "unknown"}
        ),
        "public_maintenance_fallback_runtime_ready": (
            public_maintenance.get("ready") is True
        ),
        "fresh_reboot_window_observed": not strings(
            host_boot_detection.get("stale_hosts")
        ),
        "can_claim_slo": (
            payload.get("can_claim_all_services_recovered_within_target") is True
        ),
    }


def reboot_sop_current_phase(active_blockers: list[str], can_claim: bool) -> str:
    if can_claim and not active_blockers:
        return "slo_ready"
    if active_blockers == ["host_boot_observation_older_than_target_window"]:
        return "awaiting_next_reboot_or_approved_drill"
    host_boot_blockers = {
        "all_host_reboot_detection_missing",
        "stateful_reboot_event_detection_missing",
        "host_boot_probe_missing_hosts",
        "host_unreachable_after_reboot",
        "host_boot_observation_older_than_target_window",
        "host_uptime_unknown",
        "reboot_event_missing_required_hosts",
        "reboot_event_required_host_unreachable",
        "fresh_all_host_reboot_event_missing",
        "all_required_hosts_not_in_10_minute_reboot_window",
        "windows99_remote_execution_channel_unavailable",
        "windows99_vmware_autostart_readback_missing",
        "windows99_vmrun_missing",
        "windows99_vmware_vmx_missing",
        "windows99_vmware_autostart_config_not_ready",
        "windows99_vmware_guest_power_not_ready",
        "windows99_update_no_auto_reboot_policy_not_ready",
    }
    if any(blocker in host_boot_blockers for blocker in active_blockers):
        return "host_boot_detection_blocked"
    service_blockers = {
        "post_reboot_summary_missing",
        "post_start_blocked_not_zero",
        "service_green_not_1",
        "host_188_service_green_not_1",
        "wazuh_dashboard_degraded",
    }
    if any(blocker.startswith("public_") for blocker in active_blockers):
        return "public_maintenance_fallback_blocked"
    if any(blocker in service_blockers for blocker in active_blockers):
        return "post_reboot_service_readiness_blocked"
    if any("stockplatform" in blocker or "product_data" in blocker for blocker in active_blockers):
        return "product_data_freshness_blocked"
    if any("backup" in blocker for blocker in active_blockers):
        return "backup_readback_blocked"
    if "local_disk_free_below_minimum" in active_blockers:
        return "host_capacity_blocked"
    if any(
        blocker.startswith("host_")
        or blocker.startswith("awooop_")
        or blocker.startswith("conversation_event_hot_path_")
        for blocker in active_blockers
    ):
        return "host_pressure_blocked"
    return "slo_blocked"


def reboot_sop_primary_blocker(active_blockers: list[str]) -> str:
    priority = [
        "reboot_event_required_host_unreachable",
        "host_unreachable_after_reboot",
        "all_host_reboot_detection_missing",
        "stateful_reboot_event_detection_missing",
        "host_boot_probe_missing_hosts",
        "fresh_all_host_reboot_event_missing",
        "all_required_hosts_not_in_10_minute_reboot_window",
        "host_boot_observation_older_than_target_window",
        "host_uptime_unknown",
        "windows99_remote_execution_channel_unavailable",
        "windows99_vmware_autostart_readback_missing",
        "windows99_vmrun_missing",
        "windows99_vmware_vmx_missing",
        "windows99_vmware_autostart_config_not_ready",
        "windows99_vmware_guest_power_not_ready",
        "windows99_update_no_auto_reboot_policy_not_ready",
        "public_route_raw_5xx_without_maintenance_fallback",
        "public_route_unreachable_without_external_l1_fallback",
        "public_maintenance_fallback_runtime_readback_missing",
        "post_start_blocked_not_zero",
        "service_green_not_1",
        "host_188_service_green_not_1",
        "product_data_green_not_1",
        "stockplatform_freshness_blocked",
        "stockplatform_ingestion_blocked",
        "backup_core_green_not_1",
        "local_disk_free_below_minimum",
        "wazuh_dashboard_degraded",
    ]
    for blocker in priority:
        if blocker in active_blockers:
            return blocker
    return active_blockers[0] if active_blockers else ""


def reboot_sop_eta_or_wait_reason(
    payload: dict[str, Any],
    active_blockers: list[str],
    current_phase: str,
    primary_blocker: str,
) -> str:
    if current_phase == "slo_ready":
        return "recovered_within_10_minute_slo"
    sla_eta = payload.get("sla_recovery_eta")
    if not isinstance(sla_eta, dict):
        sla_eta = {}
    reboot_event = payload.get("reboot_event_detection")
    if not isinstance(reboot_event, dict):
        reboot_event = {}
    remaining = int_value(sla_eta.get("target_seconds_remaining"), 0)
    deadline_status = str(sla_eta.get("deadline_status") or "unknown")
    if remaining > 0:
        return f"target_window_remaining_{remaining}s_but_blocked_by_{primary_blocker or 'unknown'}"
    if reboot_event.get("readback_present") is not True:
        return "reboot_event_readback_missing_eta_unavailable"
    if deadline_status == "target_window_elapsed":
        return (
            "target_window_elapsed_eta_unavailable_until_fresh_all_host_reboot_"
            "event_and_probe"
        )
    if active_blockers:
        return f"eta_unavailable_until_primary_blocker_clears:{primary_blocker}"
    return "eta_unavailable"


def build_reboot_sop_progress(
    payload: dict[str, Any],
    active_blockers: list[str],
    readiness_percent: int,
) -> dict[str, Any]:
    can_claim = payload.get("can_claim_all_services_recovered_within_target") is True
    current_phase = reboot_sop_current_phase(active_blockers, can_claim)
    primary_blocker = reboot_sop_primary_blocker(active_blockers)
    eta_or_wait_reason = reboot_sop_eta_or_wait_reason(
        payload,
        active_blockers,
        current_phase,
        primary_blocker,
    )
    sla_eta = payload.get("sla_recovery_eta")
    if not isinstance(sla_eta, dict):
        sla_eta = {}
    return {
        "current_phase": current_phase,
        "eta_or_wait_reason": eta_or_wait_reason,
        "primary_blocker": primary_blocker,
        "active_blockers": active_blockers,
        "active_blocker_count": len(active_blockers),
        "readiness_percent": readiness_percent,
        "next_safe_action": str(payload.get("safe_next_step") or ""),
        "fixed_triage_order": strings(sla_eta.get("fixed_triage_order")),
    }


def build_active_blocker_action_matrix(
    active_blockers: list[str],
    primary_blocker: str,
) -> dict[str, Any]:
    items = [
        active_blocker_action_row(
            blocker=blocker,
            priority_order=index,
            is_primary=blocker == primary_blocker,
        )
        for index, blocker in enumerate(active_blockers, start=1)
    ]
    primary_action = next((item for item in items if item.get("is_primary") is True), {})
    return {
        "schema_version": "reboot_active_blocker_action_matrix_v1",
        "item_count": len(items),
        "telegram_alert_required_count": sum(
            1 for item in items if item.get("telegram_alert_required") is True
        ),
        "category_counts": count_by(items, "category"),
        "owner_lane_counts": count_by(items, "owner_lane"),
        "primary_blocker_action": primary_action,
        "items": items,
    }


def active_blocker_action_row(
    *,
    blocker: str,
    priority_order: int,
    is_primary: bool,
) -> dict[str, Any]:
    category = "reboot_slo_unknown"
    owner_lane = "sre_reboot_slo_scorecard"
    severity = "critical"
    next_safe_action = (
        "read_active_blocker_evidence_then_rerun_reboot_slo_scorecard_no_reboot"
    )
    post_verifier = (
        "scripts/reboot-recovery/reboot-auto-recovery-slo-scorecard.py "
        "--output <scorecard.json>"
    )
    evidence_inputs = ["scorecard.active_blockers"]
    controlled_apply_mode = "verifier_only_from_reboot_slo_lane"
    forbidden_actions = [
        "host_reboot",
        "service_restart",
        "docker_daemon_restart",
        "node_drain",
        "database_write_or_restore",
        "secret_value_read",
        "workflow_dispatch",
        "github_api",
        "force_push_or_ref_delete",
    ]

    if blocker.startswith("windows99_"):
        category = "windows99_vmware_autostart"
        owner_lane = "windows99_console_or_no_secret_management_channel"
        next_safe_action = (
            "collect_windows99_vmware_autostart_verify_readback_then_rerun_"
            "reboot_scorecard_no_secret_no_reboot"
        )
        if blocker == "windows99_remote_execution_channel_unavailable":
            next_safe_action = (
                "restore_windows99_no_secret_management_channel_or_collect_local_"
                "console_verify_readback_then_rerun_reboot_scorecard_no_reboot"
            )
        post_verifier = (
            "bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh "
            "--check && rerun_reboot_auto_recovery_slo_scorecard"
        )
        evidence_inputs = [
            "windows99_management_channel",
            "windows99_vmware_autostart",
            "windows99_verify_collection",
        ]
        controlled_apply_mode = "console_verify_or_no_secret_collector_only"
        forbidden_actions.extend(
            [
                "windows_password_or_secret_collection",
                "vm_power_change",
                "windows_update_policy_apply_from_scorecard",
                "manual_registry_edit",
            ]
        )
    elif blocker in {
        "all_host_reboot_detection_missing",
        "stateful_reboot_event_detection_missing",
        "host_boot_probe_missing_hosts",
        "host_unreachable_after_reboot",
        "host_boot_observation_older_than_target_window",
        "host_uptime_unknown",
        "reboot_event_missing_required_hosts",
        "reboot_event_required_host_unreachable",
        "fresh_all_host_reboot_event_missing",
        "all_required_hosts_not_in_10_minute_reboot_window",
    }:
        category = "host_boot_detection"
        owner_lane = "reboot_event_detector_and_host_probe"
        next_safe_action = (
            "rerun_reboot_event_detector_and_host_probe_verify_only_no_reboot"
        )
        post_verifier = (
            "scripts/reboot-recovery/reboot-auto-recovery-host-probe.sh && "
            "scripts/reboot-recovery/reboot-event-detector.py"
        )
        evidence_inputs = ["host_boot_detection", "reboot_event_detection"]
    elif blocker in {
        "post_reboot_summary_missing",
        "post_start_blocked_not_zero",
        "service_green_not_1",
        "host_188_service_green_not_1",
    }:
        category = "post_reboot_service_readiness"
        owner_lane = "service_readiness_and_k3s_workloads"
        next_safe_action = (
            "collect_post_reboot_readiness_summary_and_public_health_no_restart"
        )
        post_verifier = "scripts/reboot-recovery/post-reboot-readiness-summary.sh"
        evidence_inputs = ["post_reboot_readiness", "public_health_readback"]
    elif blocker == "backup_core_green_not_1" or "backup" in blocker:
        category = "backup_observability"
        owner_lane = "backup_health_and_restore_drill"
        next_safe_action = (
            "collect_backup_health_restore_drill_and_offsite_readback_no_restore"
        )
        post_verifier = "scripts/ops/backup-health-textfile-exporter.py --help"
        evidence_inputs = [
            "post_reboot_readiness.backup_core_green",
            "backup_health_textfile",
            "restore_drill_readback",
        ]
    elif blocker == "wazuh_dashboard_degraded":
        category = "security_observability"
        owner_lane = "wazuh_dashboard_and_security_readback"
        severity = "warning"
        next_safe_action = "collect_wazuh_dashboard_readback_no_service_restart"
        post_verifier = "read_wazuh_dashboard_or_registry_health_readback"
        evidence_inputs = ["post_reboot_readiness.wazuh_dashboard_degraded"]
    elif blocker.startswith("public_"):
        category = "public_maintenance_fallback"
        owner_lane = "public_route_maintenance_fallback"
        next_safe_action = (
            "run_public_maintenance_fallback_probe_and_rerun_scorecard_no_reload"
        )
        post_verifier = (
            "python3 scripts/reboot-recovery/public-maintenance-fallback-probe.py"
        )
        evidence_inputs = ["public_maintenance_fallback"]
        controlled_apply_mode = "check_mode_then_separate_edge_or_nginx_apply"
    elif "stockplatform" in blocker or "product_data" in blocker:
        category = "product_data_freshness"
        owner_lane = "stockplatform_freshness_and_ingestion"
        next_safe_action = (
            "inspect_stockplatform_freshness_ingestion_readback_then_check_mode_"
            "recovery_if_required_no_manual_db_write"
        )
        post_verifier = "stockplatform_public_api_freshness_and_ingestion_readback"
        evidence_inputs = ["stockplatform_data_freshness"]
        controlled_apply_mode = "check_mode_then_controlled_data_recovery_lane"
        forbidden_actions.extend(
            ["manual_zero_fill", "copy_previous_trading_day_data"]
        )
    elif blocker in {
        "host_pressure_high_load",
        "host_container_cpu_attribution_stale",
        "host_110_gitea_cpu_pressure",
        "host_188_postgres_cpu_pressure",
        "awooop_conversation_event_hot_path_index_drift_detected",
        "conversation_event_hot_path_index_migration_source_missing",
    }:
        category = "host_cpu_pressure"
        owner_lane = "host_pressure_controller"
        if blocker == "conversation_event_hot_path_index_migration_source_missing":
            next_safe_action = (
                "restore_conversation_event_hot_path_migration_source_then_rerun_"
                "host_pressure_and_reboot_slo_scorecard_no_restart"
            )
            post_verifier = (
                "source_controls_conversation_event_hot_path_index_migration_"
                "and_host_pressure_readback"
            )
            evidence_inputs = ["source_controls", "runtime_metric_readback"]
        else:
            next_safe_action = (
                "collect_sanitized_host_pressure_and_apply_allowed_playbook_if_"
                "verifier_green_no_restart"
            )
            post_verifier = "host_pressure_readback_and_reboot_slo_scorecard"
            evidence_inputs = ["host_pressure"]
        controlled_apply_mode = "playbook_check_mode_then_controlled_apply_if_low_risk"
        forbidden_actions.extend(
            ["unmask_legacy_runner_or_restore_generic_labels", "kill_process_without_gate"]
        )
    elif blocker == "local_disk_free_below_minimum":
        category = "host_capacity"
        owner_lane = "capacity_and_disk_hygiene"
        next_safe_action = "collect_disk_usage_inventory_and_prepare_cleanup_dry_run"
        post_verifier = "df_and_capacity_scorecard_readback"
        evidence_inputs = ["capacity"]
        controlled_apply_mode = "cleanup_dry_run_then_controlled_apply"

    return {
        "blocker": blocker,
        "priority_order": priority_order,
        "is_primary": is_primary,
        "category": category,
        "owner_lane": owner_lane,
        "telegram_alert": "RebootAutoRecoveryActiveBlocker",
        "telegram_alert_required": True,
        "telegram_severity": severity,
        "metric_name": "awoooi_reboot_auto_recovery_slo_active_blocker",
        "evidence_inputs": evidence_inputs,
        "next_safe_action": next_safe_action,
        "post_verifier": post_verifier,
        "controlled_apply_mode": controlled_apply_mode,
        "controlled_apply_authorized_by_scorecard": False,
        "forbidden_actions": unique_strings(forbidden_actions),
    }


def count_by(items: list[dict[str, Any]], key: str) -> dict[str, int]:
    counts: dict[str, int] = {}
    for item in items:
        value = str(item.get(key) or "unknown")
        counts[value] = counts.get(value, 0) + 1
    return counts


def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
    """Add machine-readable P0-006 readback fields to the source scorecard."""
    active_blockers = strings(payload.get("active_blockers"))
    host_boot_detection = payload.get("host_boot_detection")
    if not isinstance(host_boot_detection, dict):
        host_boot_detection = {}
    post_reboot_readiness = payload.get("post_reboot_readiness")
    if not isinstance(post_reboot_readiness, dict):
        post_reboot_readiness = {}
    stockplatform = payload.get("stockplatform_data_freshness")
    if not isinstance(stockplatform, dict):
        stockplatform = {}
    public_maintenance = payload.get("public_maintenance_fallback")
    if not isinstance(public_maintenance, dict):
        public_maintenance = {}
    windows99 = payload.get("windows99_vmware_autostart")
    if not isinstance(windows99, dict):
        windows99 = {}
    windows99_management = payload.get("windows99_management_channel")
    if not isinstance(windows99_management, dict):
        windows99_management = {}
    ssh_batch = windows99_management.get("ssh_batch")
    if not isinstance(ssh_batch, dict):
        ssh_batch = {}
    capacity = payload.get("capacity")
    if not isinstance(capacity, dict):
        capacity = {}
    latest_verify_metric = payload.get("latest_verify_only_metric")
    if not isinstance(latest_verify_metric, dict):
        latest_verify_metric = {}
    controls = payload.get("source_controls")
    if not isinstance(controls, dict):
        controls = {}

    required_checks = build_required_checks(payload)
    completed_check_count = sum(1 for value in required_checks.values() if value)
    required_check_count = len(required_checks)
    readiness_percent = percent(
        completed_check_count / max(required_check_count, 1) * 100
    )
    if (
        payload.get("can_claim_all_services_recovered_within_target") is True
        and not active_blockers
    ):
        readiness_percent = 100
    blocked_by_fresh_reboot_window_only = active_blockers == [
        "host_boot_observation_older_than_target_window"
    ]
    sop_progress = build_reboot_sop_progress(payload, active_blockers, readiness_percent)
    active_blocker_action_matrix = build_active_blocker_action_matrix(
        active_blockers,
        sop_progress["primary_blocker"],
    )
    source_control_ready_count = sum(1 for value in controls.values() if value)
    source_controls_present = (
        bool(controls) and source_control_ready_count == len(controls)
    )
    windows99_verify_collection = build_windows99_verify_collection_packet(
        windows99=windows99,
        host_boot_detection=host_boot_detection,
    )

    rollups = {
        "active_blocker_count": len(active_blockers),
        "active_blocker_action_count": active_blocker_action_matrix["item_count"],
        "active_blocker_action_category_counts": active_blocker_action_matrix[
            "category_counts"
        ],
        "active_blocker_action_owner_lane_counts": active_blocker_action_matrix[
            "owner_lane_counts"
        ],
        "primary_blocker_owner_lane": active_blocker_action_matrix[
            "primary_blocker_action"
        ].get("owner_lane", ""),
        "primary_blocker_action_category": active_blocker_action_matrix[
            "primary_blocker_action"
        ].get("category", ""),
        "telegram_active_blocker_alert_required_count": active_blocker_action_matrix[
            "telegram_alert_required_count"
        ],
        "readiness_percent": readiness_percent,
        "completed_check_count": completed_check_count,
        "required_check_count": required_check_count,
        "source_control_count": len(controls),
        "source_control_ready_count": source_control_ready_count,
        "source_controls_present": source_controls_present,
        "can_claim_all_services_recovered_within_target": (
            payload.get("can_claim_all_services_recovered_within_target") is True
        ),
        "observed_host_count": len(strings(host_boot_detection.get("observed_hosts"))),
        "missing_host_count": len(strings(host_boot_detection.get("missing_hosts"))),
        "unreachable_host_count": len(
            strings(host_boot_detection.get("unreachable_hosts"))
        ),
        "stale_host_count": len(strings(host_boot_detection.get("stale_hosts"))),
        "unknown_uptime_host_count": len(
            strings(host_boot_detection.get("unknown_uptime_hosts"))
        ),
        "post_start_blocked": int_value(post_reboot_readiness.get("post_start_blocked")),
        "service_green": post_reboot_readiness.get("service_green") is True,
        "product_data_green": post_reboot_readiness.get("product_data_green") is True,
        "backup_core_green": post_reboot_readiness.get("backup_core_green") is True,
        "host_188_service_green": (
            post_reboot_readiness.get("host_188_service_green") is True
        ),
        "blocked_by_fresh_reboot_window_only": blocked_by_fresh_reboot_window_only,
        "latest_verify_only_metric_present": bool(latest_verify_metric),
        "latest_verify_only_metric_ready": int_value(latest_verify_metric.get("ready")),
        "latest_verify_only_metric_blocker_count": int_value(
            latest_verify_metric.get("blocker_count")
        ),
        "latest_verify_only_metric_max_host_uptime_seconds": int_value(
            latest_verify_metric.get("max_host_uptime_seconds")
        ),
        "latest_verify_only_metric_last_run_timestamp": int_value(
            latest_verify_metric.get("last_run_timestamp")
        ),
        "stockplatform_freshness_status": str(
            stockplatform.get("freshness_status") or "unknown"
        ),
        "stockplatform_ingestion_status": str(
            stockplatform.get("ingestion_status") or "unknown"
        ),
        "stockplatform_freshness_blocker_count": len(
            strings(stockplatform.get("freshness_blockers"))
        ),
        "stockplatform_ingestion_blocker_count": len(
            strings(stockplatform.get("ingestion_blockers"))
        ),
        "public_maintenance_runtime_readback_present": (
            public_maintenance.get("runtime_readback_present") is True
        ),
        "public_maintenance_fallback_ready": (
            public_maintenance.get("ready") is True
        ),
        "public_route_raw_5xx_without_fallback_count": int_value(
            public_maintenance.get("raw_5xx_without_fallback_count")
        ),
        "public_route_unreachable_without_external_fallback_count": int_value(
            public_maintenance.get(
                "route_unreachable_without_external_fallback_count"
            )
        ),
        "public_maintenance_fallback_route_count": int_value(
            public_maintenance.get("maintenance_fallback_route_count")
        ),
        "windows99_vmware_readback_present": windows99.get("readback_present") is True,
        "windows99_vmrun_present": windows99.get("vmrun_present") is True,
        "windows99_vmware_config_ready": windows99.get("config_ready") is True,
        "windows99_vmware_power_ready": windows99.get("power_ready") is True,
        "windows99_update_no_auto_reboot_ready": (
            windows99.get("windows_update_no_auto_reboot_ready") is True
        ),
        "windows99_vmware_verify_ready": windows99.get("verify_ready") is True,
        "windows99_vmware_missing_vmx_count": len(
            strings(windows99.get("missing_vmx_aliases"))
        ),
        "windows99_vmware_powered_off_count": len(
            strings(windows99.get("powered_off_aliases"))
        ),
        "windows99_verify_collection_status": windows99_verify_collection["status"],
        "windows99_verify_collection_can_collect_no_secret": (
            windows99_verify_collection["can_collect_no_secret_verify"] is True
        ),
        "windows99_verify_collection_blocker_count": len(
            strings(windows99_verify_collection.get("collection_blockers"))
        ),
        "windows99_host99_reachable": (
            windows99_verify_collection["host99_reachable"] is True
        ),
        "windows99_host99_uptime_known": (
            windows99_verify_collection["host99_uptime_known"] is True
        ),
        "windows99_management_readback_present": (
            windows99_management.get("readback_present") is True
        ),
        "windows99_host_reachable": windows99_management.get("host_reachable") is True,
        "windows99_remote_execution_channel_ready": (
            windows99_management.get("remote_execution_channel_ready") is True
        ),
        "windows99_can_collect_vmware_verify_without_secret": (
            windows99_management.get("can_collect_vmware_verify_without_secret") is True
        ),
        "windows99_ssh_batch_ready": ssh_batch.get("ready") is True,
        "windows99_ssh_batch_status": str(ssh_batch.get("status") or "unknown"),
        "windows99_winrm_http_open": (
            windows99_management.get("winrm_http_open") is True
        ),
        "windows99_winrm_https_open": (
            windows99_management.get("winrm_https_open") is True
        ),
        "windows99_rdp_console_reachable": (
            windows99_management.get("rdp_console_reachable") is True
        ),
        "capacity_checked": capacity.get("checked") is True,
        "capacity_free_gib": capacity.get("free_gib"),
        "capacity_min_free_gib": capacity.get("min_free_gib"),
        "capacity_below_minimum": "local_disk_free_below_minimum" in active_blockers,
    }

    readback = {
        "workplan_id": "P0-006",
        "workplan_title": "主機重啟自動偵測、自動觸發與 10 分鐘恢復 SLO",
        "source_id": "reboot_auto_recovery_slo_scorecard",
        "status": str(payload.get("status") or "unknown"),
        "current_phase": sop_progress["current_phase"],
        "eta_or_wait_reason": sop_progress["eta_or_wait_reason"],
        "target_minutes": int_value(payload.get("target_minutes")),
        "safe_next_step": str(payload.get("safe_next_step") or ""),
        "next_safe_action": sop_progress["next_safe_action"],
        "primary_blocker": sop_progress["primary_blocker"],
        "primary_blocker_owner_lane": active_blocker_action_matrix[
            "primary_blocker_action"
        ].get("owner_lane", ""),
        "primary_blocker_action_category": active_blocker_action_matrix[
            "primary_blocker_action"
        ].get("category", ""),
        "active_blockers": active_blockers,
        "active_blocker_count": len(active_blockers),
        "active_blocker_action_count": active_blocker_action_matrix["item_count"],
        "telegram_active_blocker_alert_required_count": active_blocker_action_matrix[
            "telegram_alert_required_count"
        ],
        "readiness_percent": readiness_percent,
        "blocked_by_fresh_reboot_window_only": blocked_by_fresh_reboot_window_only,
        "required_checks": required_checks,
        "source_controls_present": source_controls_present,
        "windows99_vmware_verify_ready": rollups["windows99_vmware_verify_ready"],
        "windows99_update_no_auto_reboot_ready": rollups[
            "windows99_update_no_auto_reboot_ready"
        ],
        "windows99_verify_collection_status": rollups[
            "windows99_verify_collection_status"
        ],
        "windows99_verify_collection_can_collect_no_secret": rollups[
            "windows99_verify_collection_can_collect_no_secret"
        ],
        "windows99_remote_execution_channel_ready": rollups[
            "windows99_remote_execution_channel_ready"
        ],
        "windows99_ssh_batch_status": rollups["windows99_ssh_batch_status"],
        "public_maintenance_runtime_readback_present": rollups[
            "public_maintenance_runtime_readback_present"
        ],
        "public_maintenance_fallback_ready": rollups[
            "public_maintenance_fallback_ready"
        ],
        "public_route_raw_5xx_without_fallback_count": rollups[
            "public_route_raw_5xx_without_fallback_count"
        ],
        "public_route_unreachable_without_external_fallback_count": rollups[
            "public_route_unreachable_without_external_fallback_count"
        ],
        "runtime_write_authorized_by_this_scorecard": False,
        "host_reboot_authorized_by_this_scorecard": False,
        "workflow_trigger_authorized_by_this_scorecard": False,
        "secret_value_collection_allowed": False,
    }

    summary = {
        "reboot_auto_recovery_status": str(payload.get("status") or "unknown"),
        "reboot_auto_recovery_workplan_id": "P0-006",
        "reboot_auto_recovery_current_phase": sop_progress["current_phase"],
        "reboot_auto_recovery_eta_or_wait_reason": sop_progress[
            "eta_or_wait_reason"
        ],
        "reboot_auto_recovery_primary_blocker": sop_progress["primary_blocker"],
        "reboot_auto_recovery_readiness_percent": readiness_percent,
        "reboot_auto_recovery_active_blocker_count": len(active_blockers),
        "reboot_auto_recovery_can_claim_slo": (
            payload.get("can_claim_all_services_recovered_within_target") is True
        ),
        "reboot_auto_recovery_service_green": rollups["service_green"],
        "reboot_auto_recovery_product_data_green": rollups["product_data_green"],
        "reboot_auto_recovery_backup_core_green": rollups["backup_core_green"],
        "reboot_auto_recovery_host_188_service_green": rollups[
            "host_188_service_green"
        ],
        "reboot_auto_recovery_observed_host_count": rollups["observed_host_count"],
        "reboot_auto_recovery_stale_host_count": rollups["stale_host_count"],
        "reboot_auto_recovery_stockplatform_freshness_status": rollups[
            "stockplatform_freshness_status"
        ],
        "reboot_auto_recovery_stockplatform_ingestion_status": rollups[
            "stockplatform_ingestion_status"
        ],
        "reboot_auto_recovery_public_maintenance_fallback_ready": rollups[
            "public_maintenance_fallback_ready"
        ],
        "reboot_auto_recovery_public_route_raw_5xx_without_fallback_count": rollups[
            "public_route_raw_5xx_without_fallback_count"
        ],
        "reboot_auto_recovery_public_route_unreachable_without_external_fallback_count": (
            rollups["public_route_unreachable_without_external_fallback_count"]
        ),
        "reboot_auto_recovery_safe_next_step": readback["safe_next_step"],
        "reboot_auto_recovery_next_safe_action": readback["next_safe_action"],
        "reboot_auto_recovery_source_controls_present": source_controls_present,
        "reboot_auto_recovery_windows99_vmware_verify_ready": rollups[
            "windows99_vmware_verify_ready"
        ],
        "reboot_auto_recovery_windows99_update_no_auto_reboot_ready": rollups[
            "windows99_update_no_auto_reboot_ready"
        ],
        "reboot_auto_recovery_windows99_verify_collection_status": rollups[
            "windows99_verify_collection_status"
        ],
        "reboot_auto_recovery_windows99_verify_collection_can_collect_no_secret": (
            rollups["windows99_verify_collection_can_collect_no_secret"]
        ),
        "secret_values_collected": False,
        "github_api_used": False,
        "workflow_trigger_performed": False,
        "runtime_write_authorized": False,
    }

    payload["required_checks"] = required_checks
    payload["current_phase"] = sop_progress["current_phase"]
    payload["eta_or_wait_reason"] = sop_progress["eta_or_wait_reason"]
    payload["primary_blocker"] = sop_progress["primary_blocker"]
    payload["next_safe_action"] = sop_progress["next_safe_action"]
    payload["reboot_sop_progress"] = sop_progress
    payload["active_blocker_action_matrix"] = active_blocker_action_matrix
    payload["readback"] = readback
    payload["rollups"] = rollups
    payload["summary"] = summary
    payload["windows99_verify_collection"] = windows99_verify_collection
    return payload


def build_scorecard(args: argparse.Namespace) -> dict[str, Any]:
    target_seconds = args.target_minutes * 60
    generated_at = args.generated_at or datetime.now().astimezone().isoformat(timespec="seconds")
    required_hosts = set(args.required_hosts or REQUIRED_HOSTS)
    summary = parse_kv(read_text(args.summary_file))
    host_rows = parse_host_probe(read_text(args.host_probe_file))
    reboot_event = read_json_object(args.reboot_event_file)
    stockplatform = build_stockplatform_readback(
        summary=summary,
        freshness=read_json_object(args.stock_freshness_file),
        ingestion=read_json_object(args.stock_ingestion_file),
        generated_at=generated_at,
    )
    host_pressure = build_host_pressure_readback(read_json_object(args.host_pressure_file))
    public_maintenance = parse_public_maintenance_readback(
        args.public_maintenance_file
    )
    windows99 = parse_windows99_vmware_readback(read_text(args.windows99_vmware_file))
    windows99_management = parse_windows99_management_readback(
        args.windows99_management_file
    )
    controls = source_controls()
    free_gib = disk_free_gib(args.disk_path)

    blockers: list[str] = []
    for key, value in controls.items():
        if not value:
            blockers.append(key.replace("_present", "_missing"))

    host_aliases = {str(row.get("alias", "")) for row in host_rows}
    missing_hosts = sorted(required_hosts - host_aliases)
    unreachable_hosts = sorted(str(row.get("alias")) for row in host_rows if not row.get("reachable"))
    stale_hosts = sorted(
        str(row.get("alias"))
        for row in host_rows
        if row.get("reachable") and int_value(row.get("uptime_seconds"), target_seconds + 1) > target_seconds
    )
    unknown_uptime_hosts = sorted(
        str(row.get("alias"))
        for row in host_rows
        if row.get("reachable") and int_value(row.get("uptime_seconds"), -1) < 0
    )
    if not host_rows:
        blockers.append("all_host_reboot_detection_missing")
    if missing_hosts:
        blockers.append("host_boot_probe_missing_hosts")
    if unreachable_hosts:
        blockers.append("host_unreachable_after_reboot")
    if stale_hosts:
        blockers.append("host_boot_observation_older_than_target_window")
    if unknown_uptime_hosts:
        blockers.append("host_uptime_unknown")

    reboot_event_missing_hosts = strings(reboot_event.get("missing_hosts"))
    reboot_event_unreachable_hosts = strings(reboot_event.get("unreachable_hosts"))
    reboot_event_fresh_hosts = strings(reboot_event.get("fresh_boot_hosts"))
    reboot_event_detected = reboot_event.get("reboot_detected") is True
    all_required_in_reboot_window = reboot_event.get("all_required_hosts_in_reboot_window") is True
    target_seconds_remaining = int_value(reboot_event.get("target_seconds_remaining"), 0)
    reboot_deadline_status = str(
        reboot_event.get("recovery_deadline_status") or "event_readback_missing"
    )
    if not reboot_event:
        blockers.append("stateful_reboot_event_detection_missing")
    else:
        if reboot_event_missing_hosts:
            blockers.append("reboot_event_missing_required_hosts")
        if reboot_event_unreachable_hosts:
            blockers.append("reboot_event_required_host_unreachable")
        if not reboot_event_detected:
            blockers.append("fresh_all_host_reboot_event_missing")
        if not all_required_in_reboot_window:
            blockers.append("all_required_hosts_not_in_10_minute_reboot_window")

    service_green = truthy(summary.get("SERVICE_GREEN"))
    product_data_green = truthy(summary.get("PRODUCT_DATA_GREEN"))
    backup_core_green = truthy(summary.get("BACKUP_CORE_GREEN"))
    post_start_blocked = int_value(summary.get("POST_START_BLOCKED"), 999)
    wazuh_dashboard_degraded = truthy(summary.get("WAZUH_DASHBOARD_DEGRADED"))
    host_188_service_green = truthy(summary.get("HOST_188_SERVICE_GREEN"))
    if not summary:
        blockers.append("post_reboot_summary_missing")
    if post_start_blocked != 0:
        blockers.append("post_start_blocked_not_zero")
    if not service_green:
        blockers.append("service_green_not_1")
    if not product_data_green:
        blockers.append("product_data_green_not_1")
    if stockplatform["freshness_status"] == "blocked":
        blockers.append("stockplatform_freshness_blocked")
    if stockplatform["ingestion_status"] == "blocked":
        blockers.append("stockplatform_ingestion_blocked")
    if not backup_core_green:
        blockers.append("backup_core_green_not_1")
    if not host_188_service_green:
        blockers.append("host_188_service_green_not_1")
    if wazuh_dashboard_degraded:
        blockers.append("wazuh_dashboard_degraded")
    if free_gib is not None and free_gib < args.min_free_gib:
        blockers.append("local_disk_free_below_minimum")
    blockers.extend(strings(host_pressure.get("blockers")))
    blockers.extend(strings(public_maintenance.get("blockers")))
    blockers.extend(strings(windows99.get("blockers")))
    if (
        windows99.get("readback_present") is False
        and windows99_management.get("readback_present") is True
        and windows99_management.get("remote_execution_channel_ready") is not True
    ):
        blockers.append("windows99_remote_execution_channel_unavailable")

    max_uptime = max(
        [int_value(row.get("uptime_seconds"), 0) for row in host_rows if row.get("reachable")]
        or [0]
    )
    unique_blockers = sorted(set(blockers))
    can_claim = not unique_blockers
    safe_next_step = choose_safe_next_step(
        blockers=unique_blockers,
        stockplatform=stockplatform,
        host_pressure=host_pressure,
        public_maintenance=public_maintenance,
    )
    payload = {
        "schema_version": SCHEMA_VERSION,
        "generated_at": generated_at,
        "target_minutes": args.target_minutes,
        "target_seconds": target_seconds,
        "status": "slo_ready" if can_claim else "blocked_reboot_auto_recovery_slo_not_ready",
        "can_claim_all_services_recovered_within_target": can_claim,
        "source_controls": controls,
        "host_boot_detection": {
            "required_hosts": sorted(required_hosts),
            "observed_hosts": sorted(host_aliases),
            "missing_hosts": missing_hosts,
            "unreachable_hosts": unreachable_hosts,
            "stale_hosts": stale_hosts,
            "unknown_uptime_hosts": unknown_uptime_hosts,
            "max_observed_uptime_seconds": max_uptime,
            "host_rows": host_rows,
        },
        "reboot_event_detection": {
            "readback_present": bool(reboot_event),
            "reboot_detected": reboot_event_detected,
            "rebooted_hosts": strings(reboot_event.get("rebooted_hosts")),
            "fresh_boot_hosts": reboot_event_fresh_hosts,
            "changed_boot_id_hosts": strings(reboot_event.get("changed_boot_id_hosts")),
            "missing_hosts": reboot_event_missing_hosts,
            "unreachable_hosts": reboot_event_unreachable_hosts,
            "all_required_hosts_observed": reboot_event.get("all_required_hosts_observed") is True,
            "all_required_hosts_in_reboot_window": all_required_in_reboot_window,
            "state_file": reboot_event.get("state_file") if reboot_event else None,
            "state_written": reboot_event.get("state_written") if reboot_event else False,
        },
        "sla_recovery_eta": {
            "target_minutes": args.target_minutes,
            "target_seconds_remaining": target_seconds_remaining,
            "deadline_status": reboot_deadline_status,
            "estimated_recovery_status": (
                "inside_10_minute_window"
                if target_seconds_remaining > 0 and not unique_blockers
                else (
                    "inside_10_minute_window_with_blockers"
                    if target_seconds_remaining > 0
                    else "target_window_elapsed_or_not_detected"
                )
            ),
            "next_update_due_seconds": 60,
            "fixed_triage_order": [
                "99_vmware_autostart_and_vm_power",
                "host_boot_event_and_node_exporter",
                "public_routes_and_maintenance_fallback",
                "awoooi_k3s_workloads_and_registry",
                "stockplatform_public_api_and_freshness",
                "backup_health_and_offsite_evidence",
                "telegram_alert_delivery_readback",
            ],
        },
        "post_reboot_readiness": {
            "summary_present": bool(summary),
            "post_start_result": summary.get("POST_START_RESULT", "unknown"),
            "post_start_blocked": post_start_blocked,
            "service_green": service_green,
            "product_data_green": product_data_green,
            "backup_core_green": backup_core_green,
            "host_188_service_green": host_188_service_green,
            "wazuh_dashboard_degraded": wazuh_dashboard_degraded,
            "overall_declaration": summary.get("OVERALL_DECLARATION", "unknown"),
            "next_required_gates": summary.get("NEXT_REQUIRED_GATES", "unknown"),
        },
        "stockplatform_data_freshness": stockplatform,
        "host_pressure": host_pressure,
        "public_maintenance_fallback": public_maintenance,
        "windows99_vmware_autostart": windows99,
        "windows99_management_channel": windows99_management,
        "capacity": {
            "checked": free_gib is not None,
            "free_gib": round(free_gib, 3) if free_gib is not None else None,
            "min_free_gib": args.min_free_gib,
        },
        "active_blockers": unique_blockers,
        "safe_next_step": safe_next_step,
    }
    return enrich_machine_readback(payload)


def main() -> int:
    args = parse_args()
    payload = build_scorecard(args)
    text = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(text, encoding="utf-8")
    else:
        print(text, end="")
    return 0


if __name__ == "__main__":
    sys.exit(main())
