Coverage for src / lexigram / contracts / ai / relay / operations.py: 24%

130 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-19 05:41 +0800

1"""Relay operational read and control contracts. 

2 

3Defines the health, capability, metric, report, and policy-control value 

4types and service protocols that the gateway and governance admin 

5surfaces consume. Value types are immutable, redaction-safe, and reject 

6nonsense inputs at construction time; the service protocols are the only 

7cross-package boundary for operational data. 

8""" 

9 

10from __future__ import annotations 

11 

12from collections.abc import Mapping, Sequence 

13from dataclasses import dataclass 

14from datetime import datetime 

15from typing import Literal, Protocol, runtime_checkable 

16 

17from lexigram.contracts.ai.relay.types import ConversionQuality, RelayFormat 

18 

19__all__ = [ 

20 "RelayActiveStream", 

21 "RelayChannelHealth", 

22 "RelayOperationsControlProtocol", 

23 "RelayOperationsProtocol", 

24 "RelayPolicyChange", 

25 "RelayPolicySnapshot", 

26 "RelayPolicyStoreProtocol", 

27 "RelayRegistryDiagnostics", 

28 "RelayRouteMetrics", 

29 "TimeWindow", 

30] 

31 

32_CHANNEL_HEALTH_STATUSES = frozenset({"healthy", "degraded", "unavailable", "failed"}) 

33"""Stable channel health status values.""" 

34 

35 

36@dataclass(frozen=True, slots=True) 

37class TimeWindow: 

38 """A closed, forward time range for metric aggregation. 

39 

40 Attributes: 

41 start: Inclusive window start (UTC). 

42 end: Inclusive window end (UTC), strictly after ``start``. 

43 """ 

44 

45 start: datetime 

46 end: datetime 

47 

48 def __post_init__(self) -> None: 

49 """Reject empty and inverted windows.""" 

50 if self.end <= self.start: 

51 raise ValueError("window end must be after window start") 

52 

53 

54@dataclass(frozen=True, slots=True) 

55class RelayActiveStream: 

56 """One in-flight upstream stream. 

57 

58 Attributes: 

59 stream_id: Unique identifier of the stream session. 

60 channel: Name of the channel serving the stream. 

61 model: Outbound model alias of the stream. 

62 request_id: Gateway request identifier the stream belongs to. 

63 started_at: When the stream started (UTC). 

64 """ 

65 

66 stream_id: str 

67 channel: str 

68 model: str 

69 request_id: str 

70 started_at: datetime 

71 

72 def __post_init__(self) -> None: 

73 """Reject empty identifiers.""" 

74 if not self.stream_id: 

75 raise ValueError("stream_id must not be empty") 

76 if not self.channel: 

77 raise ValueError("channel must not be empty") 

78 if not self.request_id: 

79 raise ValueError("request_id must not be empty") 

80 

81 

82@dataclass(frozen=True, slots=True) 

83class RelayChannelHealth: 

84 """One gateway channel's operational health snapshot. 

85 

86 Attributes: 

87 channel: Channel name. 

88 target: Target wire format the channel serves. 

89 status: Stable status value. 

90 model_count: Number of model aliases the channel serves. 

91 latency_ms_p50: Median request latency, or ``None`` when unknown. 

92 latency_ms_p95: P95 request latency, or ``None`` when unknown. 

93 failure_count: Upstream failures in the window. 

94 checked_at: When the snapshot was taken (UTC). 

95 detail_code: Machine-readable reason for degraded/failed status. 

96 """ 

97 

98 channel: str 

99 target: RelayFormat 

100 status: Literal["healthy", "degraded", "unavailable", "failed"] 

101 model_count: int 

102 latency_ms_p50: float | None 

103 latency_ms_p95: float | None 

104 failure_count: int 

105 checked_at: datetime 

106 detail_code: str | None = None 

107 

108 def __post_init__(self) -> None: 

109 """Validate the snapshot fields.""" 

110 if not self.channel: 

111 raise ValueError("channel must not be empty") 

112 if self.status not in _CHANNEL_HEALTH_STATUSES: 

113 raise ValueError(f"unknown channel health status {self.status!r}") 

114 if self.model_count < 0: 

115 raise ValueError("model_count must not be negative") 

116 if self.failure_count < 0: 

117 raise ValueError("failure_count must not be negative") 

118 if self.latency_ms_p50 is not None and self.latency_ms_p50 < 0: 

119 raise ValueError("latency_ms_p50 must not be negative") 

120 if self.latency_ms_p95 is not None and self.latency_ms_p95 < 0: 

121 raise ValueError("latency_ms_p95 must not be negative") 

122 

123 

124@dataclass(frozen=True, slots=True) 

125class RelayRouteMetrics: 

