__package__ = "abxpkg"

import logging as py_logging
import os
import sys
import pwd
import json
import inspect
import stat
import hashlib
import platform
import subprocess
import signal
import functools
import tempfile
import threading
import fcntl
from contextlib import contextmanager
from contextvars import ContextVar

from typing import (
    Optional,
    ClassVar,
    cast,
    final,
    Any,
    Literal,
    Protocol,
    runtime_checkable,
    TypeVar,
    TYPE_CHECKING,
)
from collections.abc import Callable, Iterable, Mapping

from typing_extensions import TypedDict
from typing import Self
from pathlib import Path

if TYPE_CHECKING:
    from .binary import Binary

from pydantic_core import ValidationError
from pydantic import (
    BaseModel,
    Field,
    TypeAdapter,
    validate_call,
    ConfigDict,
    InstanceOf,
    PrivateAttr,
    computed_field,
    model_validator,
)

from .semver import SemVer
from .base_types import (
    DEFAULT_ABXPKG_LIB_DIR,
    BinName,
    BinDirPath,
    HostBinPath,
    BinProviderName,
    PATHStr,
    InstallArgs,
    Sha256,
    MTimeNs,
    EUID,
    SelfMethodName,
    UNKNOWN_SHA256,
    UNKNOWN_MTIME,
    UNKNOWN_EUID,
    bin_name,
    path_is_executable,
    path_is_script,
    abxpkg_install_root_default,
    is_forbidden_convenience_lib_bin,
    bin_abspath,
    bin_abspaths,
    func_takes_args_or_kwargs,
)
from .logging import (
    TRACE_DEPTH,
    format_command,
    format_loaded_binary,
    format_subprocess_output,
    get_logger,
    log_with_trace_depth,
    log_subprocess_output,
    log_method_call,
    summarize_value,
)
from .exceptions import (
    BinProviderInstallError,
    BinProviderUnavailableError,
    BinProviderUninstallError,
    BinProviderUpdateError,
)
from .config import (
    apply_exec_env,
    build_exec_env,
    load_derived_cache,
    save_derived_cache,
)

logger = get_logger(__name__)

################## GLOBALS ##########################################

OPERATING_SYSTEM = platform.system().lower()
PYTHON_BIN_DIR = str(Path(sys.executable).parent)
DEFAULT_PATH = "/home/linuxbrew/.linuxbrew/bin:/opt/homebrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
DEFAULT_ENV_PATH = TypeAdapter(PATHStr).validate_python(
    ":".join(
        dict.fromkeys(
            [
                PYTHON_BIN_DIR,
                *os.environ.get("PATH", DEFAULT_PATH).split(":"),
                *DEFAULT_PATH.split(":"),
            ],
        ),
    ),
)

UNKNOWN_ABSPATH = Path("/usr/bin/true")
UNKNOWN_VERSION = cast(SemVer, SemVer.parse("999.999.999"))
ACTIVE_EXEC_LOG_PREFIX: ContextVar[str | None] = ContextVar(
    "abxpkg_active_exec_log_prefix",
    default=None,
)
MUTATION_LOCK_CONTENDED: ContextVar[bool] = ContextVar(
    "abxpkg_mutation_lock_contended",
    default=False,
)
ENV_PROJECTED_VERSION_PATH: ContextVar[Path | None] = ContextVar(
    "abxpkg_env_projected_version_path",
    default=None,
)
_MUTATION_LOCK_STATE = threading.local()


def mutation_locked(method):
    """Serialize one provider root's complete mutation lifecycle."""

    @functools.wraps(method)
    def locked(self, *args, **kwargs):
        with self.mutation_lock() as contended:
            contention_token = MUTATION_LOCK_CONTENDED.set(contended)
            try:
                return method(self, *args, **kwargs)
            finally:
                MUTATION_LOCK_CONTENDED.reset(contention_token)

    return locked


def env_flag_is_true(name: str) -> bool:
    return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"}


################## SUPPLY-CHAIN SECURITY HELPERS ######################


################## VALIDATORS #######################################

NEVER_CACHE = (
    None,
    UNKNOWN_ABSPATH,
    UNKNOWN_VERSION,
    UNKNOWN_SHA256,
)


def binprovider_cache(binprovider_method):
    """cache non-null return values for BinProvider methods on the BinProvider instance"""

    method_name = binprovider_method.__name__

    @functools.wraps(binprovider_method)
    def cached_function(self, bin_name: BinName, **kwargs):
        if kwargs.get("no_cache"):
            return binprovider_method(self, bin_name, **kwargs)

        self._cache = self._cache or {}
        self._cache[method_name] = self._cache.get(method_name, {})
        method_cache = self._cache[method_name]
        cache_key = (
            str(bin_name),
            tuple(
                sorted(
                    (
                        key,
                        str(value)
                        if isinstance(value, Path)
                        else tuple(value)
                        if isinstance(value, (list, tuple))
                        else repr(value)
                        if isinstance(value, dict)
                        else value,
                    )
                    for key, value in kwargs.items()
                    if key not in {"no_cache", "quiet"}
                ),
            ),
        )

        if cache_key in method_cache:
            cached_value = method_cache[cache_key]
            if method_name in {"get_abspath", "get_abspaths"}:
                cached_paths = (
                    cached_value
                    if isinstance(cached_value, list | tuple)
                    else [cached_value]
                )
                # These caches only memoize positive filesystem lookups. A user
                # can remove a shim or binary between operations (e.g. uv tool
                # uninstall after a missing shim), so never let a stale in-memory
                # path hide the provider's real fallback lookup.
                if not all(Path(path).exists() for path in cached_paths):
                    method_cache.pop(cache_key, None)
                else:
                    return cached_value
            else:
                return cached_value

        return_value = binprovider_method(self, bin_name, **kwargs)

        if return_value and return_value not in NEVER_CACHE:
            self._cache[method_name][cache_key] = return_value
        return return_value

    cached_function.__name__ = f"{method_name}_cached"

    return cached_function


R = TypeVar("R")


def remap_kwargs(
    renamed_kwargs: Mapping[str, str],
) -> Callable[[Callable[..., R]], Callable[..., R]]:
    def decorator(func: Callable[..., R]) -> Callable[..., R]:
        @functools.wraps(func)
        def wrapper(*args: object, **kwargs: object) -> R:
            mapped_kwargs = dict(kwargs)
            for old_name, new_name in renamed_kwargs.items():
                if old_name in mapped_kwargs:
                    mapped_kwargs.setdefault(new_name, mapped_kwargs[old_name])
                    mapped_kwargs.pop(old_name, None)
            return func(*args, **mapped_kwargs)

        return wrapper

    return decorator


class ShallowBinary(BaseModel):
    """
    Shallow version of Binary used as a return type for BinProvider methods (e.g. install()).
    (doesn't implement full Binary interface, but can be used to populate a full loaded Binary instance)
    """

    model_config = ConfigDict(
        extra="forbid",
        populate_by_name=True,
        validate_by_alias=True,
        validate_by_name=True,
        validate_default=True,
        validate_assignment=False,
        from_attributes=True,
        arbitrary_types_allowed=True,
    )

    name: BinName = ""
    description: str = ""

    binproviders: list[InstanceOf["BinProvider"]] = Field(default_factory=list)
    overrides: "BinaryOverrides" = Field(default_factory=dict)

    loaded_binprovider: InstanceOf["BinProvider"] | None = Field(
        default=None,
        alias="binprovider",
    )
    loaded_abspath: HostBinPath | None = Field(default=None, alias="abspath")
    loaded_version: SemVer | None = Field(default=None, alias="version")
    loaded_sha256: Sha256 | None = Field(default=None, alias="sha256")
    loaded_mtime: MTimeNs | None = Field(default=None, alias="mtime")
    loaded_euid: EUID | None = Field(default=None, alias="euid")

    def __getattr__(self, item: str) -> Any:
        """Allow accessing fields by both field name and alias."""
        for field, meta in type(self).model_fields.items():
            if meta.alias == item:
                return getattr(self, field)
        raise AttributeError(item)

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"name={self.name!r}, "
            f"abspath={self.loaded_abspath!r}, "
            f"version={self.loaded_version!r}, "
            f"sha256={f'...{str(self.loaded_sha256)[-6:]}' if self.loaded_sha256 else None!r}, "
            f"mtime={self.loaded_mtime!r}, "
            f"euid={self.loaded_euid!r}"
            f")"
        )

    __str__ = __repr__

    @model_validator(mode="after")
    def validate_model(self) -> Self:
        self.description = self.description or self.name
        return self

    @computed_field  # see mypy issue #1362
    @property
    def bin_filename(self) -> BinName:
        if self.is_script:
            # e.g. '.../Python.framework/Versions/3.11/lib/python3.11/sqlite3/__init__.py' -> sqlite
            name = self.name
        elif self.loaded_abspath:
            # e.g. '/opt/homebrew/bin/wget' -> wget
            name = bin_name(self.loaded_abspath)
        else:
            # e.g. 'ytdlp' -> 'yt-dlp'
            name = bin_name(self.name)
        return name

    @computed_field  # see mypy issue #1362
    @property
    def is_executable(self) -> bool:
        try:
            assert self.loaded_abspath and path_is_executable(self.loaded_abspath)
            return True
        except (ValidationError, AssertionError):
            return False

    @computed_field  # see mypy issue #1362
    @property
    def is_script(self) -> bool:
        try:
            assert self.loaded_abspath and path_is_script(self.loaded_abspath)
            return True
        except (ValidationError, AssertionError):
            return False

    @computed_field  # see mypy issue #1362
    @property
    def is_valid(self) -> bool:
        """Pure loaded-state check used by debug logging.

        Logging calls this while formatting return values, so it must stay fast
        and side-effect free. Keep any future override as a cheap in-memory
        predicate only; never resolve binaries, touch disk/network, or mutate
        state from here.
        """
        return bool(
            self.name and self.loaded_abspath and self.loaded_version,
        )

    @computed_field
    @property
    def bin_dir(self) -> BinDirPath | None:
        if not self.loaded_abspath:
            return None
        try:
            return TypeAdapter(BinDirPath).validate_python(self.loaded_abspath.parent)
        except (ValidationError, AssertionError):
            return None

    @computed_field
    @property
    def loaded_respath(self) -> HostBinPath | None:
        return self.loaded_abspath and self.loaded_abspath.resolve()

    @log_method_call(include_result=True)
    def docs_url(self, quiet: bool = True) -> str | None:
        """Return a human-readable info/docs URL for this binary.

        If this binary has been loaded via a specific provider, that provider
        is asked first. Otherwise (and as a fallback) each candidate provider
        in ``binproviders`` is tried in order until one returns a non-None URL.
        """
        if not self.name:
            return None

        tried: set[int] = set()
        candidates: list["BinProvider"] = []
        if self.loaded_binprovider is not None:
            candidates.append(self.loaded_binprovider)
        candidates.extend(self.binproviders)

        for provider in candidates:
            if id(provider) in tried:
                continue
            tried.add(id(provider))
            try:
                url = provider.get_docs_url(self.name, quiet=quiet)
            except Exception:
                if not quiet:
                    raise
                url = None
            if url:
                return url
        return None

    @log_method_call(include_result=True)
    def exec(
        self,
        bin_name: BinName | HostBinPath | None = None,
        cmd: Iterable[str | Path | int | float | bool] = (),
        cwd: str | Path = ".",
        quiet=False,
        **kwargs,
    ) -> subprocess.CompletedProcess:
        execute_loaded_binary = (
            bin_name is None
            or str(bin_name) == self.name
            or (
                self.loaded_abspath is not None
                and Path(str(bin_name)).expanduser() == self.loaded_abspath
            )
        )
        bin_name = str(bin_name or self.loaded_abspath or self.name)
        if execute_loaded_binary:
            assert self.loaded_abspath, (
                "Binary must have a loaded_abspath, make sure to load() or install() first"
            )
            assert self.loaded_version, (
                "Binary must have a loaded_version, make sure to load() or install() first"
            )
        assert os.path.isdir(cwd) and os.access(cwd, os.R_OK), (
            f"cwd must be a valid, accessible directory: {cwd}"
        )
        if (
            execute_loaded_binary
            and self.loaded_binprovider is not None
            and self.loaded_abspath is not None
        ):
            bin_name = str(
                self.loaded_binprovider._exec_bin_abspath(Path(self.loaded_abspath)),
            )
        cmd = [str(bin_name), *(str(arg) for arg in cmd)]
        logger.debug("Executing binary command: %s", format_command(cmd))
        kwargs.setdefault("capture_output", True)
        kwargs.setdefault("text", True)
        explicit_env = kwargs.pop("env", None)
        if self.loaded_binprovider is not None:
            kwargs["env"] = self.loaded_binprovider.build_exec_env(
                providers=self.loaded_binprovider.exec_env_providers(),
                base_env=explicit_env,
            )
        elif explicit_env is not None:
            kwargs["env"] = dict(explicit_env)
        return subprocess.run(
            cmd,
            cwd=str(cwd),
            **kwargs,
        )


