stoplight 5.7.0 → 5.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/UPGRADING.md +303 -0
  4. data/lib/generators/stoplight/install/install_generator.rb +6 -1
  5. data/lib/stoplight/admin/dependencies.rb +1 -1
  6. data/lib/stoplight/admin/helpers.rb +20 -4
  7. data/lib/stoplight/admin/lights_repository/light.rb +22 -6
  8. data/lib/stoplight/admin/lights_repository.rb +6 -5
  9. data/lib/stoplight/admin/views/_card.erb +8 -5
  10. data/lib/stoplight/color.rb +9 -0
  11. data/lib/stoplight/data_store.rb +28 -0
  12. data/lib/stoplight/domain/compatibility_result.rb +7 -7
  13. data/lib/stoplight/domain/config.rb +38 -39
  14. data/lib/stoplight/domain/error_tracking_policy.rb +27 -0
  15. data/lib/stoplight/domain/failure.rb +1 -1
  16. data/lib/stoplight/domain/light/configuration_builder_interface.rb +2 -0
  17. data/lib/stoplight/domain/light.rb +15 -46
  18. data/lib/stoplight/domain/light_info.rb +7 -0
  19. data/lib/stoplight/domain/metrics_snapshot.rb +58 -0
  20. data/lib/stoplight/domain/state_snapshot.rb +29 -23
  21. data/lib/stoplight/domain/storage/recovery_lock_token.rb +15 -0
  22. data/lib/stoplight/domain/strategies/green_run_strategy.rb +18 -26
  23. data/lib/stoplight/domain/strategies/red_run_strategy.rb +9 -12
  24. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +41 -51
  25. data/lib/stoplight/domain/tracker/recovery_probe.rb +16 -33
  26. data/lib/stoplight/domain/tracker/request.rb +12 -31
  27. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +8 -11
  28. data/lib/stoplight/domain/traffic_control/error_rate.rb +19 -15
  29. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +6 -10
  30. data/lib/stoplight/domain/traffic_recovery.rb +3 -4
  31. data/lib/stoplight/error.rb +46 -0
  32. data/lib/stoplight/infrastructure/{data_store/fail_safe.rb → fail_safe/data_store.rb} +39 -51
  33. data/lib/stoplight/infrastructure/fail_safe/storage/metrics.rb +65 -0
  34. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock.rb +69 -0
  35. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rb +19 -0
  36. data/lib/stoplight/infrastructure/fail_safe/storage/state.rb +62 -0
  37. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/metrics.rb +2 -2
  38. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_store.rb +10 -12
  39. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_token.rb +3 -6
  40. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/sliding_window.rb +21 -26
  41. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/state.rb +3 -3
  42. data/lib/stoplight/infrastructure/{data_store/memory.rb → memory/data_store.rb} +36 -32
  43. data/lib/stoplight/infrastructure/memory/storage/recovery_lock.rb +35 -0
  44. data/lib/stoplight/infrastructure/memory/storage/recovery_metrics.rb +16 -0
  45. data/lib/stoplight/infrastructure/memory/storage/state.rb +155 -0
  46. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +103 -0
  47. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +101 -0
  48. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +9 -21
  49. data/lib/stoplight/infrastructure/notifier/generic.rb +4 -14
  50. data/lib/stoplight/infrastructure/notifier/io.rb +1 -2
  51. data/lib/stoplight/infrastructure/notifier/logger.rb +1 -2
  52. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_store.rb +9 -22
  53. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_token.rb +7 -14
  54. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/scripting.rb +22 -20
  55. data/lib/stoplight/infrastructure/{data_store/redis.rb → redis/data_store.rb} +47 -55
  56. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +51 -0
  57. data/lib/stoplight/infrastructure/redis/storage/metrics.rb +40 -0
  58. data/lib/stoplight/infrastructure/redis/storage/recovery_lock/release_lock.lua +6 -0
  59. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +64 -0
  60. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +20 -0
  61. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +18 -0
  62. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +10 -0
  63. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +10 -0
  64. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +9 -0
  65. data/lib/stoplight/infrastructure/redis/storage/state.rb +141 -0
  66. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +28 -0
  67. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +26 -0
  68. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +123 -0
  69. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +26 -0
  70. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +36 -0
  71. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +35 -0
  72. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +174 -0
  73. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +3 -10
  74. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +8 -11
  75. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +6 -14
  76. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +6 -17
  77. data/lib/stoplight/infrastructure/system_clock.rb +16 -0
  78. data/lib/stoplight/notifier.rb +11 -0
  79. data/lib/stoplight/state.rb +9 -0
  80. data/lib/stoplight/types.rb +29 -0
  81. data/lib/stoplight/undefined.rb +16 -0
  82. data/lib/stoplight/version.rb +1 -1
  83. data/lib/stoplight/wiring/config_compatibility_validator.rb +54 -0
  84. data/lib/stoplight/wiring/configuration_dsl.rb +101 -0
  85. data/lib/stoplight/wiring/data_store_backend.rb +26 -0
  86. data/lib/stoplight/wiring/default.rb +1 -1
  87. data/lib/stoplight/wiring/default_config.rb +21 -0
  88. data/lib/stoplight/wiring/default_configuration.rb +70 -53
  89. data/lib/stoplight/wiring/light_builder.rb +76 -63
  90. data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +3 -3
  91. data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +4 -4
  92. data/lib/stoplight/wiring/light_factory.rb +78 -52
  93. data/lib/stoplight/wiring/memory/backend.rb +57 -0
  94. data/lib/stoplight/wiring/redis/backend.rb +116 -0
  95. data/lib/stoplight/wiring/storage_set.rb +12 -0
  96. data/lib/stoplight/wiring/storage_set_builder.rb +51 -0
  97. data/lib/stoplight/wiring/system/light_builder.rb +47 -0
  98. data/lib/stoplight/wiring/system/light_factory.rb +64 -0
  99. data/lib/stoplight/wiring/system.rb +129 -0
  100. data/lib/stoplight.rb +196 -25
  101. data/sig/_private/generators/stoplight/install/install_generator.rbs +22 -0
  102. data/sig/_private/stoplight/common/deprecations.rbs +9 -0
  103. data/sig/_private/stoplight/data_store.rbs +6 -0
  104. data/sig/_private/stoplight/domain/compatibility_result.rbs +18 -0
  105. data/sig/_private/stoplight/domain/config.rbs +65 -0
  106. data/sig/_private/stoplight/domain/error_tracking_policy.rbs +14 -0
  107. data/sig/_private/stoplight/domain/failure.rbs +16 -0
  108. data/sig/_private/stoplight/domain/light.rbs +25 -0
  109. data/sig/_private/stoplight/domain/light_info.rbs +19 -0
  110. data/sig/_private/stoplight/domain/metrics_snapshot.rbs +38 -0
  111. data/sig/_private/stoplight/domain/ports/clock.rbs +18 -0
  112. data/sig/_private/stoplight/domain/ports/data_store.rbs +76 -0
  113. data/{lib/stoplight/domain/light_factory.rb → sig/_private/stoplight/domain/ports/light_factory.rbs} +33 -28
  114. data/sig/_private/stoplight/domain/ports/metrics_store.rbs +29 -0
  115. data/sig/_private/stoplight/domain/ports/recovery_lock_store.rbs +52 -0
  116. data/sig/_private/stoplight/domain/ports/recovery_lock_token.rbs +6 -0
  117. data/sig/_private/stoplight/domain/ports/run_strategy.rbs +14 -0
  118. data/sig/_private/stoplight/domain/ports/state_store.rbs +79 -0
  119. data/sig/_private/stoplight/domain/ports/traffic_control.rbs +41 -0
  120. data/sig/_private/stoplight/domain/ports/traffic_recovery.rbs +47 -0
  121. data/sig/_private/stoplight/domain/state_snapshot.rbs +32 -0
  122. data/sig/_private/stoplight/domain/storage/recovery_lock_token.rbs +11 -0
  123. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +17 -0
  124. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +17 -0
  125. data/sig/_private/stoplight/domain/strategies/yellow_run_strategy.rbs +42 -0
  126. data/{lib/stoplight/domain/tracker/base.rb → sig/_private/stoplight/domain/tracker/base.rbs} +0 -4
  127. data/sig/_private/stoplight/domain/tracker/recovery_probe.rbs +25 -0
  128. data/sig/_private/stoplight/domain/tracker/request.rbs +26 -0
  129. data/sig/_private/stoplight/domain/traffic_control/consecutive_errors.rbs +9 -0
  130. data/sig/_private/stoplight/domain/traffic_control/error_rate.rbs +13 -0
  131. data/sig/_private/stoplight/domain/traffic_recovery/consecutive_successes.rbs +9 -0
  132. data/sig/_private/stoplight/domain/traffic_recovery.rbs +9 -0
  133. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +26 -0
  134. data/sig/_private/stoplight/infrastructure/fail_safe/storage/metrics.rbs +25 -0
  135. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock.rbs +29 -0
  136. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rbs +19 -0
  137. data/sig/_private/stoplight/infrastructure/fail_safe/storage/state.rbs +25 -0
  138. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +25 -0
  139. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +19 -0
  140. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +17 -0
  141. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +27 -0
  142. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +17 -0
  143. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +30 -0
  144. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_lock.rbs +15 -0
  145. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_metrics.rbs +10 -0
  146. data/sig/_private/stoplight/infrastructure/memory/storage/state.rbs +28 -0
  147. data/sig/_private/stoplight/infrastructure/memory/storage/unbounded_metrics.rbs +25 -0
  148. data/sig/_private/stoplight/infrastructure/memory/storage/window_metrics.rbs +26 -0
  149. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +17 -0
  150. data/sig/_private/stoplight/infrastructure/notifier/generic.rbs +18 -0
  151. data/sig/_private/stoplight/infrastructure/notifier/io.rbs +14 -0
  152. data/sig/_private/stoplight/infrastructure/notifier/logger.rbs +14 -0
  153. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +24 -0
  154. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +21 -0
  155. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +34 -0
  156. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +67 -0
  157. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +19 -0
  158. data/sig/_private/stoplight/infrastructure/redis/storage/metrics.rbs +17 -0
  159. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_lock.rbs +26 -0
  160. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_metrics.rbs +10 -0
  161. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +13 -0
  162. data/sig/_private/stoplight/infrastructure/redis/storage/state.rbs +32 -0
  163. data/sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs +21 -0
  164. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +34 -0
  165. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +17 -0
  166. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +13 -0
  167. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +14 -0
  168. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +14 -0
  169. data/sig/_private/stoplight/infrastructure/system_clock.rbs +7 -0
  170. data/sig/_private/stoplight/system/light_builder.rbs +23 -0
  171. data/sig/_private/stoplight/system/light_factory.rbs +17 -0
  172. data/sig/_private/stoplight/types.rbs +6 -0
  173. data/sig/_private/stoplight/wiring/config_compatibility_validator.rbs +19 -0
  174. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +43 -0
  175. data/sig/_private/stoplight/wiring/data_store_backend.rbs +11 -0
  176. data/sig/_private/stoplight/wiring/default.rbs +26 -0
  177. data/{lib/stoplight/wiring/data_store/memory.rb → sig/_private/stoplight/wiring/default_config.rbs} +1 -4
  178. data/sig/_private/stoplight/wiring/default_configuration.rbs +29 -0
  179. data/sig/_private/stoplight/wiring/light_builder.rbs +48 -0
  180. data/sig/_private/stoplight/wiring/light_factory/traffic_control_dsl.rbs +7 -0
  181. data/sig/_private/stoplight/wiring/light_factory/traffic_recovery_dsl.rbs +7 -0
  182. data/sig/_private/stoplight/wiring/light_factory.rbs +16 -0
  183. data/sig/_private/stoplight/wiring/memory/backend.rbs +26 -0
  184. data/sig/_private/stoplight/wiring/notifier_factory.rbs +10 -0
  185. data/sig/_private/stoplight/wiring/redis/backend.rbs +38 -0
  186. data/sig/_private/stoplight/wiring/storage_set.rbs +38 -0
  187. data/sig/_private/stoplight/wiring/storage_set_builder.rbs +15 -0
  188. data/sig/_private/stoplight/wiring/system.rbs +15 -0
  189. data/sig/_private/stoplight.rbs +48 -0
  190. data/sig/stoplight/color.rbs +7 -0
  191. data/sig/stoplight/data_store.rbs +19 -0
  192. data/sig/stoplight/error.rbs +20 -0
  193. data/sig/stoplight/notifier.rbs +11 -0
  194. data/sig/stoplight/ports/configuration.rbs +19 -0
  195. data/sig/stoplight/ports/exception_matcher.rbs +8 -0
  196. data/sig/stoplight/ports/light.rbs +12 -0
  197. data/sig/stoplight/ports/light_info.rbs +5 -0
  198. data/sig/stoplight/ports/state_transition_notifier.rbs +15 -0
  199. data/sig/stoplight/ports/system.rbs +21 -0
  200. data/sig/stoplight/state.rbs +7 -0
  201. data/sig/stoplight/undefined.rbs +9 -0
  202. data/sig/stoplight/version.rbs +3 -0
  203. data/sig/stoplight.rbs +66 -0
  204. metadata +175 -47
  205. data/lib/stoplight/domain/color.rb +0 -11
  206. data/lib/stoplight/domain/data_store.rb +0 -146
  207. data/lib/stoplight/domain/error.rb +0 -42
  208. data/lib/stoplight/domain/metrics.rb +0 -64
  209. data/lib/stoplight/domain/recovery_lock_token.rb +0 -15
  210. data/lib/stoplight/domain/state.rb +0 -11
  211. data/lib/stoplight/domain/state_transition_notifier.rb +0 -25
  212. data/lib/stoplight/domain/storage/metrics.rb +0 -42
  213. data/lib/stoplight/domain/storage/recovery_lock.rb +0 -56
  214. data/lib/stoplight/domain/storage/state.rb +0 -87
  215. data/lib/stoplight/domain/strategies/run_strategy.rb +0 -22
  216. data/lib/stoplight/domain/traffic_control/base.rb +0 -74
  217. data/lib/stoplight/domain/traffic_recovery/base.rb +0 -79
  218. data/lib/stoplight/wiring/data_store/base.rb +0 -11
  219. data/lib/stoplight/wiring/data_store/redis.rb +0 -25
  220. data/lib/stoplight/wiring/default_factory_builder.rb +0 -25
  221. data/lib/stoplight/wiring/light/default_config.rb +0 -18
  222. data/lib/stoplight/wiring/light/system_config.rb +0 -11
  223. data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +0 -55
  224. data/lib/stoplight/wiring/light_factory/config_normalizer.rb +0 -71
  225. data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +0 -72
  226. data/lib/stoplight/wiring/public_api.rb +0 -29
  227. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/get_metrics.lua +0 -0
  228. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_failure.lua +0 -0
  229. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_failure.lua +0 -0
  230. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_success.lua +0 -0
  231. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_success.lua +0 -0
  232. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/release_lock.lua +0 -0
  233. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_green.lua +0 -0
  234. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_red.lua +0 -0
  235. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_yellow.lua +0 -0
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Stoplight
4
2
  module Domain