126 """Aggregated conversion metrics for one directed route and window. 

127 

128 Attributes: 

129 source: Source wire format. 

130 target: Target wire format. 

131 quality: Static conversion quality of the route. 

132 request_count: Completed requests on the route in the window. 

133 loss_counts: Conversion loss codes and their counts. 

134 unsupported_count: Requests dropped for unsupported features. 

135 stream_failure_count: Streams that failed on the route. 

136 converter_id: Route converter identifier, when known. 

137 window_start: Aggregation window start (UTC). 

138 window_end: Aggregation window end (UTC). 

139 """ 

140 

141 source: RelayFormat 

142 target: RelayFormat 

143 quality: ConversionQuality 

144 request_count: int 

145 loss_counts: Mapping[str, int] 

146 unsupported_count: int 

147 stream_failure_count: int 

148 converter_id: str | None 

149 window_start: datetime 

150 window_end: datetime 

151 

152 def __post_init__(self) -> None: 

153 """Validate the aggregated counters.""" 

154 if self.request_count < 0: 

155 raise ValueError("request_count must not be negative") 

156 if self.unsupported_count < 0: 

157 raise ValueError("unsupported_count must not be negative") 

158 if self.stream_failure_count < 0: 

159 raise ValueError("stream_failure_count must not be negative") 

160 if self.window_end <= self.window_start: 

161 raise ValueError("window end must be after window start") 

162 

163 

164@dataclass(frozen=True, slots=True) 

165class RelayRegistryDiagnostics: 

166 """Read-only registry state for operational diagnostics. 

167 

168 Attributes: 

169 converter_id: Converter engine identifier. 

170 converter_version: Converter engine version string. 

171 mapper_ids: Registered mapper wire-format identifiers. 

172 supported_routes: Directed route pairs served by the engine. 

173 registration_errors: Errors observed at registration time. 

174 """ 

175 

176 converter_id: str 

177 converter_version: str 

178 mapper_ids: tuple[str, ...] 

179 supported_routes: tuple[tuple[RelayFormat, RelayFormat], ...] 

180 registration_errors: tuple[str, ...] = () 

181 

182 

183@dataclass(frozen=True, slots=True) 

184class RelayPolicySnapshot: 

185 """Current gateway routing policy. 

186 

187 Attributes: 

188 enabled_channels: Channel name to enabled flag. 

189 allowed_model_options: Channel name to allowed option names. 

190 media_allowed_schemes: Media resolver URL schemes allowlist. 

191 media_allowed_hosts: Media resolver URL hosts allowlist. 

192 max_request_bytes: Maximum accepted request body size. 

193 max_stream_seconds: Maximum streaming duration. 

194 """ 

195 

196 enabled_channels: Mapping[str, bool] 

197 allowed_model_options: Mapping[str, frozenset[str]] 

198 media_allowed_schemes: frozenset[str] 

199 media_allowed_hosts: frozenset[str] 

200 max_request_bytes: int 

201 max_stream_seconds: float 

202 

203 def __post_init__(self) -> None: 

204 """Validate the policy limits and allowlists.""" 

205 if "*" in self.media_allowed_hosts: 

206 raise ValueError("media_allowed_hosts must not contain wildcards") 

207 if self.max_request_bytes < 0: 

208 raise ValueError("max_request_bytes must not be negative") 

209 if self.max_stream_seconds <= 0: 

210 raise ValueError("max_stream_seconds must be positive") 

211 

212 

213@dataclass(frozen=True, slots=True) 

214class RelayPolicyChange: 

215 """A typed, partial policy mutation request. 

216 

217 Only the fields explicitly set are changed; ``None`` means unchanged. 

218 """ 

219 

220 channel: str | None = None 

221 enabled: bool | None = None 

222 allowed_model_options: frozenset[str] | None = None 

223 media_allowed_schemes: frozenset[str] | None = None 

224 media_allowed_hosts: frozenset[str] | None = None 

225 max_request_bytes: int | None = None 

226 max_stream_seconds: float | None = None 

227 

228 def __post_init__(self) -> None: 

229 """Validate the mutation payload.""" 

230 if self.media_allowed_hosts is not None and "*" in self.media_allowed_hosts: 

231 raise ValueError("media_allowed_hosts must not contain wildcards") 

232 if self.max_request_bytes is not None and self.max_request_bytes < 0: 

233 raise ValueError("max_request_bytes must not be negative") 

234 if self.max_stream_seconds is not None and self.max_stream_seconds <= 0: 

235 raise ValueError("max_stream_seconds must be positive") 

236 

237 

238@runtime_checkable 

239class RelayPolicyStoreProtocol(Protocol): 

240 """Persistent backend for the runtime gateway routing policy. 

241 

242 The store is the source of truth for ``RelayPolicySnapshot`` between 

243 control mutations: ``load`` returns the current snapshot and ``save`` 

244 persists a full replacement atomically. 

245 """ 