class BinProvider(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
        populate_by_name=True,
        validate_by_alias=True,
        validate_by_name=True,
        validate_default=True,
        validate_assignment=False,
        from_attributes=True,
        revalidate_instances="always",
        arbitrary_types_allowed=True,
    )
    name: BinProviderName = ""
    _log_emoji = "📦"

    # Runtime PATH for binaries installed by this provider. It starts either
    # empty or pre-seeded by the subclass, then setup_PATH() updates it lazily
    # on first real use. This is never the source of truth for resolving
    # INSTALLER_BIN; installers resolve from the ambient env instead.
    PATH: PATHStr = Field(
        default=str(Path(sys.executable).parent),
        repr=False,
    )  # e.g.  '/opt/homebrew/bin:/opt/archivebox/bin'
    INSTALLER_BIN: BinName = "env"
    INSTALLER_BINPROVIDERS: ClassVar[tuple[BinProviderName, ...] | None] = None
    INSTALLER_VERSION_ARGS: ClassVar[tuple[str, ...] | None] = None
    INSTALLER_POSTINSTALL_SCRIPTS: ClassVar[bool | None] = None
    DEFAULT_ENABLED: ClassVar[bool] = True
    DEFAULT_SUPPORTED_PLATFORMS: ClassVar[tuple[str, ...] | None] = None
    INVALIDATE_ONLY_ON_UNINSTALL: ClassVar[bool] = False
    EXEC_ONLY_ENV_KEYS: ClassVar[frozenset[str]] = frozenset()
    FIRST_WRITER_ENV_KEYS: ClassVar[frozenset[str]] = frozenset()
    CACHE_ENV_ALIASES: ClassVar[Mapping[str, tuple[str, ...]]] = {}

    euid: int | None = None
    install_root: Path | None = None
    bin_dir: Path | None = None
    dry_run: bool = Field(
        default_factory=lambda: (
            env_flag_is_true("ABXPKG_DRY_RUN")
            if "ABXPKG_DRY_RUN" in os.environ
            else env_flag_is_true("DRY_RUN")
        ),
    )
    postinstall_scripts: bool | None = Field(default=None)
    min_release_age: float | None = Field(default=None)

    overrides: "BinProviderOverrides" = Field(  # ty: ignore[invalid-assignment] https://github.com/astral-sh/ty/issues/2403
        default_factory=lambda: {
            "*": {
                "version": "self.default_version_handler",
                "abspath": "self.default_abspath_handler",
                "install_args": "self.default_install_args_handler",
                "install": "self.default_install_handler",
                "update": "self.default_update_handler",
                "uninstall": "self.default_uninstall_handler",
                "docs_url": "self.default_docs_url_handler",
                "search": "self.default_search_handler",
            },
        },
        repr=False,
    )

    install_timeout: int = Field(
        default_factory=lambda: int(os.environ.get("ABXPKG_INSTALL_TIMEOUT", "120")),
        repr=False,
    )
    version_timeout: int = Field(
        default_factory=lambda: int(os.environ.get("ABXPKG_VERSION_TIMEOUT", "10")),
        repr=False,
    )

    @computed_field(repr=False)
    @property
    def ENV(self) -> dict[str, str]:
        """Environment variables needed to run code using packages installed
        by this provider. Subclasses override to add provider-specific vars.

        Values are plain strings with optional merge semantics:
          "value"   → overwrite any existing value
          ":value"  → append (existing:value)
          "value:"  → prepend (value:existing)
        """
        return {}

    @computed_field(repr=False)
    @property
    def derived_env_path(self) -> Path | None:
        if self.install_root is None:
            return None
        return self.install_root / "derived.env"

    @staticmethod
    def apply_exec_env(
        exec_env: dict[str, str],
        env: dict[str, str],
    ) -> None:
        apply_exec_env(exec_env, env)

    @staticmethod
    def apply_env(
        exec_env: dict[str, str],
        env: dict[str, str],
    ) -> None:
        BinProvider.apply_exec_env(exec_env, env)

    @staticmethod
    def build_exec_env(
        providers=(),
        *,
        base_env: Mapping[str, str] | None = None,
        extra_env: Mapping[str, str] | None = None,
        include_exec_only_env: bool = True,
    ) -> dict[str, str]:
        return build_exec_env(
            providers=providers,
            base_env=base_env,
            extra_env=extra_env,
            include_exec_only_env=include_exec_only_env,
        )

    def mutation_lock_path(self) -> Path | None:
        """Return the process-shared lock protecting this managed install root."""
        if self.install_root is None:
            return None
        resource = str(self.install_root.expanduser().resolve(strict=False))
        lock_name = hashlib.sha256(resource.encode()).hexdigest()
        return Path("/tmp/abxpkg-mutation-locks") / f"{lock_name}.lock"

    @contextmanager
    def mutation_lock(self):
        """Lock install/update/uninstall across instances, threads, and processes."""
        lock_path = self.mutation_lock_path()
        if lock_path is None or self.dry_run:
            yield False
            return

        lock_path.parent.mkdir(parents=True, exist_ok=True, mode=0o1777)
        try:
            lock_path.parent.chmod(0o1777)
        except PermissionError:
            pass
        process_id = os.getpid()
        state_pid = getattr(_MUTATION_LOCK_STATE, "pid", None)
        if state_pid != process_id:
            _MUTATION_LOCK_STATE.pid = process_id
            _MUTATION_LOCK_STATE.held = {}
        held = _MUTATION_LOCK_STATE.held
        lock_key = str(lock_path)
        if lock_key in held:
            lock_file, depth = held[lock_key]
            held[lock_key] = (lock_file, depth + 1)
            try:
                yield MUTATION_LOCK_CONTENDED.get()
            finally:
                lock_file, depth = held[lock_key]
                held[lock_key] = (lock_file, depth - 1)
            return

        lock_fd = os.open(
            lock_path,
            os.O_RDONLY | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0),
            0o666,
        )
        lock_file = os.fdopen(lock_fd)
        contended = False
        try:
            fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except BlockingIOError:
            contended = True
            fcntl.flock(lock_file, fcntl.LOCK_EX)
        held[lock_key] = (lock_file, 1)
        try:
            yield contended
        finally:
            held.pop(lock_key, None)
            fcntl.flock(lock_file, fcntl.LOCK_UN)
            lock_file.close()

    @staticmethod
    def _provider_identity(provider: "BinProvider") -> tuple[str, str, str]:
        return (
            provider.name,
            str(provider.install_root or ""),
            str(provider.bin_dir or ""),
        )

    @staticmethod
    def _append_unique_provider(
        providers: list["BinProvider"],
        provider: "BinProvider | None",
    ) -> None:
        if provider is None:
            return
        provider_identity = BinProvider._provider_identity(provider)
        if any(
            BinProvider._provider_identity(existing) == provider_identity
            for existing in providers
        ):
            return
        providers.append(provider)

    def exec_env_providers(self) -> list["BinProvider"]:
        providers: list[BinProvider] = []
        for dependency in self.depends_on_binaries():
            self._append_unique_provider(providers, dependency.loaded_binprovider)
        installer = self._INSTALLER_BINARY
        if installer is not None:
            self._append_unique_provider(providers, installer.loaded_binprovider)
        self._append_unique_provider(providers, self)
        return providers

    def set_projection_providers(
        self,
        providers: Iterable["BinProvider"],
    ) -> None:
        return None

    def add_host_bin_dir(self, bin_dir: Path) -> None:
        return None

    def explicit_abspath_overrides(self, abspath: Path) -> "BinaryOverrides":
        return {}

    def execution_PATH(self) -> PATHStr:
        return self.PATH

    def prepare_uninstall(self, bin_name: BinName) -> bool:
        if not self.INVALIDATE_ONLY_ON_UNINSTALL:
            return False
        self.invalidate_cache(bin_name)
        return True

    @staticmethod
    def _provider_cache_fields(provider: "BinProvider | None") -> dict[str, str]:
        if provider is None:
            return {}
        fields: dict[str, str] = {"resolved_provider_name": provider.name}
        if provider.install_root is not None:
            fields["resolved_provider_install_root"] = str(provider.install_root)
        if provider.bin_dir is not None:
            fields["resolved_provider_bin_dir"] = str(provider.bin_dir)
        return fields

    def _resolved_provider_from_cache_record(
        self,
        cached_record: Mapping[str, object],
    ) -> "BinProvider":
        from . import PROVIDER_CLASS_BY_NAME

        resolved_provider_name = cached_record.get("resolved_provider_name")
        if not isinstance(resolved_provider_name, str):
            resolved_provider_name = self.name
        if resolved_provider_name == self.name:
            provider_class = type(self)
            base_provider = self
        else:
            provider_class = PROVIDER_CLASS_BY_NAME.get(
                resolved_provider_name,
                type(self),
            )
            base_provider = provider_class()

        provider_config: dict[str, object] = {}
        resolved_install_root = cached_record.get("resolved_provider_install_root")
        if isinstance(resolved_install_root, str) and resolved_install_root:
            provider_config["install_root"] = Path(resolved_install_root)
        resolved_bin_dir = cached_record.get("resolved_provider_bin_dir")
        if isinstance(resolved_bin_dir, str) and resolved_bin_dir:
            provider_config["bin_dir"] = Path(resolved_bin_dir)
        if not provider_config:
            return base_provider
        return provider_class.model_validate(
            {
                **base_provider.model_dump(mode="python", round_trip=True),
                **provider_config,
            },
        )

    def get_cache_info(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
    ) -> dict[str, list[Path]] | None:
        if not self.install_root:
            return None
        return {
            "fingerprint_paths": [
                Path(abspath).expanduser().resolve(strict=False),
            ],
        }

    class CacheFingerprint(TypedDict):
        path: str
        inode: int
        size: int
        mtime_ns: int
        euid: int

    class CacheRecord(TypedDict):
        fingerprint: list["BinProvider.CacheFingerprint"]
        cache_context: str
        loaded_version: str
        loaded_sha256: str
        loaded_euid: int
        cache_kind: str
        provider_name: str
        resolved_provider_name: str
        bin_name: str
        abspath: str
        install_args: list[str]
        inode: int
        mtime: int
        euid: int

    def _cache_context(self, bin_name: BinName) -> str:
        provider_fields = set(type(self).model_fields)
        provider_config = self.model_dump(
            mode="json",
            include=provider_fields,
            warnings=False,
        )
        provider_config["provider_class"] = (
            f"{type(self).__module__}.{type(self).__qualname__}"
        )
        provider_config["install_args"] = list(
            self.get_install_args(bin_name, quiet=True, no_cache=True),
        )
        manual_binary_env_key = f"{str(bin_name).upper()}_BINARY"
        manual_binary = os.environ.get(manual_binary_env_key)
        if manual_binary:
            # Explicit binary env overrides are part of provider resolution,
            # especially for env/python3 where hooks must keep the interpreter
            # that owns their package deps. If this is not in the cache key, a
            # stale managed link can silently beat a caller-provided override.
            provider_config["manual_binary_env"] = {
                manual_binary_env_key: manual_binary,
            }
        for alias in self.CACHE_ENV_ALIASES.get(str(bin_name), ()):
            alias_value = os.environ.get(alias)
            if alias_value:
                provider_config.setdefault("manual_binary_env", {})[alias] = alias_value
        return json.dumps(
            provider_config,
            default=str,
            separators=(",", ":"),
            sort_keys=True,
        )

    def _cache_context_hash(
        self,
        bin_name: BinName,
        cache_context: str | None = None,
    ) -> str:
        context = (
            cache_context
            if cache_context is not None
            else self._cache_context(bin_name)
        )
        return hashlib.sha256(context.encode("utf-8")).hexdigest()

    def _cached_record_matches_context(
        self,
        bin_name: BinName,
        cached_record: Mapping[str, object],
        *,
        cache_context: str | None = None,
        cache_context_hash: str | None = None,
    ) -> bool:
        expected_context_hash = cache_context_hash or self._cache_context_hash(
            bin_name,
            cache_context,
        )
        cached_context_hash = cached_record.get("cache_context_hash")
        if isinstance(cached_context_hash, str):
            return cached_context_hash == expected_context_hash
        cached_context = cached_record.get("cache_context")
        if isinstance(cached_context, str):
            return cached_context == (
                cache_context
                if cache_context is not None
                else self._cache_context(bin_name)
            )
        return False

    def _cache_key(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
        cache_context_hash: str | None = None,
    ) -> str:
        resolved_abspath = Path(abspath).expanduser().resolve(strict=False)
        return json.dumps(
            [
                self.name,
                str(bin_name),
                str(resolved_abspath),
                cache_context_hash or self._cache_context_hash(bin_name),
            ],
            separators=(",", ":"),
        )

    def _fingerprint_paths(
        self,
        paths: Iterable[Path],
    ) -> list["BinProvider.CacheFingerprint"] | None:
        fingerprints: list["BinProvider.CacheFingerprint"] = []
        for path in paths:
            resolved_path = path.expanduser().resolve(strict=False)
            try:
                stat_result = resolved_path.stat()
            except OSError:
                return None
            fingerprints.append(
                {
                    "path": str(resolved_path),
                    "inode": stat_result.st_ino,
                    "size": stat_result.st_size,
                    "mtime_ns": stat_result.st_mtime_ns,
                    "euid": stat_result.st_uid,
                },
            )
        return fingerprints

    @log_method_call(include_result=True)
    @mutation_locked
    def load_cached_binary(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
        cache: dict[str, dict[str, object]] | None = None,
        cache_context: str | None = None,
        cache_context_hash: str | None = None,
        setup_path: bool = True,
    ) -> ShallowBinary | None:
        # Cache context includes lazily-derived provider fields like PATH.
        # Direct cache readers (list/version/installer discovery) do not pass
        # through load(), so normalize the provider before comparing context or
        # every valid record looks stale in a fresh process. Installer discovery
        # skips setup here because setup may itself need the installer binary;
        # its cache context is derived from the current pre-setup provider state.
        if setup_path:
            self.setup_PATH()
        derived_env_path = self.derived_env_path
        if derived_env_path is None:
            return None

        cache_info = self.get_cache_info(bin_name, abspath)
        if cache_info is None:
            return None

        fingerprints = self._fingerprint_paths(cache_info["fingerprint_paths"])
        if fingerprints is None:
            return None

        cache = load_derived_cache(derived_env_path)
        cache_context = (
            cache_context
            if cache_context is not None
            else self._cache_context(bin_name)
        )
        cache_context_hash = (
            cache_context_hash
            if cache_context_hash is not None
            else self._cache_context_hash(bin_name, cache_context)
        )
        cache_key = self._cache_key(
            bin_name,
            abspath,
            cache_context_hash=cache_context_hash,
        )
        cached_record = cache.get(cache_key)
        cached_record_key = cache_key if isinstance(cached_record, dict) else ""
        if not isinstance(cached_record, dict):
            resolved_abspath = str(Path(abspath).expanduser().resolve(strict=False))
            for candidate_key, candidate_record in cache.items():
                if not isinstance(candidate_record, dict):
                    continue
                if (
                    candidate_record.get("provider_name") != self.name
                    or candidate_record.get("bin_name") != str(bin_name)
                    or str(
                        Path(str(candidate_record.get("abspath") or ""))
                        .expanduser()
                        .resolve(strict=False),
                    )
                    != resolved_abspath
                    or not self._cached_record_matches_context(
                        bin_name,
                        candidate_record,
                        cache_context=cache_context,
                        cache_context_hash=cache_context_hash,
                    )
                ):
                    continue
                cached_record = candidate_record
                cached_record_key = candidate_key
                break
        if not isinstance(cached_record, dict):
            return None
        if cached_record.get("fingerprint") != fingerprints:
            cache.pop(cache_key, None)
            save_derived_cache(derived_env_path, cache)
            return None

        loaded_version = cached_record.get("loaded_version")
        loaded_sha256 = cached_record.get("loaded_sha256")
        loaded_euid = cached_record.get("loaded_euid")
        if (
            not isinstance(loaded_version, str)
            or not isinstance(loaded_sha256, str)
            or not isinstance(loaded_euid, int)
        ):
            cache.pop(cache_key, None)
            save_derived_cache(derived_env_path, cache)
            return None

        try:
            version = SemVer.parse(loaded_version)
            assert version is not None
            sha256 = TypeAdapter(Sha256).validate_python(loaded_sha256)
            mtime = TypeAdapter(MTimeNs).validate_python(fingerprints[0]["mtime_ns"])
            euid = TypeAdapter(EUID).validate_python(loaded_euid)
        except Exception:
            cache.pop(cache_key, None)
            save_derived_cache(derived_env_path, cache)
            return None

        original_abspath = str(Path(abspath).expanduser().absolute())
        resolved_abspath = str(Path(abspath).expanduser().resolve(strict=False))
        resolved_provider_name = cached_record.get("resolved_provider_name")
        cache_kind = cached_record.get("cache_kind")
        cached_abspath = cached_record.get("abspath")
        if not isinstance(resolved_provider_name, str):
            resolved_provider_name = self.name
        if not isinstance(cache_kind, str):
            cache_kind = (
                "dependency" if str(bin_name) == str(self.INSTALLER_BIN) else "binary"
            )
        if not isinstance(cached_abspath, str):
            cache.pop(cache_key, None)
            save_derived_cache(derived_env_path, cache)
            return None
        if self.cached_binary_options_mismatch(
            bin_name,
            cached_record,
            cache_context=cache_context,
            cache_context_hash=cache_context_hash,
        ):
            return None
        if self.cached_binary_state_mismatch(bin_name, cached_record):
            cache.pop(cache_key, None)
            save_derived_cache(derived_env_path, cache)
            return None
        primary_fingerprint = fingerprints[0]
        if cached_abspath == resolved_abspath and original_abspath != resolved_abspath:
            cached_abspath = original_abspath
        if (
            cached_record.get("provider_name") != self.name
            or cached_record.get("resolved_provider_name") != resolved_provider_name
            or cached_record.get("cache_kind") != cache_kind
            or cached_record.get("bin_name") != str(bin_name)
            or cached_abspath not in {original_abspath, resolved_abspath}
            or cached_record.get("inode") != primary_fingerprint["inode"]
            or cached_record.get("mtime") != primary_fingerprint["mtime_ns"]
            or cached_record.get("euid") != primary_fingerprint["euid"]
            or cached_record.get("cache_context_hash") != cache_context_hash
            or cached_record.get("cache_context") != cache_context
            or cached_record_key != cache_key
        ):
            cache = {
                current_cache_key: current_cache_value
                for current_cache_key, current_cache_value in cache.items()
                if not (
                    isinstance(current_cache_value, dict)
                    and current_cache_value.get("provider_name") == self.name
                    and current_cache_value.get("bin_name") == str(bin_name)
                    and str(
                        Path(str(current_cache_value.get("abspath") or ""))
                        .expanduser()
                        .resolve(strict=False),
                    )
                    == resolved_abspath
                )
            }
            cached_record = {
                "fingerprint": fingerprints,
                "cache_context": cache_context,
                "cache_context_hash": cache_context_hash,
                "loaded_version": str(version),
                "loaded_sha256": str(sha256),
                "loaded_euid": euid,
                "cache_kind": cache_kind,
                "provider_name": self.name,
                "resolved_provider_name": resolved_provider_name,
                **self._provider_cache_fields(
                    self._resolved_provider_from_cache_record(cached_record),
                ),
                "bin_name": str(bin_name),
                "abspath": original_abspath,
                "install_args": list(
                    self.get_install_args(bin_name, quiet=True, no_cache=True),
                ),
                "inode": primary_fingerprint["inode"],
                "mtime": primary_fingerprint["mtime_ns"],
                "euid": primary_fingerprint["euid"],
            }
            cache[cache_key] = cached_record
            save_derived_cache(derived_env_path, cache)
            cached_abspath = original_abspath

        resolved_provider = self._resolved_provider_from_cache_record(cached_record)
        return ShallowBinary.model_validate(
            {
                "name": bin_name,
                "binprovider": resolved_provider,
                "abspath": Path(cached_abspath),
                "version": version,
                "sha256": sha256,
                "mtime": mtime,
                "euid": euid,
                "binproviders": [resolved_provider],
            },
        )

    def load_cached_binary_by_name(self, bin_name: BinName) -> ShallowBinary | None:
        self.setup_PATH()
        derived_env_path = self.derived_env_path
        if derived_env_path is None or not derived_env_path.is_file():
            return None

        cache = load_derived_cache(derived_env_path)
        cache_context = self._cache_context(bin_name)
        cache_context_hash = self._cache_context_hash(bin_name, cache_context)
        for cached_record in list(cache.values()):
            if not isinstance(cached_record, dict):
                continue
            if cached_record.get("provider_name") != self.name or cached_record.get(
                "bin_name",
            ) != str(bin_name):
                continue
            if not self._cached_record_matches_context(
                bin_name,
                cached_record,
                cache_context=cache_context,
                cache_context_hash=cache_context_hash,
            ):
                continue
            cached_abspath = cached_record.get("abspath")
            if not isinstance(cached_abspath, str):
                continue
            loaded = self.load_cached_binary(
                bin_name,
                Path(cached_abspath),
                cache=cache,
                cache_context=cache_context,
                cache_context_hash=cache_context_hash,
            )
            if loaded and loaded.loaded_abspath:
                return loaded
        return None

    @log_method_call()
    @mutation_locked
    def write_cached_binary(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
        loaded_version: SemVer,
        loaded_sha256: Sha256,
        resolved_provider_name: str | None = None,
        resolved_provider: "BinProvider | None" = None,
        cache_kind: str = "binary",
    ) -> tuple[MTimeNs, EUID] | None:
        derived_env_path = self.derived_env_path
        cache_info = self.get_cache_info(bin_name, abspath)
        if cache_info is None:
            return None

        fingerprints = self._fingerprint_paths(cache_info["fingerprint_paths"])
        if fingerprints is None:
            return None

        original_abspath = str(Path(abspath).expanduser().absolute())
        resolved_abspath = str(Path(abspath).expanduser().resolve(strict=False))
        primary_fingerprint = fingerprints[0]
        cache_context = self._cache_context(bin_name)
        cache_context_hash = self._cache_context_hash(bin_name, cache_context)
        record: dict[str, object] = {
            "fingerprint": fingerprints,
            "cache_context": cache_context,
            "cache_context_hash": cache_context_hash,
            "loaded_version": str(loaded_version),
            "loaded_sha256": str(loaded_sha256),
            "loaded_euid": primary_fingerprint["euid"],
            "cache_kind": cache_kind,
            "provider_name": self.name,
            "resolved_provider_name": resolved_provider_name or self.name,
            **self._provider_cache_fields(resolved_provider),
            "bin_name": str(bin_name),
            "abspath": original_abspath,
            "install_args": list(
                self.get_install_args(bin_name, quiet=True, no_cache=True),
            ),
            "inode": primary_fingerprint["inode"],
            "mtime": primary_fingerprint["mtime_ns"],
            "euid": primary_fingerprint["euid"],
        }
        if derived_env_path is None:
            return None
        if not (
            derived_env_path.parent.exists() or derived_env_path.parent.is_symlink()
        ):
            derived_env_path.parent.mkdir(parents=True, exist_ok=True)
        cache = load_derived_cache(derived_env_path)
        cache = {
            cache_key: cache_value
            for cache_key, cache_value in cache.items()
            if not (
                isinstance(cache_value, dict)
                and cache_value.get("provider_name") == self.name
                and cache_value.get("bin_name") == str(bin_name)
                and str(
                    Path(str(cache_value.get("abspath") or ""))
                    .expanduser()
                    .resolve(strict=False),
                )
                == resolved_abspath
            )
        }
        cache[
            self._cache_key(bin_name, abspath, cache_context_hash=cache_context_hash)
        ] = record
        try:
            save_derived_cache(derived_env_path, cache)
            if os.geteuid() == 0:
                try:
                    pw_record = self.get_pw_record(self.EUID)
                    os.chown(derived_env_path, self.EUID, pw_record.pw_gid)
                except (PermissionError, OSError, KeyError):
                    pass
        except Exception as err:
            logger.debug(
                "Skipping cache write for %s via %s: %s",
                bin_name,
                self.name,
                err,
            )
            return None
        return (
            TypeAdapter(MTimeNs).validate_python(fingerprints[0]["mtime_ns"]),
            TypeAdapter(EUID).validate_python(fingerprints[0]["euid"]),
        )

    _cache: dict[str, dict[str, Any]] | None = (
        None  # Per-method in-memory cache populated by @binprovider_cache during the current process only.
    )
    _INSTALLER_BINARY: ShallowBinary | None = (
        None  # cached by INSTALLER_BINARY property after first resolution
    )

    def __eq__(self, other: Any) -> bool:
        try:
            return (
                dict(self) == dict(other)
            )  # only compare pydantic fields, ignores classvars/@properties/@cached_properties/_fields/etc.
        except Exception:
            return False

    @staticmethod
    def uid_has_passwd_entry(uid: int) -> bool:
        try:
            pwd.getpwuid(uid)
        except KeyError:
            return False
        return True

    def detect_euid(
        self,
        owner_paths: Iterable[str | Path | None] = (),
        preserve_root: bool = False,
    ) -> int:
        current_euid = os.geteuid()
        candidate_euid = None

        for path in owner_paths:
            if path and os.path.isdir(path):
                candidate_euid = os.stat(path).st_uid
                break

        if candidate_euid is None:
            if preserve_root and current_euid == 0:
                candidate_euid = 0
            else:
                try:
                    if (
                        self._INSTALLER_BINARY is not None
                        and self._INSTALLER_BINARY.loaded_abspath is not None
                    ):
                        installer_abspath = self._INSTALLER_BINARY.loaded_abspath
                    else:
                        installer_abspath = bin_abspath(
                            self.INSTALLER_BIN,
                            PATH=self.PATH,
                        ) or bin_abspath(self.INSTALLER_BIN)
                    if installer_abspath is not None:
                        installer_owner = (
                            self._INSTALLER_BINARY.loaded_euid
                            if (
                                self._INSTALLER_BINARY is not None
                                and self._INSTALLER_BINARY.loaded_abspath
                                == installer_abspath
                                and self._INSTALLER_BINARY.loaded_euid is not None
                            )
                            else os.stat(installer_abspath).st_uid
                        )
                        if installer_owner != 0:
                            candidate_euid = installer_owner
                except Exception:
                    # INSTALLER_BIN is not always available (e.g. at import time, or if it dynamically changes)
                    pass

        if candidate_euid is not None and not self.uid_has_passwd_entry(candidate_euid):
            candidate_euid = current_euid

        return candidate_euid if candidate_euid is not None else current_euid

    def get_pw_record(self, uid: int) -> pwd.struct_passwd:
        try:
            return pwd.getpwuid(uid)
        except KeyError:
            if uid != os.geteuid():
                raise
            return pwd.struct_passwd(
                (
                    os.environ.get("USER") or os.environ.get("LOGNAME") or str(uid),
                    "x",
                    uid,
                    os.getegid(),
                    "",
                    os.environ.get("HOME", tempfile.gettempdir()),
                    os.environ.get("SHELL", "/bin/sh"),
                ),
            )

    @property
    def EUID(self) -> int:
        """
        Detect the user (UID) to run as when executing this binprovider's INSTALLER_BIN
        e.g. homebrew should never be run as root, we can tell which user to run it as by looking at who owns its binary
        apt should always be run as root, pip should be run as the user that owns the venv, etc.
        """

        # use user-provided value if one is set
        if self.euid is not None:
            return self.euid

        return self.detect_euid()

    def INSTALLER_BINARY(self, no_cache: bool = False) -> ShallowBinary:
        """Resolve the provider's own installer binary (e.g. npm, pip, cargo).

        Cached after first resolution. Pass no_cache=True to force re-resolution.
        Raises BinProviderUnavailableError if the installer cannot be found.
        Checks ``{INSTALLER_BIN}_BINARY`` env var (e.g. ``NPM_BINARY``) first.
        Subclasses override only if they need extra resolution logic.
        """
        from . import Binary, DEFAULT_PROVIDER_NAMES, PROVIDER_CLASS_BY_NAME

        if not no_cache and self._INSTALLER_BINARY and self._INSTALLER_BINARY.is_valid:
            return self._INSTALLER_BINARY

        derived_env_path = self.derived_env_path
        if not no_cache and derived_env_path and derived_env_path.is_file():
            cache = load_derived_cache(derived_env_path)
            for cached_record in cache.values():
                if not isinstance(cached_record, dict):
                    continue
                if cached_record.get("provider_name") != self.name or cached_record.get(
                    "bin_name",
                ) != str(self.INSTALLER_BIN):
                    continue
                cached_abspath = cached_record.get("abspath")
                if not isinstance(cached_abspath, str):
                    continue
                loaded = self.load_cached_binary(
                    self.INSTALLER_BIN,
                    Path(cached_abspath),
                    cache=cache,
                    setup_path=False,
                )
                if loaded and loaded.loaded_abspath:
                    self._INSTALLER_BINARY = loaded
                    return loaded

        raw_provider_names = os.environ.get("ABXPKG_BINPROVIDERS")
        selected_provider_names = (
            [provider_name.strip() for provider_name in raw_provider_names.split(",")]
            if raw_provider_names
            else list(DEFAULT_PROVIDER_NAMES)
        )
        preferred_provider_names = (
            list(self.INSTALLER_BINPROVIDERS)
            if self.INSTALLER_BINPROVIDERS
            else selected_provider_names
        )
        # Drop a cross-provider candidate iff that candidate's *own*
        # explicit INSTALLER_BINPROVIDERS list names ``self`` — that's a
        # genuine mutual-bootstrap cycle (e.g. a hypothetical brew with
        # INSTALLER_BINPROVIDERS=("cargo",) trying to bootstrap brew via
        # cargo while cargo's own chain bootstraps cargo via brew).
        # Candidates with INSTALLER_BINPROVIDERS=None fall back to the
        # ambient PATH via env first and don't actually recurse, so we
        # keep them — over-filtering here would break legitimate
        # cross-provider bootstraps like ``cargo`` installed via ``brew``
        # (especially under ``ABXPKG_BINPROVIDERS=...`` configurations
        # that exclude env).
        installer_provider_names = [
            provider_name
            for provider_name in preferred_provider_names
            if provider_name
            and provider_name in PROVIDER_CLASS_BY_NAME
            and provider_name != self.name
            and self.name
            not in (PROVIDER_CLASS_BY_NAME[provider_name].INSTALLER_BINPROVIDERS or ())
        ]
        installer_providers: list[BinProvider] = [
            PROVIDER_CLASS_BY_NAME[provider_name]()
            for provider_name in installer_provider_names
        ]
        for provider in installer_providers:
            provider.set_projection_providers([self])

        # Check {INSTALLER_BIN}_BINARY env var (e.g. NPM_BINARY, UV_BINARY)
        env_var = f"{self.INSTALLER_BIN.upper()}_BINARY"
        manual = os.environ.get(env_var)
        if manual and os.path.isabs(manual) and Path(manual).is_file():
            try:
                for provider in installer_providers:
                    provider.add_host_bin_dir(Path(manual).parent)
                loaded = Binary(
                    name=self.INSTALLER_BIN,
                    binproviders=installer_providers,
                    postinstall_scripts=self.INSTALLER_POSTINSTALL_SCRIPTS,
                ).install(
                    no_cache=no_cache,
                )
                if loaded and loaded.loaded_abspath:
                    if loaded.loaded_version and loaded.loaded_sha256:
                        self.write_cached_binary(
                            self.INSTALLER_BIN,
                            loaded.loaded_abspath,
                            loaded.loaded_version,
                            loaded.loaded_sha256,
                            resolved_provider_name=(
                                loaded.loaded_binprovider.name
                                if loaded.loaded_binprovider is not None
                                else self.name
                            ),
                            resolved_provider=loaded.loaded_binprovider,
                            cache_kind="dependency",
                        )
                    self._INSTALLER_BINARY = loaded
                    return loaded
            except Exception:
                pass

        try:
            loaded = Binary(
                name=self.INSTALLER_BIN,
                binproviders=installer_providers,
                postinstall_scripts=self.INSTALLER_POSTINSTALL_SCRIPTS,
            ).install(no_cache=no_cache)
            if loaded and loaded.loaded_abspath:
                if loaded.loaded_version and loaded.loaded_sha256:
                    self.write_cached_binary(
                        self.INSTALLER_BIN,
                        loaded.loaded_abspath,
                        loaded.loaded_version,
                        loaded.loaded_sha256,
                        resolved_provider_name=(
                            loaded.loaded_binprovider.name
                            if loaded.loaded_binprovider is not None
                            else self.name
                        ),
                        resolved_provider=loaded.loaded_binprovider,
                        cache_kind="dependency",
                    )
                self._INSTALLER_BINARY = loaded
                return loaded
        except Exception:
            pass

        raise BinProviderUnavailableError(
            self.__class__.__name__,
            self.INSTALLER_BIN,
        )

    def installer_version_handler(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        timeout: int | None = None,
        **context,
    ) -> "VersionFuncReturnValue":
        version_args = self.INSTALLER_VERSION_ARGS
        assert version_args is not None
        return [str(bin_name), *version_args]

    @computed_field(repr=False)
    @property
    def is_valid(self) -> bool:
        """Pure provider-health check used by debug logging.

        Logging calls this while formatting return values, so it must stay fast
        and side-effect free. Any override must remain a cheap readiness check
        only and must never install, resolve, invalidate cache, or mutate state.
        """
        if self.bin_dir is not None:
            if self.bin_dir.is_dir() and os.access(self.bin_dir, os.R_OK):
                pass
            elif self.bin_dir.exists():
                return False
            else:
                candidate_dir = self.bin_dir.parent
                remaining_hops = 3
                while remaining_hops > 0 and not candidate_dir.exists():
                    candidate_dir = candidate_dir.parent
                    remaining_hops -= 1
                if not (
                    candidate_dir.is_dir()
                    and os.access(candidate_dir, os.R_OK | os.W_OK)
                ):
                    return False
        installer = self._INSTALLER_BINARY
        if installer and installer.is_valid:
            return True
        manual_installer = os.environ.get(f"{self.INSTALLER_BIN.upper()}_BINARY")
        if manual_installer:
            manual_path = Path(manual_installer).expanduser()
            if (
                manual_path.is_absolute()
                and manual_path.is_file()
                and os.access(manual_path, os.X_OK)
            ):
                return True
        return bool(bin_abspath(self.INSTALLER_BIN, PATH=self.PATH))

    @final
    @log_method_call()
    def get_provider_with_overrides(
        self,
        overrides: Optional["BinProviderOverrides"] = None,
        dry_run: bool | None = None,
        install_timeout: int | None = None,
        version_timeout: int | None = None,
        **provider_patches: Any,
    ) -> Self:
        # created an updated copy of the BinProvider with the overrides applied, then get the handlers on it.
        # important to do this so that any subsequent calls to handler functions down the call chain
        # still have access to the overrides, we don't have to have to pass them down as args all the way down the stack

        updated_binprovider: Self = self.model_copy(deep=True)

        # main binary-specific overrides for [abspath, version, install_args, install, update, uninstall]
        overrides = overrides or {}

        # extra overrides that are also configurable, can add more in the future as-needed for tunable options
        updated_binprovider.dry_run = self.dry_run if dry_run is None else dry_run
        updated_binprovider.install_timeout = (
            self.install_timeout if install_timeout is None else install_timeout
        )
        updated_binprovider.version_timeout = (
            self.version_timeout if version_timeout is None else version_timeout
        )

        cache_must_reset = False
        for binname, bin_overrides in overrides.items():
            provider_field_overrides: dict[str, Any] = {}
            handler_overrides: dict[str, Any] = {}
            for key, value in bin_overrides.items():
                if key != "overrides" and key in type(updated_binprovider).model_fields:
                    provider_field_overrides[key] = value
                else:
                    handler_overrides[key] = value

            if (
                "install_root" in provider_field_overrides
                and "bin_dir" not in provider_field_overrides
            ):
                # Provider instances have already derived bin_dir from their
                # default install_root. Re-run that derivation for a
                # binary-specific root instead of carrying the stale path into
                # the validated copy.
                provider_field_overrides["bin_dir"] = None

            if provider_field_overrides:
                updated_binprovider = type(self).model_validate(
                    {
                        **updated_binprovider.model_dump(
                            mode="python",
                            round_trip=True,
                        ),
                        "overrides": updated_binprovider.overrides,
                        **provider_field_overrides,
                    },
                )
                cache_must_reset = True

            if handler_overrides:
                updated_binprovider.overrides[binname] = cast(
                    HandlerDict,
                    {
                        **updated_binprovider.overrides.get(binname, {}),
                        **handler_overrides,
                    },
                )
                cache_must_reset = True

        if provider_patches:
            updated_binprovider = type(self).model_validate(
                {
                    **updated_binprovider.model_dump(
                        mode="python",
                        round_trip=True,
                    ),
                    "overrides": updated_binprovider.overrides,
                    **provider_patches,
                },
            )
            cache_must_reset = True

        if cache_must_reset:
            updated_binprovider._cache = None

        return updated_binprovider

    @log_method_call(include_result=True)
    def _get_handler_keys(
        self,
        handler_type: "HandlerType",
    ) -> tuple["HandlerType", ...]:
        if handler_type in ("install_args", "packages"):
            return ("install_args", "packages")
        return (handler_type,)

    @log_method_call(include_result=True)
    def _get_handler_for_action(
        self,
        bin_name: BinName,
        handler_type: "HandlerType",
    ) -> Callable[..., "HandlerReturnValue"]:
        """
        Get the handler func for a given key + Dict of handler callbacks + fallback default handler.
        e.g. _get_handler_for_action(bin_name='yt-dlp', 'install', default_handler=self.default_install_handler, ...) -> Callable
        """

        handler: HandlerValue | None = None
        for overrides_for_bin in (
            self.overrides.get(bin_name, {}),
            self.overrides.get("*", {}),
        ):
            for handler_key in self._get_handler_keys(handler_type):
                handler = overrides_for_bin.get(handler_key)
                if handler:
                    break
            if handler:
                break
        assert handler, (
            f"🚫 BinProvider(name={self.name}) has no {handler_type} handler implemented for Binary(name={bin_name})"
        )

        # if handler_func is already a callable, return it directly
        if isinstance(handler, Callable):
            return handler

        # if handler_func is string reference to a function on self, swap it for the actual function
        elif isinstance(handler, str) and (
            handler.startswith("self.") or handler.startswith("BinProvider.")
        ):
            # special case, allow dotted path references to methods on self (where self refers to the BinProvider)
            handler_method: Callable[..., HandlerReturnValue] = getattr(
                self,
                handler.split("self.", 1)[-1],
            )
            return handler_method

        # if handler_func is any other value, treat is as a literal and return a func that provides the literal
        literal_value = TypeAdapter(HandlerReturnValue).validate_python(handler)

        def literal_handler() -> HandlerReturnValue:
            return literal_value

        return literal_handler

    @log_method_call(include_result=True)
    def _get_compatible_kwargs(
        self,
        handler_func: Callable[..., "HandlerReturnValue"],
        kwargs: dict[str, Any],
    ) -> dict[str, Any]:
        if not kwargs:
            return kwargs

        signature = inspect.signature(handler_func)
        if any(
            param.kind == inspect.Parameter.VAR_KEYWORD
            for param in signature.parameters.values()
        ):
            return kwargs

        accepted_kwargs = set(signature.parameters)
        return {key: value for key, value in kwargs.items() if key in accepted_kwargs}

    @log_method_call(include_result=True)
    def _call_handler_for_action(
        self,
        bin_name: BinName,
        handler_type: "HandlerType",
        **kwargs,
    ) -> "HandlerReturnValue":
        handler_func: Callable[..., HandlerReturnValue] = self._get_handler_for_action(
            bin_name=bin_name,  # e.g. 'yt-dlp', or 'wget', etc.
            handler_type=handler_type,  # e.g. abspath, version, install_args, install
        )

        try:
            if not func_takes_args_or_kwargs(handler_func):
                # if it's a pure argless lambda/func, dont pass bin_path and other **kwargs
                handler_func_without_args = cast(
                    Callable[[], HandlerReturnValue],
                    handler_func,
                )
                return handler_func_without_args()

            compatible_kwargs = self._get_compatible_kwargs(handler_func, kwargs)
            if hasattr(handler_func, "__self__"):
                # func is already a method bound to self, just call it directly
                return handler_func(bin_name, **compatible_kwargs)
            else:
                # func is not bound to anything, pass BinProvider as first arg
                return handler_func(self, bin_name, **compatible_kwargs)
        except TimeoutError:
            raise

    # DEFAULT HANDLERS, override these in subclasses as needed:

    def default_abspath_handler(
        self,
        bin_name: BinName | HostBinPath,
        no_cache: bool = False,
        **context,
    ) -> "AbspathFuncReturnValue":  # aka str | Path | None
        # Installer dependencies are resolved by the provider's declared
        # abxpkg chain, including EnvProvider host projection.
        if str(bin_name) == self.INSTALLER_BIN:
            installer = self.INSTALLER_BINARY(no_cache=no_cache)
            return installer.loaded_abspath

        if not self.PATH:
            return None

        bin_dir = self.bin_dir
        if bin_dir is not None:
            managed_abspath = bin_abspath(bin_name, PATH=str(bin_dir))
            if managed_abspath is not None:
                return managed_abspath
            return None

        return bin_abspath(bin_name, PATH=self.PATH)

    def default_version_handler(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        timeout: int | None = None,
        no_cache: bool = False,
        **context,
    ) -> "VersionFuncReturnValue":  # aka List[str] | Tuple[str, ...]
        return self._version_from_exec(
            bin_name,
            abspath=abspath,
            timeout=timeout,
        )

    def default_install_args_handler(
        self,
        bin_name: BinName,
        **context,
    ) -> "InstallArgsFuncReturnValue":  # aka List[str] aka InstallArgs
        return [bin_name]

    def default_docs_url_handler(
        self,
        bin_name: BinName,
        **context,
    ) -> "DocsUrlFuncReturnValue":  # aka str | None
        """Subclasses override this to return a human-readable info URL for the package.

        Providers that don't have a meaningful docs/info URL (env, bash, custom,
        etc.) should leave this returning None so callers can fall back to the
        next provider in the binproviders list.
        """
        return None

    def _docs_url_package_name(
        self,
        bin_name: BinName,
        *,
        allow_leading_at: bool = False,
    ) -> str | None:
        """Pick a canonical package name from install_args for URL building.

        Strips version specifiers (``==1.0``, ``>=2,<3``), extras (``foo[all]``),
        and trailing ``@version`` suffixes (npm-style). Falls back to ``bin_name``
        if install_args don't yield a usable candidate.
        """
        try:
            install_args = self.get_install_args(bin_name, quiet=True)
        except Exception:
            install_args = [str(bin_name)]

        candidates = list(install_args) or [str(bin_name)]
        for arg in candidates:
            if not arg or arg.startswith("-"):
                continue
            if "://" in arg or arg.startswith((".", "/", "~")):
                continue
            name = arg
            if allow_leading_at and name.startswith("@"):
                # npm scoped pkg: keep leading @, only trim version after '@' that
                # appears after the scope/name boundary.
                _, _, after_slash = name[1:].partition("/")
                if "@" in after_slash:
                    pkg, _, _ = after_slash.partition("@")
                    name = "@" + name[1:].split("/", 1)[0] + "/" + pkg
            else:
                name = name.split("@", 1)[0] if "@" in name else name
            name = name.split("[", 1)[0]
            for sep in ("==", ">=", "<=", "!=", "~=", ">", "<", ";", " "):
                if sep in name:
                    name = name.split(sep, 1)[0]
            name = name.strip()
            if name:
                return name
        return str(bin_name) or None

    def default_packages_handler(
        self,
        bin_name: BinName,
        **context,
    ) -> "InstallArgsFuncReturnValue":
        return self.default_install_args_handler(bin_name, **context)

    @remap_kwargs({"packages": "install_args"})
    def default_install_handler(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        no_cache: bool = False,
        timeout: int | None = None,
    ) -> "InstallFuncReturnValue":  # aka str
        self.setup(
            postinstall_scripts=postinstall_scripts,
            min_release_age=min_release_age,
            min_version=min_version,
            no_cache=no_cache,
        )
        install_args = install_args or self.get_install_args(bin_name)
        self.INSTALLER_BINARY(no_cache=no_cache)

        return f"🚫 {self.name} BinProvider does not implement any .install() method"

    @remap_kwargs({"packages": "install_args"})
    def default_update_handler(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        no_cache: bool = False,
        timeout: int | None = None,
    ) -> "ActionFuncReturnValue":
        self.INSTALLER_BINARY(no_cache=no_cache)
        return f"🚫 {self.name} BinProvider does not implement any .update() method"

    @remap_kwargs({"packages": "install_args"})
    def default_uninstall_handler(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        no_cache: bool = False,
        timeout: int | None = None,
    ) -> "ActionFuncReturnValue":
        self.INSTALLER_BINARY(no_cache=no_cache)
        return False

    def default_search_handler(
        self,
        bin_name: BinName,
        min_version: SemVer | None = None,
        min_release_age: float | None = None,
        timeout: int | None = None,
        **context,
    ) -> "SearchFuncReturnValue":
        """Default search handler. Providers without a real search backend return []."""
        return []

    @mutation_locked
    @log_method_call()
    def invalidate_cache(self, bin_name: BinName) -> None:
        if self._cache:
            for method_cache in self._cache.values():
                method_cache.pop(bin_name, None)
        derived_env_path = self.derived_env_path
        if derived_env_path is None:
            return
        cache = load_derived_cache(derived_env_path)
        updated_cache: dict[str, object] = {}
        for cache_key, cache_value in cache.items():
            cached_provider_name = None
            cached_bin_name = None
            if isinstance(cache_value, dict):
                cached_provider_name = cache_value.get("provider_name")
                cached_bin_name = cache_value.get("bin_name")
            try:
                key_parts = json.loads(cache_key)
                if isinstance(key_parts, list) and len(key_parts) >= 2:
                    cached_provider_name = cached_provider_name or key_parts[0]
                    cached_bin_name = cached_bin_name or key_parts[1]
            except Exception:
                continue
            if cached_provider_name == self.name and cached_bin_name == str(bin_name):
                continue
            updated_cache[cache_key] = cache_value
        if updated_cache != cache:
            save_derived_cache(derived_env_path, updated_cache)
        if str(bin_name) == self.INSTALLER_BIN:
            self._INSTALLER_BINARY = None

    @log_method_call(include_result=True)
    def has_cached_binary(self, bin_name: BinName) -> bool:
        loaded = self.load_cached_binary_by_name(bin_name)
        return bool(loaded and loaded.loaded_abspath)

    def cached_binary_options_mismatch(
        self,
        bin_name: BinName,
        cached_record: Mapping[str, object],
        *,
        cache_context: str | None = None,
        cache_context_hash: str | None = None,
    ) -> bool:
        return not self._cached_record_matches_context(
            bin_name,
            cached_record,
            cache_context=cache_context,
            cache_context_hash=cache_context_hash,
        )

    def cached_binary_state_mismatch(
        self,
        bin_name: BinName,
        cached_record: Mapping[str, object],
    ) -> bool:
        return False

    @log_method_call(include_result=True)
    def depends_on_binaries(self) -> list[ShallowBinary]:
        derived_env_path = self.derived_env_path
        if derived_env_path is None or not derived_env_path.is_file():
            return []
        cache = load_derived_cache(derived_env_path)
        dependencies: list[ShallowBinary] = []
        seen: set[tuple[str, str, str]] = set()
        for cache_key, cache_value in sorted(cache.items()):
            if not isinstance(cache_value, dict):
                continue
            cached_provider_name = cache_value.get("provider_name")
            cached_bin_name = cache_value.get("bin_name")
            cached_abspath = cache_value.get("abspath")
            cache_kind = cache_value.get("cache_kind")
            if (
                not isinstance(cached_provider_name, str)
                or not isinstance(cached_bin_name, str)
                or not isinstance(cached_abspath, str)
            ):
                try:
                    key_parts = json.loads(cache_key)
                    if isinstance(key_parts, list) and len(key_parts) >= 3:
                        cached_provider_name = key_parts[0]
                        cached_bin_name = key_parts[1]
                        cached_abspath = key_parts[2]
                except Exception:
                    continue
            if (
                cached_provider_name != self.name
                or not isinstance(cached_bin_name, str)
                or not isinstance(cached_abspath, str)
            ):
                continue
            if not isinstance(cache_kind, str):
                cache_kind = (
                    "dependency"
                    if str(cached_bin_name) == str(self.INSTALLER_BIN)
                    else "binary"
                )
            if cache_kind != "dependency":
                continue
            cached_context = cache_value.get("cache_context")
            cached_context_hash = cache_value.get("cache_context_hash")
            loaded = self.load_cached_binary(
                cached_bin_name,
                Path(cached_abspath),
                cache_context=cached_context
                if isinstance(cached_context, str)
                else None,
                cache_context_hash=(
                    cached_context_hash
                    if isinstance(cached_context_hash, str)
                    else None
                ),
            )
            if loaded is None or loaded.loaded_abspath is None:
                continue
            resolved_provider = loaded.loaded_binprovider or self
            dedupe_key = (
                loaded.name,
                str(loaded.loaded_abspath),
                resolved_provider.name,
            )
            if dedupe_key in seen:
                continue
            seen.add(dedupe_key)
            dependencies.append(
                ShallowBinary.model_validate(
                    {
                        "name": loaded.name,
                        "description": loaded.description,
                        "binprovider": resolved_provider,
                        "abspath": loaded.loaded_abspath,
                        "version": loaded.loaded_version,
                        "sha256": loaded.loaded_sha256,
                        "mtime": loaded.loaded_mtime,
                        "euid": loaded.loaded_euid,
                        "binproviders": [resolved_provider],
                        "overrides": loaded.overrides,
                    },
                ),
            )
        return dependencies

    @log_method_call(include_result=True)
    def installed_binaries(self) -> list[ShallowBinary]:
        derived_env_path = self.derived_env_path
        if derived_env_path is None or not derived_env_path.is_file():
            return []
        cache = load_derived_cache(derived_env_path)
        binaries: list[ShallowBinary] = []
        seen: set[tuple[str, str, str]] = set()
        for cache_key, cache_value in sorted(cache.items()):
            if not isinstance(cache_value, dict):
                continue
            cached_provider_name = cache_value.get("provider_name")
            cached_bin_name = cache_value.get("bin_name")
            cached_abspath = cache_value.get("abspath")
            cache_kind = cache_value.get("cache_kind")
            if (
                not isinstance(cached_provider_name, str)
                or not isinstance(cached_bin_name, str)
                or not isinstance(cached_abspath, str)
            ):
                try:
                    key_parts = json.loads(cache_key)
                    if isinstance(key_parts, list) and len(key_parts) >= 3:
                        cached_provider_name = key_parts[0]
                        cached_bin_name = key_parts[1]
                        cached_abspath = key_parts[2]
                except Exception:
                    continue
            if (
                cached_provider_name != self.name
                or not isinstance(cached_bin_name, str)
                or not isinstance(cached_abspath, str)
            ):
                continue
            if not isinstance(cache_kind, str):
                cache_kind = (
                    "dependency"
                    if str(cached_bin_name) == str(self.INSTALLER_BIN)
                    else "binary"
                )
            if cache_kind != "binary":
                continue
            cached_context = cache_value.get("cache_context")
            cached_context_hash = cache_value.get("cache_context_hash")
            loaded = self.load_cached_binary(
                cached_bin_name,
                Path(cached_abspath),
                cache_context=cached_context
                if isinstance(cached_context, str)
                else None,
                cache_context_hash=(
                    cached_context_hash
                    if isinstance(cached_context_hash, str)
                    else None
                ),
            )
            if loaded is None or loaded.loaded_abspath is None:
                continue
            resolved_provider = loaded.loaded_binprovider or self
            dedupe_key = (
                loaded.name,
                str(loaded.loaded_abspath),
                resolved_provider.name,
            )
            if dedupe_key in seen:
                continue
            seen.add(dedupe_key)
            binaries.append(
                ShallowBinary.model_validate(
                    {
                        "name": loaded.name,
                        "description": loaded.description,
                        "binprovider": resolved_provider,
                        "abspath": loaded.loaded_abspath,
                        "version": loaded.loaded_version,
                        "sha256": loaded.loaded_sha256,
                        "mtime": loaded.loaded_mtime,
                        "euid": loaded.loaded_euid,
                        "binproviders": [resolved_provider],
                        "overrides": loaded.overrides,
                    },
                ),
            )
        return binaries

    def setup_PATH(self, no_cache: bool = False) -> None:
        """Populate runtime PATH lazily.

        Called by resolution/install/update/uninstall entrypoints before they
        need provider binaries. Subclasses document whether PATH stays
        ambient, gets replaced with install_root/bin_dir paths, or prepends those dirs
        to an ambient seed. This method must not resolve INSTALLER_BINARY()
        from here or perform eager work at construction time.
        """
        for path in reversed(self.PATH.split(":")):
            if path not in sys.path:
                sys.path.insert(
                    0,
                    path,
                )  # e.g. /opt/archivebox/bin:/bin:/usr/local/bin:...

    # _require_installer_bin removed: inline ``assert self.INSTALLER_BINARY()`` at callsites

    def _merge_PATH(
        self,
        *entries: str | Path,
        PATH: str | None = None,
        prepend: bool = False,
    ) -> PATHStr:
        new_entries = [
            str(entry)
            for entry in entries
            if str(entry) and not is_forbidden_convenience_lib_bin(entry)
        ]
        existing_entries = [
            entry
            for entry in (PATH or "").split(os.pathsep)
            if entry and not is_forbidden_convenience_lib_bin(entry)
        ]
        merged_entries = (
            [*new_entries, *existing_entries]
            if prepend
            else [*existing_entries, *new_entries]
        )
        return TypeAdapter(PATHStr).validate_python(
            ":".join(dict.fromkeys(merged_entries)),
        )

    def _version_from_exec(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        timeout: int | None = None,
    ) -> SemVer | None:
        abspath = abspath or self.get_abspath(bin_name, quiet=True)
        if not abspath:
            return None

        timeout = self.version_timeout if timeout is None else timeout
        validation_err = None
        version_outputs: list[str] = []

        for version_arg in ("--version", "-version", "-v", "-V"):
            proc = self.exec(
                bin_name=abspath,
                cmd=[version_arg],
                timeout=timeout,
                quiet=True,
            )
            version_output = proc.stdout.strip() or proc.stderr.strip()
            version_outputs.append(version_output)
            if proc.returncode != 0:
                validation_err = validation_err or AssertionError(
                    f"❌ $ {bin_name} {version_arg} exited with status {proc.returncode}",
                )
                continue
            try:
                version = SemVer.parse(version_output)
                assert version, (
                    f"❌ Could not parse version from $ {bin_name} {version_arg}: {version_output}".strip()
                )
                return version
            except (ValidationError, AssertionError) as err:
                validation_err = validation_err or err

        raise ValueError(
            f"❌ Unable to find {bin_name} version from {bin_name} --version, -version, -v or -V output\n{next((output for output in version_outputs if output), '')}".strip(),
        ) from validation_err

    def _ensure_writable_cache_dir(self, cache_dir: Path) -> bool:
        if cache_dir.exists() and not cache_dir.is_dir():
            return False

        cache_dir.mkdir(parents=True, exist_ok=True)

        pw_record = self.get_pw_record(self.EUID)
        try:
            os.chown(cache_dir, self.EUID, pw_record.pw_gid)
        except PermissionError:
            pass

        try:
            cache_dir.chmod(
                cache_dir.stat().st_mode | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH,
            )
        except PermissionError:
            pass

        if not (cache_dir.is_dir() and os.access(cache_dir, os.W_OK)):
            return False
        # Cache directories can be large and shared by the underlying package
        # manager (uv/pip/pnpm/etc.). This method runs on normal load/install
        # hot paths, so it must only prove the cache root is usable. Recursing
        # through every cached artifact turns ordinary commands into minutes of
        # chown/chmod work and races active package-manager writers; ownership
        # repair of an existing tree belongs in explicit install/container setup.
        return True

    def _raise_proc_error(
        self,
        action: Literal["install", "update", "uninstall"],
        target: object,
        proc: subprocess.CompletedProcess,
    ) -> None:
        log_subprocess_output(
            logger,
            f"{self.__class__.__name__} {action}",
            proc.stdout,
            proc.stderr,
            level=py_logging.DEBUG,
        )
        exc_cls = {
            "install": BinProviderInstallError,
            "update": BinProviderUpdateError,
            "uninstall": BinProviderUninstallError,
        }[action]
        raise exc_cls(
            self.__class__.__name__,
            target,
            returncode=proc.returncode,
            output=format_subprocess_output(proc.stdout, proc.stderr),
        )

    def _exec_bin_abspath(self, bin_abspath: Path) -> Path:
        return bin_abspath

    @classmethod
    def host_projection_target(cls, source_path: Path) -> Path | None:
        """Return a provider-owned executable target for a host launcher."""
        return None

    def env_projection_target(self, source_path: Path) -> Path | None:
        """Return a provider-owned executable that EnvProvider may project."""
        return None

    def _is_managed_by_other_provider(self, abspath: HostBinPath | Path) -> bool:
        return False

    def exec(
        self,
        bin_name: BinName | HostBinPath,
        cmd: Iterable[str | Path | int | float | bool] = (),
        cwd: Path | str = ".",
        quiet=False,
        should_log_command: bool = True,
        **kwargs,
    ) -> subprocess.CompletedProcess:
        explicit_abspath = Path(str(bin_name)).expanduser()
        if (
            explicit_abspath.is_absolute()
            and explicit_abspath.is_file()
            and os.access(explicit_abspath, os.X_OK)
        ):
            bin_abspath = explicit_abspath
        else:
            bin_abspath = self.get_abspath(str(bin_name))
        assert bin_abspath, (
            f"❌ BinProvider {self.name} cannot execute bin_name {bin_name} because it could not find its abspath. (Did {self.__class__.__name__}.install({bin_name}) fail?)"
        )
        assert os.access(cwd, os.R_OK) and os.path.isdir(cwd), (
            f"cwd must be a valid, accessible directory: {cwd}"
        )
        cwd_path = Path(cwd).resolve()
        bin_abspath = self._exec_bin_abspath(Path(bin_abspath))
        cmd = [str(bin_abspath), *(str(arg) for arg in cmd)]
        is_version_probe = len(cmd) == 2 and cmd[1] in {
            "--version",
            "-version",
            "-v",
            "-V",
        }
        exec_log_prefix = ACTIVE_EXEC_LOG_PREFIX.get()
        if should_log_command:
            if exec_log_prefix:
                log_with_trace_depth(
                    logger,
                    py_logging.INFO,
                    max(TRACE_DEPTH.get() - 1, 0),
                    "  $ %s",
                    format_command(cmd),
                )
            elif self.dry_run:
                logger.info(
                    "DRY RUN (%s): %s",
                    self.__class__.__name__,
                    format_command(cmd),
                )

        # https://stackoverflow.com/a/6037494/2156113
        # copy env and modify it to run the subprocess as the the designated user
        current_euid = os.geteuid()
        explicit_env = kwargs.pop("env", None)
        base_env = self.build_exec_env(
            providers=self.exec_env_providers(),
            base_env=explicit_env,
        )
        base_env["PWD"] = str(cwd_path)
        target_pw_record = self.get_pw_record(self.EUID)
        current_pw_record = self.get_pw_record(current_euid)
        run_as_uid = target_pw_record.pw_uid
        run_as_gid = target_pw_record.pw_gid

        def _env_for_identity(
            identity: Any,
            *,
            source_env: dict[str, str],
        ) -> dict[str, str]:
            env = source_env.copy()
            env["HOME"] = identity.pw_dir
            env["LOGNAME"] = identity.pw_name
            env["USER"] = identity.pw_name
            return env

        sudo_env = _env_for_identity(target_pw_record, source_env=base_env)
        fallback_env = _env_for_identity(current_pw_record, source_env=base_env)

        def drop_privileges():
            try:
                # ArchiveBox may start under sudo, lower only its effective uid,
                # then spawn installers from that mixed ruid=0/euid=user state.
                # Permanently drop the child before exec so tools like unzip
                # cannot create root-owned cache files that later hook code
                # running as the user cannot chmod/remove.
                if os.getuid() == 0 and os.geteuid() != 0:
                    os.seteuid(0)
                os.setgid(run_as_gid)
                os.setuid(run_as_uid)
            except Exception:
                pass

        if self.dry_run and not is_version_probe:
            return subprocess.CompletedProcess(cmd, 0, "", "skipped (dry run)")

        kwargs.setdefault("capture_output", True)
        kwargs.setdefault("text", True)

        def _run_exec(
            exec_cmd: list[str],
            *,
            env: dict[str, str],
            preexec_fn: Callable[[], None] | None = None,
        ) -> subprocess.CompletedProcess:
            if kwargs.get("capture_output", True):
                return subprocess.run(
                    exec_cmd,
                    cwd=str(cwd_path),
                    env=env,
                    preexec_fn=preexec_fn,
                    **kwargs,
                )

            popen_kwargs = kwargs.copy()
            popen_kwargs.pop("capture_output", None)

            def start_child_group() -> None:
                os.setsid()
                if preexec_fn is not None:
                    preexec_fn()

            proc = subprocess.Popen(
                exec_cmd,
                cwd=str(cwd_path),
                env=env,
                preexec_fn=start_child_group,
                **popen_kwargs,
            )
            previous_handlers: dict[int, Any] = {}

            def forward_signal(signum: int, frame: Any) -> None:
                try:
                    os.killpg(proc.pid, signum)
                except ProcessLookupError:
                    pass

            for signum in (signal.SIGINT, signal.SIGTERM):
                previous_handlers[signum] = signal.signal(signum, forward_signal)

            try:
                returncode = proc.wait()
            finally:
                for signum, handler in previous_handlers.items():
                    signal.signal(signum, handler)
                if proc.poll() is None:
                    try:
                        os.killpg(proc.pid, signal.SIGTERM)
                    except ProcessLookupError:
                        pass
                    try:
                        proc.wait(timeout=5)
                    except subprocess.TimeoutExpired:
                        try:
                            os.killpg(proc.pid, signal.SIGKILL)
                        except ProcessLookupError:
                            pass
                        proc.wait()

            return subprocess.CompletedProcess(exec_cmd, returncode)

        sudo_failure_output = None
        if current_euid != 0 and run_as_uid != current_euid:
            sudo = EnvProvider().load("sudo", no_cache=True)
            sudo_abspath = (
                str(sudo.loaded_abspath) if sudo and sudo.loaded_abspath else None
            )
            if sudo_abspath:
                sudo_cmd = [sudo_abspath, "-n"]
                if run_as_uid != 0:
                    sudo_cmd.extend(["-u", target_pw_record.pw_name])
                sudo_cmd.extend(["--", *cmd])
                sudo_proc = _run_exec(
                    sudo_cmd,
                    env=sudo_env,
                )
                if sudo_proc.returncode == 0:
                    return sudo_proc
                log_subprocess_output(
                    logger,
                    f"{self.__class__.__name__} sudo exec",
                    sudo_proc.stdout,
                    sudo_proc.stderr,
                    level=py_logging.DEBUG,
                )
                sudo_failure_output = format_subprocess_output(
                    sudo_proc.stdout,
                    sudo_proc.stderr,
                )

        # When running as root but dropping to a non-root user (e.g. brew),
        # use the target user's HOME/LOGNAME/USER env so the dropped-privilege
        # subprocess finds its own cache/config dirs instead of root's.
        dropped_env = (
            sudo_env
            if current_euid == 0 and run_as_uid != current_euid
            else fallback_env
        )
        proc = _run_exec(
            cmd,
            env=dropped_env,
            preexec_fn=drop_privileges,
        )
        if sudo_failure_output and proc.returncode != 0:
            return subprocess.CompletedProcess(
                proc.args,
                proc.returncode,
                proc.stdout,
                "\n".join(
                    part
                    for part in (
                        proc.stderr,
                        f"Previous sudo attempt failed:\n{sudo_failure_output}",
                    )
                    if part
                ),
            )
        return proc

    # CALLING API, DONT OVERRIDE THESE:

    @final
    @binprovider_cache
    @log_method_call(include_result=True)
    def get_abspaths(
        self,
        bin_name: BinName,
        no_cache: bool = False,
    ) -> list[HostBinPath]:
        abspaths: list[HostBinPath] = []

        primary_abspath = self.get_abspath(bin_name, quiet=True, no_cache=no_cache)
        if primary_abspath:
            abspaths.append(primary_abspath)

        for abspath in bin_abspaths(bin_name, PATH=self.PATH):
            if abspath not in abspaths:
                abspaths.append(abspath)

        return abspaths

    @final
    @binprovider_cache
    @log_method_call(include_result=True)
    def get_sha256(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        no_cache: bool = False,
    ) -> Sha256 | None:
        """Get the sha256 hash of the binary at the given abspath (or equivalent hash of the underlying package)"""

        abspath = abspath or self.get_abspath(bin_name, no_cache=no_cache)
        if not abspath or not os.access(abspath, os.R_OK):
            return None

        hash_sha256 = hashlib.sha256()
        with open(abspath, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_sha256.update(chunk)
        return TypeAdapter(Sha256).validate_python(hash_sha256.hexdigest())

    @final
    @binprovider_cache
    @log_method_call(include_result=True)
    def get_abspath(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
    ) -> HostBinPath | None:
        self.setup_PATH(no_cache=no_cache)
        abspath = None
        try:
            abspath = cast(
                AbspathFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="abspath",
                    no_cache=no_cache,
                ),
            )
        except Exception:
            if not quiet:
                raise
        if not abspath:
            return None
        result = TypeAdapter(HostBinPath).validate_python(abspath)
        return result

    @final
    @binprovider_cache
    @log_method_call(include_result=True)
    def get_version(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        quiet: bool = False,
        no_cache: bool = False,
    ) -> SemVer | None:
        version = None
        try:
            version = cast(
                VersionFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="version",
                    abspath=abspath,
                    timeout=self.version_timeout,
                ),
            )
        except Exception as err:
            logger.warning(
                "%s failed to resolve version for %s: %s",
                self.name,
                bin_name,
                err,
            )
            if not quiet:
                raise

        if not version:
            return None

        return self._version_from_result(
            version,
            bin_name=bin_name,
            abspath=abspath,
            quiet=quiet,
        )

    def _version_from_result(
        self,
        version: "VersionFuncReturnValue",
        *,
        bin_name: BinName,
        abspath: HostBinPath | None,
        quiet: bool,
    ) -> SemVer | None:
        if version is None:
            return None
        if isinstance(version, list):
            version_command = [str(arg) for arg in version]
            if not version_command:
                return None
            version_bin_name: BinName | HostBinPath = abspath or bin_name
            if version_command[0] == str(bin_name):
                version_command = version_command[1:]
            else:
                version_bin_name = version_command[0]
                version_command = version_command[1:]
            proc = self.exec(
                bin_name=version_bin_name,
                cmd=version_command,
                timeout=self.version_timeout,
                quiet=quiet,
            )
            if proc.returncode != 0:
                return None
            version = proc.stdout.strip() or proc.stderr.strip()
            if not version:
                return None

        if isinstance(version, SemVer):
            return version
        return SemVer.parse(
            cast("str | tuple[int, ...] | tuple[str, ...]", version),
        )

    @final
    @binprovider_cache
    @log_method_call(include_result=True)
    def get_install_args(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
    ) -> InstallArgs:
        install_args = None
        try:
            install_args = cast(
                InstallArgsFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="install_args",
                ),
            )
        except Exception:
            if not quiet:
                raise

        if not install_args:
            install_args = [bin_name]
        result = TypeAdapter(InstallArgs).validate_python(install_args)
        return result

    @log_method_call(include_result=True)
    def get_packages(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
    ) -> InstallArgs:
        return self.get_install_args(bin_name, quiet=quiet, no_cache=no_cache)

    @log_method_call(include_result=True)
    def get_docs_url(
        self,
        bin_name: BinName,
        quiet: bool = True,
        no_cache: bool = False,
    ) -> str | None:
        try:
            url = cast(
                DocsUrlFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="docs_url",
                ),
            )
        except Exception:
            if not quiet:
                raise
            return None
        return url or None

    @log_method_call()
    def setup(
        self,
        *,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        no_cache: bool = False,
    ) -> None:
        """Override this to do any setup steps needed before installing packaged (e.g. create a venv, init an npm prefix, etc.)"""
        pass

    def supports_min_release_age(
        self,
        action: Literal["install", "update"],
        no_cache: bool = False,
    ) -> bool:
        return False

    def supports_postinstall_disable(
        self,
        action: Literal["install", "update"],
        no_cache: bool = False,
    ) -> bool:
        return False

    def _assert_min_version_satisfied(
        self,
        *,
        bin_name: BinName,
        action: Literal["install", "update"],
        loaded_version: SemVer | None,
        min_version: SemVer | None,
    ) -> None:
        if min_version and loaded_version and loaded_version < min_version:
            raise ValueError(
                f"🚫 {self.__class__.__name__}.{action} resolved {bin_name} with version {loaded_version} which does not satisfy min_version {min_version}",
            )

    @final
    @mutation_locked
    @log_method_call(include_result=True)
    @validate_call
    def install(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
        dry_run: bool | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> ShallowBinary | None:
        explicit_postinstall_scripts = postinstall_scripts is not None
        explicit_min_release_age = min_release_age is not None
        if dry_run is not None and dry_run != self.dry_run:
            return self.get_provider_with_overrides(dry_run=dry_run).install(
                bin_name=bin_name,
                quiet=quiet,
                no_cache=no_cache,
                postinstall_scripts=postinstall_scripts,
                min_release_age=min_release_age,
                min_version=min_version,
            )
        postinstall_scripts = (
            self.postinstall_scripts
            if postinstall_scripts is None
            else postinstall_scripts
        )
        min_release_age = (
            self.min_release_age if min_release_age is None else min_release_age
        )
        if postinstall_scripts is None:
            postinstall_scripts = not self.supports_postinstall_disable(
                "install",
                no_cache=no_cache,
            )
        # Explicit security controls are part of the user-facing contract even
        # when a dry run or cached/no-op install means no package manager work
        # will happen. Provider defaults are handled later so cached hot paths
        # do not probe installer versions just to reuse an existing binary.
        if (
            explicit_min_release_age
            and min_release_age is not None
            and min_release_age > 0
            and not self.supports_min_release_age("install", no_cache=no_cache)
        ):
            logger.warning(
                "⚠️ %s.install ignoring unsupported min_release_age=%s for provider %s",
                self.__class__.__name__,
                min_release_age,
                self.name,
            )
            min_release_age = 0.0
        if (
            explicit_postinstall_scripts
            and postinstall_scripts is False
            and not self.supports_postinstall_disable("install", no_cache=no_cache)
        ):
            logger.warning(
                "⚠️ %s.install ignoring unsupported postinstall_scripts=%s for provider %s",
                self.__class__.__name__,
                postinstall_scripts,
                self.name,
            )
            postinstall_scripts = True
        installed: ShallowBinary | None = None
        if (not no_cache or MUTATION_LOCK_CONTENDED.get()) and not self.dry_run:
            try:
                installed = self.load(
                    bin_name=bin_name,
                    quiet=True,
                    no_cache=no_cache,
                )
            except Exception:
                installed = None
            if installed is not None and (
                min_version is None
                or (
                    installed.loaded_version is not None
                    and installed.loaded_version >= min_version
                )
            ):
                if not self.cached_binary_state_mismatch(bin_name, {}):
                    return installed
        if (
            installed is not None
            and min_version is not None
            and installed.loaded_version is not None
            and installed.loaded_version < min_version
        ):
            installed = self.update(
                bin_name=bin_name,
                quiet=quiet,
                no_cache=False,
                dry_run=dry_run,
                postinstall_scripts=postinstall_scripts,
                min_release_age=min_release_age,
                min_version=min_version,
            )
            if installed:
                return installed

        install_handler = self._get_handler_for_action(
            bin_name=bin_name,
            handler_type="install",
        )
        if (
            getattr(
                getattr(install_handler, "__func__", install_handler),
                "__name__",
                "",
            )
            == "install_noop"
        ):
            result = self.load(bin_name=bin_name, quiet=quiet, no_cache=no_cache)
            if result is not None:
                self._assert_min_version_satisfied(
                    bin_name=bin_name,
                    action="install",
                    loaded_version=result.loaded_version,
                    min_version=min_version,
                )
            return result

        if min_release_age is None:
            min_release_age = (
                7.0
                if self.supports_min_release_age("install", no_cache=no_cache)
                else 0.0
            )

        # Security flag support only matters when we are about to do package
        # manager work. Cached hot-path loads, especially script shebang deps,
        # should not pay installer version probes just to rediscover that an
        # already-installed binary can be reused.
        if (
            not explicit_min_release_age
            and min_release_age is not None
            and min_release_age > 0
            and not self.supports_min_release_age("install", no_cache=no_cache)
        ):
            logger.warning(
                "⚠️ %s.install ignoring unsupported min_release_age=%s for provider %s",
                self.__class__.__name__,
                min_release_age,
                self.name,
            )
            min_release_age = 0.0
        if (
            not explicit_postinstall_scripts
            and postinstall_scripts is False
            and not self.supports_postinstall_disable(
                "install",
                no_cache=no_cache,
            )
        ):
            logger.warning(
                "⚠️ %s.install ignoring unsupported postinstall_scripts=%s for provider %s",
                self.__class__.__name__,
                postinstall_scripts,
                self.name,
            )
            postinstall_scripts = True

        install_args = self.get_install_args(bin_name, quiet=quiet, no_cache=no_cache)
        self.setup(
            postinstall_scripts=postinstall_scripts,
            min_release_age=min_release_age,
            min_version=min_version,
            no_cache=no_cache,
        )

        self.setup_PATH(no_cache=no_cache)
        install_log = None
        exec_log_prefix_token = ACTIVE_EXEC_LOG_PREFIX.set(
            f"⛟  Installing {bin_name} via {self.name}...",
        )
        logger.info(ACTIVE_EXEC_LOG_PREFIX.get())
        try:
            install_log = cast(
                InstallFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="install",
                    install_args=install_args,
                    packages=install_args,
                    no_cache=no_cache,
                    postinstall_scripts=postinstall_scripts,
                    min_release_age=min_release_age,
                    min_version=min_version,
                    timeout=self.install_timeout,
                ),
            )
        except Exception as err:
            install_log = f"❌ {self.__class__.__name__} Failed to install {bin_name}, got {err.__class__.__name__}: {err}"
            if not quiet:
                raise
        finally:
            ACTIVE_EXEC_LOG_PREFIX.reset(exec_log_prefix_token)

        if self.dry_run:
            # return fake ShallowBinary if we're just doing a dry run
            # no point trying to get real abspath or version if nothing was actually installed
            return ShallowBinary.model_construct(
                name=bin_name,
                description=bin_name,
                loaded_binprovider=self,
                loaded_abspath=UNKNOWN_ABSPATH,
                loaded_version=UNKNOWN_VERSION,
                loaded_sha256=UNKNOWN_SHA256,
                loaded_mtime=UNKNOWN_MTIME,
                loaded_euid=UNKNOWN_EUID,
                binproviders=[self],
            )

        self.invalidate_cache(bin_name)

        result = self.load(bin_name, quiet=True, no_cache=no_cache)
        if result is None:
            rollback_output = ""
            try:
                rollback_result = cast(
                    ActionFuncReturnValue,
                    self._call_handler_for_action(
                        bin_name=bin_name,
                        handler_type="uninstall",
                        install_args=install_args,
                        packages=install_args,
                        no_cache=no_cache,
                        postinstall_scripts=postinstall_scripts,
                        min_release_age=min_release_age,
                        min_version=min_version,
                        timeout=self.install_timeout,
                    ),
                )
                if isinstance(rollback_result, str):
                    rollback_output = rollback_result
            except Exception as err:
                rollback_output = (
                    f"Rollback after failed install also failed with "
                    f"{err.__class__.__name__}: {err}"
                )
            self.invalidate_cache(bin_name)
            if not quiet:
                install_output = (
                    f"Installed package did not produce runnable binary {bin_name!r}."
                )
                if install_log:
                    install_output += f"\n{install_log}"
                if rollback_output:
                    install_output += f"\n{rollback_output}"
                raise BinProviderInstallError(
                    self.__class__.__name__,
                    install_args,
                    output=install_output,
                )
            return None
        if result is not None:
            self._assert_min_version_satisfied(
                bin_name=bin_name,
                action="install",
                loaded_version=result.loaded_version,
                min_version=min_version,
            )
        return result

    @final
    @mutation_locked
    @log_method_call(include_result=True)
    @validate_call
    def update(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
        dry_run: bool | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> ShallowBinary | None:
        if dry_run is not None and dry_run != self.dry_run:
            return self.get_provider_with_overrides(dry_run=dry_run).update(
                bin_name=bin_name,
                quiet=quiet,
                no_cache=no_cache,
                postinstall_scripts=postinstall_scripts,
                min_release_age=min_release_age,
                min_version=min_version,
            )
        postinstall_scripts = (
            self.postinstall_scripts
            if postinstall_scripts is None
            else postinstall_scripts
        )
        min_release_age = (
            self.min_release_age if min_release_age is None else min_release_age
        )
        if postinstall_scripts is None:
            postinstall_scripts = not self.supports_postinstall_disable(
                "update",
                no_cache=no_cache,
            )
        if min_release_age is None:
            min_release_age = (
                7.0
                if self.supports_min_release_age("update", no_cache=no_cache)
                else 0.0
            )
        install_args = self.get_install_args(bin_name, quiet=quiet, no_cache=no_cache)
        if (
            min_release_age is not None
            and min_release_age > 0
            and not self.supports_min_release_age("update", no_cache=no_cache)
        ):
            logger.warning(
                "⚠️ %s.update ignoring unsupported min_release_age=%s for provider %s",
                self.__class__.__name__,
                min_release_age,
                self.name,
            )
            min_release_age = 0.0
        if postinstall_scripts is False and not self.supports_postinstall_disable(
            "update",
            no_cache=no_cache,
        ):
            logger.warning(
                "⚠️ %s.update ignoring unsupported postinstall_scripts=%s for provider %s",
                self.__class__.__name__,
                postinstall_scripts,
                self.name,
            )
            postinstall_scripts = True

        update_handler = self._get_handler_for_action(
            bin_name=bin_name,
            handler_type="update",
        )
        if (
            getattr(getattr(update_handler, "__func__", update_handler), "__name__", "")
            == "update_noop"
        ):
            return None

        self.setup(
            postinstall_scripts=postinstall_scripts,
            min_release_age=min_release_age,
            min_version=min_version,
            no_cache=no_cache,
        )

        self.setup_PATH(no_cache=no_cache)
        update_log = None
        exec_log_prefix_token = ACTIVE_EXEC_LOG_PREFIX.set(
            f"⬆ Updating {bin_name} via {self.name}...",
        )
        logger.info(ACTIVE_EXEC_LOG_PREFIX.get())
        try:
            update_log = cast(
                ActionFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="update",
                    install_args=install_args,
                    packages=install_args,
                    no_cache=no_cache,
                    postinstall_scripts=postinstall_scripts,
                    min_release_age=min_release_age,
                    min_version=min_version,
                    timeout=self.install_timeout,
                ),
            )
        except Exception as err:
            update_log = f"❌ {self.__class__.__name__} Failed to update {bin_name}, got {err.__class__.__name__}: {err}"
            if not quiet:
                raise
        finally:
            ACTIVE_EXEC_LOG_PREFIX.reset(exec_log_prefix_token)

        if self.dry_run:
            return ShallowBinary.model_construct(
                name=bin_name,
                description=bin_name,
                loaded_binprovider=self,
                loaded_abspath=UNKNOWN_ABSPATH,
                loaded_version=UNKNOWN_VERSION,
                loaded_sha256=UNKNOWN_SHA256,
                loaded_mtime=UNKNOWN_MTIME,
                loaded_euid=UNKNOWN_EUID,
                binproviders=[self],
            )

        self.invalidate_cache(bin_name)

        result = self.load(bin_name, quiet=True, no_cache=no_cache)
        if not quiet:
            assert result is not None, (
                f"❌ {self.__class__.__name__} Unable to find version for {bin_name} after updating. PATH={self.PATH} LOG={update_log}"
            )
        if result is not None:
            self._assert_min_version_satisfied(
                bin_name=bin_name,
                action="update",
                loaded_version=result.loaded_version,
                min_version=min_version,
            )
        return result

    @final
    @mutation_locked
    @log_method_call(include_result=True)
    @validate_call
    def uninstall(
        self,
        bin_name: BinName,
        quiet: bool = False,
        no_cache: bool = False,
        dry_run: bool | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> bool:
        if dry_run is not None and dry_run != self.dry_run:
            return self.get_provider_with_overrides(dry_run=dry_run).uninstall(
                bin_name=bin_name,
                quiet=quiet,
                no_cache=no_cache,
                postinstall_scripts=postinstall_scripts,
                min_release_age=min_release_age,
                min_version=min_version,
            )
        postinstall_scripts = (
            self.postinstall_scripts
            if postinstall_scripts is None
            else postinstall_scripts
        )
        min_release_age = (
            self.min_release_age if min_release_age is None else min_release_age
        )
        postinstall_scripts = (
            True if postinstall_scripts is None else postinstall_scripts
        )
        min_release_age = 0.0 if min_release_age is None else min_release_age
        had_cached_binary = self.has_cached_binary(bin_name)
        try:
            loaded_binary = self.load(
                bin_name,
                quiet=True,
                no_cache=no_cache,
            )
        except Exception:
            loaded_binary = None
        if loaded_binary is None:
            if had_cached_binary:
                self.invalidate_cache(bin_name)
            return False
        install_args = self.get_install_args(bin_name, quiet=quiet, no_cache=no_cache)
        self.setup_PATH(no_cache=no_cache)
        uninstall_result = None
        exec_log_prefix_token = ACTIVE_EXEC_LOG_PREFIX.set(
            f"🗑️ Uninstalling {bin_name} via {self.name}...",
        )
        logger.info(ACTIVE_EXEC_LOG_PREFIX.get())
        try:
            uninstall_result = cast(
                ActionFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="uninstall",
                    install_args=install_args,
                    packages=install_args,
                    no_cache=no_cache,
                    postinstall_scripts=postinstall_scripts,
                    min_release_age=min_release_age,
                    min_version=min_version,
                    timeout=self.install_timeout,
                ),
            )
        except Exception:
            if not quiet:
                raise
            return False
        finally:
            ACTIVE_EXEC_LOG_PREFIX.reset(exec_log_prefix_token)

        self.invalidate_cache(bin_name)

        if self.dry_run:
            return True

        if uninstall_result is not False:
            logger.info("🗑️ Uninstalled %s via %s", bin_name, self.name)
        return uninstall_result is not False

    @final
    @log_method_call(include_result=True)
    def search(
        self,
        bin_name: BinName,
        min_version: SemVer | None = None,
        min_release_age: float | None = None,
        no_cache: bool = False,
        timeout: int | None = None,
    ) -> "list[Binary]":
        """Search this provider's package index for matches of bin_name.

        Returns a list of non-loaded Binary objects (one per matching package),
        each with binproviders=[self] and overrides set so binary.install()
        will install the matched package via this provider. Empty list when
        the provider has no search backend or finds no matches.
        """
        from .binary import Binary

        try:
            results = cast(
                SearchFuncReturnValue,
                self._call_handler_for_action(
                    bin_name=bin_name,
                    handler_type="search",
                    min_version=min_version,
                    min_release_age=min_release_age,
                    # Use ``install_timeout`` rather than ``version_timeout``
                    # because search hits remote indexes (apt repos,
                    # nix flake registry, npm registry, PyPI, etc.) and
                    # the 10s ``version_timeout`` default is too tight
                    # for first-pull of e.g. the nixpkgs flake.
                    timeout=timeout or self.install_timeout,
                    no_cache=no_cache,
                ),
            )
        except Exception as err:
            logger.debug(
                "%s failed to search for %s: %s",
                self.name,
                bin_name,
                err,
            )
            return []

        if not results:
            return []
        # Each handler returns Binary objects directly so per-provider
        # logic can hydrate name/install_args/description from real
        # search output without indirection.
        binaries: list[Binary] = [
            result for result in results if isinstance(result, Binary)
        ]
        if min_version is None:
            return binaries
        # Filter by ``min_version`` centrally — handlers stash the
        # discovered package version somewhere in ``description`` (by
        # convention, the leading token, but we scan all whitespace-
        # separated tokens so handlers that put the version after a
        # module/image ref don't bypass the filter). Drop entries whose
        # discovered version falls below the floor; keep entries with
        # no parseable version (treat as version-unknown rather than
        # version-too-low) so providers without per-result version
        # output don't get over-filtered.
        filtered: list[Binary] = []
        for binary in binaries:
            discovered = next(
                (
                    parsed
                    for token in binary.description.split()
                    for parsed in (SemVer.parse(token.strip("(),")),)
                    if parsed is not None
                ),
                None,
            )
            if discovered is not None and discovered < min_version:
                continue
            filtered.append(binary)
        return filtered

    @final
    @log_method_call(include_result=True)
    @validate_call
    def load(
        self,
        bin_name: BinName,
        quiet: bool = True,
        no_cache: bool = False,
    ) -> ShallowBinary | None:
        # Cache context includes the provider fields that affect resolution.
        # setup_PATH() is allowed to populate those fields lazily, so run it
        # before both cache reads and live probes; otherwise the read context
        # and write context drift and every cached binary looks stale.
        self.setup_PATH(no_cache=no_cache)
        if not no_cache:
            cached = self.load_cached_binary_by_name(bin_name)
            if cached is not None and cached.loaded_abspath and cached.loaded_version:
                logger.info(
                    format_loaded_binary(
                        "☑️ Loaded",
                        cached.loaded_abspath,
                        cached.loaded_version,
                        self,
                        str(bin_name),
                    ),
                    extra={"abx_cli_duplicate_stdout": True},
                )
                return cached

        # When we have a managed ``bin_dir``, that's the only path we
        # ever load from — iterating ``get_abspaths`` would silently
        # surface ambient PATH candidates that this provider didn't
        # install, breaking install_root isolation. For ambient-only
        # providers (``bin_dir is None``, e.g.
        # ``EnvProvider(bin_dir=None)``), walk every ``get_abspaths``
        # candidate so a broken-on-PATH binary (e.g. linuxbrew cargo
        # with a stale ``libllhttp.so``) doesn't shadow a working one.
        if self.bin_dir is None:
            candidates = self.get_abspaths(bin_name, no_cache=no_cache)
        else:
            primary_abspath = self.get_abspath(
                bin_name,
                quiet=quiet,
                no_cache=no_cache,
            )
            candidates = [primary_abspath] if primary_abspath else []
        if not candidates:
            return None
        for candidate_abspath in candidates:
            result = self._try_load_at_abspath(
                bin_name,
                candidate_abspath,
                quiet=quiet,
                no_cache=no_cache,
            )
            if result is not None:
                logger.info(
                    format_loaded_binary(
                        "☑️ Loaded",
                        candidate_abspath,
                        result.loaded_version,
                        self,
                        str(bin_name),
                    ),
                    extra={"abx_cli_duplicate_stdout": True},
                )
                return result
        return None

    def _try_load_at_abspath(
        self,
        bin_name: BinName,
        installed_abspath: HostBinPath,
        *,
        quiet: bool = True,
        no_cache: bool = False,
    ) -> ShallowBinary | None:
        result = (
            None if no_cache else self.load_cached_binary(bin_name, installed_abspath)
        )
        if result is None:
            loaded_version = self._get_version_at_abspath(
                bin_name,
                installed_abspath,
                quiet=quiet,
            )
            if not loaded_version:
                return None
            loaded_sha256 = (
                self.get_sha256(
                    bin_name,
                    abspath=installed_abspath,
                    no_cache=True,
                )
                or UNKNOWN_SHA256
            )
            cache_write_result = self.write_cached_binary(
                bin_name,
                installed_abspath,
                loaded_version,
                loaded_sha256,
            )
            if cache_write_result is None:
                resolved_path = (
                    Path(installed_abspath).expanduser().resolve(strict=False)
                )
                stat_result = resolved_path.stat()
                loaded_mtime = TypeAdapter(MTimeNs).validate_python(
                    stat_result.st_mtime_ns,
                )
                loaded_euid = TypeAdapter(EUID).validate_python(stat_result.st_uid)
            else:
                loaded_mtime, loaded_euid = cache_write_result
            result = ShallowBinary.model_validate(
                {
                    "name": bin_name,
                    "binprovider": self,
                    "abspath": installed_abspath,
                    "version": loaded_version,
                    "sha256": loaded_sha256,
                    "mtime": loaded_mtime,
                    "euid": loaded_euid,
                    "binproviders": [self],
                },
            )
        return result

    def _get_version_at_abspath(
        self,
        bin_name: BinName,
        installed_abspath: HostBinPath,
        *,
        quiet: bool,
    ) -> SemVer | None:
        return self.get_version(
            bin_name,
            abspath=installed_abspath,
            quiet=quiet,
            no_cache=True,
        )