5
3
  # Abstract factory protocol for building +Stoplight::Light+ instances.
@@ -17,39 +15,36 @@ module Stoplight
17
15
  # - Different factory implementations can be swapped (code, database-configured, etc.)
18
16
  # - Dependency direction is preserved (Application → Domain, not Domain → Application)
19
17
  #
20
- # @abstract Subclasses must implement +#with+ and +#build+
21
- # @api private
22
- class LightFactory
18
+ interface _LightFactory
23
19
  # Creates a new factory with modified settings.
24
20
  #
25
21
  # This method must return a NEW factory instance - it should not
26
22
  # modify the current factory. The new factory should inherit all
27
23
  # configuration from the current factory, with the provided
28
24
  # settings overriding specific values.
29
- #
30
- # @param settings [Hash] Configuration and dependency overrides
31
- # @return [Stoplight::Domain::LightFactory] New factory with updated settings
32
- # @raise [NotImplementedError] Must be implemented by subclass
33
- # @abstract
34
- # :nocov:
35
- def with(**settings)
36
- raise NotImplementedError
37
- end
25
+ def with: (
26
+ ?name: optional[String],
27
+ ?cool_off_time: optional[duration],
28
+ ?threshold: optional[percentage | Integer],
29
+ ?recovery_threshold: optional[Integer],
30
+ ?window_size: optional[duration?],
31
+ ?tracked_errors: optional[Array[_ExceptionMatcher] | _ExceptionMatcher],
32
+ ?skipped_errors: optional[Array[_ExceptionMatcher] | _ExceptionMatcher],
33
+ ?error_notifier: optional[error_notifier],
34
+ ?notifiers: optional[Array[state_transition_notifier]],
35
+ ?data_store: optional[data_store],
36
+ ?traffic_control: optional[traffic_control],
37
+ ?traffic_recovery: optional[traffic_recovery],
38
+ ) -> _LightFactory
38
39
 
