Coverage for src/lilbee/providers/fleet/contract.py: 100%

35 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-11-06 08:17 +0000

1"""Decides whether a running engine can serve a lilbee's configuration. 

2 

3The contract is the per-role models, the engine build pin and the settings each 

4launch bakes in. Planner-derived values (ctx, slots) are accepted from the running 

5engine, never recomputed: they vary legitimately with GPU occupancy at plan time. 

6The one exception is ``chat_ctx_covers``: a live chat window smaller than what this process needs 

7cannot be adopted, since no prompt fit can grow it. ``vision_slots_cover`` 

8applies the same rule to the vision role's slot ceiling. 

9""" 

10 

11from __future__ import annotations 

12 

13from types import MappingProxyType 

14from typing import TYPE_CHECKING 

15 

16from lilbee.providers.fleet.launch import InstanceLaunch 

17from lilbee.providers.roles import WorkerRole 

18 

19if TYPE_CHECKING: 

20 from collections.abc import Iterable, Mapping 

21 

22 from lilbee.providers.fleet.swap_manager import SwapState 

23 

24NO_SETTINGS: Mapping[WorkerRole, str] = MappingProxyType({}) 

25 

26 

27def decoded_launches(state: SwapState) -> list[InstanceLaunch] | None: 

28 """The engine's recorded launches, or ``None`` when the contract is undecodable. 

29 

30 The single decode site. Callers previously re-decoded launches bare, relying on 

31 a preceding contract_matches call to have proven decodability, so reordering or 

32 dropping that guard turned a non-match into an unhandled exception in the bind 

33 ladder. Returning ``None`` makes "undecodable" a value every caller must handle. 

34 """ 

35 try: 

36 return [InstanceLaunch.from_state(item) for item in state.launches] 

37 except (KeyError, TypeError, ValueError): 

38 return None 

39 

40 

41def chat_ctx_covers(launches: Iterable[InstanceLaunch], demanded_ctx: int) -> bool: 

42 """Whether the engine's per-slot chat window serves *demanded_ctx* tokens. 

43 

44 ``launches`` are the running engine's recorded launches. A zero demand, no 

45 chat launch, or a record without a positive chat ctx never refuses: derived 

46 values are adopted from the running engine. A window below the demand still 

47 covers when the record's ``built_ctx_target`` reaches the demand: the same 

48 planner aimed at least as high and achieved this window, so replacing the 

49 engine would rebuild the same window in a loop. A refusal sends the ladder 

50 to its replace-or-overflow decision. 

51 """ 

52 chat = [launch for launch in launches if launch.role is WorkerRole.CHAT and launch.ctx > 0] 

53 live = min((launch.ctx for launch in chat), default=0) 

54 if demanded_ctx <= 0 or live <= 0 or demanded_ctx <= live: 

55 return True 

56 built_target = min((launch.built_ctx_target for launch in chat), default=0) 

57 return built_target > 0 and demanded_ctx <= built_target 

58 

59 

60def vision_slots_cover(launches: Iterable[InstanceLaunch], demanded_slots: int) -> bool: 

61 """Whether the engine's vision slot ceiling serves *demanded_slots* at once. 

62 

63 A fitted count below the demand still covers when the builder's 

64 ``built_slots_target`` reached it: the same ceiling produced this fit, so a 

65 replacement would fit the same count in a loop. 

66 """ 

67 vision = [ 

68 launch for launch in launches if launch.role is WorkerRole.VISION and launch.slots > 0 

69 ] 

70 live = min((launch.slots for launch in vision), default=0) 

71 if demanded_slots <= 0 or live <= 0 or demanded_slots <= live: 

72 return True 

73 built_target = min((launch.built_slots_target for launch in vision), default=0) 

74 return built_target > 0 and demanded_slots <= built_target 

75 

76 

77def contract_matches( 

78 state: SwapState, 

79 wanted: Iterable[tuple[WorkerRole, str]], 

80 pin: str, 

81 settings: Mapping[WorkerRole, str], 

82) -> bool: 

83 """Whether the engine behind *state* serves every wanted (role, model) pair. 

84 

85 The engine may serve more roles than asked; the engine build pin must 

86 equal *pin*, and an empty or undecodable served contract never matches. 

87 *settings* holds what a plan here gives each role: a launch with other 

88 settings never matches, and a role absent from it never refuses. 

89 """ 

90 if state.engine_pin != pin: 

91 return False 

92 launches = decoded_launches(state) 

93 if not launches: 

94 return False 

95 if any(settings.get(launch.role, launch.settings) != launch.settings for launch in launches): 

96 return False 

97 served = {(launch.role, launch.model) for launch in launches} 

98 return all(pair in served for pair in wanted)