class EnvProvider(BinProvider):
    name: BinProviderName = "env"
    _log_emoji = "🌍"
    INSTALLER_BIN: BinName = "which"
    INSTALLER_BINPROVIDERS: ClassVar[tuple[BinProviderName, ...] | None] = ("env",)
    INVALIDATE_ONLY_ON_UNINSTALL: ClassVar[bool] = True
    HOST_BINARY_ALIASES: ClassVar[Mapping[str, tuple[str, ...]]] = {
        "chrome": (
            "chrome",
            "chromium",
            "chromium-browser",
            "google-chrome",
            "google-chrome-stable",
        ),
        "chromium": (
            "chromium",
            "chromium-browser",
            "google-chrome",
            "google-chrome-stable",
            "chrome",
        ),
    }
    CACHE_ENV_ALIASES: ClassVar[Mapping[str, tuple[str, ...]]] = {
        "python": ("PYTHON_BINARY",),
        "python3": ("PYTHON_BINARY",),
    }
    PATH: PATHStr = (
        DEFAULT_ENV_PATH  # Ambient runtime PATH; no provider-specific setup step.
    )
    install_root: Path | None = Field(
        default_factory=lambda: (
            abxpkg_install_root_default("env") or (DEFAULT_ABXPKG_LIB_DIR / "env")
        ),
    )
    _projection_providers: list[BinProvider] = PrivateAttr(default_factory=list)

    overrides: "BinProviderOverrides" = {
        "*": {
            "version": "self.default_version_handler",
            "abspath": "self.default_abspath_handler",
            "install_args": "self.default_install_args_handler",
            "install": "self.install_noop",
            "update": "self.update_noop",
            "uninstall": "self.uninstall_noop",
            "docs_url": "self.default_docs_url_handler",
            "search": "self.default_search_handler",
        },
        "python": {
            "abspath": "self.python_abspath_handler",
            "version": "{}.{}.{}".format(*sys.version_info[:3]),
        },
        "python3": {
            "abspath": "self.python_abspath_handler",
            "version": "{}.{}.{}".format(*sys.version_info[:3]),
        },
    }

    def detect_euid(
        self,
        owner_paths: Iterable[str | Path | None] = (),
        preserve_root: bool = False,
    ) -> int:
        if os.geteuid() == 0:
            sudo_uid = os.environ.get("SUDO_UID")
            if sudo_uid:
                try:
                    uid = int(sudo_uid)
                except ValueError:
                    uid = 0
                if uid > 0 and self.uid_has_passwd_entry(uid):
                    return uid
        return super().detect_euid(
            owner_paths=owner_paths,
            preserve_root=preserve_root,
        )

    def set_projection_providers(
        self,
        providers: Iterable[BinProvider],
    ) -> None:
        self._projection_providers = [
            provider for provider in providers if not isinstance(provider, EnvProvider)
        ]

    def add_host_bin_dir(self, bin_dir: Path) -> None:
        self.PATH = self._merge_PATH(
            bin_dir,
            PATH=self.PATH,
            prepend=True,
        )

    def explicit_abspath_overrides(self, abspath: Path) -> "BinaryOverrides":
        if self._is_managed_by_other_provider(abspath):
            return {self.name: {"abspath": str(abspath)}}
        self.add_host_bin_dir(abspath.parent)
        return {}

    def project_binary(
        self,
        binary: ShallowBinary,
        bin_name: BinName | str,
    ) -> HostBinPath | None:
        if binary.loaded_abspath is None:
            return None
        self.setup_PATH()
        loaded_binprovider = binary.loaded_binprovider
        projection_record = self._projection_cache_record(
            binary.loaded_abspath,
            Path(str(bin_name)).name,
        )
        if projection_record is not None:
            loaded_binprovider = self._resolved_provider_from_cache_record(
                projection_record,
            )
        if loaded_binprovider is not None:
            self.set_projection_providers([loaded_binprovider])
        projected_abspath = self._link_loaded_binary(
            Path(str(bin_name)).name,
            binary.loaded_abspath,
        )
        self._ensure_projection_owner(projected_abspath, binary.loaded_euid)
        if (
            binary.loaded_version is not None
            and binary.loaded_sha256 is not None
            and loaded_binprovider is not None
        ):
            self.write_cached_binary(
                Path(str(bin_name)).name,
                projected_abspath,
                binary.loaded_version,
                binary.loaded_sha256,
                resolved_provider_name=loaded_binprovider.name,
                resolved_provider=loaded_binprovider,
                cache_kind="projection",
            )
        return projected_abspath

    def _ensure_projection_owner(
        self,
        projected_abspath: HostBinPath | Path,
        uid: int | None,
    ) -> None:
        if uid is None or uid < 0 or os.geteuid() != 0:
            return
        try:
            gid = self.get_pw_record(uid).pw_gid
        except KeyError:
            gid = os.getegid()
        paths = [
            self.install_root.parent.parent if self.install_root is not None else None,
            self.install_root.parent if self.install_root is not None else None,
            self.install_root,
            self.bin_dir.parent if self.bin_dir is not None else None,
            self.bin_dir,
            Path(projected_abspath),
            self.derived_env_path,
        ]
        for path in paths:
            if path is None or not (path.exists() or path.is_symlink()):
                continue
            try:
                if path.is_symlink():
                    os.lchown(path, uid, gid)
                else:
                    os.chown(path, uid, gid)
            except (NotImplementedError, PermissionError, OSError):
                pass

    def execution_PATH(self) -> PATHStr:
        """Expose accepted host projections without elevating all discovery paths."""
        return (
            TypeAdapter(PATHStr).validate_python(str(self.bin_dir))
            if self.bin_dir is not None
            else self.PATH
        )

    def setup_PATH(self, no_cache: bool = False) -> None:
        """Populate PATH lazily with install_root/bin ahead of the ambient PATH."""
        if self.bin_dir is None and self.install_root is not None:
            self.bin_dir = self.install_root / "bin"
        if self.bin_dir is not None:
            self.PATH = self._merge_PATH(
                self.bin_dir,
                PATH=self.PATH,
                prepend=True,
            )
        super().setup_PATH(no_cache=no_cache)

    def _cache_context(self, bin_name: BinName) -> str:
        provider_config = json.loads(super()._cache_context(bin_name))
        provider_config["env_projection_version"] = 3
        if str(bin_name) in {"python", "python3"}:
            provider_config["runtime_python"] = str(Path(sys.executable).absolute())
        return json.dumps(
            provider_config,
            default=str,
            separators=(",", ":"),
            sort_keys=True,
        )

    def INSTALLER_BINARY(self, no_cache: bool = False) -> ShallowBinary:
        if not no_cache and self._INSTALLER_BINARY and self._INSTALLER_BINARY.is_valid:
            return self._INSTALLER_BINARY

        derived_env_path = self.derived_env_path
        if not no_cache and derived_env_path and derived_env_path.is_file():
            cache = load_derived_cache(derived_env_path)
            for cached_record in cache.values():
                if not isinstance(cached_record, dict):
                    continue
                if cached_record.get("provider_name") != self.name or cached_record.get(
                    "bin_name",
                ) != str(self.INSTALLER_BIN):
                    continue
                cached_abspath = cached_record.get("abspath")
                if not isinstance(cached_abspath, str):
                    continue
                loaded = self.load_cached_binary(
                    self.INSTALLER_BIN,
                    Path(cached_abspath),
                )
                if loaded and loaded.loaded_abspath:
                    self._INSTALLER_BINARY = loaded
                    return loaded

        env_provider = EnvProvider(
            install_root=None,
            bin_dir=None,
        ).get_provider_with_overrides(
            overrides={
                "*": {
                    "version": ["echo", "1.0.0"],
                },
            },
        )

        env_var = f"{self.INSTALLER_BIN.upper()}_BINARY"
        manual = os.environ.get(env_var)
        if manual and os.path.isabs(manual) and Path(manual).is_file():
            env_provider.PATH = env_provider._merge_PATH(
                str(Path(manual).parent),
                PATH=env_provider.PATH,
                prepend=True,
            )

        loaded = env_provider.load(bin_name=self.INSTALLER_BIN, no_cache=no_cache)
        if loaded and loaded.loaded_abspath:
            if loaded.loaded_version and loaded.loaded_sha256:
                self.write_cached_binary(
                    self.INSTALLER_BIN,
                    loaded.loaded_abspath,
                    loaded.loaded_version,
                    loaded.loaded_sha256,
                    resolved_provider_name=(
                        loaded.loaded_binprovider.name
                        if loaded.loaded_binprovider is not None
                        else self.name
                    ),
                    resolved_provider=loaded.loaded_binprovider,
                    cache_kind="dependency",
                )
            self._INSTALLER_BINARY = loaded
            return self._INSTALLER_BINARY

        return super().INSTALLER_BINARY(no_cache=no_cache)

    def _link_loaded_binary(
        self,
        bin_name: BinName | str,
        abspath: HostBinPath | Path,
    ) -> HostBinPath:
        source_path = Path(abspath).expanduser().absolute()
        if (
            self.bin_dir is not None
            and source_path.parent == self.bin_dir
            and source_path.is_symlink()
        ):
            return TypeAdapter(HostBinPath).validate_python(source_path)
        if self.bin_dir is None:
            return TypeAdapter(HostBinPath).validate_python(source_path)
        self.bin_dir.mkdir(parents=True, exist_ok=True)
        # A caller may create a fresh lib root while an earlier abxpkg
        # ``env/bin`` projection is first on PATH (notably the isolated CLI
        # tests in CI). Do not stack projections: EnvProvider executes one
        # managed link hop so host launchers retain their original argv[0],
        # and a second env/bin hop would become argv[0] instead. Preserve the
        # final host/package-manager symlink; only peel abxpkg env/bin links.
        source_path = self._host_projection_target(source_path)
        target = source_path
        for provider in self._projection_providers:
            provider_target = provider.host_projection_target(source_path)
            if provider_target is not None:
                target = provider_target
                break

        link_name = Path(str(bin_name)).name
        if not link_name or link_name in {".", ".."} or "/" in str(bin_name):
            return TypeAdapter(HostBinPath).validate_python(target)

        link_path = self.bin_dir / link_name
        if target.is_symlink():
            target_dest = target.readlink()
            if not target_dest.is_absolute():
                target_dest = target.parent / target_dest
            if target_dest == link_path:
                return TypeAdapter(HostBinPath).validate_python(target)
        if link_path.exists() or link_path.is_symlink():
            if link_path.is_symlink() and link_path.readlink() == target:
                return TypeAdapter(HostBinPath).validate_python(link_path)
        # Hooks commonly launch in parallel and project the same host binary
        # into env/bin at the same time. Let one creator win, then reuse its
        # link. Unlinking the shared destination first lets another process
        # observe it missing (or lose a symlink_to race) and incorrectly fall
        # through to a managed provider.
        try:
            link_path.symlink_to(target)
            return TypeAdapter(HostBinPath).validate_python(link_path)
        except FileExistsError:
            if link_path.is_symlink() and link_path.readlink() == target:
                return TypeAdapter(HostBinPath).validate_python(link_path)

        # An existing link or file points somewhere else. Replace it atomically
        # so readers never observe a missing env/bin entry.
        temp_fd, temp_name = tempfile.mkstemp(
            dir=self.bin_dir,
            prefix=f".{link_name}.",
            suffix=".tmp",
        )
        os.close(temp_fd)
        temp_link = Path(temp_name)
        try:
            temp_link.unlink()
            temp_link.symlink_to(target)
            os.replace(temp_link, link_path)
        finally:
            if temp_link.exists() or temp_link.is_symlink():
                temp_link.unlink()
        return TypeAdapter(HostBinPath).validate_python(link_path)

    @staticmethod
    def _host_projection_target(source_path: Path) -> Path:
        seen_projection_paths: set[Path] = set()
        while (
            source_path.is_symlink()
            and source_path.parent.name == "bin"
            and source_path.parent.parent.name == "env"
            and source_path not in seen_projection_paths
        ):
            seen_projection_paths.add(source_path)
            projected_target = source_path.readlink()
            source_path = (
                projected_target
                if projected_target.is_absolute()
                else source_path.parent / projected_target
            ).absolute()
        return source_path

    def _host_candidate_names(self, bin_name: BinName | str) -> tuple[str, ...]:
        bin_name_str = str(bin_name)
        aliases = self.HOST_BINARY_ALIASES.get(bin_name_str, ())
        return tuple(dict.fromkeys((bin_name_str, *aliases)))

    def _exec_bin_abspath(self, bin_abspath: Path) -> Path:
        # EnvProvider exposes stable managed links under ABXPKG_LIB_DIR/env/bin so
        # PATHs and cached Binary metadata stay portable. Executing those links
        # directly can still change runtime semantics for binaries that inspect
        # argv[0] / sys.executable, notably venv Python hiding its site-packages.
        # Dereference only the managed link itself: run the binary EnvProvider
        # discovered, but do not chase any further symlink chain owned by the OS
        # or package manager.
        projected_version_path = ENV_PROJECTED_VERSION_PATH.get()
        if (
            projected_version_path is not None
            and bin_abspath.absolute() == projected_version_path
        ):
            return bin_abspath
        if (
            self.bin_dir is None
            or bin_abspath.parent != self.bin_dir
            or not bin_abspath.is_symlink()
        ):
            return bin_abspath
        linked_to = bin_abspath.readlink()
        return linked_to if linked_to.is_absolute() else bin_abspath.parent / linked_to

    def _get_version_at_abspath(
        self,
        bin_name: BinName,
        installed_abspath: HostBinPath,
        *,
        quiet: bool,
    ) -> SemVer | None:
        projected_path = Path(installed_abspath).absolute()
        token = ENV_PROJECTED_VERSION_PATH.set(projected_path)
        try:
            return self.get_version(
                bin_name,
                abspath=installed_abspath,
                quiet=quiet,
                no_cache=True,
            )
        finally:
            ENV_PROJECTED_VERSION_PATH.reset(token)

    def default_version_handler(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        timeout: int | None = None,
        no_cache: bool = False,
        **context,
    ) -> "VersionFuncReturnValue":
        owner = next(
            (
                provider
                for provider in self._projection_providers
                if str(provider.INSTALLER_BIN) == str(bin_name)
            ),
            None,
        )
        if owner is None:
            from . import PROVIDER_CLASS_BY_INSTALLER_BIN

            owner_class = PROVIDER_CLASS_BY_INSTALLER_BIN.get(str(bin_name))
            owner = owner_class() if owner_class is not None else None
        if owner is not None and owner.INSTALLER_VERSION_ARGS is not None:
            owner_abspath = abspath or self.get_abspath(
                bin_name,
                quiet=True,
                no_cache=no_cache,
            )
            return owner.installer_version_handler(
                bin_name,
                abspath=owner_abspath,
                timeout=timeout,
                no_cache=no_cache,
                **context,
            )
        return super().default_version_handler(
            bin_name,
            abspath=abspath,
            timeout=timeout,
            no_cache=no_cache,
            **context,
        )

    def _is_managed_by_other_provider(
        self,
        abspath: HostBinPath | Path,
    ) -> bool:
        if self.install_root is None:
            return False

        absolute_abspath = Path(abspath).expanduser().absolute()
        if self._projection_cache_record(absolute_abspath) is not None:
            return False

        lib_dir = self.install_root.parent
        if not lib_dir.is_dir():
            return False

        resolved_abspath = absolute_abspath.resolve(strict=False)
        for provider_root in lib_dir.iterdir():
            if not provider_root.is_dir() or provider_root == self.install_root:
                continue
            try:
                resolved_abspath.relative_to(provider_root.resolve(strict=False))
            except ValueError:
                continue
            return True

        return False

    def _projection_cache_record(
        self,
        abspath: HostBinPath | Path,
        bin_name: BinName | None = None,
    ) -> dict[str, Any] | None:
        absolute_abspath = Path(abspath).expanduser().absolute()
        if self.bin_dir is None or absolute_abspath.parent != self.bin_dir:
            return None
        derived_env_path = self.derived_env_path
        if not derived_env_path or not derived_env_path.is_file():
            return None
        for record in load_derived_cache(derived_env_path).values():
            if (
                isinstance(record, dict)
                and record.get("provider_name") == self.name
                and record.get("cache_kind") == "projection"
                and record.get("abspath") == str(absolute_abspath)
                and (bin_name is None or record.get("bin_name") == str(bin_name))
            ):
                return record
        return None

    def _try_load_at_abspath(
        self,
        bin_name: BinName,
        installed_abspath: HostBinPath,
        *,
        quiet: bool = True,
        no_cache: bool = False,
    ) -> ShallowBinary | None:
        projection_record = self._projection_cache_record(
            installed_abspath,
            bin_name,
        )
        result = super()._try_load_at_abspath(
            bin_name,
            installed_abspath,
            quiet=quiet,
            no_cache=no_cache,
        )
        if result is None or projection_record is None:
            return result

        resolved_provider = self._resolved_provider_from_cache_record(
            projection_record,
        )
        if result.loaded_version is not None and result.loaded_sha256 is not None:
            self.write_cached_binary(
                bin_name,
                installed_abspath,
                result.loaded_version,
                result.loaded_sha256,
                resolved_provider_name=resolved_provider.name,
                resolved_provider=resolved_provider,
                cache_kind="projection",
            )
        return result.model_copy(
            update={
                "loaded_binprovider": resolved_provider,
                "binproviders": [resolved_provider],
            },
        )

    def python_abspath_handler(
        self,
        bin_name: BinName,
        no_cache: bool = False,
        **context,
    ) -> HostBinPath:
        self.setup_PATH(no_cache=no_cache)
        manual = os.environ.get(f"{str(bin_name).upper()}_BINARY") or os.environ.get(
            "PYTHON_BINARY",
        )
        if manual:
            manual_path = Path(manual).expanduser()
            if manual_path.is_absolute() and manual_path.is_file():
                # Python script hooks need the interpreter that owns their
                # installed package deps. Respecting the normal *_BINARY
                # override keeps ``abxpkg run --script ... python3`` from
                # drifting to the interpreter that happens to run abxpkg.
                return self._link_loaded_binary(bin_name, manual_path)
        return self._link_loaded_binary(bin_name, Path(sys.executable).absolute())

    def default_abspath_handler(
        self,
        bin_name: BinName | HostBinPath,
        no_cache: bool = False,
        **context,
    ) -> "AbspathFuncReturnValue":
        bin_name_str = str(bin_name)

        explicit_path = Path(bin_name_str).expanduser()
        if (
            explicit_path.is_absolute()
            and explicit_path.is_file()
            and self._is_managed_by_other_provider(explicit_path)
        ):
            # An explicit path inside another abxpkg provider is already a
            # managed binary, not a host candidate. Preserve its original
            # location so package-manager launchers that resolve files relative
            # to argv[0] keep working.
            return TypeAdapter(HostBinPath).validate_python(explicit_path)

        if self.bin_dir is not None:
            projected_abspath = bin_abspath(bin_name_str, PATH=str(self.bin_dir))
            if (
                projected_abspath is not None
                and self._projection_cache_record(projected_abspath, str(bin_name))
                is not None
            ):
                projected_path = Path(projected_abspath)
                with self.mutation_lock():
                    projected_target = (
                        projected_path.readlink()
                        if projected_path.is_symlink()
                        else None
                    )
                    if (
                        self._get_version_at_abspath(
                            bin_name_str,
                            projected_abspath,
                            quiet=True,
                        )
                        is not None
                    ):
                        return projected_abspath
                    if (
                        projected_target is not None
                        and projected_path.is_symlink()
                        and projected_path.readlink() == projected_target
                    ):
                        projected_path.unlink()

        search_paths = []
        for entry in str(self.PATH or "").split(os.pathsep):
            if not entry:
                continue
            if self.bin_dir is not None and Path(entry) == self.bin_dir:
                continue
            search_paths.append(entry)

        candidates: list[HostBinPath] = []
        for candidate_name in self._host_candidate_names(bin_name_str):
            for abspath in bin_abspaths(
                candidate_name,
                PATH=os.pathsep.join(search_paths),
            ):
                if self.bin_dir is not None and Path(abspath).parent == self.bin_dir:
                    continue
                # EnvProvider projects binaries discovered from the host OS into
                # ``ABXPKG_LIB_DIR/env/bin``. Paths owned by another managed
                # provider are already part of that provider's runtime and must
                # fall through to it instead of being reverse-linked into env/bin.
                # In particular, pnpm's generated shell launchers resolve package
                # files relative to $0 and break when invoked through such a link.
                if self._is_managed_by_other_provider(abspath):
                    continue
                if abspath not in candidates:
                    candidates.append(abspath)

        for abspath in candidates:
            version = self._get_version_at_abspath(
                bin_name_str,
                abspath,
                quiet=True,
            )
            if version is not None:
                if self.bin_dir is None:
                    return abspath
                link_path = self.bin_dir / Path(bin_name_str).name
                with self.mutation_lock():
                    projected_abspath = self._link_loaded_binary(
                        bin_name_str,
                        abspath,
                    )
                    projected_path = Path(projected_abspath)
                    projected_target = (
                        link_path.readlink()
                        if projected_path == link_path and link_path.is_symlink()
                        else None
                    )
                    if (
                        self._get_version_at_abspath(
                            bin_name_str,
                            projected_abspath,
                            quiet=True,
                        )
                        is not None
                    ):
                        return projected_abspath
                    if (
                        projected_target is not None
                        and link_path.is_symlink()
                        and link_path.readlink() == projected_target
                    ):
                        link_path.unlink()

        for provider in self._projection_providers:
            try:
                loaded = provider.load(bin_name_str, no_cache=no_cache)
            except Exception:
                continue
            if loaded is None or loaded.loaded_abspath is None:
                continue
            projection_target = provider.env_projection_target(
                Path(loaded.loaded_abspath),
            )
            if projection_target is None:
                continue
            projected_abspath = self.project_binary(
                loaded.model_copy(update={"loaded_abspath": projection_target}),
                bin_name_str,
            )
            if projected_abspath is None:
                continue
            return projected_abspath

        managed_abspath = None
        if self.bin_dir is not None:
            managed_abspath = bin_abspath(bin_name_str, PATH=str(self.bin_dir))
        if managed_abspath:
            if self._is_managed_by_other_provider(managed_abspath):
                managed_path = Path(managed_abspath)
                if managed_path.is_symlink() and managed_path.parent == self.bin_dir:
                    managed_path.unlink(missing_ok=True)
                return None
            managed_path = Path(managed_abspath)
            with self.mutation_lock():
                managed_target = (
                    managed_path.readlink() if managed_path.is_symlink() else None
                )
                if (
                    self._get_version_at_abspath(
                        bin_name_str,
                        managed_abspath,
                        quiet=True,
                    )
                    is not None
                ):
                    return managed_abspath
                if (
                    managed_target is not None
                    and managed_path.is_symlink()
                    and managed_path.readlink() == managed_target
                ):
                    managed_path.unlink()
        return None

    def supports_min_release_age(
        self,
        action: Literal["install", "update"],
        no_cache: bool = False,
    ) -> bool:
        return False

    def supports_postinstall_disable(
        self,
        action: Literal["install", "update"],
        no_cache: bool = False,
    ) -> bool:
        return False

    @remap_kwargs({"packages": "install_args"})
    @log_method_call(include_result=True)
    def install_noop(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> str:
        """The env BinProvider is ready-only and does not install any packages, so this is a no-op"""
        return "env is ready-only and just checks for existing binaries in $PATH"

    @remap_kwargs({"packages": "install_args"})
    @log_method_call(include_result=True)
    def update_noop(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> str:
        return "env is read-only and just checks for existing binaries in $PATH"

    @remap_kwargs({"packages": "install_args"})
    @log_method_call(include_result=True)
    def uninstall_noop(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
    ) -> bool:
        return False

    @log_method_call(include_result=True)
    def load_cached_binary(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
        cache: dict[str, dict[str, object]] | None = None,
        cache_context: str | None = None,
        cache_context_hash: str | None = None,
        setup_path: bool = True,
    ) -> ShallowBinary | None:
        if self._is_managed_by_other_provider(abspath):
            self.invalidate_cache(bin_name)
            return None
        if logger.isEnabledFor(py_logging.DEBUG):
            log_with_trace_depth(
                logger,
                py_logging.DEBUG,
                TRACE_DEPTH.get(),
                "%s(%s)",
                "BinProvider.load_cached_binary",
                ", ".join(
                    (
                        summarize_value(bin_name, 80),
                        f"abspath={summarize_value(abspath, 120)}",
                    ),
                ),
            )
        return cast(Any, BinProvider.load_cached_binary).__wrapped__(
            self,
            bin_name,
            abspath,
            cache=cache,
            cache_context=cache_context,
            cache_context_hash=cache_context_hash,
            setup_path=setup_path,
        )

    @log_method_call()
    def write_cached_binary(
        self,
        bin_name: BinName,
        abspath: HostBinPath,
        loaded_version: SemVer,
        loaded_sha256: Sha256,
        resolved_provider_name: str | None = None,
        resolved_provider: "BinProvider | None" = None,
        cache_kind: str = "binary",
    ) -> tuple[MTimeNs, EUID] | None:
        absolute_abspath = Path(abspath).expanduser().absolute()
        is_direct_projection = (
            self.bin_dir is not None and absolute_abspath.parent == self.bin_dir
        )
        projection_record = (
            self._projection_cache_record(absolute_abspath, bin_name)
            if is_direct_projection and cache_kind != "projection"
            else None
        )
        if projection_record is not None:
            resolved_provider = self._resolved_provider_from_cache_record(
                projection_record,
            )
            resolved_provider_name = resolved_provider.name
            cache_kind = "projection"
        if not is_direct_projection and self._is_managed_by_other_provider(abspath):
            self.invalidate_cache(bin_name)
            return None
        if logger.isEnabledFor(py_logging.DEBUG):
            log_with_trace_depth(
                logger,
                py_logging.DEBUG,
                TRACE_DEPTH.get(),
                "%s(%s)",
                "BinProvider.write_cached_binary",
                ", ".join(
                    (
                        summarize_value(bin_name, 80),
                        f"abspath={summarize_value(abspath, 120)}",
                        f"version={summarize_value(loaded_version, 80)}",
                        f"sha256={summarize_value(loaded_sha256, 21)}",
                    ),
                ),
            )
        result = cast(Any, BinProvider.write_cached_binary).__wrapped__(
            self,
            bin_name,
            abspath,
            loaded_version,
            loaded_sha256,
            resolved_provider_name,
            resolved_provider,
            cache_kind,
        )
        if result is not None:
            _, loaded_euid = result
            self._ensure_projection_owner(abspath, loaded_euid)
        return result


############################################################################################################


AbspathFuncReturnValue = str | HostBinPath | None
VersionFuncReturnValue = (
    str | list[str] | tuple[int, ...] | tuple[str, ...] | SemVer | None
)  # list[str] is also accepted as an argv override for version probes
InstallArgsFuncReturnValue = list[str] | tuple[str, ...] | str | InstallArgs | None
PackagesFuncReturnValue = InstallArgsFuncReturnValue
InstallFuncReturnValue = str | None
ActionFuncReturnValue = str | bool | None
DocsUrlFuncReturnValue = str | None
SearchFuncReturnValue = list[ShallowBinary] | tuple[ShallowBinary, ...] | None
ProviderFuncReturnValue = (
    AbspathFuncReturnValue
    | VersionFuncReturnValue
    | InstallArgsFuncReturnValue
    | InstallFuncReturnValue
    | ActionFuncReturnValue
    | DocsUrlFuncReturnValue
    | SearchFuncReturnValue
)


@runtime_checkable
class AbspathFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        **context,
    ) -> "AbspathFuncReturnValue": ...


@runtime_checkable
class VersionFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        **context,
    ) -> "VersionFuncReturnValue": ...


