Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-governance/src/lexigram/ai/governance/relay_logs/di.py: 50%

42 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""DI registrations and gateway hooks for relay request logs. 

2 

3The governance provider root keeps the log store/sink hierarchy 

4unbound during ``register()`` (the store needs a database that is only 

5resolvable in ``boot()``). :func:`boot_relay_logs` resolves the 

6database through its contract, builds the SQL store and usage service, 

7and binds them so the relay gateway's best-effort logger and the admin 

8read surface resolve the same instances through the container. 

9 

10Nothing in this module imports gateway implementations. 

11""" 

12 

13from __future__ import annotations 

14 

15from typing import TYPE_CHECKING 

16 

17from lexigram.ai.governance import GovernanceConfig 

18from lexigram.ai.governance.relay_logs import ( 

19 RelayUsageService, 

20 SqlRelayRequestLogStore, 

21) 

22from lexigram.contracts.ai.relay import ( 

23 RelayDailyUsage, 

24 RelayModelRank, 

25 RelayRequestLogEntry, 

26 RelayRequestLogStoreProtocol, 

27 RelayUsageServiceProtocol, 

28) 

29from lexigram.contracts.data import DatabaseProviderProtocol 

30from lexigram.logging import get_logger 

31 

32if TYPE_CHECKING: 

33 from lexigram.contracts.core.di import ( 

34 BootContainerProtocol, 

35 ContainerRegistrarProtocol, 

36 ) 

37 

38logger = get_logger(__name__) 

39 

40__all__ = [ 

41 "NoopRelayRequestLogStore", 

42 "NoopRelayUsageService", 

43 "boot_relay_logs", 

44 "register_relay_logs", 

45] 

46 

47 

48class NoopRelayRequestLogStore(RelayRequestLogStoreProtocol): 

49 """Drop-everything request-log sink used when logging is disabled.""" 

50 

51 async def append(self, entry: RelayRequestLogEntry) -> None: 

52 """Discard *entry*; logging is disabled.""" 

53 del entry 

54 

55 

56class NoopRelayUsageService(RelayUsageServiceProtocol): 

57 """Empty usage read service used when logging is disabled.""" 

58 

59 async def daily_usage(self, user_id: str, days: int) -> list[RelayDailyUsage]: 

60 """Return no usage for *user_id*.""" 

61 del user_id, days 

62 return [] 

63 

64 async def model_rank(self, days: int, limit: int) -> list[RelayModelRank]: 

65 """Return no ranked models.""" 

66 del days, limit 

67 return [] 

68 

69 async def list_requests( 

70 self, 

71 days: int, 

72 page: int, 

73 page_size: int, 

74 *, 

75 user_id: str | None = None, 

76 token_id: str | None = None, 

77 ) -> list[RelayRequestLogEntry]: 

78 """Return no request-log entries.""" 

79 del days, page, page_size, user_id, token_id 

80 return [] 

81 

82 

83def register_relay_logs( 

84 container: ContainerRegistrarProtocol, 

85 config: object, 

86) -> None: 

87 """Register the relay request-log hierarchy by contract. 

88 

89 The root always exposes ``RelayRequestLogStoreProtocol`` and 

90 ``RelayUsageServiceProtocol`` behind no-op instances so the gateway 

91 and admin surfaces can resolve the same protocol-shaped code path 

92 even when logging is disabled or the database is unavailable. When 

93 logging is enabled, the durable store is built from the resolved 

94 database contract during :func:`boot_relay_logs` and the placeholders 

95 are rebound. 

96 

97 Args: 

98 container: The container registrar to bind into. 

99 config: Governance configuration; ``enabled`` gates logging. 

100 """ 

101 container.singleton(RelayRequestLogStoreProtocol, NoopRelayRequestLogStore()) 

102 container.singleton(RelayUsageServiceProtocol, NoopRelayUsageService()) 

103 if not isinstance(config, GovernanceConfig) or not config.enabled: 

104 logger.info("relay_logs_disabled", reason="governance disabled") 

105 return 

106 logger.info("relay_logs_registered") 

107 

108 

109async def boot_relay_logs( 

110 container: BootContainerProtocol, 

111 config: object, 

112) -> None: 

113 """Build the relay log store and usage service and bind them by contract. 

114 

115 Resolution is contract-scoped (only 

116 :class:`~lexigram.contracts.data.DatabaseProviderProtocol`). When 

117 the database is missing, nothing is bound and a startup diagnostic 

118 is logged so the missing dependency is discoverable; the gateway 

119 keeps no-oping on the absent store. 

120 

121 Args: 

122 container: The boot container used to resolve contracts. 

123 config: Governance configuration driving the bootstrap. 

124 """ 

125 if not isinstance(config, GovernanceConfig) or not config.enabled: 

126 logger.info("relay_logs_boot_skipped", reason="governance disabled") 

127 return 

128 

129 database = await container.resolve_optional(DatabaseProviderProtocol) 

130 if database is None: 

131 logger.warning( 

132 "relay_logs_missing_dependency", 

133 missing="DatabaseProviderProtocol", 

134 ) 

135 return 

136 

137 store: RelayRequestLogStoreProtocol = SqlRelayRequestLogStore(database) 

138 service: RelayUsageServiceProtocol = RelayUsageService(database) 

139 container.bind(RelayRequestLogStoreProtocol, store) # type: ignore[type-abstract] 

140 container.bind(RelayUsageServiceProtocol, service) # type: ignore[type-abstract] 

141 logger.info("relay_logs_booted", store="SqlRelayRequestLogStore")