"""Gateway runtime status helpers: PID/lock/marker files under ``{HERMES_HOME}`` (one set per
home/profile) that tell whether the gateway daemon is running."""

import asyncio
import contextlib
import copy
import hashlib
import json
import logging
import math
import os
import re
import shlex
import signal
import subprocess
import sys
import threading
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable, NamedTuple, Optional

from hermes_constants import _get_platform_default_hermes_home, get_hermes_home, get_process_hermes_home
from hermes_cli._subprocess_compat import pid_exists_stdlib
from utils import atomic_json_write

if sys.platform == "win32":
    import msvcrt
else:
    import fcntl

_GATEWAY_KIND = "hermes-gateway"
_RUNTIME_STATUS_FILE = "gateway_state.json"
_LOCKS_DIRNAME = "gateway-locks"
_IS_WINDOWS = sys.platform == "win32"
_UNSET = object()
_GATEWAY_LOCK_FILENAME = "gateway.lock"
_gateway_lock_handle = None
# Windows byte-range locks are mandatory for other readers: lock a byte well past
# the JSON payload so status/PID readers can read while another process holds it.
_WINDOWS_LOCK_OFFSET = 1024 * 1024
_GATEWAY_RUNNING_PID_CACHE_TTL_SECONDS = 1.0
_gateway_running_pid_cache_lock = threading.Lock()
# key: (pid_path, cleanup_stale, include_runtime_status) -> (cached_at, file signature, pid)
_gateway_running_pid_cache: dict[tuple[str, bool, bool], tuple[float, tuple, Optional[int]]] = {}

logger = logging.getLogger(__name__)


class _RuntimeStatusWriter:
    """Persist the latest complete status snapshot on one daemon thread.

    Runtime status is diagnostic state, not an event log. While one write is
    blocked in filesystem I/O, newer submissions replace the single pending
    snapshot. This bounds memory and keeps every status producer off asyncio.
    """

    def __init__(self, write_fn: Optional[Callable[[Path, dict[str, Any]], None]] = None):
        self._write_fn = write_fn
        self._condition = threading.Condition()
        self._pending: Optional[tuple[int, Path, dict[str, Any]]] = None
        self._writing_generation = 0
        self._submitted_generation = 0
        self._completed_generation = 0
        self._successful_generation = 0
        self._last_error: Optional[BaseException] = None
        self._failure_logged = False
        self._thread: Optional[threading.Thread] = None

    def submit(self, path: Path, payload: dict[str, Any]) -> int:
        with self._condition:
            self._submitted_generation += 1
            generation = self._submitted_generation
            self._pending = (generation, path, copy.deepcopy(payload))
            if self._thread is None or not self._thread.is_alive():
                self._thread = threading.Thread(
                    target=self._run, daemon=True, name="gateway-runtime-status-writer")
                self._thread.start()
            self._condition.notify_all()
            return generation

    def wait(self, generation: int, timeout: Optional[float] = None) -> bool:
        """Block until ``generation`` (or a later snapshot) is persisted; ``False`` on timeout/failure."""
        deadline = None if timeout is None else time.monotonic() + max(timeout, 0.0)
        with self._condition:
            while (state := self.settled(generation)) is None:
                if deadline is None:
                    self._condition.wait()
                    continue
                remaining = deadline - time.monotonic()
                if remaining <= 0:
                    return False
                self._condition.wait(timeout=remaining)
            if not state and timeout is None and self._last_error is not None:
                raise self._last_error
            return state

    def flush(self, timeout: float = 2.0) -> bool:
        with self._condition:
            generation = self._submitted_generation
        return generation == 0 or self.wait(generation, timeout=timeout)

    def settled(self, generation: int) -> Optional[bool]:
        """``True`` once ``generation`` persisted, ``False`` once it can no longer, else ``None``."""
        with self._condition:
            if self._successful_generation >= generation:
                return True
            no_more_work = (
                self._completed_generation >= generation
                and self._writing_generation == 0
                and self._pending is None
            )
            return False if no_more_work else None

    def _run(self) -> None:
        while True:
            with self._condition:
                while self._pending is None:
                    self._condition.wait()
                generation, path, payload = self._pending
                self._pending = None
                self._writing_generation = generation
            error: Optional[BaseException] = None
            try:
                (self._write_fn or _write_json_file)(path, _merge_over_on_disk(path, payload))
            except BaseException as exc:
                error = exc
            with self._condition:
                self._writing_generation = 0
                self._completed_generation = max(self._completed_generation, generation)
                if error is None:
                    self._successful_generation = max(self._successful_generation, generation)
                    self._last_error = None
                else:
                    self._last_error = error
                self._condition.notify_all()
            if error is None:
                if self._failure_logged:
                    logger.info("Gateway runtime-status persistence recovered")
                    self._failure_logged = False
            elif not self._failure_logged:
                logger.warning(
                    "Failed to persist gateway runtime status; later updates will retry: %s", error)
                self._failure_logged = True
            else:
                logger.debug("Failed to persist gateway runtime status: %s", error)


_runtime_status_state_lock = threading.RLock()
_runtime_status_state_path: Optional[Path] = None
_runtime_status_state: Optional[dict[str, Any]] = None


def _merge_over_on_disk(path: Path, payload: dict[str, Any]) -> dict[str, Any]:
    """Lay the canonical snapshot over whatever is on disk right before writing. Out-of-process
    writers (the migration's compensator clearing multiplex-owned status, container_boot
    seeding ``desired_state``) stamp this file directly; the gateway's fields win, theirs survive."""
    existing = _read_json_file(path)
    return {**existing, **payload} if isinstance(existing, dict) else payload


_runtime_status_writer: Optional[_RuntimeStatusWriter] = None


def _get_runtime_status_writer() -> _RuntimeStatusWriter:
    """Lazily create the single writer; callers serialise on ``_runtime_status_state_lock``."""
    global _runtime_status_writer
    with _runtime_status_state_lock:
        if _runtime_status_writer is None:
            _runtime_status_writer = _RuntimeStatusWriter()
        return _runtime_status_writer


def flush_runtime_status(timeout: float = 2.0) -> bool:
    """Wait boundedly for all runtime-status updates submitted so far."""
    writer = _runtime_status_writer
    return True if writer is None else writer.flush(timeout=timeout)


async def flush_runtime_status_async(timeout: float = 2.0) -> bool:
    """Await the current writer generation without blocking the event loop."""
    writer = _runtime_status_writer
    if writer is None:
        return True
    # ``flush()`` returns at its deadline, so the worker thread is bounded.
    return await asyncio.to_thread(writer.flush, timeout=max(float(timeout), 0.0))


class StormInfo(NamedTuple):
    """Respawn-storm check result: start count, window, and backoff to sleep."""

    count: int
    window_s: float
    backoff_s: float


def record_start_and_check_storm(
    max_starts: int = 5, window_s: float = 120.0, *, backoff_cap_s: float = 300.0
) -> Optional[StormInfo]:
    """Record this start; :class:`StormInfo` when > ``max_starts`` landed in ``window_s``.
    Best-effort: a broken ``gateway-starts.log`` ledger is logged and swallowed, never fatal."""
    try:
        path = get_hermes_home() / "gateway-starts.log"
        path.parent.mkdir(parents=True, exist_ok=True)
        now = datetime.now(timezone.utc).timestamp()
        existing: list[float] = []
        if path.exists():
            for line in path.read_text(encoding="utf-8-sig").splitlines():
                line = line.strip()
                if not line:
                    continue
                try:
                    existing.append(float(line))
                except ValueError:
                    pass  # corrupt/truncated ledger line — skip, never fatal
        existing.append(now)
        recent = [ts for ts in existing if now - ts <= window_s]
        # Ring-buffer the persisted file so it stays bounded.
        to_write = existing[-max(max_starts * 4, 40):]
        tmp = path.with_suffix(".tmp")
        tmp.write_text("\n".join(repr(ts) for ts in to_write) + "\n", encoding="utf-8")
        os.replace(tmp, path)
        if len(recent) <= max_starts:
            return None
        backoff = min(backoff_cap_s, 5.0 * (2 ** min(len(recent) - max_starts, 6)))
        return StormInfo(count=len(recent), window_s=window_s, backoff_s=backoff)
    except Exception as _e:
        logger.debug("respawn-storm breaker bookkeeping failed (non-fatal): %s", _e)
        return None


def _get_process_hermes_home() -> Path:
    """Launch-home HERMES_HOME for identity files (PID, lock, status, markers):
    ``get_hermes_home()`` honors the per-session ``_HERMES_HOME_OVERRIDE`` and would misroute
    them."""
    return get_process_hermes_home()


def _canonical_hermes_home(path: Path | str) -> Path:
    """Stable absolute HERMES_HOME path for persisted identity data."""
    return Path(path).expanduser().resolve(strict=False)


def _same_hermes_home(left: Path | str, right: Path | str) -> bool:
    """Compare HERMES_HOME paths with the host platform's case semantics."""
    left_c = os.path.normcase(str(_canonical_hermes_home(left)))
    return left_c == os.path.normcase(str(_canonical_hermes_home(right)))


def recorded_gateway_home_conflicts(
    record: Optional[dict[str, Any]], *, expected_home: Optional[Path | str] = None
) -> bool:
    """True when a persisted gateway record names a DIFFERENT HERMES_HOME (cross-profile kill guard:
    profile B's stop must never SIGTERM profile A). ``expected_home`` overrides the comparison base.
    Legacy records without ``hermes_home`` prove nothing -> False; a comparison failure fails
    closed -> True."""
    recorded_home = record.get("hermes_home") if isinstance(record, dict) else None
    if not isinstance(recorded_home, str) or not recorded_home.strip():
        return False
    try:
        base = expected_home if expected_home is not None else _get_process_hermes_home()
        return not _same_hermes_home(recorded_home, base)
    except Exception:
        return True


# Mirrors hermes_cli.profiles._PROFILE_ID_RE -- duplicated so gateway identity code
# stays import-light (hermes_constants + stdlib only).
_PROFILE_LABEL_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")


def _profile_label_for_home(home: Path | str) -> Optional[str]:
    """Best-effort label: ``<root>/profiles/<name>`` -> name, root home -> "default", else None."""
    try:
        canonical = _canonical_hermes_home(home)
    except Exception:
        return None
    if canonical.parent.name == "profiles" and _PROFILE_LABEL_RE.match(canonical.name):
        return canonical.name
    import hermes_constants
    default_homes = (hermes_constants.get_default_hermes_root, _get_platform_default_hermes_home)
    for default_home in default_homes:
        with contextlib.suppress(Exception):
            if _same_hermes_home(canonical, default_home()):
                return "default"
    return None


def scoped_lock_owner_label(record: Optional[dict[str, Any]]) -> Optional[str]:
    """Profile label of a scoped-lock owner (None: PID-only wording): the validated ``profile``
    field stamped by :func:`acquire_scoped_lock`, else inferred from ``hermes_home`` (old locks)."""
    if not isinstance(record, dict):
        return None
    profile = record.get("profile")
    if isinstance(profile, str) and _PROFILE_LABEL_RE.match(profile.strip()):
        return profile.strip()
    home = record.get("hermes_home")
    return _profile_label_for_home(home) if isinstance(home, str) and home.strip() else None


def _get_pid_path() -> Path:
    return _get_process_hermes_home() / "gateway.pid"


def _get_gateway_lock_path(pid_path: Optional[Path] = None) -> Path:
    return (pid_path or _get_pid_path()).with_name(_GATEWAY_LOCK_FILENAME)


def _get_runtime_status_path() -> Path:
    return _get_process_hermes_home() / _RUNTIME_STATUS_FILE


def _get_lock_dir() -> Path:
    """Cross-profile rendezvous dir for machine-local locks; ``HERMES_GATEWAY_LOCK_DIR`` overrides.

    Scope is the **OS user**, not the kernel host: separate users have separate ``$HOME``s,
    separate ``~/.hermes`` profile roots and separate credentials, so "one gateway per host"
    means "one per host per OS user". Holds the token-scoped locks (:func:`acquire_scoped_lock`)
    and the host-role lock + rendezvous record (``gateway/host_rendezvous.py``); the per-home
    ``gateway.pid``/``gateway.lock`` above deliberately stay under each profile's HERMES_HOME.
    """
    override = os.getenv("HERMES_GATEWAY_LOCK_DIR")
    if override:
        return Path(override)
    # XDG spec: a relative $XDG_STATE_HOME is INVALID and must be ignored. Honouring one made the
    # lock dir CWD-relative, so two serves started from different directories shared no singleton.
    state_home_env = os.getenv("XDG_STATE_HOME") or ""
    state_home = Path(state_home_env) if os.path.isabs(state_home_env) else Path.home() / ".local" / "state"
    return state_home / "hermes" / _LOCKS_DIRNAME


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