@runtime_checkable
class InstallArgsFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        **context,
    ) -> "InstallArgsFuncReturnValue": ...


PackagesFuncWithArgs = InstallArgsFuncWithArgs


@runtime_checkable
class InstallFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        **context: Any,
    ) -> "InstallFuncReturnValue": ...


@runtime_checkable
class ActionFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        **context: Any,
    ) -> "ActionFuncReturnValue": ...


@runtime_checkable
class DocsUrlFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        **context,
    ) -> "DocsUrlFuncReturnValue": ...


@runtime_checkable
class SearchFuncWithArgs(Protocol):
    def __call__(
        _self,
        binprovider: "BinProvider",
        bin_name: BinName,
        min_version: SemVer | None = None,
        min_release_age: float | None = None,
        **context: Any,
    ) -> "SearchFuncReturnValue": ...


AbspathFuncWithNoArgs = Callable[[], AbspathFuncReturnValue]
VersionFuncWithNoArgs = Callable[[], VersionFuncReturnValue]
InstallArgsFuncWithNoArgs = Callable[[], InstallArgsFuncReturnValue]
PackagesFuncWithNoArgs = InstallArgsFuncWithNoArgs
InstallFuncWithNoArgs = Callable[[], InstallFuncReturnValue]
ActionFuncWithNoArgs = Callable[[], ActionFuncReturnValue]
DocsUrlFuncWithNoArgs = Callable[[], DocsUrlFuncReturnValue]
SearchFuncWithNoArgs = Callable[[], SearchFuncReturnValue]

