"""Composition-root :class:`Clock` factory (AZ-398). Composition resolves :class:`Clock` exactly once per process per Invariant — Single Clock per process. Live / research / operator binaries call :func:`build_clock(kind="wall")`; the replay binary calls :func:`build_clock(kind="tlog", source=...)` (the replay composition root, AZ-401, wires the tlog timestamp source). Concrete strategy modules (``wall_clock``, ``tlog_derived``) live under :mod:`gps_denied_onboard.clock`; they are imported eagerly here because the Clock has no Tier-specific runtime dependency and the selection happens at startup. """ from __future__ import annotations from collections.abc import Callable, Iterable from typing import TYPE_CHECKING from gps_denied_onboard.clock.tlog_derived import TlogDerivedClock from gps_denied_onboard.clock.wall_clock import WallClock if TYPE_CHECKING: from gps_denied_onboard.clock import Clock __all__ = ["build_clock"] def build_clock( *, kind: str = "wall", source: Callable[[], int] | Iterable[int] | None = None, ) -> "Clock": """Construct the :class:`Clock` strategy for this process. ``kind`` is one of ``"wall"`` (default) or ``"tlog"``. ``source`` is required when ``kind == "tlog"`` (it carries the tlog parser's timestamp stream) and forbidden otherwise. Raises :class:`ValueError` on an unknown ``kind`` or a misconfigured source — neither is recoverable, so failing loudly at composition time is correct. """ if kind == "wall": if source is not None: raise ValueError( "build_clock(kind='wall'): source must be None; " "WallClock takes no upstream timestamp stream." ) return WallClock() if kind == "tlog": if source is None: raise ValueError( "build_clock(kind='tlog'): source is required (the tlog " "timestamp stream from the replay parser)." ) return TlogDerivedClock(source) raise ValueError( f"build_clock: unknown kind {kind!r}; expected 'wall' or 'tlog'" )