# Epochs before 2000-01-01 are corrupt/hand-edited state (e.g. an accidental 0).
_EPOCH_MIN_PLAUSIBLE = 946684800.0  # 2000-01-01T00:00:00Z


def normalize_updated_at(value: Any) -> Optional[str]:
    """Coerce a persisted ``updated_at`` (ISO string, legacy epoch, hand edit, garbage) to the
    RFC3339 ``string | null`` that ``/api/status`` promises. ``str``: iff fromisoformat parses
    (trailing ``Z`` tolerated; naive -> UTC). Epoch: before 2000-01-01, > 1 day ahead or
    non-finite -> None. ``bool``/other -> None."""
    if isinstance(value, str):
        raw = value.strip()
        # Python < 3.11 fromisoformat rejects a trailing 'Z'; tolerate it.
        if raw.endswith(("Z", "z")):
            raw = raw[:-1] + "+00:00"
        try:
            parsed = datetime.fromisoformat(raw)
        except ValueError:
            return None
        return (parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)).isoformat()
    if isinstance(value, (int, float)) and not isinstance(value, bool):
        seconds = float(value)
        now = datetime.now(timezone.utc).timestamp()
        if not math.isfinite(seconds) or seconds < _EPOCH_MIN_PLAUSIBLE or seconds > now + 86400:
            return None
        try:
            return datetime.fromtimestamp(seconds, tz=timezone.utc).isoformat()
        except (OverflowError, OSError, ValueError):
            return None
    return None


# ``exit_reason`` values the out-of-loop watchdogs (gateway/shutdown_watchdog.py) stamp together with
# ``gateway_state: degraded`` right before they hard-exit a wedged process (#113372).
WATCHDOG_EXIT_REASONS = frozenset({"loop_liveness_watchdog", "shutdown_watchdog"})


def retained_gateway_state(runtime: Any) -> str:
    """What a NOT-running gateway's retained ``gateway_state.json`` says about it now:
    ``"startup_failed"`` (or a watchdog-stamped ``"degraded"``) only while the operator still
    wants it running, else ``"stopped"``.

    ``hermes gateway stop`` keeps the last ``startup_failed`` + ``exit_reason`` on disk for
    diagnostics and records the durable stop intent as ``desired_state``; a profile the operator
    stopped is "stopped", not a current failure. A watchdog exit (``degraded`` + an exit_reason in
    ``WATCHDOG_EXIT_REASONS``) is the same kind of current failure as ``startup_failed`` and is kept
    under the same rule, so the dashboard agrees with ``hermes gateway status``. Any other retained
    state of a dead process (``running``, ``starting``, missing) is just "stopped". Shared by
    ``/api/status`` and ``/api/messaging/platforms`` so the sidebar strip and the Channels page
    cannot disagree."""
    rt = runtime if isinstance(runtime, dict) else {}
    if rt.get("desired_state") != "stopped":
        if rt.get("gateway_state") == "startup_failed":
            return "startup_failed"
        if rt.get("gateway_state") == "degraded" and rt.get("exit_reason") in WATCHDOG_EXIT_REASONS:
            return "degraded"
    return "stopped"


def terminate_pid(
    pid: int, *, force: bool = False, expected_start_time: Optional[float] = None
) -> None:
    """Terminate a PID; POSIX SIGTERM/SIGKILL, Windows taskkill /T /F for force. Identity guard:
    Windows ``force`` REQUIRES a matching ``expected_start_time`` (taskkill on a recycled PID has
    killed svchost.exe); POSIX optional, but a provided mismatch refuses the kill everywhere.

    On POSIX an expectation is optional, but when the caller provides one and it no longer matches the live
    process, the kill is refused on every platform — a mismatched fingerprint always means the PID was
    recycled. See #89614.
    """
    if force and (_IS_WINDOWS or expected_start_time is not None):
        if expected_start_time is None:
            raise OSError(f"refusing to force-kill PID {pid} without a process start-time guard")
        current_start_time = _get_process_start_time(pid)
        if current_start_time is None:
            raise OSError(f"refusing to force-kill PID {pid}; process start time is unavailable")
        try:
            if not _start_times_agree(current_start_time, expected_start_time):
                raise OSError(f"refusing to force-kill PID {pid}; process identity changed")
        except (TypeError, ValueError) as exc:
            raise OSError(f"refusing to force-kill PID {pid}; malformed start time") from exc
    if not (force and _IS_WINDOWS):
        os.kill(pid, signal.SIGTERM if not force else getattr(signal, "SIGKILL", signal.SIGTERM))
        return
    # Hide flags: a bare taskkill spawn from windowless pythonw.exe would flash a conhost window.
    from hermes_cli._subprocess_compat import windows_hide_flags

    try:
        result = subprocess.run(
            ["taskkill", "/PID", str(pid), "/T", "/F"], capture_output=True, text=True,
            encoding="utf-8", errors="replace", timeout=10, creationflags=windows_hide_flags(),
        )
    except FileNotFoundError:
        os.kill(pid, signal.SIGTERM)
        return
    if result.returncode != 0:
        details = (result.stderr or result.stdout or "").strip()
        raise OSError(details or f"taskkill failed for PID {pid}")


def _start_times_agree(current: Any, *recorded: Any) -> bool:
    """Same process object: all fingerprints > 0 and within 1ms of ``current``; raises on junk."""
    cur = float(current)
    return cur > 0 and all(r > 0 and abs(r - cur) <= 0.001 for r in map(float, recorded))


# Same-host start-time readings can drift by ~1 s between the claim-time and a later liveness read
# (macOS ``kern.boottime`` adjustment, #117505). Both fingerprint scales are ×100 (Linux /proc ticks,
# psutil centiseconds), so 200 means 2 s on either platform — a recycled PID is essentially never
# that close to the original's start time.
START_TIME_DRIFT_TOLERANCE = 200


def start_time_fingerprints_match(recorded: Any, current: Any, tolerance: int = START_TIME_DRIFT_TOLERANCE) -> bool:
    """Liveness-reconciliation comparator for :func:`get_process_start_time` fingerprints: the
    recorded owner and the current reading are the same incarnation when they agree within
    ``tolerance``. Raises on junk; callers decide what an unreadable (``None``) side means."""
    return abs(int(current) - int(recorded)) <= tolerance


def _scope_hash(identity: str) -> str:
    return hashlib.sha256(identity.encode("utf-8")).hexdigest()[:16]


def _get_scope_lock_path(scope: str, identity: str) -> Path:
    return _get_lock_dir() / f"{scope}-{_scope_hash(identity)}.lock"


def _get_process_start_time(pid: int) -> Optional[int]:
    """Return a stable per-process start-time fingerprint, or None.

    Used as a PID-reuse guard: a ``(pid, start_time)`` pair uniquely identifies
    a process, so a recycled PID (same number, different process) yields a
    different value and is never mistaken for the original.

    On Linux this is field 22 of ``/proc/<pid>/stat`` (start time in clock
    ticks since boot, an int).  On platforms without ``/proc`` (macOS, Windows)
    we fall back to ``psutil.Process(pid).create_time()`` — a float epoch
    timestamp — quantized to an int (centiseconds) for stable equality.

    The two sources are never mixed on a single platform: ``/proc`` always
    succeeds first on Linux, and always fails on macOS/Windows so psutil is
    always used there.  Because the guard only compares the value recorded at
    spawn against the live value *on the same host*, the differing units across
    platforms are irrelevant — only same-source equality matters.
    """
    stat_path = Path(f"/proc/{pid}/stat")
    try:
        # Field 22 in /proc/<pid>/stat is process start time (clock ticks).
        return int(stat_path.read_text(encoding="utf-8").split()[21])  # windows-footgun: ok (/proc is BOM-free)
    except (FileNotFoundError, IndexError, PermissionError, ValueError, OSError):
        pass

    # No /proc (macOS / Windows): psutil is a hard dependency and exposes a
    # cross-platform creation time.  Quantize to centiseconds so repeated reads
    # of the same process compare equal without float-precision fragility.
    try:
        import psutil  # type: ignore
        return int(round(psutil.Process(pid).create_time() * 100))
    except Exception:
        return None


def get_process_start_time(pid: int) -> Optional[int]:
    """Public wrapper for retrieving a process start time when available."""
    return _get_process_start_time(pid)


def _read_process_cmdline(pid: int) -> Optional[str]:
    """Process command line as one string: /proc, then psutil, then ``ps``.

    Order is by cost, and this runs per live gateway on every roster/status poll. ``psutil`` reads
    the process table in-process (a ``sysctl`` on macOS) where ``ps`` costs a fork+exec — measured
    0.02ms against 4.2ms on macOS for the same string. It cannot always answer: on macOS it raises
    ``AccessDenied`` for a process owned by another user, which ``ps`` still reports, so ``ps``
    stays as the fallback rather than being replaced."""
    with contextlib.suppress(OSError):
        raw = Path(f"/proc/{pid}/cmdline").read_bytes()
        if raw:
            return raw.replace(b"\x00", b" ").decode("utf-8", errors="ignore").strip()
    with contextlib.suppress(Exception):
        import psutil  # type: ignore
        cmdline_parts = psutil.Process(pid).cmdline()
        if cmdline_parts:
            return " ".join(cmdline_parts)
    if not _IS_WINDOWS:
        with contextlib.suppress(OSError, subprocess.TimeoutExpired):
            result = subprocess.run(
                ["ps", "-p", str(pid), "-o", "command="],
                capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5,
            )
            if result.returncode == 0 and result.stdout.strip():
                return result.stdout.strip()
    return None



def inline_source_flag_index(tokens: list[str]) -> int | None:
    """Index of the ``-c`` token when *tokens* is an interpreter running INLINE SOURCE, else None.

    Everything after ``-c`` is data the inline program receives, not this process's own identity.
    The detached gateway restart watcher (``gateway._spawn_gateway_restart_watcher``) is spawned as
    ``python -c <watcher source> <old_pid> <python> -m hermes_cli.main gateway run``: its trailing
    argv is the command the watcher will LATER spawn, so every argv matcher used to read it as a
    live gateway. See #107002 and the "never infer process identity from argv substrings" rule.

    Only interpreter options may precede ``-c``; the first non-option token ends the option block
    (``python -m hermes_cli.main …`` therefore never matches).

    The walk is VALUE-AWARE: ``-X``/``-W``/``-Q`` and ``--check-hash-based-pycs``/``--jit`` take a
    SEPARATE operand, so a naive "first non-option token ends the block" walk mistakes that operand
    for the end of the block and never reaches the ``-c`` behind it (``python -X utf8 -c <src> …``
    was still read as a live gateway). The operand sets are the canonical ones in
    ``hermes_state_holders``, not a second hand-rolled copy.

    *tokens* must be CASE-PRESERVING: the operand-taking ``-Q``/``-W``/``-X`` differ from the
    operand-less ``-q``/``-b``, so a lowercased argv would skip the token after a plain ``-q``.
    """
    from hermes_state_holders import (
        _PYTHON_LONG_OPTIONS_WITH_OPERANDS,
        _PYTHON_SHORT_OPTIONS_WITH_OPERANDS,
    )

    index = 1
    while index < len(tokens):
        token = tokens[index]
        if token == "--":
            return None
        if token in _PYTHON_LONG_OPTIONS_WITH_OPERANDS:
            index += 2  # the next token is this option's operand, not the end of the option block
            continue
        if token.startswith("--"):
            index += 1  # ``--opt=value`` and operand-less long options
            continue
        if not token.startswith("-") or token == "-":
            return None
        # Clustered short options (``-uc``, ``-IsB``). An operand-taking letter consumes the rest of
        # the cluster as its attached value, or the following token when the cluster ends there --
        # so ``-Xc`` is ``-X c``, NOT an inline-source ``-c``.
        cluster = token[1:]
        for position, letter in enumerate(cluster):
            if letter == "c":
                return index
            if letter in _PYTHON_SHORT_OPTIONS_WITH_OPERANDS:
                index += 1 if cluster[position + 1 :] else 2
                break
        else:
            index += 1
    return None


def command_line_runs_inline_source(tokens: list[str]) -> bool:
    """True when *tokens* is an interpreter running INLINE SOURCE (``python -c <src> [args]``)."""
    return inline_source_flag_index(tokens) is not None