39
- # Builds a +Stoplight::Light+ instance with the current configuration.
40
+ # Builds a Light instance with the current configuration.
40
41
  #
41
42
  # This method must construct a fully-wired Light with all required
42
43
  # dependencies (strategies, data store, notifiers, etc.). The Light
43
44
  # should be ready to use immediately after construction.
44
45
  #
45
- # @return [Stoplight::Light] Configured circuit breaker instance
46
- # @raise [NotImplementedError] Must be implemented by subclass
47
46
  # @raise [Stoplight::Error::ConfigurationError] If configuration is invalid
48
- # @abstract
49
- def build
50
- raise NotImplementedError
51
- end
52
- # :nocov:
47
+ def build: -> Light
53
48
 
54
49
  # Convenience method to configure and build in one operation.
55
50
  #
@@ -57,9 +52,6 @@ module Stoplight
57
52
  # useful when you want to create a customized Light without keeping
58
53
  # a reference to the intermediate factory.
59
54
  #
60
- # @param settings [Hash] Settings to override before building
61
- # @return [Stoplight::Light] Configured circuit breaker instance
62
- #
63
55
  # @example Usage
64
56
  # # Instead of:
65
57
  # new_factory = factory.with(threshold: 10)
@@ -67,9 +59,22 @@ module Stoplight
67
59
  #
