stoplight 5.8.3 → 6.0.0.rc2

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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/LICENSE.md +1 -0
  4. data/README.md +132 -79
  5. data/UPGRADING.md +265 -6
  6. data/lib/stoplight/admin/actions/action.rb +4 -12
  7. data/lib/stoplight/admin/actions/lock.rb +12 -9
  8. data/lib/stoplight/admin/actions/lock_all.rb +27 -0
  9. data/lib/stoplight/admin/actions/remove.rb +12 -9
  10. data/lib/stoplight/admin/actions/stats.rb +15 -12
  11. data/lib/stoplight/admin/actions/unlock.rb +10 -9
  12. data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
  13. data/lib/stoplight/admin/assets/favicon.ico +0 -0
  14. data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
  15. data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
  16. data/lib/stoplight/admin/assets/icon.svg +7 -0
  17. data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
  18. data/lib/stoplight/admin/config_registry.rb +40 -0
  19. data/lib/stoplight/admin/dependencies.rb +29 -28
  20. data/lib/stoplight/admin/helpers.rb +50 -17
  21. data/lib/stoplight/admin/light_view.rb +184 -0
  22. data/lib/stoplight/admin/lights_stats.rb +7 -9
  23. data/lib/stoplight/admin/views/_card.erb +19 -21
  24. data/lib/stoplight/admin/views/index.erb +9 -3
  25. data/lib/stoplight/admin/views/layout.erb +45 -13
  26. data/lib/stoplight/admin.rb +110 -21
  27. data/lib/stoplight/data_store.rb +18 -8
  28. data/lib/stoplight/domain/config.rb +4 -1
  29. data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
  30. data/lib/stoplight/domain/failure.rb +4 -0
  31. data/lib/stoplight/domain/id.rb +13 -0
  32. data/lib/stoplight/domain/light.rb +35 -77
  33. data/lib/stoplight/domain/lock_control.rb +55 -0
  34. data/lib/stoplight/domain/matcher_validator.rb +24 -0
  35. data/lib/stoplight/domain/state_snapshot.rb +2 -6
  36. data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
  37. data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
  38. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
  39. data/lib/stoplight/domain/telemetry/bus.rb +90 -0
  40. data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
  41. data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
  42. data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
  43. data/lib/stoplight/domain/telemetry/failure.rb +13 -0
  44. data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
  45. data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
  46. data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
  47. data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
  48. data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
  49. data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
  50. data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
  51. data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
  52. data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
  53. data/lib/stoplight/domain/telemetry/settings.rb +21 -0
  54. data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
  55. data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
  56. data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
  57. data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
  58. data/lib/stoplight/domain/tracker/request.rb +17 -8
  59. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
  60. data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
  61. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
  62. data/lib/stoplight/error.rb +10 -1
  63. data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
  64. data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
  65. data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
  66. data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
  67. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
  68. data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
  69. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
  70. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
  71. data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
  72. data/lib/stoplight/infrastructure/redis/key.rb +25 -0
  73. data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
  74. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
  75. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
  76. data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
  77. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
  78. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
  79. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
  80. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
  81. data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
  82. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
  83. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
  84. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
  85. data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
  86. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
  87. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
  88. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
  89. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
  90. data/lib/stoplight/infrastructure/system_clock.rb +4 -0
  91. data/lib/stoplight/telemetry.rb +19 -0
  92. data/lib/stoplight/types.rb +15 -2
  93. data/lib/stoplight/version.rb +1 -1
  94. data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
  95. data/lib/stoplight/wiring/default.rb +1 -1
  96. data/lib/stoplight/wiring/default_config.rb +2 -1
  97. data/lib/stoplight/wiring/default_configuration.rb +3 -2
  98. data/lib/stoplight/wiring/external_caller.rb +27 -0
  99. data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
  100. data/lib/stoplight/wiring/global_state.rb +93 -0
  101. data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
  102. data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
  103. data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
  104. data/lib/stoplight/wiring/light_factory.rb +154 -97
  105. data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
  106. data/lib/stoplight/wiring/redis/backend.rb +1 -1
  107. data/lib/stoplight/wiring/system/storage.rb +69 -0
  108. data/lib/stoplight/wiring/system.rb +125 -56
  109. data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
  110. data/lib/stoplight.rb +50 -194
  111. data/sig/sinatra/base.rbs +6 -0
  112. data/sig/stdlib/securerandom.rbs +4 -0
  113. data/sig/stoplight/admin/actions/action.rbs +10 -0
  114. data/sig/stoplight/admin/actions/lock.rbs +17 -0
  115. data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
  116. data/sig/stoplight/admin/actions/remove.rbs +22 -0
  117. data/sig/stoplight/admin/actions/stats.rbs +28 -0
  118. data/sig/stoplight/admin/actions/unlock.rbs +17 -0
  119. data/sig/stoplight/admin/config_registry.rbs +20 -0
  120. data/sig/stoplight/admin/dependencies.rbs +16 -0
  121. data/sig/stoplight/admin/helpers.rbs +17 -0
  122. data/sig/stoplight/admin/light_view.rbs +39 -0
  123. data/sig/stoplight/admin/lights_stats.rbs +30 -0
  124. data/sig/stoplight/admin.rbs +14 -0
  125. data/sig/stoplight/color.rbs +3 -3
  126. data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
  127. data/sig/stoplight/data_store/memory.rbs +9 -0
  128. data/sig/stoplight/data_store/redis.rbs +17 -0
  129. data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
  130. data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
  131. data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
  132. data/sig/stoplight/domain/id.rbs +7 -0
  133. data/sig/stoplight/domain/light.rbs +39 -0
  134. data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
  135. data/sig/stoplight/domain/lock_control.rbs +16 -0
  136. data/sig/stoplight/domain/matcher_validator.rbs +11 -0
  137. data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
  138. data/sig/stoplight/domain/ports/data_store.rbs +9 -0
  139. data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
  140. data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
  141. data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
  142. data/sig/stoplight/domain/ports/registry.rbs +13 -0
  143. data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
  144. data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
  145. data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
  146. data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
  147. data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
  148. data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
  149. data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
  150. data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
  151. data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
  152. data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
  153. data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
  154. data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
  155. data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
  156. data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
  157. data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
  158. data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
  159. data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
  160. data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
  161. data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
  162. data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
  163. data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
  164. data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
  165. data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
  166. data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
  167. data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
  168. data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
  169. data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
  170. data/sig/stoplight/domain/telemetry/types.rbs +32 -0
  171. data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
  172. data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
  173. data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
  174. data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
  175. data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
  176. data/sig/stoplight/domain/types.rbs +25 -0
  177. data/sig/stoplight/error.rbs +8 -0
  178. data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
  179. data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
  180. data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
  181. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
  182. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
  183. data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
  184. data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
  185. data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
  186. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
  187. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
  188. data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
  189. data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
  190. data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
  191. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
  192. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
  193. data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
  194. data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
  195. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
  196. data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
  197. data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
  198. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
  199. data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
  200. data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
  201. data/sig/stoplight/infrastructure/types.rbs +6 -0
  202. data/sig/stoplight/notifier.rbs +1 -1
  203. data/sig/stoplight/ports/system.rbs +16 -10
  204. data/sig/stoplight/state.rbs +3 -3
  205. data/sig/stoplight/telemetry.rbs +17 -0
  206. data/sig/stoplight/types.rbs +9 -0
  207. data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
  208. data/sig/stoplight/wiring/default.rbs +26 -0
  209. data/sig/stoplight/wiring/default_configuration.rbs +39 -0
  210. data/sig/stoplight/wiring/external_caller.rbs +13 -0
  211. data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
  212. data/sig/stoplight/wiring/global_state.rbs +36 -0
  213. data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
  214. data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
  215. data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
  216. data/sig/stoplight/wiring/light_factory.rbs +78 -0
  217. data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
  218. data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
  219. data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
  220. data/sig/stoplight/wiring/system/storage.rbs +35 -0
  221. data/sig/stoplight/wiring/system.rbs +30 -0
  222. data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
  223. data/sig/stoplight/wiring/types.rbs +6 -0
  224. data/sig/stoplight.rbs +37 -54
  225. metadata +190 -143
  226. data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
  227. data/lib/stoplight/admin/actions/lock_green.rb +0 -23
  228. data/lib/stoplight/admin/actions/lock_red.rb +0 -23
  229. data/lib/stoplight/admin/lights_repository/light.rb +0 -171
  230. data/lib/stoplight/admin/lights_repository.rb +0 -87
  231. data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
  232. data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
  233. data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
  234. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
  235. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
  236. data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
  237. data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
  238. data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
  239. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
  240. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
  241. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
  242. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
  243. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
  244. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
  245. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
  246. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
  247. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
  248. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
  249. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
  250. data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
  251. data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
  252. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
  253. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
  254. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
  255. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
  256. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
  257. data/lib/stoplight/wiring/light_builder.rb +0 -198
  258. data/lib/stoplight/wiring/notifier_factory.rb +0 -26
  259. data/lib/stoplight/wiring/system/light_builder.rb +0 -47
  260. data/lib/stoplight/wiring/system/light_factory.rb +0 -64
  261. data/sig/_private/stoplight/domain/light.rbs +0 -25
  262. data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
  263. data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
  264. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
  265. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
  266. data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
  267. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
  268. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
  269. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
  270. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
  271. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
  272. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
  273. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
  274. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
  275. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
  276. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
  277. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
  278. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
  279. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
  280. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
  281. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
  282. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
  283. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
  284. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
  285. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
  286. data/sig/_private/stoplight/system/light_builder.rbs +0 -23
  287. data/sig/_private/stoplight/system/light_factory.rbs +0 -17
  288. data/sig/_private/stoplight/types.rbs +0 -6
  289. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
  290. data/sig/_private/stoplight/wiring/default.rbs +0 -26
  291. data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
  292. data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
  293. data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
  294. data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
  295. data/sig/_private/stoplight/wiring/system.rbs +0 -15
  296. data/sig/_private/stoplight.rbs +0 -48
  297. data/sig/stoplight/data_store.rbs +0 -19
  298. data/sig/stoplight/ports/configuration.rbs +0 -19
  299. data/sig/stoplight/ports/exception_matcher.rbs +0 -8
  300. data/sig/stoplight/ports/light.rbs +0 -12
  301. data/sig/stoplight/ports/light_info.rbs +0 -5
  302. data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
  303. /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
  304. /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
  305. /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
  306. /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
  307. /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
  308. /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
  309. /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
  310. /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
  311. /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
  312. /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
  313. /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
  314. /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
  315. /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
  316. /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
  317. /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
  318. /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
  319. /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
  320. /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
  321. /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
  322. /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
  323. /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe8903aa5cfb542051938b5579384312614da2ffbb937b7eb0be6ce75ae71951