# Hermes' own inline bootstraps hand control to a Hermes entry point IN this process, so the argv
# they run with is this process's own identity; every other ``-c`` program keeps its trailing argv
# as data (#107002). Each pattern is one emitted source shape, anchored at both ends so a program
# merely CARRYING a bootstrap command line (the restart watcher's respawn argv) never matches.
_Q = r"""['"]?"""
_MAIN = rf"{_Q}__main__{_Q}"
_RUN_MODULE = rf"runpy\.run_module\(\s*{_Q}(?P<target>[\w.]+){_Q}\s*,\s*run_name\s*=\s*{_MAIN}\s*,\s*alter_sys\s*=\s*True\s*\)"
_BOOTSTRAPS = (
    # hermes_cli._launchers.runtime_command (store launcher, the Windows updater's relaunch)
    ("module", re.compile(rf"import os, sys, runpy;.*\b{_RUN_MODULE}", re.S)),
    # hermes_cli.venv_sync.relaunch_command: argv is assigned inside the source
    ("module", re.compile(rf"import sys, runpy; sys\.path\.insert\(.*\b{_RUN_MODULE}", re.S)),
    ("path", re.compile(
        rf"import sys, runpy; sys\.path\.insert\(.*\brunpy\.run_path\(\s*{_Q}(?P<target>[^'\"]+?){_Q}\s*,\s*run_name\s*=\s*{_MAIN}\s*\)",
        re.S)),
    # hermes_cli._launchers._launcher_script (the published POSIX shell / Windows .cmd launcher)
    ("entry", re.compile(r"import os, re, sys\s.*\bfrom\s+(?P<target>[\w.]+)\s+import\s+(?P<func>\w+)\b.*\bsys\.exit\(\s*(?P=func)\(\)\s*\)", re.S)),
    # hermes_cli._launchers._write_cmd_launcher: the launcher script, base64-encoded
    ("base64", re.compile(rf"import base64; exec\(base64\.b64decode\({_Q}(?P<target>[A-Za-z0-9+/=]+){_Q}\)\)")),
)
_ASSIGNED_ARGV = re.compile(r"\bsys\.argv\s*=\s*\[(.*?)\]\s*;")


def _bootstrap_entry(source: str, argv: list[str]) -> list[str] | None:
    """``[-m, <module>, *argv]`` (or ``[<path>, *argv]``) the inline *source* runs in-process, else None."""
    source = source.strip()
    kind, match = next(((k, m) for k, p in _BOOTSTRAPS if (m := p.fullmatch(source))), (None, None))
    if match is None:
        return None
    target = match["target"]
    if kind == "base64":
        import base64
        import binascii
        try:
            return _bootstrap_entry(base64.b64decode(target, validate=True).decode("utf-8"), argv)
        except (binascii.Error, UnicodeDecodeError):
            return None
    if kind == "entry":  # the launcher script's own ``--run-module <module>`` switch
        return ["-m", argv[1], *argv[2:]] if argv[:1] == ["--run-module"] and len(argv) > 1 else ["-m", target, *argv]
    if assigned := _ASSIGNED_ARGV.search(source):
        argv = [item.strip().strip("'\"") for item in assigned.group(1).split(",")][1:]
    return [target, *argv] if kind == "path" else ["-m", target, *argv]


def inline_bootstrap_argv(tokens: list[str]) -> list[str] | None:
    """*tokens* as the equivalent ``python -m <module> <argv…>`` when this interpreter's ``-c`` source
    is a Hermes bootstrap running an entry point in-process; None for any other inline source.

    Command lines usually arrive space-joined (``/proc``, psutil, ``ps``), which splits the source
    across tokens; the shortest token run that ends in a recognised tail is the source, whatever
    joined it, and the tokens after it are the entry point's argv.
    """
    index = inline_source_flag_index(tokens)
    if index is None:
        return None
    for end in range(index + 1, len(tokens)):
        if tokens[end].rstrip().endswith(")"):
            entry = _bootstrap_entry(" ".join(tokens[index + 1 : end + 1]), tokens[end + 1 :])
            if entry is not None:
                return [tokens[0], *entry]
    return None


def _gateway_command_subcommand(command: str | None) -> str | None:
    """Hermes gateway lifecycle subcommand from a command line, or None. No loose substring matches
    (``"gateway" in cmdline`` also matched ``gateway status`` / ``python -m tui_gateway``): needs a
    Hermes entrypoint plus the ``gateway`` subcommand, or a gateway-dedicated entrypoint. Tokenizes
    quote-aware (Windows paths with spaces); ``--profile``/``-p`` selectors are stripped anywhere in
    argv since ``_apply_profile_override`` removes them before argparse."""
    if not command:
        return None
    try:
        raw_tokens = shlex.split(command, posix=False)
    except ValueError:
        raw_tokens = command.split()
    # Strip surrounding quotes, normalize slashes + case per token.
    cased_tokens = [t.strip("\"'").replace("\\", "/") for t in raw_tokens]
    tokens = [t.lower() for t in cased_tokens]
    if not tokens:
        return None
    basenames = [t.rsplit("/", 1)[-1] for t in tokens]
    # ``python -c <src> … -m hermes_cli.main gateway run``: the trailing argv belongs to the program
    # the inline source will spawn later, not to this process (#107002). Case-preserving tokens:
    # the operand-taking ``-X``/``-W``/``-Q`` must not be conflated with ``-q``/``-b``.
    if command_line_runs_inline_source(cased_tokens):
        # …unless the source is a Hermes bootstrap running the entry point in THIS process (store
        # launcher, launcher script, venv_sync re-entry): then its argv is this process's (#124318).
        cased_tokens = inline_bootstrap_argv(cased_tokens)
        if cased_tokens is None:
            return None
        tokens = [t.lower() for t in cased_tokens]
        basenames = [t.rsplit("/", 1)[-1] for t in tokens]
    # The launchd job's osascript wrapper (gateway_launchd.launchd_program_arguments) carries the gateway argv
    # inside one JXA script string; the gateway itself is its child and is matched on its own command line.
    if basenames[0] == "osascript":
        return None
    # Gateway-dedicated entrypoints carry no subcommand to inspect.
    if any(t == "gateway/run.py" or t.endswith("/gateway/run.py") for t in tokens):
        return "run"
    # Atomic Hermes' bundled desktop runner shares HERMES_HOME with the CLI; without this,
    # `gateway run --replace` does not recognise it as a running gateway, skips the
    # terminate-and-scoped-lock-handoff path, and collides with its still-held scoped locks
    # (e.g. the Discord bot-token lock). See #22418.
    if any(b == "desktop-gateway.py" for b in basenames):
        return "run"
    if any(b in ("hermes-gateway", "hermes-gateway.exe") for b in basenames):
        return "run"
    joined = " ".join(tokens)
    if "hermes_cli.main" not in joined and "hermes_cli/main.py" not in joined and not any(
        b in ("hermes", "hermes.exe") for b in basenames
    ):
        return None
    # Drop --profile X / -p X / --profile=X / -p=X (consumes a VALUE of "gateway" too).
    filtered: list[str] = []
    skip_next = False
    for token in tokens:
        if skip_next:
            skip_next = False
        elif token in ("--profile", "-p"):
            skip_next = True
        elif not token.startswith(("--profile=", "-p=")):
            filtered.append(token)
    for i, token in enumerate(filtered):
        if token == "gateway":
            # Bare `hermes gateway` defaults to `run`.
            return filtered[i + 1] if i + 1 < len(filtered) else "run"
    return None


def gateway_spawn_intent_subcommand(command: str | None) -> str | None:
    """Gateway lifecycle subcommand a command line would EVENTUALLY launch, or None.

    The identity matcher (``_gateway_command_subcommand``) deliberately refuses ``python -c <src>
    …``: the trailing argv is the inline program's data, not that process's own identity (#107002).
    Callers that inspect a command line as SPAWN INTENT — "if I launch this, does a gateway runtime
    eventually appear?" — need the opposite answer, because
    ``gateway._spawn_gateway_restart_watcher`` hides a real ``… -m hermes_cli.main gateway run``
    behind exactly that wrapper. ``tests/_fixtures/live_system_guard.py`` is the canonical caller.

    Still no substring matching: the wrapper is peeled token-wise and each remaining suffix is
    handed to the same canonical matcher.
    """
    direct = _gateway_command_subcommand(command)
    if direct is not None or not command:
        return direct
    try:
        raw_tokens = shlex.split(command, posix=False)
    except ValueError:
        raw_tokens = command.split()
    cased_tokens = [t.strip("\"'").replace("\\", "/") for t in raw_tokens]
    flag_index = inline_source_flag_index(cased_tokens)
    if flag_index is None:
        return None
    # Skip the interpreter, its options, ``-c`` and the source literal; then try every suffix —
    # the embedded argv starts at an unknown offset (the watcher prefixes it with the old PID).
    start = flag_index + 2
    for i in range(start, len(cased_tokens)):
        nested = _gateway_command_subcommand(" ".join(raw_tokens[i:]))
        if nested is not None:
            return nested
    return None


def looks_like_gateway_command_line(command: str | None) -> bool:
    """True only for a real ``gateway run`` process command line."""
    return _gateway_command_subcommand(command) == "run"


def looks_like_gateway_runtime_command_line(command: str | None) -> bool:
    """True for command lines that can host the runtime (``run`` or ``restart``: without a service
    manager the manual restart fallback runs ``run_gateway()`` in-process). For validating
    Hermes-owned records / cleanup scans only; ``looks_like_gateway_command_line`` stays strict."""
    return _gateway_command_subcommand(command) in {"run", "restart"}


def _looks_like_gateway_process(pid: int) -> bool:
    """True when the live PID still looks like the Hermes gateway."""
    cmdline = _read_process_cmdline(pid)
    return bool(cmdline) and looks_like_gateway_command_line(cmdline)


def _record_looks_like_gateway(record: dict[str, Any]) -> bool:
    """Validate gateway identity from PID-file metadata when cmdline is unavailable."""
    argv = record.get("argv")
    if record.get("kind") != _GATEWAY_KIND or not isinstance(argv, list) or not argv:
        return False
    return looks_like_gateway_runtime_command_line(" ".join(str(part) for part in argv))


def _profile_name_for_home(profile_home: Path) -> Optional[str]:
    """Profile id for ``<root>/profiles/<name>``; None for the root/default home (bare gateway)."""
    return profile_home.name if profile_home.parent.name == "profiles" else None


def profile_flag_value(command: str) -> Optional[str]:
    """The ``-p``/``--profile`` argument of a command line, or None. Token equality is the only safe
    profile match: a substring test lets ``-p ops`` claim (and ``gateway stop`` SIGTERM) ``-p ops-2``."""
    tokens = command.split()
    for i, tok in enumerate(tokens):
        if tok.startswith("--profile="):
            return tok.partition("=")[2]
        if tok in ("-p", "--profile") and i + 1 < len(tokens):
            return tokens[i + 1]
    return None


_HERMES_HOME_ASSIGNMENT_RE = re.compile(r"(?:^|\s)hermes_home=(?:\"([^\"]*)\"|'([^']*)'|(\S+))")


def hermes_home_assignments(command: str) -> list[str]:
    """Values of every ``HERMES_HOME=<value>`` assignment in ``command`` (the caller lowercases
    and normalizes separators). Values are token-bounded, quotes stripped: the substring test
    this replaces let ``HERMES_HOME=/root/profiles/ops`` claim a ``/root/profiles/ops2`` gateway.
    The name is token-bounded too (``FOO=hermes_home=/x`` is not an assignment), and a trailing
    separator on the value is stripped -- ``HERMES_HOME=/root/.hermes/`` (systemd ``Environment=``
    or a shell wrapper spelling) is the same home as ``/root/.hermes``; callers strip the profile
    home the same way."""
    return [
        next(g for g in m.groups() if g is not None).rstrip("/")
        for m in _HERMES_HOME_ASSIGNMENT_RE.finditer(command)
    ]


def command_line_names_hermes_home(command_lc: str, home_lc: str) -> bool:
    """True when ``command_lc`` carries ``HERMES_HOME=<home_lc>`` (both lowercased, ``/``-separated,
    no trailing separator). Argv reaches us space-joined, so an unquoted value with a space in it
    (``HERMES_HOME=C:/Users/John Doe/.hermes``) is cut at the space by the token parser; a
    token-bounded literal match of the whole home recovers that spelling."""
    if home_lc in hermes_home_assignments(command_lc):
        return True
    return re.search(rf"(?:^|\s)hermes_home={re.escape(home_lc)}/?(?=\s|$)", command_lc) is not None