246 

247 async def load(self) -> RelayPolicySnapshot: 

248 """Return the current policy snapshot. 

249 

250 Returns: 

251 The current snapshot; a store that has never been written 

252 returns the initial snapshot it was constructed with. 

253 """ 

254 ... 

255 

256 async def save(self, snapshot: RelayPolicySnapshot) -> None: 

257 """Atomically replace the stored snapshot with *snapshot*. 

258 

259 Args: 

260 snapshot: The full replacement snapshot. Partial updates are 

261 composed by the caller before saving. 

262 """ 

263 ... 

264 

265 

266@runtime_checkable 

267class RelayOperationsProtocol(Protocol): 

268 """Read-only operational queries for admin surfaces.""" 

269 

270 async def channel_health(self) -> Sequence[RelayChannelHealth]: 

271 """Return a health snapshot per gateway channel. 

272 

273 Returns: 

274 One snapshot per configured channel. A dependency that is 

275 not registered yields an ``unavailable`` snapshot, never a 

276 fabricated healthy one. 

277 """ 

278 ... 

279 

280 async def route_metrics( 

281 self, 

282 window: TimeWindow, 

283 ) -> Sequence[RelayRouteMetrics]: 

284 """Return aggregated conversion metrics inside *window*. 

285 

286 Args: 

287 window: Bounded aggregation window; unbounded windows are 

288 rejected by the caller. 

289 

290 Returns: 

291 One aggregation per directed route that saw activity. 

292 """ 

293 ... 

294 

295 async def registry_diagnostics(self) -> RelayRegistryDiagnostics: 

296 """Return converter and mapper registry state. 

297 

298 Returns: 

299 Engine identifier, version, mapper ids, and supported route 

300 pairs. A missing converter is a failed dependency and is 

301 reported by the caller as such. 

302 """ 

303 ... 

304 

305 async def policy_snapshot(self) -> RelayPolicySnapshot: 

306 """Return the current routing policy. 

307 

308 Returns: 

309 The current enabled-channel, option, media, and limit 

310 settings. 

311 """ 

312 ... 

313 

314 async def active_streams(self) -> Sequence[RelayActiveStream]: 

315 """Return the currently in-flight upstream streams. 

316 

317 Returns: 

318 One row per active stream, oldest first; an empty sequence 

319 when no stream is in flight. 

320 """ 

321 ... 

322 

323 

324@runtime_checkable 

325class RelayOperationsControlProtocol(Protocol): 

326 """Permissioned runtime control mutations.""" 

327 

328 async def set_channel_state( 

329 self, 

330 channel: str, 

331 enabled: bool, 

332 actor_id: str, 

333 ) -> None: 

334 """Enable or drain *channel* for new requests. 

335 

336 Args: 

337 channel: Channel name; unknown names are rejected. 

338 enabled: ``False`` drains the channel for new requests while 

339 existing streams finish. 

340 actor_id: Operator identity recorded in the audit event. 

341 

342 Raises: 

343 ValueError: The channel is unknown. 

344 """ 

345 ... 

346 

347 async def update_policy( 

348 self, 

349 change: RelayPolicyChange, 

350 actor_id: str, 

351 ) -> None: 

352 """Apply a typed policy change. 

353 

354 Args: 

355 change: Partial policy mutation; only set fields change. 

356 actor_id: Operator identity recorded in the audit event. 

357 

358 Raises: 

359 ValueError: The change references unknown channels or options. 

360 """ 

361 ... 

362 

363 async def policy_snapshot(self, actor_id: str) -> RelayPolicySnapshot: 

364 """Return the current routing policy for *actor_id*. 

365 

366 Args: 

367 actor_id: Operator identity; ``relay.read`` permission is 

368 required. 

369 

370 Returns: 

371 The current enabled-channel, option, media, and limit 

372 settings. 

373 

374 Raises: 

375 RelayGatewayError: With ``PERMISSION_DENIED`` when the actor 

376 lacks ``relay.read``. 

377 """ 

378 ... 

379 

380 async def force_cancel_stream( 

381 self, 

382 stream_id: str, 

383 actor_id: str, 

384 ) -> None: 

385 """Force-cancel an in-flight upstream stream. 

386 

387 Args: 

388 stream_id: Identifier of the stream to cancel. Unknown 

389 streams are rejected. 

390 actor_id: Operator identity recorded in the audit event; 

391 ``relay.stream_control`` permission is required. 

392 

393 Raises: 

394 ValueError: The stream identifier is unknown. 

395 RelayGatewayError: With ``PERMISSION_DENIED`` when the actor 

396 lacks ``relay.stream_control``. 

397 """ 

398 ...