4
- data.tar.gz: 5866c336e440464a53b0b7266a44494734c86574e80351bf9ec0406ee9c51f10
3
+ metadata.gz: 0dc15c81e94d811df03fe4acdd4ff76e3633931a1c4df022b7c26480834135e8
4
+ data.tar.gz: 82b93fba4751312a3ad6ac33602b0b50c40fc6ebd5d72554d66f040ab7efd133
5
5
  SHA512:
6
- metadata.gz: a27f2a8da1e01f183d13719efae03132ec938edc6bfaa575c916fa34a4b11da0071135e937d9de1763191a2c99ce2bed55092793b2413f482020ae626c46eac6
7
- data.tar.gz: f77a6c5e858e4b8427a4bca1b09450ddbbc9b5aa4d11804167151c0efcfc8e7731f50a1c7e9b40952addb77fd08287c8948b98896ca97d45740e2882933665df
6
+ metadata.gz: 3a97e8dec725453ffd76e95d7d2ab59ef8929b89aa6ac2350a816d3e280aa49f893d3818dc28d6b465e20bf508c9cf10b37265e6968c436836ba9b5bdc374bb8
7
+ data.tar.gz: 9550e279df8c4ceaa1cc4de1a76a85dcb2fa84cb079e7dbd3716e699479b5cf94fe1d577f99f713840b693b6520d1c19b8f1b2b01fc9325b6db7cf89abdcfed3
data/CHANGELOG.md CHANGED
@@ -4,4 +4,4 @@ Stoplight uses [Semantic Versioning][].
4
4
  The change log is available [on GitHub][].