def _command_line_belongs_to_profile(command: str, profile_home: Path) -> bool:
    """True when a gateway command line belongs to ``profile_home`` (mirrors
    ``hermes_cli.gateway._matches_current_profile``): a stale state file can record a PID recycled
    onto ANOTHER profile's live gateway. Named profiles carry ``-p``/``--profile <name>`` or
    ``HERMES_HOME=`` on argv; the default gateway runs bare. Separators normalized."""
    command_lc = command.lower().replace("\\", "/")
    profile_name = _profile_name_for_home(profile_home)
    home_lc = str(profile_home).lower().replace("\\", "/").rstrip("/")
    if profile_name is not None and profile_name != "default":
        if profile_flag_value(command_lc) == profile_name.lower():
            return True
        return command_line_names_hermes_home(command_lc, home_lc)
    # Default profile: accept unless argv names ANOTHER profile (any spelling the CLI pre-parser
    # accepts, ``--profile=ops`` included -- a substring test let that gateway pass as the default's)
    # or a conflicting explicit HERMES_HOME= (its absence is not disqualifying -- HERMES_HOME usually
    # arrives via the env). ``--profile default`` names this profile: a hand-written launchd plist
    # mirrors the named-profile service shape, and rejecting it reported a live default gateway
    # as stopped (#100817).
    if profile_flag_value(command_lc) not in (None, "default"):
        return False
    return not hermes_home_assignments(command_lc) or command_line_names_hermes_home(command_lc, home_lc)


def _host_gateway_serves_home(pid: int, profile_home: Path) -> bool:
    """Does the ONE host gateway — PID ``pid`` — serve ``profile_home``'s profile?

    Argv cannot answer this: the host singleton runs ONE home's (usually bare/default) command line
    while multiplexing every profile, so :func:`_command_line_belongs_to_profile` rejects every
    secondary and the profile reads as "not running" while its messages are being served. The live
    served set is the only proof; the argv rule stays as the fallback when no record exists.
    """
    try:
        from gateway.host_attach import host_gateway, profile_name_for_home

        owner = host_gateway()
    except Exception:
        return False
    return owner is not None and owner.pid == pid and owner.serves(profile_name_for_home(profile_home))


def _record_matches_live_gateway_pid(
    record: dict[str, Any], pid: int, *, expected_home: Optional[Path] = None
) -> bool:
    """True when a live PID still identifies as this gateway record. The live command line wins (a
    stale record's argv must not make a recycled PID count as a gateway; with ``expected_home`` it
    must also belong to that profile — or serve it as the host multiplexer); unreadable cmdline
    (Windows/EACCES) -> persisted record."""
    live_cmdline = _read_process_cmdline(pid)
    if not live_cmdline:
        return _record_looks_like_gateway(record)
    if not looks_like_gateway_runtime_command_line(live_cmdline):
        return False
    if expected_home is not None and _host_gateway_serves_home(pid, expected_home):
        return True
    return expected_home is None or _command_line_belongs_to_profile(live_cmdline, expected_home)


def _record_argv() -> list[str]:
    """``sys.argv`` with the inline-source placeholder replaced by the entry point this process runs.

    The published launcher script (``_launchers._launcher_script``: the POSIX ``bin/hermes`` shell
    launcher and the Windows ``.cmd``) imports ``hermes_cli.main`` inside ``python -I -c <script>``,
    so ``sys.argv`` is ``["-c", "gateway", "run"]`` — a record the argv matcher can never accept
    once the live command line is unreadable (Windows/EACCES fallback in
    ``_record_matches_live_gateway_pid``). Recording the module path follows runpy's ``alter_sys``
    convention, which is what the ``--run-module`` and store-launcher forms already persist."""
    argv = list(sys.argv)
    entry = sys.modules.get("hermes_cli.main")
    if argv[:1] == ["-c"] and getattr(entry, "__file__", None):
        argv[0] = entry.__file__
    return argv


def _build_pid_record() -> dict:
    return {
        "pid": os.getpid(), "kind": _GATEWAY_KIND, "argv": _record_argv(),
        "start_time": _get_process_start_time(os.getpid()),
        # Scoped locks are machine-global; the owner's home lets a cross-profile
        # --replace place its takeover marker where the target will read it.
        "hermes_home": str(_canonical_hermes_home(_get_process_hermes_home())),
    }


def _get_code_identity_fields() -> dict[str, Any]:
    """Code identity of THIS process for ``gateway_state.json`` (restart picked up new code?).
    Lazy import keeps ``gateway.status`` free of ``hermes_cli`` at import time. Never raises.

    A gateway keeps serving the module versions it imported at startup, so stamping the identity into
    ``gateway_state.json`` lets `hermes update` (and the dashboard) prove whether a running gateway actually
    picked up new code after the restart phase — instead of assuming it did (#88654, #69754). Never raises;
    degrades to absent fields.
    """
    try:
        from hermes_cli.version_info import get_code_identity

        identity = get_code_identity()
        return {"code_sha": identity.get("sha"), "code_version": identity.get("version")}
    except Exception:
        return {}


def _pid_record_belongs_to_current_profile(record: Optional[dict[str, Any]]) -> bool:
    """True when the record's ``hermes_home`` matches the current process (legacy records: True);
    another HERMES_HOME's record must be ignored or the default gateway assumes its identity."""
    if not isinstance(record, dict):
        return False
    record_home = record.get("hermes_home")
    return not record_home or _same_hermes_home(record_home, _get_process_hermes_home())


def _build_runtime_status_record() -> dict[str, Any]:
    return {
        **_build_pid_record(), "gateway_state": "starting", "exit_reason": None,
        "restart_requested": False, "active_agents": 0, "platforms": {},
        "session_store": {"status": "unknown"}, "updated_at": _utc_now_iso(),
        **_get_code_identity_fields(),
    }


def _read_json_file(path: Path, *, bare_pid_ok: bool = False) -> Optional[dict[str, Any]]:
    """JSON object at ``path``, or None when absent/empty/unreadable/invalid. ``bare_pid_ok`` also
    accepts legacy bare-integer PID files as ``{"pid": N}``."""
    try:
        raw = path.read_text(encoding="utf-8-sig").strip()
    except (OSError, UnicodeDecodeError):
        # OSError: file vanished or permission flipped between exists() and
        # read. UnicodeDecodeError: file holds non-UTF-8 / binary garbage
        # (a truncated or clobbered status file). Either way it's unusable.
        return None
    if not raw:
        return None
    try:
        payload = json.loads(raw)
    except json.JSONDecodeError:
        payload = None
        if bare_pid_ok:
            with contextlib.suppress(ValueError):
                payload = int(raw)
    if bare_pid_ok and isinstance(payload, int):
        return {"pid": payload}
    return payload if isinstance(payload, dict) else None


def _write_json_file(path: Path, payload: dict[str, Any]) -> None:
    atomic_json_write(path, payload, indent=None, separators=(",", ":"))


def _unlink_quietly(path: Path) -> None:
    with contextlib.suppress(OSError):
        path.unlink(missing_ok=True)


def _read_pid_record(pid_path: Optional[Path] = None) -> Optional[dict]:
    return _read_json_file(pid_path or _get_pid_path(), bare_pid_ok=True)


def _read_gateway_lock_record(lock_path: Optional[Path] = None) -> Optional[dict[str, Any]]:
    return _read_json_file(lock_path or _get_gateway_lock_path(), bare_pid_ok=True)


def _pid_from_record(record: Optional[dict[str, Any]], key: str = "pid") -> Optional[int]:
    try:
        return int(record[key])
    except (KeyError, TypeError, ValueError):
        return None


def _start_times_conflict(recorded_start: Any, current_start: Any) -> bool:
    """PID-reuse guard: True only when BOTH start times are known and differ."""
    return None not in (recorded_start, current_start) and current_start != recorded_start


def _live_pid_from_record(record: Optional[dict[str, Any]]) -> Optional[int]:
    """Record's PID when it is alive and passes the start-time PID-reuse guard, else None."""
    pid = _pid_from_record(record)
    if pid is None or not _pid_exists(pid):
        return None
    if _start_times_conflict(record.get("start_time"), _get_process_start_time(pid)):
        return None
    return pid


def _clear_running_pid_cache() -> None:
    with _gateway_running_pid_cache_lock:
        _gateway_running_pid_cache.clear()


def _file_cache_signature(path: Path) -> tuple[bool, Optional[int], Optional[int]]:
    try:
        st = path.stat()
    except OSError:
        return (False, None, None)
    return (True, st.st_mtime_ns, st.st_size)


def _cleanup_invalid_pid_path(
    pid_path: Path, *, cleanup_stale: bool, unlink_lock: bool = True
) -> None:
    """Force-unlink a stale PID file + sibling lock (lock confirmed inactive, so no pid check).
    ``unlink_lock=False`` drops only the PID file: the caller saw the lock HELD."""
    if not cleanup_stale:
        return
    _clear_running_pid_cache()
    for path in (pid_path, _get_gateway_lock_path(pid_path)) if unlink_lock else (pid_path,):
        with contextlib.suppress(Exception):
            path.unlink(missing_ok=True)


def _try_acquire_file_lock(handle) -> bool:
    try:
        if _IS_WINDOWS:
            handle.seek(0, os.SEEK_END)
            if handle.tell() == 0:
                handle.write("\n")
                handle.flush()
            handle.seek(_WINDOWS_LOCK_OFFSET)
            msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
        else:
            fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        return True
    except (BlockingIOError, OSError):
        return False


def _pid_exists(pid: int) -> bool:
    """Cross-platform "is this PID alive" check that does NOT kill the target. CRITICAL on Windows:
    ``os.kill(pid, 0)`` sends ``CTRL_C_EVENT`` to the whole console group (bpo-14484), so prefer
    psutil, then ctypes ``OpenProcess`` (Windows) / ``os.kill(pid, 0)`` (POSIX). Zombies report
    dead: treating one as alive makes --replace wait forever under systemd Restart=always."""
    pid = int(pid)
    try:
        import psutil  # type: ignore
        # Best-effort zombie check: status-read failures fall through to pid_exists().
        # Windows has no POSIX zombies, and this probe costs ~7 ms per call — once per
        # registry entry inside the session file lock (#115578). Skip it on Windows and
        # let pid_exists() below (or the ctypes fallback) decide.
        probe_zombie = os.name != "nt"
        try:
            # A zombie (defunct) process is still in the process table, so ``psutil.pid_exists()`` returns
            # True for it — but it is already dead: SIGKILL has no effect and it cannot be a running
            # gateway. Treating a zombie as alive makes ``--replace`` wait for the old PID to die (it never
            # does, until its parent reaps it), then abort with exit 1 — a silent crash loop under systemd
            # ``Restart=always``, which respawns the gateway before reaping the previous process (issue
            # #42126). Report zombies as dead so the takeover proceeds. Best-effort: any failure to read
            # status (partial/stub psutil, access denied, transient race) falls through to the authoritative
            # ``pid_exists()`` below rather than raising.
            if probe_zombie and psutil.Process(pid).status() == psutil.STATUS_ZOMBIE:
                return False
        except getattr(psutil, "NoSuchProcess", ()):
            return False
        except Exception:
            pass
        return bool(psutil.pid_exists(pid))
    except ImportError:
        pass  # Fall through to stdlib fallback.
    return pid_exists_stdlib(pid)


def _release_file_lock(handle) -> None:
    with contextlib.suppress(OSError):
        if _IS_WINDOWS:
            handle.seek(_WINDOWS_LOCK_OFFSET)
            msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
        else:
            fcntl.flock(handle.fileno(), fcntl.LOCK_UN)


def acquire_gateway_runtime_lock() -> bool:
    """Claim the cross-process runtime lock; the OS releases it if the process dies."""
    global _gateway_lock_handle
    if _gateway_lock_handle is not None:
        return True
    path = _get_gateway_lock_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    try:
        handle = open(path, "a+", encoding="utf-8")
    except PermissionError:
        # Stale root-owned lock (launchd session that ran as root): the directory owner can
        # unlink it; retry once with a fresh file.
        try:
            path.unlink()
            handle = open(path, "a+", encoding="utf-8")
        except OSError:
            return False
    if not _try_acquire_file_lock(handle):
        handle.close()
        return False
    handle.seek(0)
    handle.truncate()
    json.dump(_build_pid_record(), handle)
    handle.flush()
    with contextlib.suppress(OSError):
        os.fsync(handle.fileno())
    _gateway_lock_handle = handle
    _clear_running_pid_cache()
    return True


def release_gateway_runtime_lock() -> None:
    """Release the gateway runtime lock when owned by this process."""
    global _gateway_lock_handle
    handle, _gateway_lock_handle = _gateway_lock_handle, None
    if handle is None:
        return
    _release_file_lock(handle)
    with contextlib.suppress(OSError):
        handle.close()
    _clear_running_pid_cache()


def owns_gateway_runtime_lock() -> bool:
    """True when THIS process holds the runtime lock. ``is_gateway_runtime_lock_active`` answers
    "does anyone?"; re-probing our own flock succeeds on POSIX, so only the handle discriminates."""
    return _gateway_lock_handle is not None