68
60
  # # You can do:
69
61
  # light = factory.build_with(threshold: 10)
70
- def build_with(**settings)
71
- with(**settings).build
72
- end
62
+ def build_with: (
63
+ ?name: optional[String],
64
+ ?cool_off_time: optional[duration],
65
+ ?threshold: optional[percentage | Integer],
66
+ ?recovery_threshold: optional[Integer],
67
+ ?window_size: optional[duration?],
68
+ ?tracked_errors: optional[Array[_ExceptionMatcher] | _ExceptionMatcher],
69
+ ?skipped_errors: optional[Array[_ExceptionMatcher] | _ExceptionMatcher],
70
+ ?error_notifier: optional[error_notifier],
71
+ ?notifiers: optional[Array[state_transition_notifier]],
72
+ ?data_store: optional[data_store],
73
+ ?traffic_control: optional[traffic_control],
74
+ ?traffic_recovery: optional[traffic_recovery],
75
+ ) -> Light
76
+
77
+ def ==: (untyped) -> bool
73
78
  end
74
79
  end
75
80
  end
@@ -0,0 +1,29 @@
1
+ module Stoplight
2
+ module Domain
3
+ # Encapsulates metrics storage for circuit breaker execution tracking.
4
+ #
5
+ # This abstraction isolates metrics collection and retrieval from the
6
+ # broader data store concerns, enabling:
7
+ # - Purpose-built implementations optimized for time-series data
8
+ # - Independent scaling and optimization of metrics vs. state storage
9
+ # - Clearer separation between "what happened" (metrics) and "what to do" (state)
10
+ #
11
+ # Lifecycle: A Metrics instance is scoped to a single circuit breaker
12
+ # configuration. Each circuit gets its own metrics store instance,
13
+ # allowing different circuits to use different storage strategies.
14
+ #
15
+ interface _MetricsStore
16
+ # Retrieves a snapshot of current metrics for decision-making.
17
+ def metrics_snapshot: () -> Domain::MetricsSnapshot
18
+
19
+ # Records a successful circuit breaker execution
20
+ def record_success: () -> void
21
+
22
+ # Records a failed circuit breaker execution
23
+ def record_failure: (StandardError error) -> void
24
+
25
+ # Clears all metrics for this circuit
26
+ def clear: () -> void
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Domain
5
+ # Encapsulates recovery lock management for coordinating recovery probes.
6
+ #
7
+ # When a circuit enters YELLOW state (half-open), it begins sending
8
+ # "recovery probes" - test requests to check if the protected service
9
+ # has recovered. In distributed deployments with multiple instances,
10
+ # recovery locks ensure only ONE instance sends probes at a time.
11
+ #
12
+ # Without coordination, all instances would simultaneously:
13
+ # 1. Detect the circuit is YELLOW
14
+ # 2. Send recovery probes to the struggling service
15
+ # 3. Potentially overwhelm it with "test" traffic
16
+ #
17
+ # Lock Lifecycle:
18
+ #
19
+ # Instance A: acquire_lock -> probe -> release_lock
20
+ # Instance B: acquire_lock -> nil (already held) -> skip probe
21
+ # Instance C: acquire_lock -> nil (already held) -> skip probe
22
+ #
23
+ # Lock Semantics:
24
+ # - Returns +nil+ if lock is already held. Never blocks waiting for lock availability
25
+ # - Locks must automatically expire when persisted storage is used
26
+ # - Failed releases are acceptable (timeout provides safety)
27
+ #
28
+ # @see Stoplight::Domain::Strategies::YellowRunStrategy
29
+ interface _RecoveryLockStore
30
+ # Attempts to acquire recovery lock for exclusive probe execution.
31
+ #
32
+ # This method tries to acquire a lock that serializes recovery probe
33
+ # execution across multiple instances. If the lock is already held by
34
+ # another instance, returns +nil+ immediately without blocking.
35
+ #
36
+ # @return
37
+ # - +RecoveryLockToken+: Lock acquired, caller should send probe
38
+ # - +nil+: Lock unavailable, another instance is probing
39
+ #
40
+ def acquire_lock: -> Storage::RecoveryLockToken?
41
+
42
+ # Releases a previously acquired lock.
43
+ #
44
+ # This method releases the lock token returned by +#acquire_lock+,
45
+ # allowing other instances to acquire it. Release should be called
46
+ # in an ensure block to guarantee cleanup even if probe fails.
47
+ #
48
+ # @param lock The token returned by +#acquire_lock+
49
+ def release_lock: (Storage::RecoveryLockToken) -> void
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ module Stoplight
2
+ module Domain
3
+ interface _RecoveryLockToken
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ module Stoplight
2
+ module Domain
3
+ # Represents an abstract strategy for running a light's operations.
4
+ # Every new strategy should implement this interface
5
+ #
6
+ interface _RunStrategy
7
+ # @param fallback A fallback proc to execute in case of an error.
8
+ def execute: [T] (
9
+ (^(StandardError?) -> T)? fallback,
10
+ state_snapshot: StateSnapshot
11
+ ) { () -> T } -> T
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,79 @@
1
+ module Stoplight
2
+ module Domain
3
+ # Encapsulates circuit breaker state storage.
4
+ #
5
+ # State management handles the current operational mode of a circuit breaker:
6
+ # - Color (GREEN/YELLOW/RED) - whether the circuit is open or closed
7
+ # - Lock state (LOCKED_GREEN/LOCKED_RED/UNLOCKED) - manual overrides
8
+ # - State transitions - tracking color changes for notifications #
9
+ #
10
+ # State requires stronger consistency than metrics because:
11
+ # - Multiple instances must agree on circuit color
12
+ # - Race conditions during transitions must be handled
13
+ # - Lock states must be immediately visible across instances
14
+ #
15
+ interface _StateStore
16
+ # Retrieves current state snapshot for decision-making.
17
+ #
18
+ # The snapshot is an immutable view of the circuit's current state,
19
+ # including its color and lock status. This method is called on every
20
+ # circuit breaker invocation to determine whether to allow traffic.
21
+ #
22
+ # This is called on every request, so implementations should be fast.
23
+ #
24
+ def state_snapshot: () -> StateSnapshot
25
+
26
+ # Sets the lock state of the circuit.
27
+ #
28
+ # Locks allow manual override of circuit behavior:
29
+ # - LOCKED_GREEN: Force circuit closed (allow all traffic)
30
+ # - LOCKED_RED: Force circuit open (block all traffic)
31
+ # - UNLOCKED: Follow normal circuit breaker rules
32
+ #
33
+ # Lock states take precedence over color states. A locked circuit
34
+ # ignores failure thresholds and stays in the locked state until
35
+ # explicitly unlocked.
36
+ #
37
+ # Use Cases:
38
+ # - Emergency traffic control during incidents
39
+ # - Maintenance windows (lock RED to prevent traffic)
40
+ # - Gradual rollout (lock GREEN during testing)
41
+ #
42
+ # @param state The new state to set.
43
+ # @return The state that was set.
44
+ def set_state: (state) -> state
45
+
46
+ # Transitions the Stoplight to the specified color.
47
+ #
48
+ # This method performs a color transition operation that works across distributed instances
49
+ # of the light. It ensures that in a multi-instance environment, only one instance
50
+ # is considered the "first" to perform the transition (and therefore responsible for
51
+ # triggering notifications).
52
+ #
53
+ # @param color The target color/state to transition to.
54
+ # Should be one of Stoplight::Color::GREEN, Stoplight::Color::YELLOW, or Stoplight::Color::RED.
55
+ #
56
+ # @return Returns +true+ if this instance was the first to perform this specific transition
57
+ # (and should therefore trigger notifications). Returns +false+ if another instance already
58
+ # initiated this transition.
59
+ #
60
+ # @note In distributed environments with multiple instances, race conditions can occur when instances
61
+ # attempt conflicting transitions simultaneously (e.g., one instance tries to transition from
62
+ # YELLOW to GREEN while another tries YELLOW to RED). The implementation handles this, but
63
+ # be aware that the last operation may determine the final color of the light.
64
+ #
65
+ def transition_to_color: (color) -> bool
66
+
67
+ # Clears all state data for this circuit.
68
+ #
69
+ # This removes the circuit from storage entirely, resetting it to
70
+ # default (unlocked, green) state. The next invocation will start
71
+ # with fresh state.
72
+ #
73
+ # @note This does NOT clear metrics. If you want to fully
74
+ # reset a circuit, clear both state and metrics stores.
75
+ #
76
+ def clear: () -> void
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,41 @@
1
+ module Stoplight
2
+ module Domain
3
+ # Strategies for determining when a Stoplight should change color to red.
4
+ #
5
+ # These strategies evaluate the current state and metrics of a Stoplight to decide
6
+ # if traffic should be stopped (i.e., if the light should turn RED).
7
+ #
8
+ # @example Creating a custom strategy
9
+ # class ErrorRateStrategy
10
+ # def check_compatibility(config)
11
+ # if config.window_size.nil?
12
+ # incompatible("`window_size` should be set")
13
+ # else
14
+ # compatible
15
+ # end
16
+ # end
17
+ #
18
+ # def stop_traffic?(config, metrics)
19
+ # total = metrics.successes + metrics.failures
20
+ # return false if total < 10 # Minimum sample size
21
+ #
22
+ # error_rate = metrics.failures.fdiv(total)
23
+ # error_rate >= 0.5 # Stop traffic when error rate reaches 50%
24
+ # end
25
+ # end
26
+ #
27
+ interface _TrafficControl
28
+ # Checks if the strategy is compatible with the given Stoplight configuration.
29
+ def check_compatibility: (Config) -> CompatibilityResult
30
+
31
+ # Determines whether traffic should be stopped based on the Stoplight's
32
+ # current state and metrics.
33
+ # @return +true+ if traffic should be stopped otherwise false
34
+ def stop_traffic?: (Config, Domain::MetricsSnapshot) -> bool
35
+
36
+ # Object methods
37
+ def ==: (untyped) -> bool
38
+ def is_a?: (untyped) -> bool
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ module Stoplight
2
+ module Domain
3
+ type decision = :green | :yellow | :red
4
+
5
+ # Strategies for determining how to recover traffic flow through the Stoplight.
6
+ # These strategies evaluate recovery metrics to decide which color the Stoplight should
7
+ # transition to during the recovery process.
8
+ #
9
+ # @example Creating a custom traffic recovery strategy
10
+ # class GradualRecovery
11
+ # def initialize(min_success_rate: 0.8, min_samples: 100)
12
+ # @min_success_rate = min_success_rate
13
+ # @min_samples = min_samples
14
+ # end
15
+ #
16
+ # def determine_color(config, metrics)
17
+ # total_probes = metrics.recovery_probe_successes + metrics.recovery_probe_errors
18
+ #
19
+ # if total_probes < @min_samples
20
+ # return Color::YELLOW # Keep recovering, not enough samples
21
+ # end
22
+ #
23
+ # success_rate = metrics.recovery_probe_successes.fdiv(total_probes)
24
+ # if success_rate >= @min_success_rate
25
+ # Color::GREEN # Recovery successful
26
+ # elsif success_rate <= 0.2
27
+ # Color::RED # Recovery failed, too many errors
28
+ # else
29
+ # Color::YELLOW # Continue recovery
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ interface _TrafficRecovery
35
+ # Checks if the strategy is compatible with the given Stoplight configuration.
36
+ def check_compatibility: (Config) -> CompatibilityResult
37
+
38
+ # Determines the appropriate recovery state based on the Stoplight's
39
+ # current metrics and recovery progress.
40
+ def determine_color: (Config, Domain::MetricsSnapshot) -> decision
41
+
42
+ # Object methods
43
+ def ==: (untyped) -> bool
44
+ def is_a?: (untyped) -> bool
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module Stoplight
2
+ module Domain
3
+ class StateSnapshot < Data
4
+ def breached_at: -> Time?
5
+ def locked_state: -> state
6
+ def recovery_scheduled_after: -> Time?
7
+ def recovery_scheduled_after!: -> Time
8
+ def recovery_started_at: -> Time?
9
+ def recovery_started_at!: -> Time
10
+ def time: -> Time
11
+
12
+ def self.new: (
13
+ breached_at: Time?,
14
+ locked_state: state,
15
+ recovery_scheduled_after: Time?,
16
+ recovery_started_at: Time?,
17
+ time: Time
18
+ ) -> instance
19
+
20
+ def initialize: (
21
+ breached_at: Time?,
22
+ locked_state: state,
23
+ recovery_scheduled_after: Time?,
24
+ recovery_started_at: Time?,
25
+ time: Time
26
+ ) -> void
27
+
28
+ def color: -> color
29
+ def recovery_started?: -> bool
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Storage
4
+ class RecoveryLockToken
5
+ include _RecoveryLockToken
6
+
7
+ attr_reader token: String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Strategies
4
+ class GreenRunStrategy
5
+ include _RunStrategy
6
+
7
+ attr_reader request_tracker: Tracker::Request
8
+ @error_tracking_policy: ErrorTrackingPolicy
9
+
10
+ def initialize: (error_tracking_policy: ErrorTrackingPolicy, request_tracker: Tracker::Request) -> void
11
+
12
+ def record_error: (StandardError) -> void
13
+ def record_success: -> void
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Strategies
4
+ class RedRunStrategy
5
+ include _RunStrategy
6
+
7
+ @name: String
8
+ @cool_off_time: duration
9
+
10
+ def initialize: (name: String, cool_off_time: duration) -> void
11
+
12
+ private def record_error: (StandardError) -> void
13
+ private def record_success: -> void
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Strategies
4
+ class YellowRunStrategy
5
+ include _RunStrategy
6
+
7
+ attr_reader state_store: Domain::_StateStore
8
+ attr_reader metrics_store: _MetricsStore
9
+ attr_reader recovery_lock_store: _RecoveryLockStore
10
+ attr_reader notifiers: Array[state_transition_notifier]
11
+ attr_reader request_tracker: Tracker::RecoveryProbe
12
+ attr_reader red_run_strategy: RedRunStrategy
13
+
14
+ @name: String
15
+ @config: Domain::Config
16
+ @error_tracking_policy: ErrorTrackingPolicy
17
+
18
+ def initialize: (
19
+ name: String,
20
+ error_tracking_policy: ErrorTrackingPolicy,
21
+ notifiers: Array[state_transition_notifier],
22
+ request_tracker: Tracker::RecoveryProbe,
23
+ red_run_strategy: RedRunStrategy,
24
+ state_store: Domain::_StateStore,
25
+ metrics_store: _MetricsStore,
26
+ recovery_lock_store: _RecoveryLockStore,
27
+ config: Domain::Config,
28
+ ) -> void
29
+
30
+ def with_recovery_lock: [T] (
31
+ fallback: (^(StandardError?) -> T)?,
32
+ state_snapshot: StateSnapshot,
33
+ code: ^() -> T,
34
+ ) { () -> T } -> T
35
+
36
+ def record_recovery_probe_success: () -> void
37
+ def record_recovery_probe_failure: (StandardError) -> void
38
+ def enter_recovery: (StateSnapshot) -> void
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,10 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Stoplight
4
2
  module Domain
