1"""DI registrations and gateway hooks for durable relay channels.
2
3The governance provider root keeps the channel store unbounded during
4``register()`` (it needs a database that is only resolvable in
5``boot()``). :func:`boot_relay_channels` resolves the database through
6its contract and binds ``RelayChannelStoreProtocol`` to the SQL store
7so the gateway's boot reconcile and the admin CRUD actions resolve the
8same instance through the container. When no database is bound, the
9protocol stays unbounded and the gateway keeps its static-configure
10default behavior.
11
12Nothing in this module imports gateway implementations.
13"""
14
15from __future__ import annotations
16
17from typing import TYPE_CHECKING
18
19from lexigram.ai.governance import GovernanceConfig
20from lexigram.ai.governance.relay_channels import SqlRelayChannelStore
21from lexigram.contracts.ai.relay.store import RelayChannelStoreProtocol
22from lexigram.contracts.data import DatabaseProviderProtocol
23from lexigram.logging import get_logger
24
25if TYPE_CHECKING:
26 from lexigram.contracts.core.di import (
27 BootContainerProtocol,
28 ContainerRegistrarProtocol,
29 )
30
31logger = get_logger(__name__)
32
33__all__ = ["boot_relay_channels", "register_relay_channels"]
34
35
36def register_relay_channels(
37 container: ContainerRegistrarProtocol,
38 config: object,
39) -> None:
40 """Register durable relay channel services.
41
42 Nothing is bound when governance is disabled. The SQL store is
43 built and bound during :func:`boot_relay_channels` once the
44 database contract is resolvable; until then the gateway keeps its
45 static channel configuration.
46
47 Args:
48 container: The container registrar to bind into.
49 config: Governance configuration; ``enabled`` gates the store.
50 """
51 del container
52 if not isinstance(config, GovernanceConfig) or not config.enabled:
53 logger.info("relay_channels_disabled", reason="governance disabled")
54 return
55 logger.info("relay_channels_registered")
56
57
58async def boot_relay_channels(
59 container: BootContainerProtocol,
60 config: object,
61) -> None:
62 """Build the SQL channel store and bind it under its contract.
63
64 Resolution is contract-scoped (only
65 :class:`~lexigram.contracts.data.DatabaseProviderProtocol`). When
66 the database is missing, nothing is bound and a startup diagnostic
67 is logged so the missing dependency is discoverable; the gateway
68 keeps its static-configure default.
69
70 Args:
71 container: The boot container used to resolve contracts.
72 config: Governance configuration driving the bootstrap.
73 """
74 if not isinstance(config, GovernanceConfig) or not config.enabled:
75 logger.info("relay_channels_boot_skipped", reason="governance disabled")
76 return
77
78 database = await container.resolve_optional(DatabaseProviderProtocol)
79 if database is None:
80 logger.warning(
81 "relay_channels_missing_dependency",
82 missing="DatabaseProviderProtocol",
83 )
84 return
85
86 store = SqlRelayChannelStore(database)
87 container.singleton(RelayChannelStoreProtocol, store)
88 logger.info("relay_channels_booted", store="SqlRelayChannelStore")