def _probe_lock_file(handle) -> bool:
    """True when another process holds the lock (a won probe is released); closes ``handle``."""
    try:
        held = not _try_acquire_file_lock(handle)
        if not held:
            _release_file_lock(handle)
        return held
    finally:
        with contextlib.suppress(OSError):
            handle.close()


def is_gateway_runtime_lock_active(lock_path: Optional[Path] = None) -> bool:
    """True when some process currently owns the gateway runtime lock."""
    resolved_lock_path = lock_path or _get_gateway_lock_path()
    if _gateway_lock_handle is not None and resolved_lock_path == _get_gateway_lock_path():
        return True
    if not resolved_lock_path.exists():
        return False
    try:
        handle = open(resolved_lock_path, "a+", encoding="utf-8")
    except PermissionError:
        # Stale root-owned lock (see acquire_gateway_runtime_lock): report inactive.
        _unlink_quietly(resolved_lock_path)
        return False
    return _probe_lock_file(handle)


def _strict_path_exists(path: Path, label: str) -> bool:
    """Like ``path.exists()`` but raises RuntimeError instead of False on EACCES-style errors."""
    try:
        path.stat()
        return True
    except FileNotFoundError:
        return False
    except OSError as exc:
        raise RuntimeError(f"{label} metadata is not inspectable: {exc}") from exc


def _is_gateway_runtime_lock_active_strict(lock_path: Path) -> bool:
    """Probe ownership without treating access failures as absence."""
    try:
        handle = open(lock_path, "r+", encoding="utf-8")
    except FileNotFoundError:
        return False
    except OSError as exc:
        raise RuntimeError(f"gateway runtime lock is not inspectable: {exc}") from exc
    try:
        return _probe_lock_file(handle)
    except OSError as exc:
        raise RuntimeError(f"gateway runtime lock probe failed: {exc}") from exc


def write_pid_file() -> None:
    """Write this process's PID record via O_CREAT|O_EXCL; a racing gateway's FileExistsError
    propagates for the caller to decide."""
    path = _get_pid_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    _write_json_excl(path, _build_pid_record())
    _clear_running_pid_cache()


def _write_json_excl(path: Path, record: dict[str, Any]) -> None:
    """Create ``path`` with O_CREAT|O_EXCL and dump ``record``; unlinks on a failed write."""
    fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as handle:
            json.dump(record, handle)
    except Exception:
        _unlink_quietly(path)
        raise


def _apply_set_fields(target: dict[str, Any], fields) -> None:
    """Assign each ``(key, value, coerce)`` whose value was explicitly passed (not ``_UNSET``)."""
    for key, value, coerce in fields:
        if value is not _UNSET:
            target[key] = coerce(value) if coerce is not None else value


def _coerce_session_store(session_store: Any) -> dict[str, str]:
    state = str(session_store.get("status") or "") if isinstance(session_store, dict) else ""
    return {"status": state if state in {"ok", "unavailable", "retrying"} else "unknown"}


def _prepare_runtime_status_update(
    *, gateway_state: Any = _UNSET, exit_reason: Any = _UNSET, restart_requested: Any = _UNSET,
    active_agents: Any = _UNSET, active_work: Any = _UNSET, platform: Any = _UNSET, platform_state: Any = _UNSET,
    error_code: Any = _UNSET, error_message: Any = _UNSET, needs_attention: Any = _UNSET,
    retrying_since: Any = _UNSET, served_profiles: Any = _UNSET, session_store: Any = _UNSET,
    multiplex_standalone_reason: Any = _UNSET,
    platform_metrics: Any = _UNSET,
    ingress_url: Any = _UNSET, listener_base: Any = _UNSET, clear_profile_platforms: bool = False,
    drop_profile_platforms: Optional[str] = None,
    load_existing: bool = True, reload_existing: bool = False,
) -> tuple[Path, dict[str, Any], dict[str, Any]]:
    """Merge one update into the process-wide canonical status snapshot."""
    global _runtime_status_state_path, _runtime_status_state
    path = _get_runtime_status_path()
    with _runtime_status_state_lock:
        if reload_existing or _runtime_status_state_path != path or _runtime_status_state is None:
            _runtime_status_state_path = path
            _runtime_status_state = (
                (_read_json_file(path) if load_existing else None) or _build_runtime_status_record())
        # The module snapshot is only ever reassigned (never mutated in place) and
        # submit() copies again, so the previous snapshot can be handed out as-is.
        previous_payload = _runtime_status_state
        payload = copy.deepcopy(previous_payload)
        current_record = _build_pid_record()
        payload.setdefault("platforms", {})
        if not isinstance(payload["platforms"], dict):
            payload["platforms"] = {}
        if clear_profile_platforms or drop_profile_platforms:
            drop_prefix = f"{drop_profile_platforms}:" if drop_profile_platforms else None
            payload["platforms"] = {
                k: v for k, v in payload["platforms"].items()
                if not isinstance(k, str) or ":" not in k
                or (drop_prefix is not None and not k.startswith(drop_prefix))
            }
        payload.update({key: current_record[key] for key in ("kind", "pid", "argv", "start_time")})
        payload["updated_at"] = _utc_now_iso()
        payload.update(_get_code_identity_fields())
        _apply_set_fields(payload, (
            ("gateway_state", gateway_state, None), ("exit_reason", exit_reason, None),
            ("restart_requested", restart_requested, bool),
            ("active_agents", active_agents, parse_active_agents),
            ("active_work", active_work, lambda v: list(v) if v else None),
            ("served_profiles", served_profiles, lambda v: list(v or [])),
            ("multiplex_standalone_reason", multiplex_standalone_reason, lambda v: str(v) if v else None),
            ("session_store", session_store, _coerce_session_store),
        ))
        if platform is not _UNSET:
            platform_payload = copy.deepcopy(payload["platforms"].get(platform, {}))
            if not isinstance(platform_payload, dict):
                platform_payload = {}
            if platform_state == "connected":
                needs_attention = False if needs_attention is _UNSET else needs_attention
                retrying_since = None if retrying_since is _UNSET else retrying_since
            _apply_set_fields(platform_payload, (
                ("state", platform_state, None), ("error_code", error_code, None),
                ("error_message", error_message, None),
                ("needs_attention", needs_attention, bool),
                ("retrying_since", retrying_since, None),
                ("metrics", platform_metrics, None),
                ("ingress_url", ingress_url, None),
                ("listener_base", listener_base, None),
            ))
            platform_payload.update(
                updated_at=_utc_now_iso(), writer_pid=current_record["pid"],
                writer_start_time=current_record["start_time"])
            payload["platforms"][platform] = platform_payload
        _runtime_status_state = payload
        return path, payload, previous_payload


def _emit_runtime_status_transition(
    previous_payload: dict[str, Any], payload: dict[str, Any]
) -> None:
    with contextlib.suppress(Exception):
        from agent.monitoring.gateway_health import emit_runtime_status_transition
        emit_runtime_status_transition(previous_payload, payload)

def write_runtime_status(
    *, reload_existing: bool = False, wait_timeout: Optional[float] = None, **fields: Any,
) -> bool:
    """Synchronously persist status for CLI callers and off-loop startup.

    ``wait_timeout`` bounds how long the caller waits for durable persistence.
    A timed-out update remains queued for the single background writer.
    Keyword ``fields`` are those of ``_prepare_runtime_status_update``.
    """
    with _runtime_status_state_lock:
        path, payload, previous_payload = _prepare_runtime_status_update(
            reload_existing=reload_existing, **fields)
        writer = _get_runtime_status_writer()
        generation = writer.submit(path, payload)
    # Report the transition once it is queued (matching ``publish_runtime_status``): a
    # timed-out update is still written by the background writer, so its transition happened.
    _emit_runtime_status_transition(previous_payload, payload)
    return writer.wait(generation, timeout=wait_timeout)


def publish_runtime_status(**fields: Any) -> int:
    """Merge and enqueue status without waiting for filesystem persistence.

    Keyword ``fields`` are those of ``_prepare_runtime_status_update``.
    """
    with _runtime_status_state_lock:
        path, payload, previous_payload = _prepare_runtime_status_update(
            load_existing=False, **fields)
        generation = _get_runtime_status_writer().submit(path, payload)
    _emit_runtime_status_transition(previous_payload, payload)
    return generation


def read_runtime_status(path: Optional[Path] = None) -> Optional[dict[str, Any]]:
    """Read ``gateway_state.json``; ``path`` lets callers inspect another profile's file."""
    return _read_json_file(path or _get_runtime_status_path())


# Max age of a ``gateway_state.json`` snapshot before its liveness claim is suspect: an older record
# outlived an ungracefully-killed writer (taskkill /F, OOM, power loss) — or, with the PID alive, the
# housekeeping thread that re-stamps ``updated_at`` every tick has wedged (#113372). 2x the 60 s
# housekeeping interval.
_RUNTIME_STATUS_STALE_TTL_S = 120


def runtime_status_is_stale(
    record: Optional[dict[str, Any]], ttl_s: int = _RUNTIME_STATUS_STALE_TTL_S
) -> bool:
    """True when the snapshot's ``updated_at`` is older than ``ttl_s`` (or missing/unparseable)."""
    return not isinstance(record, dict) or _marker_is_stale(record.get("updated_at") or "", ttl_s)


def runtime_status_heartbeat_age_s(record: Optional[dict[str, Any]]) -> Optional[int]:
    """Whole seconds since the snapshot's ``updated_at``; None when missing/unparseable (an
    unparseable stamp is a stale *file*, not a wedged heartbeat)."""
    updated_at = normalize_updated_at(record.get("updated_at")) if isinstance(record, dict) else None
    if not updated_at:
        return None
    return max(0, int((datetime.now(timezone.utc) - datetime.fromisoformat(updated_at)).total_seconds()))


def runtime_status_pid_is_live(record: Optional[dict[str, Any]]) -> bool:
    """True when the snapshot's PID is alive and passes the start-time PID-reuse guard."""
    return _live_pid_from_record(record) is not None


def parse_active_agents(raw: Any) -> int:
    """Coerce ``active_agents`` to a non-negative int; shared by writer and both HTTP readers."""
    try:
        return max(0, int(raw))
    except (TypeError, ValueError):
        return 0


# Live, serving states: a valid begin-drain target. ``degraded`` is a serving gateway with a parked
# platform (a dead watchdog-stamped ``degraded`` is already excluded by ``gateway_running=False``).
_DRAINABLE_GATEWAY_STATES = frozenset({"running", "degraded"})


def derive_gateway_busy(*, gateway_running: bool, gateway_state: Any, active_agents: Any) -> bool:
    """Busy iff live, serving (``running``/``degraded``), and ``active_agents > 0`` -- the contract NAS gates on. Liveness
    keys off ``gateway_running``, NEVER ``updated_at`` (a stale heartbeat is a health warning, not death)."""
    if not derive_gateway_drainable(gateway_running=gateway_running, gateway_state=gateway_state):
        return False
    return parse_active_agents(active_agents) > 0


def derive_gateway_drainable(*, gateway_running: bool, gateway_state: Any) -> bool:
    """Drainable iff live and serving; independent of ``active_agents`` (idle drains finish)."""
    return bool(gateway_running) and gateway_state in _DRAINABLE_GATEWAY_STATES


@dataclass(frozen=True)
class GatewayLiveness:
    """Resolved gateway liveness for one dashboard surface. ``source``: which ladder rung answered
    (logging/tests only -- never branch product behavior on it). ``probe_error``: a rung raised;
    lets fail-open callers tell "down" from "unknown"."""

    running: bool
    pid: Optional[int]
    source: str
    health_body: Optional[dict[str, Any]] = None
    probe_error: bool = False
    # The multiplexer's own ``gateway_state.json`` when the ``multiplexer`` rung answered: a served
    # profile writes no runtime record of its own, so its platform states live there under
    # ``<profile>:<platform>`` keys.
    runtime: Optional[dict[str, Any]] = None


def profile_name_for_home(profile_home: Path) -> Optional[str]:
    """Profile id of any Hermes home: ``<root>/profiles/<name>`` → ``<name>``, the default root →
    ``"default"``, anything else → None. Multiplex-only makes ``default`` an ordinary served
    profile, so reporting surfaces need a name for it too."""
    home = Path(profile_home)
    named = _profile_name_for_home(home)
    if named:
        return named
    try:
        from hermes_constants import get_default_hermes_root
        if home.resolve() == Path(get_default_hermes_root()).resolve():
            return "default"
    except Exception:
        return None
    return None