5
5
 
6
6
  [semantic versioning]: http://semver.org/spec/v2.0.0.html
7
- [on github]: https://github.com/orgsync/stoplight/releases
7
+ [on github]: https://github.com/bolshakov/stoplight/releases
data/LICENSE.md CHANGED
@@ -1,6 +1,7 @@
1
1
  The MIT License (MIT)
2
2
 
3
3
  Copyright (c) 2015-2016 Cameron Desautels, Taylor Fausak & Justin Steffy
4
+ Copyright (c) 2019-2026 Tëma Bolshakov, George Asfour and contributors
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -8,8 +8,8 @@ Stoplight is a traffic control for code. It's an implementation of the circuit b
8
8
 
9
9
  ---
10
10
 
11
- :warning:️ You're currently browsing the documentation for Stoplight 5.x. If you're looking for
12
- the documentation of the previous version 4.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/v4.1.1).
11
+ :warning:️ You're currently browsing the documentation for Stoplight 6. If you're looking for
12
+ the documentation of the previous version 5.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/v5.8.3).
13
13
 
14
14
  Stoplight helps your application gracefully handle failures in external dependencies
15
15
  (like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
@@ -38,22 +38,7 @@ Stoplight uses [Semantic Versioning][]. Check out [the change log][] for a detai
38
38
 
39
39
  Stoplight operates like a traffic light with three states:
40
40
 
41
- ```mermaid
42
- stateDiagram
43
- Green --> Red: Errors reach threshold
44
- Red --> Yellow: After cool_off_time
45
- Yellow --> Green: Successful recovery
46
- Yellow --> Red: Failed recovery
47
- Green --> Green: Success
48
-
49
- classDef greenState fill:#28a745,stroke:#1e7e34,stroke-width:2px,color:#fff
50
- classDef redState fill:#dc3545,stroke:#c82333,stroke-width:2px,color:#fff
51
- classDef yellowState fill:#ffc107,stroke:#e0a800,stroke-width:2px,color:#000
52
-
53
- class Green greenState
54
- class Red redState
55
- class Yellow yellowState
56
- ```
41
+ ![Stoplight state diagram][]
57
42
 
58
43
  - **Green**: Normal operation. Code runs as expected. (Circuit closed)
59
44
  - **Red**: Failure state. Fast-fails without running the code. (Circuit open)
@@ -142,53 +127,51 @@ receives `nil`. In both cases, the return value of the fallback becomes the retu
142
127
 
143
128
  ## Admin Panel
144
129
 
145
- Stoplight comes with a built-in Admin Panel that can track all active Lights and manually lock them in the desired state (`Green` or `Red`). Locking lights in certain states might be helpful in scenarios like E2E testing.
130
+ Stoplight comes with a built-in Admin Panel for observing and controlling all lights across your application. It
131
+ displays each light's current state, recent failures, and provides controls to lock/unlock lights manually.
146
132
 
147
133
  ![Admin Panel Screenshot](assets/admin.png)
148
134
 
149
- To add Admin Panel protected by basic authentication to your Rails project, add this configuration to your `config/routes.rb` file.
135
+ ### Basic Setup
136
+
137
+ Add the Admin Panel to your Rails application with authentication:
150
138
 
151
139
  ```ruby
152
140
  Rails.application.routes.draw do
153
- # ...
154
-
155
141
  Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
156
142
  username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
157
143
  end
158
144
  mount Stoplight::Admin => '/stoplights'
159
-
160
- # ...
161
145
  end
162
146
  ```
163
147
 
164
- Then set up `STOPLIGHT_ADMIN_USERNAME` and `STOPLIGHT_ADMIN_PASSWORD` env variables to access your Admin panel.
148
+ Then set environment variables:
149
+ ```bash
150
+ export STOPLIGHT_ADMIN_USERNAME=admin
151
+ export STOPLIGHT_ADMIN_PASSWORD=secret
152
+ ```
165
153
 
166
- **IMPORTANT:** Stoplight Admin Panel requires you to have `sinatra` and `sinatra-contrib` gems installed. You can either add them to your Gemfile:
154
+ **IMPORTANT:** Stoplight Admin Panel requires `sinatra` and `sinatra-contrib` gems:
167
155
 
168
156
  ```ruby
169
157
  gem "sinatra", require: false
170
158
  gem "sinatra-contrib", require: false
171
159
  ```
172
160
 
173
- Or install it manually:
174
- ```ruby
175
- gem install sinatra
176
- gem install sinatra-contrib
177
- ```
161
+ ### Standalone Docker Setup
178
162
 
179
- ### Standalone Admin Panel Setup
180
-
181
- It is possible to run the Admin Panel separately from your application using the `stoplight-admin:<release-version>` docker image.
163
+ Run the Admin Panel as a separate service:
182
164
 
183
165
  ```shell
184
- docker run --net=host bolshakov/stoplight-admin
166
+ docker run \
167
+ -e REDIS_URL=redis://localhost:6379 \
168
+ -e STOPLIGHT_ADMIN_USERNAME=admin \
169
+ -e STOPLIGHT_ADMIN_PASSWORD=secret \
170
+ -p 4567:4567 \
171
+ bolshakov/stoplight-admin
185
172
  ```
186
173
 
187
- **IMPORTANT:** Standalone Admin Panel should use the same Redis your application uses. To achieve this, set the `REDIS_URL` ENV variable via `-e REDIS_URL=<url-to-your-redis-servier>.` E.g.:
188
-
189
- ```shell
190
- docker run -e REDIS_URL=redis://localhost:6378 --net=host bolshakov/stoplight-admin
191
- ```
174
+ For complete setup and multi-system configuration details, see the [Admin Panel guide](docs/admin.md).
192
175
 
193
176
  ## Configuration
194
177
 
@@ -226,36 +209,16 @@ light = Stoplight("Payment Service")
226
209
  You can also provide settings during creation:
227
210
 
228
211
  ```ruby
229
- data_store = Stoplight::DataStore::Redis.new(Redis.new)
230
-
231
212
  light = Stoplight("Payment Service",
232
213
  window_size: 300, # Only count errors in the last five minutes
233
214
  threshold: 5, # 5 errors before turning red
234
215
  cool_off_time: 60, # Wait 60 seconds before attempting recovery
235
216
  recovery_threshold: 1, # 1 successful attempt to turn green again
236
- data_store: data_store, # Use Redis for persistence
237
217
  tracked_errors: [TimeoutError], # Only count TimeoutError
238
218
  skipped_errors: [ValidationError] # Ignore ValidationError
239
219
  )
240
220
  ```
241
221
 
242
- ### Modifying Stoplights
243
-
244
- You can create specialized versions of existing stoplights:
245
-
246
- ```ruby
247
- # Base configuration for API calls
248
- base_api = Stoplight("Service API")
249
-
250
- # Create specialized version for the users endpoint
251
- users_api = base_api.with(
252
- tracked_errors: [TimeoutError] # Only track timeouts
253
- )
254
- ```
255
-
256
- The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
257
- specialized stoplights from a common configuration.
258
-
259
222
  ## Error Handling
260
223
 
261
224
  By default, Stoplight tracks all `StandardError` exceptions.
@@ -277,8 +240,41 @@ light = Stoplight("Example API", tracked_errors: [NetworkError, Timeout::Error])
277
240
 
278
241
  When both methods are used, `skipped_errors` takes precedence over `tracked_errors`.
279
242
 
243
+ Either list can be replaced for a single call without changing the light's configuration:
244
+
245
+ ```ruby
246
+ light.run(tracked_errors: [Timeout::Error]) { fetch_data }
247
+ light.run(skipped_errors: [ValidationError]) { process_data }
248
+ ```
249
+
250
+ Any list omitted from `run` keeps its configured value. The provided list is replaced only for that call, and
251
+ `skipped_errors` still takes precedence over `tracked_errors`.
252
+
280
253
  ## Advanced Configuration
281
254
 
255
+ ### Registering Lights
256
+
257
+ Calling `Stoplight("name", ...)` at every call site works well for a handful of lights. As an app
258
+ grows, repeating the same settings everywhere makes them easy to drift out of sync, and there's no
259
+ single place listing what lights exist.
260
+
261
+ Register a light once and look it up by name wherever you need it, instead of repeating the same
262
+ settings at every call site.
263
+
264
+ ```ruby
265
+ # config/initializers/stoplight.rb
266
+ Stoplight.register("Payment Service", threshold: 5, cool_off_time: 60)
267
+ ```
268
+
269
+ ```ruby
270
+ # anywhere else in your app
271
+ Stoplight.light("Payment Service").run { payment_gateway.process(order) }
272
+ ```
273
+
274
+ `Stoplight("name", ...)` still works as shown above -- registration is an addition, not a replacement.
275
+ `Stoplight.light` is also approximately 10 times faster, since it's a plain lookup rather than re-validating
276
+ the configuration on every call.
277
+
282
278
  ### Traffic Control Strategies
283
279
 
284
280
  You've seen how Stoplight transitions from green to red when errors reach the threshold. But **how exactly does it
@@ -340,18 +336,9 @@ light = Stoplight(
340
336
 
341
337
  Monitors error rate over a 5-minute sliding window. The stoplight turns red when error rate exceeds 50%.
342
338
 
343
- ```ruby
344
- light = Stoplight(
345
- "Payment API",
346
- traffic_control: {
347
- error_rate: { min_requests: 20 },
348
- },
349
- window_size: 300,
350
- threshold: 0.5,
351
- )
352
- ```
353
-
354
- Only evaluates error rate after at least 20 requests within the window. Default `min_requests` is 10.
339
+ Error rate evaluation starts only after 100 requests within the window — enough samples
340
+ for a statistically reliable estimate. If your service handles fewer than 100 requests
341
+ per window, the breaker will never trip on error rate; use `traffic_control: :consecutive_errors` instead.
355
342
 
356
343
 
357
344
  #### When to use:
@@ -488,6 +475,24 @@ By default, Stoplight logs state transitions to STDERR.
488
475
  Pull requests to update this section are welcome. If you want to implement your own notifier, refer to
489
476
  the [notifier interface documentation] for detailed instructions. Pull requests to update this section are welcome.
490
477
 
478
+ ### Telemetry
479
+
480
+ Notifiers only fire on state transitions. For everything else a light does - every run, trip, recovery probe, and
481
+ manual lock - subscribe to the telemetry bus:
482
+
483
+ ```ruby
484
+ Stoplight.telemetry.subscribe(Stoplight::Telemetry::TrafficBreached) do |envelope|
485
+ logger.warn("#{envelope.light_name} tripped: #{envelope.payload.failure&.exception&.message}")
486
+ end
487
+ ```
488
+
489
+ The [stoplight-statsd] gem is built on this bus. It forwards every event to Statsd, so a dashboard of your circuit
490
+ breakers is a `bundle add` away:
491
+
492
+ ![Stoplight metrics in Grafana](assets/grafana.png)
493
+
494
+ See the [Telemetry guide](docs/telemetry.md) for the full event list and the envelope format.
495
+
491
496
  ### Error Notifiers
492
497
 
493
498
  Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
@@ -522,6 +527,52 @@ light.lock(Stoplight::Color::GREEN)
522
527
  light.unlock
523
528
  ```
524
529
 
530
+ ### Multiple Independent Systems
531
+
532
+ By default, all lights share the same global configuration and data store. For larger applications with multiple
533
+ services or tenants, you can create **named systems** -- completely isolated instances with their own configuration,
534
+ notifiers, and data store:
535
+
536
+ ```ruby
537
+ # Create independent systems with separate data stores
538
+ Payments = Stoplight.register_system("Payments", threshold: 3, cool_off_time: 30)
539
+ Analytics = Stoplight.register_system("Analytics", threshold: 5, cool_off_time: 60)
540
+
541
+ # Register lights in each system
542
+ Payments.register("stripe", cool_off_time: 30)
543
+ Analytics.register("amplitude")
544
+
545
+ # Use them — one system's state does not affect another
546
+ Payments.light("stripe").run { charge_card }
547
+ Analytics.light("amplitude").run { track_event }
548
+ ```
549
+
550
+ Use cases for multiple systems:
551
+
552
+ * **Multi-tenancy**: Each tenant gets its own isolated system and data store
553
+ * **Service boundaries**: Separate failure domains with independent SLOs (e.g., payments vs. analytics)
554
+ * **Independent data stores**: One service uses Redis for persistence, another uses in-memory
555
+
556
+ For complete details on system configuration, boot-time registration patterns, and isolation guarantees, see
557
+ the [Systems guide](docs/systems.md).
558
+
559
+ ### Admin
560
+
561
+ Admin Panel can work in an read-only which could be useful for observability. To enabled read-only mode:
562
+
563
+ ```ruby
564
+ Stoplight::Admin.configure do |config|
565
+ config.read_only = true
566
+ end
567
+ ```
568
+
569
+ Read-only mode could be turned on for a pre-built docker image by passing `STOPLIGHT_ADMIN_READ_ONLY` environment
570
+ variable:
571
+
572
+ ```sh
573
+ docker run -e REDIS_URL=redis://localhost:6378 -e STOPLIGHT_ADMIN_READ_ONLY=true --net=host bolshakov/stoplight-admin
574
+ ```
575
+
525
576
  ## Rails Integration
526
577
 
527
578
  Wrap controller actions with minimal effort:
@@ -602,13 +653,13 @@ We only actively support the latest major version of Stoplight.
602
653
 
603
654
  **Ruby**: Major versions that receive security updates (see [Ruby Maintenance Branches]):
604
655
 
605
- * Currently: Ruby 3.2.x, 3.3.x and 3.4.x
656
+ * Currently: Ruby 3.3.x, 3.4.x and 4.0.x
606
657
  * We test against these versions in CI
607
658
 
608
659
  **Data Stores**: Current supported versions from upstream (versions that receive security updates):
609
660
 
610
- * Redis: 8.0.x, 7.4.x, 7.2.x, 6.2.x (following [Redis's support policy])
611
- * Valkey: 8.0.x, 7.2.x (following [Valkey's support policy])
661
+ * Redis: 8.6.x, 8.4.x, 7.4.x (following [Redis's support policy])
662
+ * Valkey: 9.1.x, 9.0.x, 8.1.x, 8.0.x, 7.2.x (following [Valkey's support policy])
612
663
  * We test against the latest version of each major release
613
664
 
614
665
  For dependencies:
@@ -622,7 +673,7 @@ For dependencies:
622
673
  * Ruby: When Ruby core team ends security support, we drop it in our next major release
623
674
  * Data Stores: When Redis/Valkey ends maintenance, we drop it in our next major release
624
675
 
625
- Example: "Ruby 3.2 reaches end-of-life in March 2026, so Stoplight 6.0 will require Ruby 3.3+"
676
+ Example: "Ruby 3.3 reaches end-of-life in March 2027, so Stoplight 7.0 will require Ruby 3.4+"
626
677
 
627
678
  ## Development
628
679
 
@@ -639,15 +690,16 @@ Fowler’s [CircuitBreaker][] article.
639
690
  [Stoplight]: https://github.com/bolshakov/stoplight
640
691
  [Version badge]: https://img.shields.io/gem/v/stoplight.svg?label=version
641
692
  [version]: https://rubygems.org/gems/stoplight
642
- [Build badge]: https://github.com/bolshakov/stoplight/workflows/Specs/badge.svg
643
- [build]: https://github.com/bolshakov/stoplight/actions?query=branch%3Amaster
644
- [Coverage badge]: https://img.shields.io/coveralls/bolshakov/stoplight/master.svg?label=coverage
693
+ [Build badge]: https://github.com/bolshakov/stoplight/actions/workflows/ci.yml/badge.svg?branch=main
694
+ [build]: https://github.com/bolshakov/stoplight/actions?query=branch%3Amain
695
+ [Coverage badge]: https://img.shields.io/coveralls/bolshakov/stoplight/main.svg?label=coverage
645
696
  [coverage]: https://coveralls.io/r/bolshakov/stoplight
646
697
  [stoplight-admin]: https://github.com/bolshakov/stoplight-admin
647
698
  [Semantic Versioning]: http://semver.org/spec/v2.0.0.html
648
699
  [the change log]: CHANGELOG.md
649
700
  [stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry
650
701
  [stoplight-honeybadger]: https://github.com/qoqa/stoplight-honeybadger
702
+ [stoplight-statsd]: https://github.com/bolshakov/stoplight-statsd
651
703
  [notifier interface documentation]: https://github.com/bolshakov/stoplight/blob/main/lib/stoplight/domain/state_transition_notifier.rb
652
704
  [camdez]: https://github.com/camdez
653
705
  [tfausak]: https://github.com/tfausak
@@ -663,3 +715,4 @@ Fowler’s [CircuitBreaker][] article.
663
715
  [Valkey's support policy]: https://valkey.io/topics/releases/
664
716
  [DragonflyDB]: https://www.dragonflydb.io/
665
717
  [DragonflyDB documentation]: https://www.dragonflydb.io/docs/managing-dragonfly/scripting#script-flags
718
+ [Stoplight state diagram]: assets/state-diagram.svg
data/UPGRADING.md CHANGED
@@ -1,3 +1,261 @@
1
+ ## Stoplight 6.0
2
+
3
+ Stoplight 6.0 is built around a single idea: a light is a named thing your application declares once, not an object
4
+ you rebuild at every call site. Most of the changes below follow from that. The first two need some thought, the rest
5
+ are mechanical.
6
+
7
+ Here's what you'll want to tackle during your upgrade, roughly ordered from the most involved to the most trivial:
8
+
9
+ - [ ] Make every `Stoplight("name", ...)` call site for the same name pass identical settings
10
+ - [ ] Move `data_store`, `notifiers`, and `error_notifier` from individual lights to `Stoplight.configure`
11
+ - [ ] Replace the `with_*` builder methods with settings passed at creation time
12
+ - [ ] Replace proc and anonymous-class error matchers with named classes or modules
13
+ - [ ] Account for Stoplight state reset after deployment
14
+ - [ ] Re-check `error_rate` lights - `min_requests` is gone and the fixed minimum sample is now 100 requests
15
+ - [ ] Round any fractional `window_size` up to a whole number of seconds
16
+ - [ ] Round any fractional `cool_off_time` up to a whole number of seconds, at least 1
17
+ - [ ] Drop `warn_on_clock_skew` from your Redis data store setup
18
+ - [ ] Test thoroughly in a staging environment
19
+
20
+ ### Lights Are Registered and Reused
21
+
22
+ In 5.x, each `Stoplight("Payment Service", threshold: 5)` call built a brand-new light. Nothing tied the call sites
23
+ together, so two places could configure the same name differently and both would run - which settings a process ended
24
+ up with depended on which file it executed.
25
+
26
+ In 6.0, `Stoplight()` registers the light on first call and returns that same cached instance afterwards. If a later
27
+ call passes settings that differ from the registration, Stoplight raises `Stoplight::Error::ConfigurationError`. The
28
+ message names where the light was first registered, and the error's backtrace points at the conflicting call:
29
+
30
+ ```
31
+ Light `Payment Service` already registered with different configuration.
32
+
33
+ Originally registered at:
34
+ app/services/payment_service.rb:14:in 'charge'
35
+
36
+ Lights must have consistent configuration across all call sites.
37
+ ```
38
+
39
+ Your existing code keeps working as long as each name is configured consistently, so for many applications this
40
+ upgrade is a no-op until a stray call site raises. The more durable fix is to register lights once at boot and look
41
+ them up by name:
42
+
43
+ ```ruby
44
+ # config/initializers/stoplight.rb
45
+ Stoplight.register("Payment Service", threshold: 5, cool_off_time: 60)
46
+ ```
47
+
48
+ ```ruby
49
+ # anywhere else in your app
50
+ Stoplight.light("Payment Service").run { payment_gateway.charge(order) }
51
+ ```
52
+
53
+ `Stoplight()` still works everywhere it used to - it just expects the same settings each time. `Stoplight.light` is
54
+ the alternative: it takes a name and nothing else, so there's no settings list to keep in sync, and it raises
55
+ `Stoplight::Error::UnregisteredLightError` if that name was never registered.
56
+
57
+ Lights built from dynamic names (`Stoplight("api-#{endpoint}")`) still work - caching is per name, so each distinct
58
+ name registers once.
59
+
60
+ ### Infrastructure Configuration Moved Out of Individual Lights
61
+
62
+ `Stoplight()` and `Stoplight.light()` no longer accept `data_store`, `notifiers`, or `error_notifier`. Passing them
63
+ now raises `ArgumentError: unknown keyword`.
64
+
65
+ These options aren't per-circuit behavior - they describe where your application keeps state and how it reports
66
+ problems. When each light could carry its own, the same light name could end up backed by different stores in
67
+ different processes, so instances never saw each other's failures. It also left no single place listing which lights
68
+ exist and where their state lives, which the Admin dashboard's registry needs.
69
+
70
+ ```ruby
71
+ # Old way that won't work anymore
72
+ light = Stoplight("Payment Service", data_store: Stoplight::DataStore::Redis.new(redis))
73
+ ```
74
+
75
+ Configure infrastructure once, at boot:
76
+
77
+ ```ruby
78
+ # New way
79
+ Stoplight.configure do |config|
80
+ config.data_store = Stoplight::DataStore::Redis.new(redis)
81
+ config.notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)]
82
+ config.error_notifier = ->(error) { Bugsnag.notify(error) }
83
+ end
84
+ ```
85
+
86
+ If you genuinely need more than one data store - separate Redis instances per tenant, for example - use named
87
+ systems, which own their own infrastructure. See [Systems: Namespace Isolation & Configuration][systems] for setup.
88
+
89
+ ### The Light Builder API Is Gone
90
+
91
+ `Light#with` and the whole `with_*` family are removed: `with_data_store`, `with_cool_off_time`, `with_threshold`,
92
+ `with_window_size`, `with_notifiers`, `with_error_notifier`, `with_tracked_errors`, and `with_skipped_errors`. They
93
+ were marked deprecated in 5.0 and have printed a warning naming this release since 5.7, so most codebases should
94
+ already be clean.
95
+
96
+ Cloning a light produced a second light sharing the first one's name but not its settings - exactly the configuration
97
+ drift the registration model now rejects.
98
+
99
+ ```ruby
100
+ # Old way
101
+ light = Stoplight("Payment Service").with_threshold(10).with_cool_off_time(30)
102
+
103
+ # New way
104
+ light = Stoplight("Payment Service", threshold: 10, cool_off_time: 30)
105
+ ```
106
+
107
+ If you were cloning in order to vary error handling per call, you don't need a second light for that anymore -
108
+ `#run` takes per-call overrides:
109
+
110
+ ```ruby
111
+ light.run(skipped_errors: [ActiveRecord::RecordNotFound]) { find_customer }
112
+ ```
113
+
114
+ ### Error Matchers Must Be Named Classes or Modules
115
+
116
+ `tracked_errors` and `skipped_errors` accept named `Class` or `Module` constants only - `StandardError`, your own
117
+ error classes, or a custom class or module overriding `===`. Procs, anonymous classes (`Class.new(StandardError)`),
118
+ and instances raise `ArgumentError`. In 5.x anything responding to `===` was accepted.
119
+
120
+ Both the registry and the consistency check need a matcher's *name*. The registry writes each light's configuration
121
+ to Redis so the Admin dashboard can list lights it never loaded, and a matcher is stored there as its class name - a
122
+ proc has no name to write. Comparing call sites has the same problem from the other direction: two files that each
123
+ build their own `->(error) { ... }` produce two different objects, so identical-looking configuration would be
124
+ reported as a conflict. A constant is the same matcher everywhere it's mentioned.
125
+
126
+ ```ruby
127
+ # Old way that won't work anymore
128
+ light = Stoplight("Payment Service", skipped_errors: [->(error) { error.is_a?(Timeout::Error) }])
129
+
130
+ # New way
131
+ light = Stoplight("Payment Service", skipped_errors: [Timeout::Error])
132
+ ```
133
+
134
+ For matching that genuinely needs logic, give it a name and let it override `===`:
135
+
136
+ ```ruby
137
+ module TransientHTTPError
138
+ def self.===(error) = error.is_a?(Net::HTTPError) && error.response.code.start_with?("5")
139
+ end
140
+
141
+ light = Stoplight("Payment Service", tracked_errors: [TransientHTTPError])
142
+ ```
143
+
144
+ The restriction applies to registration only. The same two lists can be passed to `#run` as a per-call override, and
145
+ those are never registered, serialized, or compared against another call site - so anything responding to `===` still
146
+ works there, procs included:
147
+
148
+ ```ruby
149
+ light.run(skipped_errors: ->(error) { error.message.include?("rate limit") }) { charge_card }
150
+ ```
151
+
152
+ ### Redis Data Gets a Fresh Start Again
153
+
154
+ Like 5.0 before it, 6.0 moves to a new Redis key schema. Keys are now namespaced `stoplight:v6:...` instead of
155
+ `stoplight:v5:...`, and the storage behind them was decomposed into focused stores for state, metrics, and recovery
156
+ locks.
157
+
158
+ The reason is the same one that motivated the split: a single monolithic key structure made every read pull data no
159
+ caller needed, and it left no room for the per-light configuration the registry and dashboard now persist. Migrating
160
+ the old format would have meant a rewrite of live data for a value that decays within minutes anyway.
161
+
162
+ Nothing to change in your code. After deploying, every circuit breaker starts green with no history. For most
163
+ applications that's a non-event, since circuit breakers are meant to react to current conditions - but if you're
164
+ deploying while a dependency is already failing, expect the first failures after the deploy to be passed through
165
+ before the light trips again.
166
+
167
+ Your old `stoplight:v5:*` keys are ignored from the moment you deploy, and they carry TTLs, so they expire on their
168
+ own. There's no cleanup to do.
169
+
170
+ ### Error Rate No Longer Takes `min_requests`
171
+
172
+ The `error_rate` traffic control strategy dropped its `min_requests` option. The minimum sample size is now fixed at
173
+ 100 requests internally, up from a default of 10.
174
+
175
+ This changes behavior even if you never passed the option: an `error_rate` light now needs 100 requests in its window
176
+ before it can trip at all.
177
+
178
+ An error rate is only a useful signal when it's measured over enough requests. Across 10 requests - the old default -
179
+ a single failure moves the rate by 10 percentage points, so the light reacted to normal variation as readily as to a
180
+ real problem. At 100 requests each individual request moves the measurement by one point, which is stable enough to
181
+ act on. Since the value that makes the strategy trustworthy is the one nobody should tune down, it stopped being a
182
+ knob.
183
+
184
+ The practical consequence is that `error_rate` is a strategy for lights that see real traffic. If a light won't see
185
+ 100 requests within its `window_size`, use `:consecutive_errors` instead.
186
+
187
+ ```ruby
188
+ # Old way that won't work anymore
189
+ light = Stoplight("Payment Service", traffic_control: {error_rate: {min_requests: 10}}, threshold: 0.5)
190
+
191
+ # New way
192
+ light = Stoplight("Payment Service", traffic_control: :error_rate, threshold: 0.5, window_size: 300)
193
+ ```
194
+
195
+ The hash form of `traffic_control` is gone entirely, so passing it raises `Stoplight::Error::ConfigurationError`.
196
+ `Stoplight::Domain::TrafficControl::ErrorRate.new` takes no arguments.
197
+
198
+ ### `window_size` Must Be a Whole Number of Seconds
199
+
200
+ `window_size` now accepts only an `Integer` of at least 1. A `Float`, or anything below one second, raises
201
+ `Stoplight::Error::ConfigurationError` when the value is applied - at `Stoplight()`, `Stoplight.register_system`, or
202
+ `Stoplight.configure`. `nil` still means "no window".
203
+
204
+ ```ruby
205
+ # Old way that won't work anymore
206
+ light = Stoplight("Payment Service", window_size: 59.5, traffic_control: :error_rate, threshold: 0.5)
207
+
208
+ # New way
209
+ light = Stoplight("Payment Service", window_size: 60, traffic_control: :error_rate, threshold: 0.5)
210
+ ```
211
+
212
+ Metrics are counted in per-second buckets, so a second is the smallest span the window can actually measure. A
213
+ fractional value was never honoured as written - both stores truncated it to whole buckets, so `window_size: 59.5`
214
+ measured 59 seconds on Redis and 59 or 60 in memory depending on when you asked, and anything under a second evicted
215
+ the current bucket on most calls, so failures rarely accumulated enough to trip the light. None of that has changed -
216
+ the window is measured exactly as it always was. What changes is that the mismatch is no longer hidden: instead of
217
+ quietly measuring a different span than you asked for, Stoplight escalates it as an error the moment the light is
218
+ configured.
219
+
220
+ ### `cool_off_time` Must Be a Whole Number of Seconds
221
+
222
+ `cool_off_time` now accepts only an `Integer` of at least 1. A `Float`, or anything below one second, raises
223
+ `Stoplight::Error::ConfigurationError` when the value is applied - at `Stoplight()`, `Stoplight.register_system`, or
224
+ `Stoplight.configure`.
225
+
226
+ ```ruby
227
+ # Old way that won't work anymore
228
+ light = Stoplight("Payment Service", cool_off_time: 1.5)
229
+
230
+ # New way
231
+ light = Stoplight("Payment Service", cool_off_time: 2)
232
+ ```
233
+
234
+ Below a second the light turns yellow again before the failing dependency could plausibly have recovered, so it
235
+ probes on nearly every call instead of breaking the circuit. Above that, a fraction of a second is marginal against
236
+ the window the light measures failures over, so whole seconds are the only granularity worth expressing.
237
+
238
+ ### Clock Skew Detection Is Gone
239
+
240
+ `Stoplight::DataStore::Redis.new` no longer accepts `warn_on_clock_skew:`. Passing it raises `ArgumentError`.
241
+
242
+ ```ruby
243
+ # Old way that won't work anymore
244
+ Stoplight::DataStore::Redis.new(redis, warn_on_clock_skew: false)
245
+
246
+ # New way
247
+ Stoplight::DataStore::Redis.new(redis)
248
+ ```
249
+
250
+ All time-dependent decisions are now made from Redis's own clock, so every instance reads the same time no matter
251
+ what its host believes. With the coordination problem solved at the source, there is no skew left to warn about.
252
+
253
+ ### Getting Help
254
+
255
+ If you run into anything this guide doesn't cover, post to our [Discussions forum]. The configuration errors in 6.0
256
+ try hard to tell you exactly which two call sites disagree and what to do about it, so start by reading the message
257
+ in full - it usually names the file and line you need.
258
+
1
259
  ## Stoplight 5.0