5
3
  module Tracker
6
- # @api private
7
- # @abstract
8
4
  class Base
9
5
  end
10
6
  end
@@ -0,0 +1,25 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Tracker
4
+ class RecoveryProbe < Base
5
+ attr_reader traffic_recovery: _TrafficRecovery
6
+ attr_reader notifiers: Array[state_transition_notifier]
7
+ attr_reader config: Config
8
+ attr_reader metrics_store: _MetricsStore
9
+ attr_reader state_store: _StateStore
10
+
11
+ def initialize: (
12
+ config: Config,
13
+ traffic_recovery: _TrafficRecovery,
14
+ notifiers: Array[state_transition_notifier],
15
+ metrics_store: _MetricsStore,
16
+ state_store: _StateStore,
17
+ ) -> void
18
+
19
+ def record_failure: (StandardError exception) -> void
20
+ def record_success: -> void
21
+ def recover: -> void
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Stoplight
2
+ module Domain
3
+ module Tracker
4
+ class Request < Base
5
+ attr_reader config: Config
6
+ attr_reader traffic_control: _TrafficControl
7
+ attr_reader notifiers: Array[state_transition_notifier]
8
+ attr_reader metrics_store: _MetricsStore
9
+ attr_reader state_store: _StateStore
10
+
11
+ def initialize: (
12
+ config: Config,
13
+ traffic_control: _TrafficControl,
14
+ notifiers: Array[state_transition_notifier],
15
+ metrics_store: _MetricsStore,
16
+ state_store: _StateStore,
17
+ ) -> void
18
+
19
+ def record_failure: (StandardError exception) -> void
20
+ def record_success: -> void
21
+
22
+ private def transition_to_red: (StandardError exception, metrics: MetricsSnapshot) -> void
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module Stoplight
2
+ module Domain
3
+ module TrafficControl
4
+ class ConsecutiveErrors
5
+ include _TrafficControl
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Stoplight
2
+ module Domain
3
+ module TrafficControl
4
+ class ErrorRate
5
+ include _TrafficControl
6
+
7
+ attr_reader min_requests: Integer
8
+
9
+ def initialize: (?min_requests: Integer) -> void
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Stoplight
2
+ module Domain
3
+ module TrafficRecovery
4
+ class ConsecutiveSuccesses
5
+ include _TrafficRecovery
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Stoplight
2
+ module Domain
3
+ module TrafficRecovery
4
+ GREEN: :green
5
+ YELLOW: :yellow
6
+ RED: :red
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module Stoplight
2
+ module Infrastructure
3
+ module FailSafe
4
+ class DataStore
5
+ include Domain::_DataStore
6
+ attr_reader data_store: Domain::_DataStore
7
+ attr_reader failover_data_store: Domain::_DataStore
8
+ attr_reader error_notifier: error_notifier
9
+ private attr_reader circuit_breaker: Domain::Light
10
+
11
+ def initialize: (
12
+ data_store: Domain::_DataStore,
13
+ error_notifier: error_notifier,
14
+ failover_data_store: Domain::_DataStore,
15
+ circuit_breaker: Domain::Light
16
+ ) -> void
17
+
18
+ private def with_fallback: [T] (
19
+ String | Symbol method_name,
20
+ *untyped,
21
+ **untyped,
22
+ ) { () -> T } -> T
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module Stoplight
2
+ module Infrastructure
3
+ module FailSafe
4
+ module Storage
5
+ class Metrics
6
+ include Domain::_MetricsStore
7
+
8
+ attr_reader primary_store: Domain::_MetricsStore
9
+ attr_reader error_notifier: error_notifier
10
+ attr_reader failover_store: Domain::_MetricsStore
11
+ attr_reader circuit_breaker: Domain::Light
12
+
13
+ def initialize: (
14
+ primary_store: Domain::_MetricsStore,
15
+ error_notifier: error_notifier,
16
+ failover_store: Domain::_MetricsStore,
17
+ circuit_breaker: Domain::Light
18
+ ) -> void
19
+
20
+ def fallback: [T] { () -> T } -> ^(StandardError?) -> T
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end