def multiplexer_liveness_for_profile(profile_dir: Path) -> Optional[tuple[int, dict[str, Any]]]:
    """``(pid, host gateway_state.json)`` when the ONE live host gateway serves the profile whose home
    is ``profile_dir``; None for a home it does not serve or when no gateway owns the host role.

    Multiplex-only: ``default`` is just another served profile, not the owner of a private topology —
    resolving from the host rendezvous record (``gateway/host_topology.py``) is what lets it be
    reported as SERVED rather than only as owner. A served profile owns no
    ``gateway.pid``/``gateway_state.json`` (#97120), so every PID-file rung of the dashboard ladder
    reports it stopped while ``hermes -p X status`` says running — the two must agree.
    """
    name = profile_name_for_home(Path(profile_dir))
    if not name:
        return None
    from gateway.host_topology import host_gateway_topology
    from hermes_cli.gateway import named_profile_served_by_running_multiplexer
    from hermes_cli.gateway_multiplex_served import live_default_gateway_pid
    from hermes_constants import get_default_hermes_root
    # The roster is matched by NAME, and the multiplexer only serves ``<default root>/profiles/<name>``:
    # a profile directory copied to another root (sandbox, restore-from-backup) keeps the name but is
    # not the home being served, so it must not borrow the multiplexer's PID.
    if name != "default" and not _same_hermes_home(profile_dir, get_default_hermes_root() / "profiles" / name):
        return None
    topology = host_gateway_topology()
    # The multiplexer's record lives in the home that LAUNCHED it; a named-hosted multiplexer leaves
    # a possibly stale standalone record at the default root, which must not be projected.
    launch_home = get_default_hermes_root()
    if topology is not None and topology.serves(name):
        pid: Optional[int] = topology.pid
        launch_home = topology.home or launch_home
    elif name != "default" and named_profile_served_by_running_multiplexer(name):
        # Config-derived fallback for a record that predates ``served_profiles``.
        pid = live_default_gateway_pid()
    else:
        return None
    if pid is None:
        return None
    return pid, read_runtime_status(launch_home / "gateway_state.json") or {}


def shared_listener_mirror_platforms(runtime: Optional[dict[str, Any]], profile: str) -> dict[str, Any]:
    """Entries for the api_server/webhook mirrors a served ``profile`` gets from the DEFAULT's
    listener. The multiplexer never builds those adapters for a secondary (``gateway.run_adapters``
    skips them: ``SHARED_LISTENER_MIRROR_PLATFORMS``), so the record has no ``<profile>:api_server``
    entry and every reader fell through to ``pending_restart`` — "Restart needed" forever while
    ``/p/<profile>/v1/...`` answered. Only a live default entry is mirrored; its state is the profile's
    state, plus the ``/p/<profile>`` URL the client must actually call.
    """
    from gateway.config import SHARED_LISTENER_MIRROR_PATHS, SHARED_LISTENER_MIRROR_PLATFORMS
    plats = (runtime or {}).get("platforms")
    if not profile or profile == "default" or not isinstance(plats, dict):
        return {}
    mirrored: dict[str, Any] = {}
    for name in sorted(SHARED_LISTENER_MIRROR_PLATFORMS):
        entry = plats.get(name)
        if not isinstance(entry, dict) or entry.get("state") not in {"connected", "connecting", "retrying"}:
            continue
        # api_server and webhook bind separate ports; each mirror hangs off its own listener. A record
        # from an older gateway carries no ``listener_base``: connected, URL unknown.
        base = entry.get("listener_base")
        url = f"{base}/p/{profile}{SHARED_LISTENER_MIRROR_PATHS.get(name, '')}" if isinstance(base, str) and base else None
        mirrored[name] = {k: v for k, v in entry.items() if k != "listener_base"}
        mirrored[name].update(ingress_url=url, mirrored_from="default")
    return mirrored


def profile_platforms_from_multiplexer(runtime: Optional[dict[str, Any]], profile: str) -> dict[str, Any]:
    """The ``<profile>:<platform>`` entries of a multiplexer record, re-keyed to bare platform names — the
    same shape a standalone gateway for ``profile`` writes into its own ``gateway_state.json`` — plus the
    default listener's api_server/webhook mirrors the profile is served through (``ingress_url`` set)."""
    plats = (runtime or {}).get("platforms")
    if not isinstance(plats, dict):
        return {}
    prefix = f"{profile}:"
    own = {key[len(prefix):]: value for key, value in plats.items()
           if isinstance(key, str) and key.startswith(prefix) and isinstance(value, dict)}
    if profile == "default":
        # The flat keys ARE the default's own adapters (a multiplex host's primary map is always
        # ``default``, whoever launched it; a standalone gateway writes only flat keys). Dropping
        # them projected ``{}`` for the one profile the record never prefixes → "Restart needed"
        # forever on /api/status and the Messaging card (#123088, #123869).
        own.update({key: value for key, value in plats.items()
                    if isinstance(key, str) and ":" not in key and isinstance(value, dict)})
    return {**shared_listener_mirror_platforms(runtime, profile), **own}


def resolve_gateway_liveness(
    *, profile_dir: Optional[Path] = None, runtime: Any = _UNSET,
    health_probe: Optional[Callable[[], tuple[bool, Optional[dict[str, Any]]]]] = None,
    use_cache: bool = True, pid_probe: Optional[Callable[..., Optional[int]]] = None,
    runtime_reader: Optional[Callable[..., Optional[dict[str, Any]]]] = None,
    runtime_pid_probe: Optional[Callable[..., Optional[int]]] = None,
) -> GatewayLiveness:
    """Single source of truth for "is the gateway up?" across dashboard surfaces. Ladder, most to
    least authoritative: (1) PID file + runtime lock (scoped to ``profile_dir``; cached by default
    so polling does not re-flock ``gateway.lock``); (2) caller-supplied HTTP health probe (gateway
    in another container); (3) LOCAL runtime status PID validated against the live process table
    with ``expected_home`` (a recycled PID of another profile never counts; pass ``runtime`` if
    already read); (4) for a named ``profile_dir`` only, the live default multiplexer that records the
    profile in ``served_profiles`` (a served profile writes no identity files of its own). ``*_probe``/
    ``runtime_reader`` are the dashboard's injection/test seam. A rung
    that raises degrades to the next (never 500 a status endpoint) and sets ``probe_error``.

    Before this existed, ``/api/status`` and ``/api/messaging/platforms`` each open-coded their own ladder
    and disagreed on the same page load — the sidebar read "running" while the Channels page rendered "The
    gateway is not running."  Three deployments hit it: a cross-container gateway (only ``/api/status`` ran
    the HTTP health probe), a profile-scoped dashboard (only ``/api/status`` passed the profile's paths, so
    messaging borrowed another profile's runtime state — issue #71211), and a launch-service-managed gateway
    with no PID file (only some callers used the runtime-status fallback).
    """
    _pid_probe = pid_probe or (get_running_pid_cached if use_cache else get_running_pid)
    _runtime_reader = runtime_reader or read_runtime_status
    _runtime_pid_probe = runtime_pid_probe or get_runtime_status_running_pid
    probe_error = False
    scoped = profile_dir is not None

    def guarded(fn, *args, fallback=None, **kwargs):
        nonlocal probe_error
        try:
            return fn(*args, **kwargs)
        except Exception:
            probe_error = True
            return fallback

    # Zero-arg call when unscoped: callers monkeypatch with zero-arg lambdas and
    # /api/status's cache signature is keyed on the call shape.
    pid = guarded(_pid_probe, profile_dir / "gateway.pid") if scoped else guarded(_pid_probe)
    if pid is not None:
        return GatewayLiveness(running=True, pid=pid, source="pid")
    health_body: Optional[dict[str, Any]] = None
    if health_probe is not None:
        alive, health_body = guarded(health_probe, fallback=(False, None))
        if alive:
            # Display-only PID: it belongs to the remote container.
            remote_pid = health_body.get("pid") if health_body else None
            return GatewayLiveness(
                running=True, pid=remote_pid, source="health", health_body=health_body
            )
    if runtime is _UNSET:
        reader_kwargs = {"path": profile_dir / "gateway_state.json"} if scoped else {}
        runtime = guarded(_runtime_reader, **reader_kwargs)
    # A scoped home with no ``gateway_state.json`` gets an EMPTY record, never ``None``: ``None`` makes
    # the probe re-read the PROCESS home's record and lend that gateway's PID to a home it does not
    # own (a profile directory copied out of another root).
    probe_kwargs = {"expected_home": profile_dir} if scoped else {}
    runtime_pid = guarded(_runtime_pid_probe, {} if (scoped and runtime is None) else runtime, **probe_kwargs)
    if runtime_pid is not None:
        return GatewayLiveness(
            running=True, pid=runtime_pid, source="runtime_status", health_body=health_body
        )
    # (4) A named profile served by the live default multiplexer: no identity files of its own, but
    # the multiplexer IS its gateway (mirrors `hermes -p X status` / `gateway list`). Unscoped, the
    # question is about the process's OWN home — which is a named profile inside a pooled
    # `hermes --profile X serve` (the Desktop's per-profile backend answers its REST without
    # `?profile=`), so it takes the same rung instead of reporting the served profile stopped.
    own_home = profile_dir if scoped else _get_process_hermes_home()
    served = guarded(multiplexer_liveness_for_profile, own_home)
    if served is not None:
        mux_pid, mux_runtime = served
        return GatewayLiveness(
            running=True, pid=mux_pid, source="multiplexer", health_body=health_body, runtime=mux_runtime
        )
    return GatewayLiveness(
        running=False, pid=None, source="none", health_body=health_body, probe_error=probe_error
    )


def get_runtime_status_running_pid(
    runtime: Optional[dict[str, Any]] = None, *, expected_home: Optional[Path] = None
) -> Optional[int]:
    """Live gateway PID from the runtime status record, or None: the ``get_running_pid()`` fallback
    for launch-service-managed gateways with a fresh ``gateway_state.json`` but no ``gateway.pid``.
    ``expected_home`` scopes the OS-identity check to another profile's home so a PID recycled onto
    a different profile's gateway is not reported running for the dead one."""
    payload = runtime if runtime is not None else read_runtime_status()
    if not isinstance(payload, dict):
        return None
    if payload.get("gateway_state") in {None, "stopped", "startup_failed"}:
        return None
    pid = _live_pid_from_record(payload)
    if pid is None:
        return None
    # The record's hermes_home must match the home asked about (this process unscoped) so a stale
    # or copied record cannot lend another home's gateway identity; legacy records without the
    # stamp prove nothing either way and fall through to the live command-line check.
    if expected_home is None and not _pid_record_belongs_to_current_profile(payload):
        return None
    if expected_home is not None and recorded_gateway_home_conflicts(payload, expected_home=expected_home):
        return None
    if not _record_matches_live_gateway_pid(payload, pid, expected_home=expected_home):
        return None
    return pid


def live_gateway_pid_for_home(home: Path) -> Optional[int]:
    """Verified PID of the gateway owned by ``home`` (pid file + runtime lock first, then the runtime
    status record), or None. Every reader of another home's gateway identity goes through this so
    they all prove the same thing: the PID passes the start-time reuse guard, its live command line is
    a gateway's belonging to ``home``, and the record is not ``stopped``. Bare PID existence is not
    identity -- a stale record whose PID was recycled by an unrelated process lent it ``served_profiles``
    and put phantom gateways into the update inventory (#109680) -- while a launch-service gateway whose
    ``gateway.pid`` was unlinked is still live (#110166). Never unlinks ``home``'s identity files."""
    home = Path(home)
    # Cached: dashboard surfaces poll this for every served profile; the cache invalidates on any
    # pid/lock file change, so a stopped or replaced gateway is seen at once.
    pid = get_running_pid_cached(home / "gateway.pid", cleanup_stale=False)
    if pid is not None:
        return pid
    return get_runtime_status_running_pid(read_runtime_status(home / "gateway_state.json"), expected_home=home)


def remove_pid_file() -> None:
    """Remove the PID file only if it belongs to this process: during --replace the old process's
    atexit can fire AFTER the new process wrote its own record."""
    with contextlib.suppress(Exception):
        path = _get_pid_path()
        file_pid = _pid_from_record(_read_json_file(path))
        if file_pid is not None and file_pid != os.getpid():
            return  # Belongs to a different process — leave it alone.
        path.unlink(missing_ok=True)
        _clear_running_pid_cache()


def _scoped_lock_record_is_stale(existing: dict[str, Any], existing_pid: Optional[int]) -> bool:
    """True when a foreign scoped-lock record no longer names a live gateway: PID missing/dead,
    start time changed (PID reuse), or the live process is not a gateway -- a readable cmdline says
    so (also catches boot-time PID+start_time collisions; systemd spawns deterministically);
    cmdline unreadable AND start_time unknown on either side => the lock record's own argv is the
    only signal left. Stopped (SIGTSTP) processes look alive to _pid_exists; stale so --replace
    works."""
    if existing_pid is None or not _pid_exists(existing_pid):
        return True
    recorded_start = existing.get("start_time")
    current_start = _get_process_start_time(existing_pid)
    if _start_times_conflict(recorded_start, current_start):
        return True
    if not _looks_like_gateway_process(existing_pid):
        if _read_process_cmdline(existing_pid) is not None:
            return True
        if None in (recorded_start, current_start) and not _record_looks_like_gateway(existing):
            return True
    return _process_is_stopped(existing_pid)