2
260
 
3
261
  Stoplight 5.0 introduces several breaking changes, so you'll need to set aside some time to update your code. The good
@@ -7,12 +265,12 @@ more powerful setup.
7
265
  Here's what you'll want to tackle during your upgrade. Don't worry if this looks like a lot - most of these are simple
8
266
  find-and-replace operations:
9
267
 
10
- - [] Update global configuration to use the new block syntax
11
- - [] Replace any remaining `Stoplight() {}` calls with `Stoplight().run {}`
12
- - [] Convert error handlers to tracked/skipped error lists
13
- - [] Move fallbacks from configuration to `#run` method calls
14
- - [] Account for Stoplight state reset after deployment
15
- - [] Test thoroughly in a staging environment
268
+ - [ ] Update global configuration to use the new block syntax
269
+ - [ ] Replace any remaining `Stoplight() {}` calls with `Stoplight().run {}`
270
+ - [ ] Convert error handlers to tracked/skipped error lists
271
+ - [ ] Move fallbacks from configuration to `#run` method calls
272
+ - [ ] Account for Stoplight state reset after deployment
273
+ - [ ] Test thoroughly in a staging environment
16
274
 
17
275
  ### Global Configuration Redesign
18
276
 
@@ -300,4 +558,5 @@ Nothing. Stoplight will function as usual.
300
558
  [dropped notifiers]: https://github.com/bolshakov/stoplight/tree/v3.0.1/lib/stoplight/notifier
