Coverage for src / lexigram / ai / relay / gateway / job_registry.py: 100%

29 statements  

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

1"""In-memory relay job registry for job-based media generation. 

2 

3Maps a gateway-issued job handle (a UUID generated at insertion) to a 

4``RelayJobRecord`` describing which channel owns the upstream job and 

5what the upstream's own job id is. Records are evicted lazily on 

6lookup once older than the configured ``job_ttl_seconds``; an expired 

7or unknown handle returns ``None`` so callers can treat both as the 

8same not-found outcome. 

9 

10Note: 

11 The registry is in-memory only: a gateway process restart loses 

12 all in-flight job mappings, and a caller polling a lost job 

13 receives the same not-found outcome as an unknown id. A durable 

14 job store is an explicit v1 non-goal (see the async job-relay 

15 plan). 

16""" 

17 

18from __future__ import annotations 

19 

20from collections.abc import Callable 

21from dataclasses import dataclass 

22import time 

23 

24from lexigram.identity.ambient import new_uuid 

25 

26__all__ = ["RelayJobRecord", "RelayJobRegistry"] 

27 

28 

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

30class RelayJobRecord: 

31 """One in-flight relayed job's channel-affinity mapping. 

32 

33 Attributes: 

34 channel_name: Name of the channel that owns the upstream job. 

35 Every poll for this job routes to this same channel. 

36 upstream_job_id: The job id issued by the upstream provider; 

37 never exposed to the caller. 

38 endpoint_kind: The endpoint kind the job was submitted through 

39 (e.g. ``"video_generation"``). 

40 created_at: Monotonic timestamp of the record; the registry 

41 evicts records older than the configured TTL. 

42 """ 

43 

44 channel_name: str 

45 upstream_job_id: str 

46 endpoint_kind: str 

47 created_at: float 

48 

49 

50class RelayJobRegistry: 

51 """Maps gateway job ids to relay job records with TTL eviction. 

52 

53 The gateway job id is generated by this registry at ``put`` time — 

54 callers never choose it — so a caller-visible handle can never 

55 collide with an upstream-issued job id. 

56 

57 Attributes: 

58 _job_ttl_seconds: Age after which a record is evicted on the 

59 next lookup. 

60 _clock: Monotonic clock used for eviction decisions; tests 

61 inject a fake. 

62 _records: Gateway job id to record mapping. 

63 """ 

64 

65 def __init__( 

66 self, 

67 job_ttl_seconds: int, 

68 *, 

69 clock: Callable[[], float] = time.monotonic, 

70 ) -> None: 

71 """Bind the registry to a TTL and clock. 

72 

73 Args: 

74 job_ttl_seconds: Age in seconds after which a record is 

75 evicted on the next ``get``. The configuration layer 

76 validates this is positive. 

77 clock: Callable returning the current monotonic time. 

78 Defaults to ``time.monotonic``; tests inject a fake. 

79 """ 

80 self._job_ttl_seconds = job_ttl_seconds 

81 self._clock = clock 

82 self._records: dict[str, RelayJobRecord] = {} 

83 

84 def put(self, record: RelayJobRecord) -> str: 

85 """Store *record* under a fresh gateway-issued job id. 

86 

87 Args: 

88 record: The channel-affinity record to store. 

89 

90 Returns: 

91 The gateway job id the caller can hand back to consumers 

92 for status polling. 

93 """ 

94 gateway_job_id = new_uuid() 

95 self._records[gateway_job_id] = record 

96 return gateway_job_id 

97 

98 def get(self, gateway_job_id: str) -> RelayJobRecord | None: 

99 """Return the record for *gateway_job_id*, evicting when stale. 

100 

101 An expired record is removed from storage (not merely hidden) 

102 so memory does not grow unbounded across many expired lookups. 

103 

104 Args: 

105 gateway_job_id: The gateway-issued job id from ``put``. 

106 

107 Returns: 

108 The stored record when it exists and is not older than 

109 ``job_ttl_seconds``, otherwise ``None`` (both unknown and 

110 expired ids behave identically). 

111 """ 

112 record = self._records.get(gateway_job_id) 

113 if record is None: 

114 return None 

115 if self._clock() - record.created_at > self._job_ttl_seconds: 

116 del self._records[gateway_job_id] 

117 return None 

118 return record