def _process_is_stopped(pid: int) -> bool:
    """True for a stopped / tracing-stop state (T/t) in ``/proc/<pid>/status``."""
    with contextlib.suppress(OSError):
        for line in Path(f"/proc/{pid}/status").read_text(encoding="utf-8").splitlines():
            if line.startswith("State:"):
                return line.split()[1] in {"T", "t"}
    return False


def acquire_scoped_lock(
    scope: str, identity: str, metadata: Optional[dict[str, Any]] = None
) -> tuple[bool, Optional[dict[str, Any]]]:
    """Acquire a machine-local lock keyed by scope + identity (one Telegram token across homes)."""
    lock_path = _get_scope_lock_path(scope, identity)
    lock_path.parent.mkdir(parents=True, exist_ok=True)
    record = {
        **_build_pid_record(), "scope": scope, "identity_hash": _scope_hash(identity),
        "metadata": metadata or {}, "updated_at": _utc_now_iso(),
    }
    # Profile label for cross-profile conflict diagnostics ("token already in use (PID 559)" alone
    # does not say WHICH profile). Omitted when not inferable; readers fall back to hermes_home.
    profile = _profile_label_for_home(_get_process_hermes_home())
    if profile:
        record["profile"] = profile
    existing = _read_json_file(lock_path)
    if existing is None and lock_path.exists():
        # Empty/invalid JSON: previous process died between O_EXCL create and json.dump().
        _unlink_quietly(lock_path)
    if existing:
        existing_pid = _pid_from_record(existing)
        # Our own PID: always self-reacquire. start_time guards reuse of OTHER PIDs; requiring
        # equality here rejects reconnects when the on-disk record has start_time null.
        # Same live PID as this process: always self-reacquire. ``start_time`` is a PID-reuse guard for
        # *other* PIDs; it cannot distinguish two processes that share the caller's own PID (impossible
        # while we are alive). Requiring start_time equality here falsely rejects reconnects when the
        # on-disk record has ``start_time: null`` (older writers / psutil failure at first write) while the
        # freshly built record has a real value — the gateway then reports itself as the foreign squatter of
        # its own token (#81468).
        if existing_pid == os.getpid():
            _write_json_file(lock_path, record)
            return True, existing
        if not _scoped_lock_record_is_stale(existing, existing_pid):
            return False, existing
        # Rename to a tombstone instead of unlink(): with unlink()+O_EXCL two racing starters
        # could both win. os.replace() lets exactly one claim it; a failed replace means another
        # racer claimed it and O_EXCL below decides.
        with contextlib.suppress(OSError):
            tombstone = lock_path.with_name(lock_path.name + ".stale")
            os.replace(lock_path, tombstone)
            _unlink_quietly(tombstone)
    try:
        _write_json_excl(lock_path, record)
    except FileExistsError:
        return False, _read_json_file(lock_path)
    return True, None


def release_scoped_lock(scope: str, identity: str) -> None:
    """Release a scope lock owned by this PID. No start_time equality check: on-disk null vs a live
    fingerprint would wedge reconnects."""
    lock_path = _get_scope_lock_path(scope, identity)
    if (_read_json_file(lock_path) or {}).get("pid") == os.getpid():
        _unlink_quietly(lock_path)


def release_all_scoped_locks(
    *, owner_pid: Optional[int] = None, owner_start_time: Optional[int] = None
) -> int:
    """Remove scoped lock files (--replace cleanup); returns the count removed. With ``owner_pid``
    only that gateway's records go (``owner_start_time`` narrows against PID reuse)."""
    lock_dir = _get_lock_dir()
    if not lock_dir.exists():
        return 0
    removed = 0
    for lock_file in lock_dir.glob("*.lock"):
        if owner_pid is not None:
            record = _read_json_file(lock_file) or {}
            if _pid_from_record(record) != owner_pid or (
                owner_start_time is not None and record.get("start_time") != owner_start_time
            ):
                continue
        with contextlib.suppress(OSError):
            lock_file.unlink(missing_ok=True)
            removed += 1
    return removed


# ── --replace takeover marker ─────────────────────────────────────────
# SIGTERM exits the gateway with code 1 so Restart=on-failure revives it after unexpected
# kills -- which would also revive a --replace target (flap loop against the replacer). The
# replacer therefore writes a short-lived marker naming the target PID + start_time BEFORE
# SIGTERM; the target's shutdown handler treats a matching marker as a planned takeover and
# exits 0. Unlinked once consumed, so a stale one can grief at most one future shutdown on
# the same PID, within _TAKEOVER_MARKER_TTL_S.
# When a new gateway starts with ``--replace``, it SIGTERMs the existing gateway so it can take over the bot
# token. ``hermes.service`` + ``hermes- gateway.service``). See #5646.
_TAKEOVER_MARKER_FILENAME = ".gateway-takeover.json"
_TAKEOVER_MARKER_TTL_S = 60  # Marker older than this is treated as stale
_PLANNED_STOP_MARKER_FILENAME = ".gateway-planned-stop.json"
_PLANNED_STOP_MARKER_TTL_S = 60


def _get_takeover_marker_path(hermes_home: Optional[Path] = None) -> Path:
    """Takeover marker path; ``hermes_home`` is given only for a verified cross-home handoff."""
    home = _canonical_hermes_home(hermes_home or _get_process_hermes_home())
    return home / _TAKEOVER_MARKER_FILENAME


def _get_planned_stop_marker_path() -> Path:
    return _get_process_hermes_home() / _PLANNED_STOP_MARKER_FILENAME


def _marker_is_stale(written_at: str, ttl_s: int) -> bool:
    try:
        age = datetime.now(timezone.utc) - datetime.fromisoformat(written_at)
        return age.total_seconds() > ttl_s
    except (TypeError, ValueError):
        return True


def _read_live_pid_marker(path: Path, ttl_s: int) -> Optional[tuple[dict[str, Any], int, Any]]:
    """``(record, target_pid, target_start_time)`` for a usable marker, else None. Malformed/expired
    markers can never match anyone, so they are unlinked here (must not wedge a new instance)."""
    record = _read_json_file(path)
    if not record:
        return None
    target_pid = _pid_from_record(record, "target_pid")
    if target_pid is None or _marker_is_stale(record.get("written_at") or "", ttl_s):
        _unlink_quietly(path)
        return None
    return record, target_pid, record.get("target_start_time")


def _pid_marker_names_self(target_pid: int, target_start_time: Any) -> bool:
    """PID match with an optional start-time PID-reuse guard (watcher probe + consume). Both start
    times known -> must match; either unknown -> PID equality decides (bounded by the marker TTL):
    ``_get_process_start_time`` is None without /proc (macOS, native Windows -- where the
    planned-stop watcher matters most) and requiring a match there would misclassify a legitimate
    ``hermes gateway stop`` as an unexpected exit revived by the service manager."""
    if target_pid != os.getpid():
        return False
    our_start_time = _get_process_start_time(target_pid)
    return None in (target_start_time, our_start_time) or target_start_time == our_start_time


def _consume_pid_marker_for_self(path: Path, *, ttl_s: int) -> bool:
    parsed = _read_live_pid_marker(path, ttl_s)
    if parsed is None:
        return False
    record, target_pid, target_start_time = parsed
    # Cross-profile guard: new markers name the verified TARGET home, which permits a deliberate
    # cross-HERMES_HOME --replace while ignoring a marker accidentally written into another
    # profile's directory. Legacy markers have no target field: keep the same-replacer-home rule.
    # See #29092.
    our_home = _get_process_hermes_home()
    target_home = record.get("target_hermes_home")
    if target_home is not None:
        if not isinstance(target_home, str) or not _same_hermes_home(target_home, our_home):
            return False
    else:
        replacer_home = record.get("replacer_hermes_home")
        if replacer_home is not None and not _same_hermes_home(replacer_home, our_home):
            return False
    matches = _pid_marker_names_self(target_pid, target_start_time)
    _unlink_quietly(path)
    return matches


def write_takeover_marker(
    target_pid: int, *, target_home: Optional[Path] = None, target_start_time: Any = _UNSET
) -> bool:
    """Record that ``target_pid`` is being replaced by this process; True on success. Captures the
    target's ``start_time`` (PID-reuse guard) + a timestamp for TTL. A verified cross-home handoff
    passes ``target_home`` + validated ``target_start_time`` so the marker lands in the target's
    home; such callers must fail closed on False (the target's supervisor could revive it)."""
    try:
        marker_home = _canonical_hermes_home(target_home or _get_process_hermes_home())
        if target_start_time is _UNSET:
            target_start_time = _get_process_start_time(target_pid)
        return _write_marker(_get_takeover_marker_path(marker_home), {
            "target_pid": target_pid, "target_start_time": target_start_time,
            "target_hermes_home": str(marker_home), "replacer_pid": os.getpid(),
            "replacer_hermes_home": str(_canonical_hermes_home(_get_process_hermes_home())),
            "written_at": _utc_now_iso(),
        })
    except OSError:
        return False


def _write_marker(path: Path, record: dict[str, Any]) -> bool:
    """Atomically write a marker record; False (never raise) on OS failure."""
    try:
        _write_json_file(path, record)
        return True
    except OSError:
        return False


def consume_takeover_marker_for_self() -> bool:
    """Consume the takeover marker; True => planned takeover (exit 0); unlinked on match/stale."""
    return _consume_pid_marker_for_self(_get_takeover_marker_path(), ttl_s=_TAKEOVER_MARKER_TTL_S)


def clear_takeover_marker(target_home: Optional[Path] = None) -> None:
    """Remove the takeover marker unconditionally. Safe to call repeatedly."""
    _unlink_quietly(_get_takeover_marker_path(target_home))


def _validated_scoped_lock_gateway_owner(record: dict[str, Any]) -> Optional[tuple[int, int, Path]]:
    """Resolve a live scoped-lock owner to a verified ``(pid, start_time, home)``. A lock file is
    only a claim: the record, the target home's PID record, and the live process must agree on
    PID, start-time, gateway identity, and home. Missing legacy metadata fails closed."""
    if not isinstance(record, dict) or not _record_looks_like_gateway(record):
        return None
    owner_pid = _pid_from_record(record)
    owner_start_time = record.get("start_time")
    raw_home = record.get("hermes_home")
    if (
        owner_pid is None or owner_pid <= 0 or owner_pid == os.getpid()
        or not isinstance(owner_start_time, int) or isinstance(owner_start_time, bool)
        or not isinstance(raw_home, str) or not raw_home.strip()
        or not Path(raw_home).expanduser().is_absolute()
    ):
        return None
    target_home = _canonical_hermes_home(raw_home)
    if _scoped_lock_owner_state(owner_pid, owner_start_time) != "same":
        return None
    live_cmdline = _read_process_cmdline(owner_pid)
    if live_cmdline is not None and not looks_like_gateway_runtime_command_line(live_cmdline):
        return None
    # The target home's own PID record must corroborate the claim.
    pid_record = _read_json_file(target_home / "gateway.pid") or {}
    pid_record_home = pid_record.get("hermes_home")
    if (
        not _record_looks_like_gateway(pid_record)
        or _pid_from_record(pid_record) != owner_pid
        or pid_record.get("start_time") != owner_start_time
        or not isinstance(pid_record_home, str)
        or not _same_hermes_home(pid_record_home, target_home)
    ):
        return None
    return owner_pid, owner_start_time, target_home


def _scoped_lock_owner_state(owner_pid: int, owner_start_time: int) -> str:
    """Return ``same``, ``exited``, or ``unknown`` for a validated owner."""
    if not _pid_exists(owner_pid):
        return "exited"
    live_start_time = _get_process_start_time(owner_pid)
    # A different start time means the PID was recycled; never signal the replacement.
    if live_start_time is None:
        return "unknown"
    return "same" if live_start_time == owner_start_time else "exited"


def _wait_for_scoped_lock_owner_exit(
    owner_pid: int, owner_start_time: int, *, attempts: int, delay: float
) -> tuple[bool, bool]:
    """Return ``(exited, safe_to_force)`` after bounded identity-aware waits."""
    for _ in range(max(0, attempts)):
        state = _scoped_lock_owner_state(owner_pid, owner_start_time)
        if state == "exited":
            return True, False
        if state == "unknown":
            return False, False
        time.sleep(max(0.0, delay))
    return False, _scoped_lock_owner_state(owner_pid, owner_start_time) == "same"