301
559
  [without passing an empty block]: https://github.com/bolshakov/stoplight-admin/blob/9c9848eb94410e46b20972548f0863db224cb6da/lib/sinatra/stoplight_admin.rb#L30
302
560
  [sliding window]: https://github.com/bolshakov/stoplight#custom-window-size
561
+ [systems]: https://github.com/bolshakov/stoplight/blob/master/docs/systems.md
303
562
  [Discussions forum]: https://github.com/bolshakov/stoplight/discussions/categories/q-a
@@ -5,18 +5,10 @@ module Stoplight
5
5
  module Actions
6
6
  # @abstract
7
7
  class Action
8
- # @!attribute lights_repository
9
- # @return [Stoplight::Admin::LightsRepository]
10
- attr_reader :lights_repository
11
- private :lights_repository
12
-
13
- # @return lights_repository [Stoplight::Admin::LightsRepository]
14
- def initialize(lights_repository:)
15
- @lights_repository = lights_repository
16
- end
17
-
18
- def call(params)
19
- raise NotImplementedError
8
+ # Unwinds to Sinatra's route dispatch via `throw :halt`, skipping the rest of the
9
+ # action and the route block, and using +status+ as the response status.
10
+ def halt(status)
11
+ throw :halt, status
20
12
  end
21
13
  end
22
14
  end