"""H — :class:`LibvirtHypervisorBackend`, the 21-method ``HypervisorBackend``.

Implements ``mgf.vm.core.contracts.HypervisorBackend`` (contracts.md §3)
against libvirt / KVM / QEMU. Built **on top of** the foundation slice —
it never re-implements connection handling, error translation, or XML:

* every native libvirt call routes through
  :meth:`mgf.vm.libvirt._connection.LibvirtConnection.call` (RLock-guarded,
  §8 thread-safety);
* every public method is decorated with
  :func:`mgf.vm.libvirt._errors.translate_at_seam` so a ``libvirtError``
  becomes a typed ``mgf.vm.core.exceptions`` error at the seam (§7);
* ``define`` idempotency uses
  :func:`mgf.vm.libvirt._xml.compare.semantic_equal` — NEVER a byte
  compare (libvirt reorders/decorates XML);
* a :class:`mgf.vm.libvirt._spec.LibvirtVmSpec` is the bridge between the
  core ``VmSpec`` and libvirt domain XML.

``DomainId`` is the **libvirt domain UUID** (opaque per contracts.md §2.1).

Cancellation (contracts.md §9): libvirt's blocking calls expose no native
cancel for ``create``/``shutdown``, so long ops run on a daemon worker
thread and the calling thread polls the :class:`CancellationToken` with a
bounded wait, checking the engine's observable state at each yield point.
Success wins over a late cancel (the ``start`` rule).
"""

from __future__ import annotations

import threading
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from xml.etree import ElementTree as ET

from defusedxml import ElementTree as DefusedET
from mgf.vm.core.capabilities import HypervisorCapabilities
from mgf.vm.core.contracts import VmState
from mgf.vm.core.domain import IsolationLevel
from mgf.vm.core.exceptions import (
    DeviceAttachFailed,
    DeviceDetachFailed,
    DomainAlreadyExistsError,
    DomainNotFound,
    DomainStateInvalid,
    GuestUnresponsive,
    HypervisorError,
    MigrationCancelled,
    MigrationFailed,
    MigrationIncompatible,
    OperationCancelled,
    SnapshotExists,
    SnapshotInUse,
    SnapshotNotFound,
    SpecConflict,
)
from mgf.vm.core.ids import DomainId, SnapshotId

from mgf.vm.libvirt._connection import LibvirtConnection
from mgf.vm.libvirt._errors import LibvirtErrorTranslator, translate_at_seam
from mgf.vm.libvirt._event_pump import EventPump, LibvirtEventPump
from mgf.vm.libvirt._events import EventSequencer
from mgf.vm.libvirt._spec import LibvirtVmSpec
from mgf.vm.libvirt._xml import (
    canonicalize,
    semantic_equal,
    vmspec_to_xml,
    xml_to_domaininfo,
)

if TYPE_CHECKING:
    from collections.abc import Callable, Iterator
    from pathlib import Path
    from types import TracebackType

    from mgf.vm.core.cancellation import CancellationToken
    from mgf.vm.core.contracts import (
        DeviceAssignment,
        DomainInfo,
        EventGap,
        MigrationPlan,
        MigrationTarget,
        NetworkLease,
        NetworkProfile,
        VmEvent,
        VmSpec,
        _SnapshotInfoContract,
    )

# libvirt virDomainState codes (stable ABI; named here so the module type-
# checks without importing the binding).
_VIR_DOMAIN_RUNNING = 1
_VIR_DOMAIN_BLOCKED = 2
_VIR_DOMAIN_PAUSED = 3
_VIR_DOMAIN_SHUTDOWN = 4
_VIR_DOMAIN_SHUTOFF = 5
_VIR_DOMAIN_CRASHED = 6
_VIR_DOMAIN_PMSUSPENDED = 7
_RUNNING_STATES = frozenset({_VIR_DOMAIN_RUNNING, _VIR_DOMAIN_BLOCKED})

# Forensic-acquisition escape-hatch flags (stable ABI; D-G22).
_VIR_DUMP_LIVE = 1
_VIR_DOMAIN_CORE_DUMP_FORMAT_RAW = 0

# Domain-snapshot ``<state>`` strings → neutral VmState (D-G21). Unlisted /
# missing values (e.g. ``disk-snapshot``, ``nostate``) degrade to ``None``.
_SNAPSHOT_STATE_MAP: dict[str, VmState] = {
    "running": VmState.RUNNING,
    "blocked": VmState.RUNNING,
    "paused": VmState.PAUSED,
    "pmsuspended": VmState.PAUSED,
    "shutdown": VmState.STOPPED,
    "shutoff": VmState.STOPPED,
    "crashed": VmState.FAILED,
}

# Flags (stable ABI).
_VIR_DOMAIN_AFFECT_LIVE = 1
_VIR_DOMAIN_AFFECT_CONFIG = 2
_VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = 1
_VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = 1
_VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA = 2
_VIR_MIGRATE_LIVE = 1
_VIR_MIGRATE_PERSIST_DEST = 8
_VIR_MIGRATE_UNDEFINE_SOURCE = 16
# interfaceAddresses source: DHCP leases known to the network (works for a
# running domain without a guest agent). Stable ABI value (0).
_VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE = 0

# Cancellation poll cadence (seconds). Bounded so token cancellation is
# observed promptly without busy-spinning.
_POLL_INTERVAL = 0.1
# Default graceful-stop timeout (seconds) when no token bounds the wait.
_DEFAULT_GRACEFUL_TIMEOUT = 30.0