def _snapshot_gateway_children(pid: int) -> list:
    """Best-effort snapshot of ``pid``'s live descendants (POSIX only; never raises). Take it while
    the parent is alive -- once it exits the children are reparented and undiscoverable. ``[]`` on
    Windows (taskkill /T tree-kills)."""
    if _IS_WINDOWS:
        return []
    try:
        import psutil  # type: ignore
        return psutil.Process(int(pid)).children(recursive=True)
    except Exception:
        logger.debug("Could not snapshot children of gateway PID %d", pid, exc_info=True)
        return []


def reap_gateway_children(children: list, *, parent_pid: int, timeout: float = 5.0) -> int:
    """Best-effort reap of a dead gateway's orphaned descendants (POSIX; surviving adapter
    subprocesses keep holding token locks); returns count signalled. Call only AFTER the parent is
    confirmed dead, with a :func:`_snapshot_gateway_children` snapshot. ``is_running()`` is
    identity-aware so a recycled child PID is never signalled; a child whose ppid still equals
    ``parent_pid`` is skipped (parent alive => not an orphan). SIGTERM, bounded wait, SIGKILL
    survivors. Never raises."""
    if _IS_WINDOWS or not children:
        return 0
    reaped = 0
    try:
        import psutil  # type: ignore
        live = []
        for child in children:
            try:
                if not child.is_running() or child.status() == psutil.STATUS_ZOMBIE:
                    continue
                if child.ppid() == parent_pid:
                    logger.debug("Skipping child PID %d of old gateway %d: parent still appears "
                                 "alive", child.pid, parent_pid)
                    continue
                child.terminate()
                live.append(child)
            except psutil.NoSuchProcess:
                continue
            except Exception:
                logger.debug("Could not terminate child PID %s of old gateway %d",
                             getattr(child, "pid", "?"), parent_pid, exc_info=True)
        if not live:
            return 0
        gone, alive = psutil.wait_procs(live, timeout=max(0.0, timeout))
        reaped = len(gone)
        for child in alive:
            try:
                child.kill()
                reaped += 1
            except Exception:
                logger.debug("Could not force-kill child PID %s of old gateway %d",
                             getattr(child, "pid", "?"), parent_pid, exc_info=True)
        if reaped:
            logger.info("Reaped %d orphaned child process(es) of replaced gateway PID %d.",
                        reaped, parent_pid)
    except Exception:
        logger.debug("Child reap for replaced gateway PID %d failed", parent_pid, exc_info=True)
    return reaped


def take_over_scoped_lock_holder(
    record: dict[str, Any], *, graceful_attempts: int = 20, force_attempts: int = 20
) -> Optional[int]:
    """Terminate one verified scoped-lock holder for explicit ``--replace``. Returns the owner PID
    only after that exact PID/start-time identity exited; validation or marker-write failure returns
    None without signalling (a cross-home handoff must place a consumable marker in the target's
    home or its supervisor revives it: flap loop). On POSIX the snapshotted children are reaped."""
    owner = _validated_scoped_lock_gateway_owner(record)
    if owner is None:
        return None
    owner_pid, owner_start_time, target_home = owner
    # Snapshot while the owner is alive; afterwards children are reparented.
    owner_children = _snapshot_gateway_children(owner_pid)
    if not write_takeover_marker(
        owner_pid, target_home=target_home, target_start_time=owner_start_time
    ):
        return None
    try:
        replaced = _terminate_verified_owner(
            owner_pid, owner_start_time, graceful_attempts=graceful_attempts,
            force_attempts=force_attempts,
        )
    finally:
        # The target normally consumes the marker; clean up any remainder.
        clear_takeover_marker(target_home)
    if replaced is not None:
        reap_gateway_children(owner_children, parent_pid=owner_pid)
    return replaced


def _terminate_verified_owner(
    owner_pid: int, owner_start_time: int, *, graceful_attempts: int, force_attempts: int
) -> Optional[int]:
    """Bounded identity-aware SIGTERM-then-SIGKILL of a verified owner; the PID once it exited, else
    None. Per signal step: ``ProcessLookupError`` => already gone; other ``OSError`` => refuse."""
    state = _scoped_lock_owner_state(owner_pid, owner_start_time)
    if state == "exited":
        return owner_pid
    if state != "same":
        return None
    for attempts, delay, kwargs in (
        (graceful_attempts, 0.5, {"force": False}),
        (force_attempts, 0.25, {"force": True, "expected_start_time": owner_start_time}),
    ):
        try:
            terminate_pid(owner_pid, **kwargs)
        except ProcessLookupError:
            return owner_pid
        except OSError:
            return None
        exited, safe_to_force = _wait_for_scoped_lock_owner_exit(
            owner_pid, owner_start_time, attempts=attempts, delay=delay
        )
        if exited:
            return owner_pid
        if not safe_to_force:
            return None
    return None


def write_planned_stop_marker(target_pid: int) -> bool:
    """Record that ``target_pid`` is being stopped intentionally: unexpected SIGTERM exits non-zero
    so service managers revive the gateway; the CLI writes this first so a deliberate stop exits
    cleanly."""
    return _write_marker(_get_planned_stop_marker_path(), {
        "target_pid": target_pid, "target_start_time": _get_process_start_time(target_pid),
        "stopper_pid": os.getpid(), "written_at": _utc_now_iso(),
    })


def consume_planned_stop_marker_for_self() -> bool:
    """Return True when the current process is being intentionally stopped."""
    return _consume_pid_marker_for_self(
        _get_planned_stop_marker_path(), ttl_s=_PLANNED_STOP_MARKER_TTL_S
    )


def planned_stop_marker_targets_self() -> bool:
    """Non-destructive watcher probe: True when a live planned-stop marker names us. Never unlinks a
    matching marker (the shutdown handler does the authoritative consume); malformed/expired ones
    are still cleaned up; markers naming another PID are left alone."""
    parsed = _read_live_pid_marker(_get_planned_stop_marker_path(), _PLANNED_STOP_MARKER_TTL_S)
    return parsed is not None and _pid_marker_names_self(parsed[1], parsed[2])


def get_running_pid(
    pid_path: Optional[Path] = None, *, cleanup_stale: bool = True
) -> Optional[int]:
    """PID of a running gateway (lock + PID file verified against the live process), or None.
    An explicit ``pid_path`` is a scoped query into that home's identity files: records are
    validated against the probed home (not the serve process's) (#106406). While the runtime lock
    is HELD, a live SAME-home record the identity matcher rejects is never cleanup-unlinked, scoped
    or not: the holder is a gateway whatever its command line reads as, and unlinking a held
    ``gateway.lock`` leaves it locking a deleted inode so the next starter double-runs (#125610,
    #123109). Unscoped, a live ``gateway.pid`` that truthfully names ANOTHER home's gateway is
    poison inside this home and is unlinked on refusal — the held lock stays (#89315). Dead
    records and inactive-lock metadata still take the full poison-file cleanup."""
    resolved_pid_path = pid_path or _get_pid_path()
    resolved_lock_path = _get_gateway_lock_path(resolved_pid_path)
    if is_gateway_runtime_lock_active(resolved_lock_path):
        records = (
            _read_pid_record(resolved_pid_path), _read_gateway_lock_record(resolved_lock_path),
        )
        expected_home = pid_path.parent if pid_path is not None else None
        saw_live_pid = False
        foreign_live_pid = False
        for record in records:
            pid = _live_pid_from_record(record)
            if pid is None:
                continue
            home_ok = (
                _pid_record_belongs_to_current_profile(record) if expected_home is None
                else not recorded_gateway_home_conflicts(record, expected_home=expected_home)
            )
            if home_ok and _record_matches_live_gateway_pid(
                record, pid, expected_home=expected_home
            ):
                return pid
            # A same-home record the matcher could not adopt may still be a real gateway (an
            # identity matcher that lags a new launcher shape, a record written by an older
            # version): that rejection is not stale-file authority while the PID is alive and the
            # lock held. A scoped poll never unlinks the other home's files either (#106406).
            if home_ok or expected_home is not None:
                saw_live_pid = True
            else:
                foreign_live_pid = True
        if not saw_live_pid:
            _cleanup_invalid_pid_path(
                resolved_pid_path, cleanup_stale=cleanup_stale, unlink_lock=not foreign_live_pid
            )
        return get_runtime_status_running_pid() if pid_path is None else None
    # Lock inactive: the runtime-status fallback runs BEFORE cleanup here.
    runtime_pid = get_runtime_status_running_pid() if pid_path is None else None
    if runtime_pid is None:
        _cleanup_invalid_pid_path(resolved_pid_path, cleanup_stale=cleanup_stale)
    return runtime_pid


def get_running_pid_identity_strict(pid_path: Path) -> Optional[tuple[int, float]]:
    """Return a verified process identity or fail on ambiguous runtime state."""
    resolved_pid_path = Path(pid_path)
    resolved_lock_path = _get_gateway_lock_path(resolved_pid_path)
    pid_exists = _strict_path_exists(resolved_pid_path, "gateway PID")
    # A stale PID file without a lock is not a live gateway; the lock probe is authoritative
    # for absence.
    if not _strict_path_exists(resolved_lock_path, "gateway lock"):
        return None
    if not _is_gateway_runtime_lock_active_strict(resolved_lock_path):
        return None
    lock_record = _read_gateway_lock_record(resolved_lock_path)
    # The PID file is advisory beside a HELD lock: a launch-service gateway keeps serving after its
    # gateway.pid was unlinked (#110166) and --replace force-unlinks the old one (#123430). The
    # holder wrote its own identity into the lock at acquisition, so that record validated against
    # the live process below is the same proof — raising here blocked every following update.
    records = (_read_pid_record(resolved_pid_path), lock_record) if pid_exists else (lock_record,)
    if not all(records):
        raise RuntimeError("gateway PID or lock metadata is malformed")
    pid = _pid_from_record(records[0])
    if pid is None or pid <= 0 or any(_pid_from_record(record) != pid for record in records[1:]):
        raise RuntimeError("gateway PID and lock identities disagree")
    if not _pid_exists(pid):
        raise RuntimeError("gateway identity is not live")
    current_start = _get_process_start_time(pid)
    starts = tuple(record.get("start_time") for record in records)
    if current_start is None or any(start is None for start in starts):
        raise RuntimeError("gateway creation time is unavailable")
    try:
        if not _start_times_agree(current_start, *starts):
            raise RuntimeError("gateway process identity changed")
    except (TypeError, ValueError) as exc:
        raise RuntimeError("gateway creation time is malformed") from exc
    if not all(_record_matches_live_gateway_pid(record, pid) for record in records):
        raise RuntimeError("runtime metadata does not identify a live gateway")
    current = float(current_start)
    if not _IS_WINDOWS:
        return pid, current
    # Windows persists a centisecond fingerprint; SCM checks need the exact psutil epoch.
    # Re-read only after validation and prove it rounds to the same value.
    try:
        import psutil  # type: ignore

        exact_create_time = float(psutil.Process(pid).create_time())
    except Exception as exc:
        raise RuntimeError("exact gateway creation time is unavailable") from exc
    if int(round(exact_create_time * 100)) != int(current):
        raise RuntimeError("gateway process identity changed")
    return pid, exact_create_time


def get_running_pid_cached(
    pid_path: Optional[Path] = None, *, cleanup_stale: bool = True,
    ttl_seconds: float = _GATEWAY_RUNNING_PID_CACHE_TTL_SECONDS,
) -> Optional[int]:
    """Cached ``get_running_pid()`` for dashboard polling: short TTL, invalidated on PID/lock/
    runtime-status file changes, so status endpoints do not re-flock ``gateway.lock`` constantly."""
    if ttl_seconds <= 0:
        return get_running_pid(pid_path, cleanup_stale=cleanup_stale)
    resolved_pid_path = pid_path or _get_pid_path()
    include_runtime_status = pid_path is None
    # The signature covers the PID file, its sibling lock and (unscoped) the runtime status file.
    watched = [resolved_pid_path, _get_gateway_lock_path(resolved_pid_path)]
    if include_runtime_status:
        watched.append(_get_runtime_status_path())
    signature = tuple(_file_cache_signature(p) for p in watched)
    key = (str(resolved_pid_path), bool(cleanup_stale), include_runtime_status)
    now = time.monotonic()
    with _gateway_running_pid_cache_lock:
        cached = _gateway_running_pid_cache.get(key)
        if cached is not None and now - cached[0] <= ttl_seconds and cached[1] == signature:
            return cached[2]
    pid = get_running_pid(pid_path, cleanup_stale=cleanup_stale)
    refreshed_signature = tuple(_file_cache_signature(p) for p in watched)
    with _gateway_running_pid_cache_lock:
        _gateway_running_pid_cache[key] = (time.monotonic(), refreshed_signature, pid)
    return pid