AbspathHandlerValue = (
    SelfMethodName
    | AbspathFuncWithNoArgs
    | AbspathFuncWithArgs
    | AbspathFuncReturnValue
)
VersionHandlerValue = (
    SelfMethodName
    | VersionFuncWithNoArgs
    | VersionFuncWithArgs
    | VersionFuncReturnValue
)
InstallArgsHandlerValue = (
    SelfMethodName
    | InstallArgsFuncWithNoArgs
    | InstallArgsFuncWithArgs
    | InstallArgsFuncReturnValue
)
PackagesHandlerValue = InstallArgsHandlerValue
InstallHandlerValue = (
    SelfMethodName
    | InstallFuncWithNoArgs
    | InstallFuncWithArgs
    | InstallFuncReturnValue
)
ActionHandlerValue = (
    SelfMethodName | ActionFuncWithNoArgs | ActionFuncWithArgs | ActionFuncReturnValue
)
DocsUrlHandlerValue = (
    SelfMethodName
    | DocsUrlFuncWithNoArgs
    | DocsUrlFuncWithArgs
    | DocsUrlFuncReturnValue
)
SearchHandlerValue = (
    SelfMethodName | SearchFuncWithNoArgs | SearchFuncWithArgs | SearchFuncReturnValue
)

HandlerType = Literal[
    "abspath",
    "version",
    "install_args",
    "packages",
    "install",
    "update",
    "uninstall",
    "docs_url",
    "search",
]
HandlerValue = (
    AbspathHandlerValue
    | VersionHandlerValue
    | InstallArgsHandlerValue
    | InstallHandlerValue
    | ActionHandlerValue
    | DocsUrlHandlerValue
    | SearchHandlerValue
)
HandlerReturnValue = (
    AbspathFuncReturnValue
    | VersionFuncReturnValue
    | InstallArgsFuncReturnValue
    | InstallFuncReturnValue
    | ActionFuncReturnValue
    | DocsUrlFuncReturnValue
    | SearchFuncReturnValue
)