class LibvirtHypervisorBackend:
    """``HypervisorBackend`` over a single libvirt connection.

    Construct with a connection URI (opens a real connection) or adopt an
    already-wrapped :class:`LibvirtConnection` (tests inject a fake
    ``virConnect`` this way). One connection per instance; all native
    calls serialize through it.

    **Engine-specific escape hatch (NOT part of the neutral contract).**
    :meth:`read_domain_xml` and :meth:`redefine_domain_xml` deliberately
    sit OFF the cross-engine ``HypervisorBackend`` Protocol: they pass raw
    libvirt domain XML as a plain ``str``. The engine-neutral SDK MUST NOT
    surface them — they exist solely so a libvirt-aware consumer (e.g. the
    app's ``vm xml`` / config-read commands) can route directly to this
    adapter when it explicitly wants raw libvirt XML. Because they return
    engine XML, any parsing of that XML is the consumer's concern (use
    ``defusedxml`` on the consumer side); this adapter does not parse it.
    """

    def __init__(
        self,
        connection_uri: str | None = None,
        *,
        connection: LibvirtConnection | None = None,
        event_pump: EventPump | None = None,
    ) -> None:
        if connection is not None:
            self._conn = connection
        elif connection_uri is not None:
            self._conn = LibvirtConnection.open(connection_uri)
        else:  # pragma: no cover - guarded by typing at call sites
            raise HypervisorError("either connection_uri or connection is required")
        self._translator = LibvirtErrorTranslator()
        self._caps_cache: HypervisorCapabilities | None = None
        # The D-G17 live-event fan-out. Defaults to the process-singleton
        # pump (resolved lazily in ``watch_events`` so constructing a
        # backend never starts the event-loop thread); tests inject a fake.
        self._event_pump = event_pump
        # Domain type for generated XML (kvm on a real host; test on the
        # test driver). Resolved lazily from the engine's own driver type.
        self._domain_type: str | None = None

    # -- seam glue ----------------------------------------------------------

    def translate_exception(self, exc: Exception) -> Any:
        """Delegate to the foundation translator (used by ``translate_at_seam``)."""
        return self._translator.translate_exception(exc)

    # -- lifecycle ----------------------------------------------------------

    @translate_at_seam
    def close(self) -> None:
        """Release the engine connection (DP-13). Idempotent."""
        self._conn.close()

    def __enter__(self) -> LibvirtHypervisorBackend:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        self.close()

    # -- internal: native-object lookups under the connection lock ----------

    def _engine_domain_type(self) -> str:
        """The libvirt driver type (``kvm`` / ``qemu`` / ``test``).

        Generated domain XML uses ``<domain type=...>``; the test driver
        rejects ``type='kvm'``. We read the engine's own type once and
        cache it. ``getType()`` returns e.g. ``QEMU`` or ``TEST`` — the
        domain attribute is the lowercased form, with ``qemu`` mapped to
        ``kvm`` (the production default).
        """
        if self._domain_type is None:
            raw = str(self._conn.call(self._conn.raw().getType)).lower()
            self._domain_type = "kvm" if raw == "qemu" else raw
        return self._domain_type

    def _lookup(self, d: DomainId) -> Any:
        """Look up a ``virDomain`` by UUID, or raise ``DomainNotFound``.

        Holds the connection lock for the lookup. The returned native
        object MUST only be used by another :meth:`_call_dom` (which
        re-takes the lock) — never touched outside a lock hold.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            return conn.lookupByUUIDString(d.value)

    def _state_code(self, dom: Any) -> int:
        with self._conn.lock:
            state, _reason = dom.state()
        return int(state)

    def _domain_info(self, dom: Any) -> DomainInfo:
        """Build a fully-observed :class:`DomainInfo` for one domain.

        Reads the persistent/live XML + state code, then folds in the live
        observed sub-state — disk sizes (``blockInfo``) and NIC IPs
        (``interfaceAddresses``) — via best-effort enrichment closures. All
        native calls happen under the connection lock (§8); any per-disk /
        per-domain enrichment failure degrades that field, never the read
        (see :meth:`_block_info_fn` / :meth:`_iface_addrs_fn`).
        """
        with self._conn.lock:
            xml = dom.XMLDesc(0)
            state, _reason = dom.state()
            return xml_to_domaininfo(
                xml,
                state_code=int(state),
                block_info=self._block_info_fn(dom),
                interface_addresses=self._iface_addrs_fn(dom),
            )

    @staticmethod
    def _block_info_fn(dom: Any) -> Callable[[str], tuple[int, int] | None]:
        """A best-effort ``target -> (capacity, allocation)`` reader.

        Wraps ``virDomain.blockInfo(target)`` (returns
        ``[capacity, allocation, physical]``). Invoked by the XML reader
        while the connection lock is held (the caller's ``with`` block).
        Any ``libvirtError`` (domain off, missing backing file, …) returns
        ``None`` so only that disk's sizes degrade. The raw error is
        swallowed inside the reader's own ``_safe_block_info`` too — this
        is belt-and-braces and keeps the closure self-contained.
        """

        def _fn(target: str) -> tuple[int, int] | None:
            info = dom.blockInfo(target, 0)
            # libvirt returns [capacity, allocation, physical].
            return int(info[0]), int(info[1])

        return _fn

    @staticmethod
    def _iface_addrs_fn(dom: Any) -> Callable[[], dict[str, Any] | None]:
        """A best-effort interface-address reader (lease source).

        Wraps ``virDomain.interfaceAddresses`` against the DHCP-lease
        source. Off domains / no-lease / unsupported all raise inside
        libvirt; the reader's ``_addrs_by_mac`` swallows that and leaves
        every NIC ``ip=None``.
        """

        def _fn() -> dict[str, Any] | None:
            result = dom.interfaceAddresses(_VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)
            return result if isinstance(result, dict) else None

        return _fn

    def _generated_xml(self, spec: VmSpec | LibvirtVmSpec) -> str:
        """Render spec → domain XML, rewriting the domain type + stamping a sig.

        The spec signature (:meth:`_spec_signature`) is embedded in a
        private ``<metadata>`` namespace element. libvirt preserves
        namespaced metadata across ``XMLDesc()`` unchanged, so it gives
        ``define`` a like-for-like idempotency key: the live domain's
        stored signature vs. the new spec's signature. This sidesteps the
        false-conflict trap of comparing our terse generator output
        against libvirt's heavily-decorated ``XMLDesc`` (which injects
        ``<video>``, ``<mac>``, unit-converted memory, …).
        """
        lv_spec = spec if isinstance(spec, LibvirtVmSpec) else LibvirtVmSpec.from_core(spec)
        bare = self._retype_domain(vmspec_to_xml(lv_spec), self._engine_domain_type())
        return self._stamp_signature(bare, self._spec_signature(bare))

    @staticmethod
    def _retype_domain(xml: str, domain_type: str) -> str:
        """Rewrite the root ``<domain type=...>`` attribute deterministically."""
        root = DefusedET.fromstring(xml)
        if root.get("type") != domain_type:
            root.set("type", domain_type)
        return ET.tostring(root, encoding="unicode")

    @staticmethod
    def _spec_signature(bare_xml: str) -> str:
        """A deterministic semantic signature of the generated domain XML.

        Derived from :func:`canonicalize` (the same machinery
        :func:`semantic_equal` uses — NOT a byte hash of the raw XML), so
        two specs that are semantically equal produce the same signature
        and two that differ produce different ones.
        """
        import hashlib

        canonical = canonicalize(bare_xml, strip_runtime_elements=False)
        return hashlib.sha256(canonical.encode("utf-8")).hexdigest()

    @staticmethod
    def _stamp_signature(xml: str, signature: str) -> str:
        """Embed ``signature`` into a private ``<metadata>`` element."""
        root = DefusedET.fromstring(xml)
        metadata = root.find("metadata")
        if metadata is None:
            metadata = ET.SubElement(root, "metadata")
        spec_el = ET.SubElement(metadata, "{urn:mgf:vm:libvirt:1}spec")
        spec_el.set("sig", signature)
        return ET.tostring(root, encoding="unicode")

    @staticmethod
    def _read_signature(live_xml: str) -> str | None:
        """Extract the stored spec signature from a live domain's XML, if any."""
        try:
            root = DefusedET.fromstring(live_xml)
        except Exception:
            return None
        spec_el = root.find("metadata/{urn:mgf:vm:libvirt:1}spec")
        if spec_el is None:
            return None
        return spec_el.get("sig")

    # -- H1: read / identity ------------------------------------------------

    @translate_at_seam
    def list_domains(self) -> list[DomainInfo]:
        """List every domain the engine knows about. Idempotent."""
        with self._conn.lock:
            conn = self._conn.raw()
            domains = list(conn.listAllDomains(0))
            infos: list[DomainInfo] = []
            for dom in domains:
                xml = dom.XMLDesc(0)
                state, _reason = dom.state()
                infos.append(
                    xml_to_domaininfo(
                        xml,
                        state_code=int(state),
                        block_info=self._block_info_fn(dom),
                        interface_addresses=self._iface_addrs_fn(dom),
                    )
                )
        return infos

    @translate_at_seam
    def get_domain(self, d: DomainId) -> DomainInfo:
        """Fetch one domain. Raises ``DomainNotFound`` if absent."""
        dom = self._lookup(d)
        return self._domain_info(dom)

    @translate_at_seam
    def capabilities(self) -> HypervisorCapabilities:
        """Report engine capabilities (cached by adapter). Idempotent."""
        if self._caps_cache is not None:
            return self._caps_cache
        with self._conn.lock:
            conn = self._conn.raw()
            caps_xml = conn.getCapabilities()
            version = int(conn.getVersion())
            domain_type = str(conn.getType()).lower()
        self._caps_cache = self._parse_capabilities(caps_xml, version, domain_type)
        return self._caps_cache

    # -- H1b: host-network observation (D-G14) ------------------------------

    @translate_at_seam
    def list_networks(self) -> tuple[NetworkProfile, ...]:
        """List the host virtual networks the engine knows about (D-G14).

        Sources each profile from ``conn.listAllNetworks()`` →
        :class:`NetworkProfile` with the bridge from ``net.bridgeName()``,
        the active flag from ``net.isActive()``, and the forward mode parsed
        from the network XML ``<forward mode=...>`` element. Read-only and
        idempotent.

        Per-network degrade (never abort): if inspecting one network raises
        (a transient lookup, a missing bridge on an inactive net, …) that one
        network is dropped from the result rather than failing the whole list.
        A bad bridge / forward-mode read inside an otherwise-OK network only
        degrades that field (left ``None``).
        """
        with self._conn.lock:
            conn = self._conn.raw()
            nets = list(conn.listAllNetworks(0))
            out: list[NetworkProfile] = []
            for net in nets:
                profile = self._network_profile(net)
                if profile is not None:
                    out.append(profile)
        return tuple(out)

    @staticmethod
    def _network_profile(net: Any) -> NetworkProfile | None:
        """Build one :class:`NetworkProfile`, or ``None`` if the net is unusable.

        The name is required; if even that cannot be read the network is
        skipped. The bridge and forward-mode are best-effort — each degrades
        to ``None`` independently so a partially-inspectable network still
        surfaces (mirrors the legacy ``_network_info`` field sourcing).
        """
        from mgf.vm.core.contracts import NetworkProfile

        try:
            name = str(net.name())
        except Exception:
            return None
        if not name:
            return None
        try:
            bridge: str | None = str(net.bridgeName())
        except Exception:
            bridge = None
        try:
            active = bool(net.isActive())
        except Exception:
            active = False
        forward_mode: str | None = None
        try:
            root = DefusedET.fromstring(net.XMLDesc(0))
            forward_el = root.find("forward")
            if forward_el is not None:
                forward_mode = forward_el.get("mode")
        except Exception:
            forward_mode = None
        return NetworkProfile(
            name=name,
            bridge=bridge or None,
            active=active,
            forward_mode=forward_mode,
        )

    @translate_at_seam
    def network_leases(self, network: str) -> tuple[NetworkLease, ...]:
        """List observed DHCP leases on ``network`` (by name) (D-G14).

        Resolves the network via ``conn.networkLookupByName`` then reads
        ``net.DHCPLeases()`` (mirrors the legacy ``network_leases``). Each raw
        lease dict maps to :class:`NetworkLease`: ``mac`` ← ``mac``,
        ``ip`` ← ``ipaddr``, ``hostname`` ← ``hostname``, and ``expires_at``
        from the lease ``expirytime`` (a POSIX seconds value → UTC datetime).

        An off / unknown network has **no observed leases**: an unresolvable
        network (or a ``DHCPLeases`` that raises because the net is inactive)
        degrades to an empty tuple rather than raising (idempotent read).
        """
        with self._conn.lock:
            conn = self._conn.raw()
            try:
                net = conn.networkLookupByName(network)
                raw = net.DHCPLeases()
            except Exception:
                return ()
            out: list[NetworkLease] = []
            for lease in raw or ():
                out.append(self._network_lease(lease))
        return tuple(out)

    @staticmethod
    def _network_lease(lease: dict[str, Any]) -> NetworkLease:
        """Map one libvirt ``DHCPLeases()`` dict to a :class:`NetworkLease`."""
        from mgf.vm.core.contracts import NetworkLease

        mac = str(lease.get("mac") or "")
        ip = lease.get("ipaddr") or None
        hostname = lease.get("hostname") or None
        expires_at: datetime | None = None
        expiry = lease.get("expirytime")
        if expiry is not None:
            try:
                expires_at = datetime.fromtimestamp(int(expiry), tz=UTC)
            except (ValueError, TypeError, OSError):
                expires_at = None
        return NetworkLease(
            mac=mac,
            ip=str(ip) if ip is not None else None,
            hostname=str(hostname) if hostname is not None else None,
            expires_at=expires_at,
        )

    @translate_at_seam
    def bridge_for_domain(self, d: DomainId) -> str | None:
        """Resolve the host bridge a domain's interface is on, or ``None`` (D-G14).

        Resolves the domain by UUID and parses its live XML (``defusedxml`` →
        stdlib Element, the boundary convention). For the first interface that
        resolves to a bridge:

        * ``<interface type='network'><source network='...'/>`` → resolve that
          network via ``conn.networkLookupByName(...).bridgeName()``;
        * ``<interface><source bridge='...'/>`` (e.g. ``type='bridge'``) → the
          ``bridge`` attribute directly.

        Returns the bridge name, or ``None`` when the domain has no
        bridged/network interface (macvtap / vhostuser / no NIC). Mirrors the
        legacy ``bridge_for_vm`` resolution. An unknown domain surfaces as
        ``DomainNotFound`` via the seam.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            xml = dom.XMLDesc(0)
            try:
                root = DefusedET.fromstring(xml)
            except Exception:
                return None
            for iface in root.findall(".//devices/interface"):
                source = iface.find("source")
                if source is None:
                    continue
                iface_type = iface.get("type")
                if iface_type == "network":
                    network_name = source.get("network")
                    if not network_name:
                        continue
                    try:
                        net = conn.networkLookupByName(network_name)
                        return str(net.bridgeName())
                    except Exception:
                        return None
                bridge = source.get("bridge")
                if bridge:
                    return bridge
        return None

    @staticmethod
    def _parse_capabilities(
        caps_xml: str,
        version: int,
        driver_type: str,
    ) -> HypervisorCapabilities:
        """Parse host-capabilities XML into ``HypervisorCapabilities``."""
        root = DefusedET.fromstring(caps_xml)
        host = root.find("host")
        cpu_features: set[str] = set()
        if host is not None:
            cpu = host.find("cpu")
            if cpu is not None:
                model = cpu.find("model")
                if model is not None and model.text:
                    cpu_features.add(f"model:{model.text.strip()}")
                for feat in cpu.findall("feature"):
                    name = feat.get("name")
                    if name:
                        cpu_features.add(name)
        # libvirt version int is encoded major*1_000_000 + minor*1_000 + rel.
        major = version // 1_000_000
        minor = (version % 1_000_000) // 1_000
        rel = version % 1_000
        is_test = driver_type == "test"
        return HypervisorCapabilities(
            engine="libvirt-kvm" if driver_type in {"qemu", "kvm"} else f"libvirt-{driver_type}",
            engine_version=f"{major}.{minor}.{rel}",
            # The test driver fakes migration; real live-migration is only
            # advertised for kvm/qemu.
            supports_live_migration=not is_test,
            supports_memory_snapshots=not is_test,
            supports_device_attach_live=not is_test,
            max_cpus=255,
            max_memory_bytes=(1 << 40) * 4,  # 4 TiB ceiling (advisory)
            cpu_features=frozenset(cpu_features),
            storage_pool_types=frozenset({"dir", "logical", "netfs"}),
            isolation_profiles=frozenset(
                {IsolationLevel.OFF, IsolationLevel.STRICT, IsolationLevel.AIRGAP}
            ),
        )

    # -- H2: definition + lifecycle -----------------------------------------

    @translate_at_seam
    def define(self, spec: VmSpec) -> DomainId:
        """Define a new domain (idempotent on same spec).

        If a domain with the same name already exists, compare it to the
        new spec: equal → return the existing ``DomainId`` (idempotent);
        differing → raise ``SpecConflict``. New → ``defineXML``.

        Equivalence is decided by :meth:`_specs_equivalent`, which prefers
        the stored spec signature (a canonical-form hash — see
        :meth:`_generated_xml`) and falls back to
        :func:`semantic_equal` for domains defined out-of-band (no stored
        signature, e.g. created with ``virsh``).
        """
        xml = self._generated_xml(spec)
        with self._conn.lock:
            conn = self._conn.raw()
            existing = self._find_by_name(conn, spec.name)
            if existing is not None:
                existing_xml = existing.XMLDesc(0)
                if self._specs_equivalent(xml, existing_xml):
                    return DomainId(existing.UUIDString())
                raise SpecConflict(
                    f"a domain named {spec.name!r} already exists with a "
                    "different spec; call redefine() to replace it"
                )
            dom = conn.defineXML(xml)
            return DomainId(dom.UUIDString())

    def _specs_equivalent(self, generated_xml: str, live_xml: str) -> bool:
        """Whether our generated XML matches an existing live domain.

        Prefers the embedded spec signature (like-for-like comparison of
        generator output, immune to libvirt's ``XMLDesc`` decoration). If
        the live domain carries no signature (defined out-of-band), falls
        back to the foundation's :func:`semantic_equal` on the
        signature-stripped XML.
        """
        live_sig = self._read_signature(live_xml)
        if live_sig is not None:
            return live_sig == self._read_signature(generated_xml)
        return semantic_equal(
            self._strip_signature(generated_xml),
            self._strip_signature(live_xml),
        )

    @staticmethod
    def _strip_signature(xml: str) -> str:
        """Remove our private ``<metadata>`` signature element, if present.

        Used only on the :func:`semantic_equal` fallback path so the
        comparison is bare-vs-bare (the generated side carries a stamp the
        out-of-band live side does not).
        """
        root = DefusedET.fromstring(xml)
        metadata = root.find("metadata")
        if metadata is not None:
            for spec_el in list(metadata.findall("{urn:mgf:vm:libvirt:1}spec")):
                metadata.remove(spec_el)
            if len(metadata) == 0 and not (metadata.text or "").strip():
                root.remove(metadata)
        return ET.tostring(root, encoding="unicode")

    @staticmethod
    def _find_by_name(conn: Any, name: str) -> Any | None:
        """Look up a domain by name; return ``None`` if absent (not raise)."""
        try:
            return conn.lookupByName(name)
        except Exception:
            return None

    @translate_at_seam
    def redefine(self, d: DomainId, spec: VmSpec) -> None:
        """Replace the definition of a stopped domain. Idempotent on same spec.

        Raises ``DomainStateInvalid`` if running.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            state, _reason = dom.state()
            if int(state) in _RUNNING_STATES:
                raise DomainStateInvalid(
                    f"domain {d.value!r} is running; stop it before redefine()"
                )
            generated = self._generated_xml(spec)
            existing_xml = dom.XMLDesc(0)
            if self._specs_equivalent(generated, existing_xml):
                return  # idempotent — already the requested spec
            # defineXML on an existing persistent domain replaces it.
            conn.defineXML(generated)

    @translate_at_seam
    def undefine(self, d: DomainId, *, delete_storage: bool = False) -> None:
        """Remove the domain. Idempotent (missing domain is success).

        Raises ``DomainStateInvalid`` if running.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = self._find_by_uuid(conn, d)
            if dom is None:
                return  # already gone — idempotent success
            state, _reason = dom.state()
            if int(state) in _RUNNING_STATES:
                raise DomainStateInvalid(
                    f"domain {d.value!r} is running; stop it before undefine()"
                )
            flags = _VIR_DOMAIN_UNDEFINE_MANAGED_SAVE | _VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA
            try:
                dom.undefineFlags(flags)
            except Exception:
                dom.undefine()

    @translate_at_seam
    def rename(self, d: DomainId, new_name: str) -> None:
        """Rename an inactive domain in place (``virDomainRename``), preserving
        the UUID (D-G26).

        Raises ``DomainNotFound`` if absent, ``DomainStateInvalid`` if running,
        and ``DomainAlreadyExists`` if ``new_name`` is taken by another domain;
        renaming to the current name is a no-op.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = self._find_by_uuid(conn, d)
            if dom is None:
                raise DomainNotFound(f"no domain {d.value!r}")
            if dom.name() == new_name:
                return  # no-op
            state, _reason = dom.state()
            if int(state) in _RUNNING_STATES:
                raise DomainStateInvalid(f"domain {d.value!r} is running; stop it before rename()")
            if self._find_by_name(conn, new_name) is not None:
                raise DomainAlreadyExistsError(f"a domain named {new_name!r} already exists")
            dom.rename(new_name, 0)

    @staticmethod
    def _find_by_uuid(conn: Any, d: DomainId) -> Any | None:
        try:
            return conn.lookupByUUIDString(d.value)
        except Exception:
            return None

    @translate_at_seam
    def start(self, d: DomainId, *, token: CancellationToken | None = None) -> None:
        """Boot the domain. Idempotent (already-running is success).

        Honors ``token`` at engine-yield points; success wins over a late
        cancel (contracts.md §3 ``start``).
        """
        dom = self._lookup(d)
        if self._state_code(dom) in _RUNNING_STATES:
            return  # already running — idempotent
        self._run_cancellable(
            lambda: self._conn.call(dom.create),
            is_done=lambda: self._state_code(dom) in _RUNNING_STATES,
            token=token,
            timeout=None,
        )

    @translate_at_seam
    def stop(
        self,
        d: DomainId,
        *,
        force: bool = False,
        token: CancellationToken | None = None,
    ) -> None:
        """Shut down the domain. Idempotent.

        ``force=False`` is graceful: signal shutdown, wait until stopped
        or the token / default timeout elapses; on timeout raise
        ``GuestUnresponsive``. ``force=True`` pulls the plug — must
        succeed.
        """
        dom = self._lookup(d)
        if self._state_code(dom) == _VIR_DOMAIN_SHUTOFF:
            return  # already stopped — idempotent

        if force:
            self._conn.call(dom.destroy)
            return

        self._conn.call(dom.shutdown)
        deadline_hit = not self._wait_until(
            lambda: self._state_code(dom) == _VIR_DOMAIN_SHUTOFF,
            token=token,
            timeout=_DEFAULT_GRACEFUL_TIMEOUT,
        )
        if token is not None and token.cancelled:
            raise OperationCancelled("graceful stop cancelled by caller")
        if deadline_hit:
            raise GuestUnresponsive(
                f"domain {d.value!r} did not shut down gracefully within "
                f"{_DEFAULT_GRACEFUL_TIMEOUT}s; retry with force=True"
            )

    @translate_at_seam
    def pause(self, d: DomainId) -> None:
        """Pause execution (CPU only). Idempotent."""
        dom = self._lookup(d)
        if self._state_code(dom) == _VIR_DOMAIN_PAUSED:
            return
        self._conn.call(dom.suspend)

    @translate_at_seam
    def resume(self, d: DomainId) -> None:
        """Resume from paused. Idempotent."""
        dom = self._lookup(d)
        if self._state_code(dom) in _RUNNING_STATES:
            return
        self._conn.call(dom.resume)

    @translate_at_seam
    def reboot(
        self,
        d: DomainId,
        *,
        force: bool = False,
        token: CancellationToken | None = None,
    ) -> None:
        """Reboot the guest. ``force`` resets (hard); else graceful reboot."""
        dom = self._lookup(d)
        if force:
            self._conn.call(lambda: dom.reset(0))
            return
        self._run_cancellable(
            lambda: self._conn.call(lambda: dom.reboot(0)),
            is_done=lambda: True,  # reboot returns once signalled
            token=token,
            timeout=None,
        )

    # -- H3 helpers: cancellation -------------------------------------------

    def _wait_until(
        self,
        predicate: Callable[[], bool],
        *,
        token: CancellationToken | None,
        timeout: float | None,
    ) -> bool:
        """Poll ``predicate`` until true / cancelled / timed-out.

        Returns ``True`` if the predicate became true, ``False`` on
        timeout. Cancellation breaks the loop and also returns whatever
        the predicate last reported (the caller inspects the token).
        """
        start = datetime.now(UTC)
        while True:
            if predicate():
                return True
            if token is not None and token.cancelled:
                return bool(predicate())
            if timeout is not None:
                elapsed = (datetime.now(UTC) - start).total_seconds()
                if elapsed >= timeout:
                    return False
            # Bounded wait so cancellation is observed within one cadence.
            if token is not None:
                token.wait(timeout=_POLL_INTERVAL)
            else:
                threading.Event().wait(_POLL_INTERVAL)

    def _run_cancellable(
        self,
        blocking_call: Callable[[], object],
        *,
        is_done: Callable[[], bool],
        token: CancellationToken | None,
        timeout: float | None,
    ) -> None:
        """Run a blocking native call on a worker thread, polling ``token``.

        libvirt's ``create``/``reboot`` block with no native cancel. We
        run them on a daemon thread and poll the token from the caller.
        Per contracts.md §3 ``start``: if the engine reports done before
        the cancel takes effect, success wins (no raise). Otherwise a
        cancelled token raises ``OperationCancelled``.
        """
        if token is None:
            blocking_call()
            return

        error: list[BaseException] = []
        finished = threading.Event()

        def _worker() -> None:
            try:
                blocking_call()
            except BaseException as exc:
                error.append(exc)
            finally:
                finished.set()

        worker = threading.Thread(target=_worker, daemon=True)
        worker.start()

        while not finished.wait(timeout=_POLL_INTERVAL):
            if token.cancelled:
                # Give the engine a brief grace; success wins if it lands.
                if finished.wait(timeout=_POLL_INTERVAL) or is_done():
                    break
                # The native call is still blocked and uncancellable; we
                # cannot abort it cleanly, so report cancellation. The
                # worker thread is daemonic and will not leak the process.
                raise OperationCancelled("operation cancelled by caller")
        if error:
            raise error[0]
        if token.cancelled and not is_done():
            raise OperationCancelled("operation cancelled by caller")

    # -- H4: snapshots ------------------------------------------------------

    @translate_at_seam
    def snapshot(
        self,
        d: DomainId,
        name: str,
        *,
        include_memory: bool = False,
    ) -> SnapshotId:
        """Take a snapshot. Idempotent on same name + same content.

        Raises ``SnapshotExists`` if a snapshot with this name exists but
        was taken with a different ``include_memory`` posture.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            existing = self._find_snapshot(dom, name)
            if existing is not None:
                if self._snapshot_has_memory(existing) != include_memory:
                    raise SnapshotExists(
                        f"snapshot {name!r} already exists with a different memory-capture setting"
                    )
                return SnapshotId(self._snapshot_id(d, name))
            mem = "internal" if include_memory else "no"
            snap_xml = (
                "<domainsnapshot>"
                f"<name>{_xml_escape(name)}</name>"
                f"<memory snapshot='{mem}'/>"
                "</domainsnapshot>"
            )
            try:
                dom.snapshotCreateXML(snap_xml, 0)
            except Exception:
                fallback = f"<domainsnapshot><name>{_xml_escape(name)}</name></domainsnapshot>"
                dom.snapshotCreateXML(fallback, 0)
            return SnapshotId(self._snapshot_id(d, name))

    @translate_at_seam
    def list_snapshots(self, d: DomainId) -> list[_SnapshotInfoContract]:
        """List snapshots for this domain."""
        from mgf.vm.core.contracts import _SnapshotInfoContract

        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            snaps = list(dom.listAllSnapshots(0))
            out: list[_SnapshotInfoContract] = []
            for snap in snaps:
                name = snap.getName()
                has_mem = self._snapshot_has_memory(snap)
                created = self._snapshot_created_at(snap)
                description = self._snapshot_description(snap)
                parent_name = self._snapshot_parent_name(snap)
                state = self._snapshot_state(snap)
                out.append(
                    _SnapshotInfoContract(
                        snapshot_id=SnapshotId(self._snapshot_id(d, name)),
                        parent=d,
                        name=name,
                        created_at=created,
                        has_memory=has_mem,
                        description=description,
                        parent_name=parent_name,
                        state=state,
                    )
                )
        return out

    @translate_at_seam
    def revert_snapshot(
        self,
        d: DomainId,
        s: SnapshotId,
        *,
        token: CancellationToken | None = None,
    ) -> None:
        """Restore the domain to a snapshot."""
        name = self._snapshot_name_from_id(d, s)
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            snap = self._find_snapshot(dom, name)
            if snap is None:
                raise SnapshotNotFound(f"no snapshot {name!r} for domain {d.value!r}")
            if token is not None:
                token.raise_if_cancelled()
            dom.revertToSnapshot(snap, 0)

    @translate_at_seam
    def delete_snapshot(self, d: DomainId, s: SnapshotId) -> None:
        """Delete a snapshot. Idempotent. Raises ``SnapshotInUse`` if it has children."""
        name = self._snapshot_name_from_id(d, s)
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            snap = self._find_snapshot(dom, name)
            if snap is None:
                return  # already gone — idempotent
            if int(snap.numChildren(0)) > 0:
                raise SnapshotInUse(
                    f"snapshot {name!r} has descendant snapshots; delete them first"
                )
            snap.delete(0)

    # snapshot helpers ------------------------------------------------------

    @staticmethod
    def _snapshot_id(d: DomainId, name: str) -> str:
        """Stable opaque SnapshotId string: ``<domain-uuid>/<snap-name>``."""
        return f"{d.value}/{name}"

    @staticmethod
    def _snapshot_name_from_id(d: DomainId, s: SnapshotId) -> str:
        prefix = f"{d.value}/"
        if s.value.startswith(prefix):
            return s.value[len(prefix) :]
        return s.value

    @staticmethod
    def _find_snapshot(dom: Any, name: str) -> Any | None:
        try:
            return dom.snapshotLookupByName(name, 0)
        except Exception:
            return None

    @staticmethod
    def _snapshot_has_memory(snap: Any) -> bool:
        try:
            xml = snap.getXMLDesc(0)
            root = DefusedET.fromstring(xml)
        except Exception:
            return False
        mem = root.find("memory")
        if mem is None:
            return False
        return mem.get("snapshot", "no") not in {"no", ""}

    @staticmethod
    def _snapshot_created_at(snap: Any) -> datetime:
        try:
            xml = snap.getXMLDesc(0)
            root = DefusedET.fromstring(xml)
            raw = root.findtext("creationTime")
        except Exception:
            return datetime.now(UTC)
        if raw:
            return datetime.fromtimestamp(int(raw), tz=UTC)
        return datetime.now(UTC)

    @staticmethod
    def _snapshot_description(snap: Any) -> str:
        """Human-readable snapshot description from the snapshot XML.

        Reads the top-level ``<description>`` element of the domain-snapshot
        XML (mirrors the legacy ``_snapshot_info`` reader). Returns ``""``
        when the engine reports none or the XML cannot be parsed — a missing
        description degrades to empty, never an error (D-D6 defaults).
        """
        try:
            xml = snap.getXMLDesc(0)
            root = DefusedET.fromstring(xml)
        except Exception:
            return ""
        return root.findtext("description") or ""

    @staticmethod
    def _snapshot_parent_name(snap: Any) -> str | None:
        """The name of this snapshot's parent in the snapshot tree, or ``None``.

        Linkage is by **name** (engine-neutral) per the contract. Prefers the
        snapshot XML's top-level ``<parent><name>`` element when present; if
        absent, falls back to ``virDomainSnapshot.getParent()`` and reads that
        object's name. A root snapshot (no parent) yields ``None`` — libvirt
        raises ``VIR_ERR_NO_DOMAIN_SNAPSHOT`` from ``getParent()`` at the root,
        which we swallow to ``None`` rather than surface.
        """
        try:
            xml = snap.getXMLDesc(0)
            root = DefusedET.fromstring(xml)
        except Exception:
            root = None
        if root is not None:
            name = root.findtext("parent/name")
            if name:
                return name
        try:
            parent = snap.getParent()
        except Exception:
            return None
        if parent is None:
            return None
        try:
            return str(parent.getName())
        except Exception:
            return None

    @staticmethod
    def _snapshot_state(snap: Any) -> VmState | None:
        """The VM run-state captured in this domain snapshot (D-G21), or ``None``.

        Reads the top-level ``<state>`` element of the domain-snapshot XML
        (libvirt's domain-state string) and folds it onto the neutral
        :class:`VmState`. An unknown / missing / unparseable state degrades to
        ``None`` rather than an error (D-D6 defaults).
        """
        try:
            xml = snap.getXMLDesc(0)
            root = DefusedET.fromstring(xml)
        except Exception:
            return None
        raw = (root.findtext("state") or "").strip()
        return _SNAPSHOT_STATE_MAP.get(raw)

    # -- H6: migration ------------------------------------------------------

    @translate_at_seam
    def migrate(
        self,
        d: DomainId,
        dest: MigrationTarget,
        *,
        plan: MigrationPlan,
        token: CancellationToken | None = None,
    ) -> None:
        """Live-migrate to ``dest``. Atomic — on failure source stays running.

        Refuses incompatible destinations before transferring state
        (raises ``MigrationIncompatible``). On token-cancel raises
        ``MigrationCancelled`` (source left running). On engine failure
        raises ``MigrationFailed``.

        **Mid-flight cancellation (contracts.md §3/§9).** ``migrateToURI``
        is a long blocking call with no native ``token`` parameter, so it
        runs on a daemon worker thread while the calling thread polls the
        :class:`CancellationToken`. When the token is cancelled mid-flight
        we issue a **best-effort abort** via ``virDomainAbortJob``
        (``dom.abortJob()``) on the *source* domain — libvirt's documented
        mechanism to tear down an in-progress migration job and roll the
        guest back to the source. The outcome is then mapped per §3:

        * abort accepted, source still running → ``MigrationCancelled``
          (rollback ok, source left running);
        * abort raised / source no longer running → ``MigrationFailed``
          (rollback could not be confirmed).

        Success still wins over a late cancel (the ``start`` rule): if the
        migrate worker completes before the abort lands, no error is
        raised.

        **Engine-dependent / cross-host-untested caveat.** A faithful end-
        to-end migration needs a *second* libvirtd to receive the domain;
        the ``test:///default`` driver used in conformance has none and
        the unit suite has no real engine, so the abort/rollback path here
        is exercised only against a mock that emulates ``migrateToURI``
        blocking + ``abortJob`` interrupting it. The real cross-host
        rollback semantics depend on the destination engine and transport
        and are validated out-of-band on a two-host bed, not here.
        """
        if not plan.compatibility.compatible:
            raise MigrationIncompatible(
                "destination failed pre-flight compatibility: "
                + "; ".join(plan.compatibility.reasons or ("unspecified",))
            )
        if dest.libvirt_uri is None:
            raise MigrationIncompatible(
                "libvirt migration requires a destination libvirt_uri in the target"
            )
        if token is not None and token.cancelled:
            raise MigrationCancelled("migration cancelled before it began")

        dom = self._lookup(d)
        if self._state_code(dom) not in _RUNNING_STATES:
            raise DomainStateInvalid(f"domain {d.value!r} must be running to migrate")

        flags = _VIR_MIGRATE_LIVE | _VIR_MIGRATE_PERSIST_DEST | _VIR_MIGRATE_UNDEFINE_SOURCE

        # Fast path: no token → run the blocking call inline (no worker).
        if token is None:
            try:
                self._conn.call(lambda: dom.migrateToURI(dest.libvirt_uri, flags, None, 0))
            except Exception as exc:
                raise self._migrate_failure(d, dest.libvirt_uri, exc) from exc
            return

        # Cancellable path: blocking migrate on a worker, poll the token,
        # and abort the source job mid-flight on cancel.
        error: list[BaseException] = []
        finished = threading.Event()

        def _worker() -> None:
            try:
                self._conn.call(lambda: dom.migrateToURI(dest.libvirt_uri, flags, None, 0))
            except BaseException as exc:  # re-raised on join
                error.append(exc)
            finally:
                finished.set()

        worker = threading.Thread(target=_worker, daemon=True)
        worker.start()

        aborted = False
        while not finished.wait(timeout=_POLL_INTERVAL):
            if token.cancelled and not aborted:
                aborted = True
                # Best-effort abort of the in-flight migration job on the
                # source. virDomainAbortJob rolls the guest back to source.
                self._abort_migration_job(dom)

        if error:
            # The migrate itself raised. If we asked for an abort, the
            # engine's abort-induced error means cancellation succeeded
            # (source rolled back); otherwise it's a genuine failure.
            failure = error[0]
            if not isinstance(failure, Exception):
                raise failure  # never swallow BaseException (KeyboardInterrupt …)
            if aborted:
                raise MigrationCancelled(
                    f"migration of {d.value!r} aborted mid-flight; source "
                    "rolled back and left running"
                ) from failure
            raise self._migrate_failure(d, dest.libvirt_uri, failure) from failure

        # The migrate worker completed cleanly.
        if aborted:
            # We requested an abort but the migrate finished first: success
            # wins over the late cancel (the ``start`` rule).
            return
        return

    def _abort_migration_job(self, dom: Any) -> None:
        """Best-effort ``virDomainAbortJob`` on a source domain mid-migrate.

        Swallows abort-time errors: a failed abort is reported indirectly
        by inspecting the migrate worker's outcome in :meth:`migrate`. We
        never let an abort error mask the migrate's own result.
        """
        try:
            self._conn.call(dom.abortJob)
        except Exception:  # noqa: S110 - outcome decided by worker result
            pass

    def _migrate_failure(
        self, d: DomainId, uri: str, exc: Exception
    ) -> MigrationCancelled | MigrationFailed:
        """Map a ``migrateToURI`` error to ``MigrationCancelled``/``MigrationFailed``."""
        translated = self._translator.translate_exception(exc)
        if isinstance(translated, (OperationCancelled, MigrationCancelled)):
            # Engine reported the operation aborted — source remains.
            return MigrationCancelled(f"migration of {d.value!r} aborted; source left running")
        return MigrationFailed(
            f"migration of {d.value!r} to {uri!r} failed; "
            "source state: running (rollback by engine)"
        )

    # -- H6: device passthrough --------------------------------------------

    @translate_at_seam
    def attach_device(
        self,
        d: DomainId,
        assignment: DeviceAssignment,
        *,
        live: bool = True,
    ) -> None:
        """Plug an already-leased device into the guest. Idempotent on (domain, device)."""
        device_xml = self._hostdev_xml(assignment)
        flags = _VIR_DOMAIN_AFFECT_CONFIG | (_VIR_DOMAIN_AFFECT_LIVE if live else 0)
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            if self._has_hostdev(dom, assignment):
                return  # idempotent — already attached
            try:
                dom.attachDeviceFlags(device_xml, flags)
            except Exception as exc:
                translated = self._translator.translate_exception(exc)
                if isinstance(translated, DomainNotFound):
                    raise translated from exc
                raise DeviceAttachFailed(
                    f"failed to attach device {assignment.device_id} to {d.value!r}: {exc}"
                ) from exc

    @translate_at_seam
    def detach_device(
        self,
        d: DomainId,
        assignment: DeviceAssignment,
        *,
        live: bool = True,
    ) -> None:
        """Unplug a device from the guest. Idempotent."""
        device_xml = self._hostdev_xml(assignment)
        flags = _VIR_DOMAIN_AFFECT_CONFIG | (_VIR_DOMAIN_AFFECT_LIVE if live else 0)
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByUUIDString(d.value)
            if not self._has_hostdev(dom, assignment):
                return  # idempotent — already absent
            try:
                dom.detachDeviceFlags(device_xml, flags)
            except Exception as exc:
                translated = self._translator.translate_exception(exc)
                if isinstance(translated, DomainNotFound):
                    raise translated from exc
                raise DeviceDetachFailed(
                    f"failed to detach device {assignment.device_id} from {d.value!r}: {exc}"
                ) from exc

    @staticmethod
    def _hostdev_xml(assignment: DeviceAssignment) -> str:
        """Synthesize ``<hostdev>`` XML from a broker ``DeviceAssignment``.

        The ``host_handle`` is an engine-friendly PCI BDF in
        ``domain:bus:slot.function`` form (e.g. ``0000:01:00.0``). We do
        NOT claim or fence leases here (the broker owns that) — we only
        render the guest-side passthrough element.
        """
        bdf = assignment.host_handle
        domain, bus, rest = "0x0000", "0x00", "0x00"
        slot, func = "0x00", "0x0"
        parts = bdf.replace("_", ":").split(":")
        try:
            if len(parts) == 3:
                domain = f"0x{int(parts[0], 16):04x}"
                bus = f"0x{int(parts[1], 16):02x}"
                slot_func = parts[2].split(".")
                slot = f"0x{int(slot_func[0], 16):02x}"
                func = f"0x{int(slot_func[1], 16):x}" if len(slot_func) > 1 else "0x0"
            elif len(parts) == 2:
                bus = f"0x{int(parts[0], 16):02x}"
                slot_func = parts[1].split(".")
                slot = f"0x{int(slot_func[0], 16):02x}"
                func = f"0x{int(slot_func[1], 16):x}" if len(slot_func) > 1 else "0x0"
        except (ValueError, IndexError):
            # Unparseable handle → emit a minimal element; libvirt will
            # reject it and the seam surfaces DeviceAttachFailed.
            pass
        _ = rest
        hostdev = ET.Element("hostdev", {"mode": "subsystem", "type": "pci", "managed": "yes"})
        source = ET.SubElement(hostdev, "source")
        ET.SubElement(
            source,
            "address",
            {"domain": domain, "bus": bus, "slot": slot, "function": func},
        )
        return ET.tostring(hostdev, encoding="unicode")

    def _has_hostdev(self, dom: Any, assignment: DeviceAssignment) -> bool:
        """Whether ``dom`` already has a hostdev matching ``assignment``."""
        try:
            xml = dom.XMLDesc(0)
            root = DefusedET.fromstring(xml)
        except Exception:
            return False
        want = self._hostdev_address(self._hostdev_xml(assignment))
        for hostdev in root.findall(".//devices/hostdev"):
            addr = hostdev.find("source/address")
            if addr is not None and self._addr_tuple(addr) == want:
                return True
        return False

    @staticmethod
    def _hostdev_address(hostdev_xml: str) -> tuple[str, str, str, str] | None:
        root = DefusedET.fromstring(hostdev_xml)
        addr = root.find("source/address")
        if addr is None:
            return None
        return LibvirtHypervisorBackend._addr_tuple(addr)

    @staticmethod
    def _addr_tuple(addr: Any) -> tuple[str, str, str, str]:
        def norm(v: str | None) -> str:
            if not v:
                return "0x0"
            return f"0x{int(v, 16):x}"

        return (
            norm(addr.get("domain")),
            norm(addr.get("bus")),
            norm(addr.get("slot")),
            norm(addr.get("function")),
        )

    # -- H5: events ---------------------------------------------------------

    @translate_at_seam
    def watch_events(
        self,
        d: DomainId | None = None,
    ) -> Iterator[VmEvent | EventGap]:
        """Stream domain events. Resumable; closing the iterator releases the sub.

        Returns a :class:`_EventStream` — a context-manageable, closable,
        **blocking** iterator. The causal ordering + gap synthesis live in
        the pure :class:`EventSequencer`; live delivery is the shared
        :class:`LibvirtEventPump` (D-G17), which the stream subscribes to.

        Like every other public method this is behind the exception seam
        (§7/EH-02): the stream is primed **eagerly** inside
        :meth:`_EventStream.__init__` (it makes raw
        ``lookupByUUIDString``/``listAllDomains``/``state`` calls), so any
        ``libvirtError`` raised while priming — e.g. a non-existent domain
        filter (``DomainNotFound``) or a transient connection issue
        (``HypervisorTransientError``) — is translated to a typed
        ``mgf.vm.core.exceptions`` error here. The decorator spans the
        synchronous construction; thereafter live events arrive via the
        pump's single ``virEventRegisterAny`` callback.
        """
        pump = self._event_pump or LibvirtEventPump.instance()
        return _EventStream(self._conn, sequencer=EventSequencer(), domain_filter=d, pump=pump)

    # -- libvirt escape hatch: raw domain XML (OFF the neutral Protocol) -----
    #
    # These two methods are engine-specific extras, deliberately NOT on the
    # cross-engine ``HypervisorBackend`` Protocol (see the class docstring).
    # They pass raw libvirt domain XML as ``str`` so a libvirt-aware
    # consumer can read / redefine a domain's exact engine XML. The neutral
    # SDK must not expose them. They preserve the legacy ``LibvirtProvider``
    # semantics: ``domain_xml`` was ``virDomain.XMLDesc()`` (flags ``0``)
    # and ``redefine_domain`` was ``virConnect.defineXML(xml)`` (the
    # persistent-redefine path).

    @translate_at_seam
    def read_domain_xml(self, name: str) -> str:
        """Return a domain's raw libvirt XML by name (libvirt escape hatch).

        Resolves the domain by name, then returns ``virDomain.XMLDesc(0)``
        — identical to the legacy ``LibvirtProvider.domain_xml`` reader,
        which called ``dom.XMLDesc()`` (the binding default is flags ``0``:
        the persistent/inactive-aware active-config dump, no secure/inactive
        flags). An unknown domain surfaces as the core ``DomainNotFound``
        via the exception seam.

        NOT part of the neutral ``HypervisorBackend`` contract; the SDK must
        not surface it (see the class docstring). The returned string is raw
        engine XML — parsing it (with ``defusedxml``) is the consumer's job.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByName(name)
            return str(dom.XMLDesc(0))

    @translate_at_seam
    def redefine_domain_xml(self, xml: str) -> None:
        """Persistently (re)define a domain from raw libvirt XML (escape hatch).

        Calls ``virConnect.defineXML(xml)`` — the exact persistent-redefine
        path the legacy ``LibvirtProvider.redefine_domain`` used (it did not
        resolve a handle first; ``defineXML`` keys on the name/UUID embedded
        in the XML and replaces an existing persistent domain in place).
        Raw ``libvirtError`` is translated to the core taxonomy at the seam.

        NOT part of the neutral ``HypervisorBackend`` contract; the SDK must
        not surface it. The ``xml`` argument is raw libvirt domain XML.
        """
        with self._conn.lock:
            conn = self._conn.raw()
            conn.defineXML(xml)

    # -- libvirt escape hatch: forensic acquisition (OFF the neutral Protocol)
    #
    # Live memory / screen acquisition for the forensics domain (D-G22).
    # Engine-specific (libvirt ``coreDumpWithFormat`` / ``screenshot``),
    # deliberately NOT on the neutral ``HypervisorBackend`` Protocol and not
    # surfaced by the SDK. They preserve the legacy ``LibvirtProvider``
    # ``dump_memory`` / ``screenshot`` semantics.

    @translate_at_seam
    def dump_memory(self, name: str, dest: Path, *, live: bool = True) -> None:
        """Core-dump a domain's memory to ``dest`` (libvirt escape hatch; D-G22).

        Wraps ``virDomain.coreDumpWithFormat`` (RAW format). ``live=True``
        keeps the VM running across the dump (DUMP_LIVE); ``live=False`` powers
        it off after. Resolves the domain by name; an unknown domain surfaces
        as the core ``DomainNotFound`` via the exception seam.
        """
        dest.parent.mkdir(parents=True, exist_ok=True)
        flags = _VIR_DUMP_LIVE if live else 0
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByName(name)
            dom.coreDumpWithFormat(str(dest), _VIR_DOMAIN_CORE_DUMP_FORMAT_RAW, flags)

    @translate_at_seam
    def screenshot(self, name: str, dest: Path) -> None:
        """Capture a domain's primary-display screenshot to ``dest`` (D-G22).

        Wraps ``virDomain.screenshot`` over a ``virStream`` (screen 0), draining
        the stream into ``dest``. Engine-specific; NOT on the neutral Protocol.
        """
        dest.parent.mkdir(parents=True, exist_ok=True)
        with self._conn.lock:
            conn = self._conn.raw()
            dom = conn.lookupByName(name)
            stream = conn.newStream(0)
            dom.screenshot(stream, 0, 0)
            try:
                with dest.open("wb") as out:
                    while True:
                        chunk = stream.recv(65536)
                        if not chunk:
                            break
                        out.write(chunk)
            finally:
                stream.finish()


# ---------------------------------------------------------------------------
# Event stream — the live wiring around the pure sequencer.
# ---------------------------------------------------------------------------


class _EventStream:
    """Closable, **blocking** iterator over ``VmEvent | EventGap``.

    Bridges libvirt's lifecycle callback into the pure
    :class:`EventSequencer`. The shared :class:`LibvirtEventPump`
    (D-G17) pushes raw observations onto this stream's queue from the
    single event-loop thread; the consumer pulls them by iterating, and
    :meth:`__next__` **blocks** until an event arrives or the stream is
    closed (it is a live stream, not a one-shot drain). Per contracts.md
    §8: closing tears the subscription down deterministically — it
    unsubscribes from the pump (no leaked registration, DP-13).

    On construction the stream is seeded with the *current* state of the
    filtered domains (one synthetic observation each) so consumers always
    get a starting point before the first live transition.
    """

    __slots__ = (
        "_closed",
        "_cond",
        "_conn",
        "_domain_filter",
        "_pending",
        "_pump",
        "_sequencer",
        "_sub_id",
    )

    def __init__(
        self,
        conn: LibvirtConnection,
        *,
        sequencer: EventSequencer,
        domain_filter: DomainId | None,
        pump: EventPump,
    ) -> None:
        self._conn = conn
        self._sequencer = sequencer
        self._domain_filter = domain_filter
        self._pump = pump
        self._cond = threading.Condition()
        self._pending: list[VmEvent | EventGap] = []
        self._closed = False
        self._sub_id: int | None = None
        # Seed current state FIRST (deterministic prime-before-live order),
        # then subscribe for live events. The tiny window between the two
        # may drop an event; the sequencer's gap synthesis is the safety
        # net for any consumer that later resumes.
        for domain_id, payload in self._collect_current_state():
            self._pending.extend(self._sequencer.observe(domain_id, payload))
        self._sub_id = pump.subscribe(conn, domain_filter, self.push_raw)

    def _collect_current_state(self) -> list[tuple[DomainId, dict[str, Any]]]:
        """Read the current state of the filtered domains (eager prime)."""
        with self._conn.lock:
            conn = self._conn.raw()
            if self._domain_filter is not None:
                doms = [conn.lookupByUUIDString(self._domain_filter.value)]
            else:
                doms = list(conn.listAllDomains(0))
            observations: list[tuple[DomainId, dict[str, Any]]] = []
            for dom in doms:
                state, reason = dom.state()
                observations.append(
                    (
                        DomainId(dom.UUIDString()),
                        {"event": "current-state", "state": int(state), "reason": int(reason)},
                    )
                )
        return observations

    def __iter__(self) -> _EventStream:
        return self

    def __next__(self) -> VmEvent | EventGap:
        with self._cond:
            while not self._pending and not self._closed:
                self._cond.wait()
            if self._pending:
                return self._pending.pop(0)
            raise StopIteration

    def push_raw(
        self,
        domain_id: DomainId,
        payload: dict[str, Any],
    ) -> None:
        """Hook the pump's fan-out (or a test) uses to feed a raw observation.

        Wakes any thread blocked in :meth:`__next__`.
        """
        with self._cond:
            if self._closed:
                return
            self._pending.extend(self._sequencer.observe(domain_id, payload))
            self._cond.notify_all()

    def close(self) -> None:
        """Release the subscription. Idempotent (DP-13).

        Unsubscribes from the pump **outside** the stream lock — the
        pump's unsubscribe takes its own lock, and holding both would
        invert the lock order against the fan-out path.
        """
        with self._cond:
            if self._closed:
                return
            self._closed = True
            self._pending.clear()
            self._cond.notify_all()
        if self._sub_id is not None:
            self._pump.unsubscribe(self._sub_id)

    def __enter__(self) -> _EventStream:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        self.close()


def _xml_escape(value: str) -> str:
    """Minimal XML escaping for text injected into snapshot XML."""
    return (
        value.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
    )


__all__ = ["LibvirtHypervisorBackend"]