class HandlerDict(TypedDict, total=False):
    PATH: PATHStr
    INSTALLER_BIN: BinName
    euid: int | None
    install_root: Path | None
    bin_dir: Path | None
    alias_bin_dir: Path | None
    dry_run: bool
    postinstall_scripts: bool | None
    min_release_age: float | None
    install_timeout: int
    version_timeout: int
    apt_gpg_keys: dict[str, str]
    apt_sources: dict[str, str]
    apt_system_groups: dict[str, dict[str, Any]]
    apt_system_users: dict[str, dict[str, Any]]
    abspath: AbspathHandlerValue
    version: VersionHandlerValue
    install_args: InstallArgsHandlerValue
    packages: InstallArgsHandlerValue
    install: InstallHandlerValue
    update: ActionHandlerValue
    uninstall: ActionHandlerValue
    docs_url: DocsUrlHandlerValue
    search: SearchHandlerValue


# Binary.overrides map BinProviderName:ProviderFieldOrHandlerPatch
# {'brew': {'install_args': [...], 'min_release_age': 0}}
BinaryOverrides = dict[BinProviderName, HandlerDict]

# BinProvider.overrides map BinName:ProviderFieldOrHandlerPatch
# {'wget': {'install_args': [...], 'version_timeout': 30}}
BinProviderOverrides = dict[BinName | Literal["*"], HandlerDict]

# Resolve forward refs at import time so downstream subclasses don't need to call model_rebuild().
ShallowBinary.model_rebuild(_types_namespace=globals())
BinProvider.model_rebuild(_types_namespace=globals())
EnvProvider.model_rebuild(_types_namespace=globals())
