tasker-engine 1.0.0

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 (605) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +443 -0
  4. data/Rakefile +10 -0
  5. data/app/controllers/tasker/analytics_controller.rb +179 -0
  6. data/app/controllers/tasker/application_controller.rb +45 -0
  7. data/app/controllers/tasker/graphql_controller.rb +193 -0
  8. data/app/controllers/tasker/handlers_controller.rb +217 -0
  9. data/app/controllers/tasker/health_controller.rb +229 -0
  10. data/app/controllers/tasker/metrics_controller.rb +111 -0
  11. data/app/controllers/tasker/page_sort.rb +97 -0
  12. data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
  13. data/app/controllers/tasker/tasks_controller.rb +123 -0
  14. data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
  15. data/app/graphql/examples/all_tasks.graphql +22 -0
  16. data/app/graphql/examples/pending_tasks.graphql +23 -0
  17. data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
  18. data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
  19. data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
  20. data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
  21. data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
  22. data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
  23. data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
  24. data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
  25. data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
  26. data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
  27. data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
  28. data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
  29. data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
  30. data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
  31. data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
  32. data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
  33. data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
  34. data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
  35. data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
  36. data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
  37. data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
  38. data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
  39. data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
  40. data/app/graphql/tasker/graph_ql_types.rb +42 -0
  41. data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
  42. data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
  43. data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
  44. data/app/graphql/tasker/mutations/create_task.rb +52 -0
  45. data/app/graphql/tasker/mutations/update_step.rb +36 -0
  46. data/app/graphql/tasker/mutations/update_task.rb +41 -0
  47. data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
  48. data/app/graphql/tasker/queries/all_tasks.rb +23 -0
  49. data/app/graphql/tasker/queries/base_query.rb +9 -0
  50. data/app/graphql/tasker/queries/helpers.rb +16 -0
  51. data/app/graphql/tasker/queries/one_step.rb +24 -0
  52. data/app/graphql/tasker/queries/one_task.rb +18 -0
  53. data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
  54. data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
  55. data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
  56. data/app/jobs/tasker/application_job.rb +8 -0
  57. data/app/jobs/tasker/metrics_export_job.rb +252 -0
  58. data/app/jobs/tasker/task_runner_job.rb +224 -0
  59. data/app/models/tasker/annotation_type.rb +26 -0
  60. data/app/models/tasker/application_record.rb +70 -0
  61. data/app/models/tasker/dependent_system.rb +26 -0
  62. data/app/models/tasker/dependent_system_object_map.rb +64 -0
  63. data/app/models/tasker/diagram/edge.rb +106 -0
  64. data/app/models/tasker/diagram/flowchart.rb +137 -0
  65. data/app/models/tasker/diagram/node.rb +99 -0
  66. data/app/models/tasker/named_step.rb +41 -0
  67. data/app/models/tasker/named_task.rb +121 -0
  68. data/app/models/tasker/named_tasks_named_step.rb +82 -0
  69. data/app/models/tasker/step_dag_relationship.rb +65 -0
  70. data/app/models/tasker/step_readiness_status.rb +59 -0
  71. data/app/models/tasker/task.rb +424 -0
  72. data/app/models/tasker/task_annotation.rb +36 -0
  73. data/app/models/tasker/task_diagram.rb +332 -0
  74. data/app/models/tasker/task_execution_context.rb +29 -0
  75. data/app/models/tasker/task_namespace.rb +41 -0
  76. data/app/models/tasker/task_transition.rb +235 -0
  77. data/app/models/tasker/workflow_step.rb +461 -0
  78. data/app/models/tasker/workflow_step_edge.rb +94 -0
  79. data/app/models/tasker/workflow_step_transition.rb +434 -0
  80. data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
  81. data/app/serializers/tasker/handler_serializer.rb +109 -0
  82. data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
  83. data/app/serializers/tasker/task_serializer.rb +168 -0
  84. data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
  85. data/app/services/tasker/analytics_service.rb +409 -0
  86. data/app/views/tasker/task/_diagram.html.erb +32 -0
  87. data/config/initializers/dry_struct.rb +11 -0
  88. data/config/initializers/statesman.rb +6 -0
  89. data/config/initializers/tasker_orchestration.rb +17 -0
  90. data/config/initializers/time_formats.rb +4 -0
  91. data/config/routes.rb +34 -0
  92. data/config/tasker/subscriptions/example_integrations.yml +67 -0
  93. data/config/tasker/system_events.yml +305 -0
  94. data/db/functions/calculate_dependency_levels_v01.sql +45 -0
  95. data/db/functions/get_analytics_metrics_v01.sql +137 -0
  96. data/db/functions/get_slowest_steps_v01.sql +82 -0
  97. data/db/functions/get_slowest_tasks_v01.sql +96 -0
  98. data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
  99. data/db/functions/get_step_readiness_status_v01.sql +139 -0
  100. data/db/functions/get_system_health_counts_v01.sql +108 -0
  101. data/db/functions/get_task_execution_context_v01.sql +108 -0
  102. data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
  103. data/db/init/schema.sql +2277 -0
  104. data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
  105. data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
  106. data/docs/APPLICATION_GENERATOR.md +384 -0
  107. data/docs/AUTH.md +1780 -0
  108. data/docs/CIRCUIT_BREAKER.md +224 -0
  109. data/docs/DEVELOPER_GUIDE.md +2665 -0
  110. data/docs/EVENT_SYSTEM.md +637 -0
  111. data/docs/EXECUTION_CONFIGURATION.md +341 -0
  112. data/docs/FLOW_CHART.md +149 -0
  113. data/docs/HEALTH.md +542 -0
  114. data/docs/METRICS.md +731 -0
  115. data/docs/OPTIMIZATION_PLAN.md +1479 -0
  116. data/docs/OVERVIEW.md +552 -0
  117. data/docs/QUICK_START.md +270 -0
  118. data/docs/REGISTRY_SYSTEMS.md +373 -0
  119. data/docs/REST_API.md +632 -0
  120. data/docs/ROADMAP.md +221 -0
  121. data/docs/SQL_FUNCTIONS.md +1408 -0
  122. data/docs/TASK_DIAGRAM.md +252 -0
  123. data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
  124. data/docs/TELEMETRY.md +795 -0
  125. data/docs/TROUBLESHOOTING.md +756 -0
  126. data/docs/TaskHandlerGenerator.html +255 -0
  127. data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
  128. data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
  129. data/docs/Tasker/Analysis.html +117 -0
  130. data/docs/Tasker/AnalyticsController.html +450 -0
  131. data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
  132. data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
  133. data/docs/Tasker/AnalyticsService.html +2221 -0
  134. data/docs/Tasker/AnnotationType.html +137 -0
  135. data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
  136. data/docs/Tasker/ApplicationController.html +147 -0
  137. data/docs/Tasker/ApplicationJob.html +128 -0
  138. data/docs/Tasker/ApplicationRecord.html +378 -0
  139. data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
  140. data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
  141. data/docs/Tasker/Authentication/Coordinator.html +242 -0
  142. data/docs/Tasker/Authentication/Interface.html +560 -0
  143. data/docs/Tasker/Authentication/InterfaceError.html +124 -0
  144. data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
  145. data/docs/Tasker/Authentication.html +119 -0
  146. data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
  147. data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
  148. data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
  149. data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
  150. data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
  151. data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
  152. data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
  153. data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
  154. data/docs/Tasker/Authorization.html +582 -0
  155. data/docs/Tasker/CacheCapabilities.html +167 -0
  156. data/docs/Tasker/CacheStrategy.html +1297 -0
  157. data/docs/Tasker/Concerns/Authenticatable.html +116 -0
  158. data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
  159. data/docs/Tasker/Concerns/Authorizable.html +816 -0
  160. data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
  161. data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
  162. data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
  163. data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
  164. data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
  165. data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
  166. data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
  167. data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
  168. data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
  169. data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
  170. data/docs/Tasker/Concerns.html +117 -0
  171. data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
  172. data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
  173. data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
  174. data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
  175. data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
  176. data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
  177. data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
  178. data/docs/Tasker/Configuration.html +1669 -0
  179. data/docs/Tasker/ConfigurationError.html +143 -0
  180. data/docs/Tasker/ConfiguredTask.html +514 -0
  181. data/docs/Tasker/Constants/EventDefinitions.html +590 -0
  182. data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
  183. data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
  184. data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
  185. data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
  186. data/docs/Tasker/Constants/RegistryEvents.html +285 -0
  187. data/docs/Tasker/Constants/StepEvents.html +177 -0
  188. data/docs/Tasker/Constants/TaskEvents.html +167 -0
  189. data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
  190. data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
  191. data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
  192. data/docs/Tasker/Constants/TaskExecution.html +126 -0
  193. data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
  194. data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
  195. data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
  196. data/docs/Tasker/Constants/TaskFinalization.html +126 -0
  197. data/docs/Tasker/Constants/TaskStatuses.html +223 -0
  198. data/docs/Tasker/Constants/TestEvents.html +163 -0
  199. data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
  200. data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
  201. data/docs/Tasker/Constants.html +561 -0
  202. data/docs/Tasker/DependentSystem.html +137 -0
  203. data/docs/Tasker/DependentSystemObjectMap.html +250 -0
  204. data/docs/Tasker/DetectorRegistry.html +598 -0
  205. data/docs/Tasker/Diagram/Edge.html +1191 -0
  206. data/docs/Tasker/Diagram/Flowchart.html +1539 -0
  207. data/docs/Tasker/Diagram/Node.html +1165 -0
  208. data/docs/Tasker/Diagram.html +117 -0
  209. data/docs/Tasker/Engine.html +215 -0
  210. data/docs/Tasker/Error.html +139 -0
  211. data/docs/Tasker/Events/Bus.html +1226 -0
  212. data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
  213. data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
  214. data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
  215. data/docs/Tasker/Events/Catalog.html +1291 -0
  216. data/docs/Tasker/Events/CustomRegistry.html +943 -0
  217. data/docs/Tasker/Events/DefinitionLoader.html +575 -0
  218. data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
  219. data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
  220. data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
  221. data/docs/Tasker/Events/Publisher.html +365 -0
  222. data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
  223. data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
  224. data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
  225. data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
  226. data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
  227. data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
  228. data/docs/Tasker/Events/Subscribers.html +117 -0
  229. data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
  230. data/docs/Tasker/Events.html +294 -0
  231. data/docs/Tasker/EventsGenerator.html +459 -0
  232. data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
  233. data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
  234. data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
  235. data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
  236. data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
  237. data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
  238. data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
  239. data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
  240. data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
  241. data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
  242. data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
  243. data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
  244. data/docs/Tasker/Functions.html +117 -0
  245. data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
  246. data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
  247. data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
  248. data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
  249. data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
  250. data/docs/Tasker/Generators.html +117 -0
  251. data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
  252. data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
  253. data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
  254. data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
  255. data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
  256. data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
  257. data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
  258. data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
  259. data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
  260. data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
  261. data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
  262. data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
  263. data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
  264. data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
  265. data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
  266. data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
  267. data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
  268. data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
  269. data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
  270. data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
  271. data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
  272. data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
  273. data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
  274. data/docs/Tasker/GraphQLTypes.html +130 -0
  275. data/docs/Tasker/GraphqlController.html +251 -0
  276. data/docs/Tasker/HandlerFactory.html +1518 -0
  277. data/docs/Tasker/HandlerSerializer.html +682 -0
  278. data/docs/Tasker/HandlersController.html +574 -0
  279. data/docs/Tasker/HashIdentityStrategy.html +278 -0
  280. data/docs/Tasker/Health/ReadinessChecker.html +712 -0
  281. data/docs/Tasker/Health/StatusChecker.html +653 -0
  282. data/docs/Tasker/Health.html +117 -0
  283. data/docs/Tasker/HealthController.html +523 -0
  284. data/docs/Tasker/IdentityStrategy.html +276 -0
  285. data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
  286. data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
  287. data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
  288. data/docs/Tasker/LifecycleEvents/Events.html +204 -0
  289. data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
  290. data/docs/Tasker/LifecycleEvents.html +799 -0
  291. data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
  292. data/docs/Tasker/Logging.html +115 -0
  293. data/docs/Tasker/MetricsController.html +293 -0
  294. data/docs/Tasker/MetricsExportJob.html +414 -0
  295. data/docs/Tasker/Mutations/BaseMutation.html +128 -0
  296. data/docs/Tasker/Mutations/CancelStep.html +219 -0
  297. data/docs/Tasker/Mutations/CancelTask.html +221 -0
  298. data/docs/Tasker/Mutations/CreateTask.html +243 -0
  299. data/docs/Tasker/Mutations/UpdateStep.html +243 -0
  300. data/docs/Tasker/Mutations/UpdateTask.html +243 -0
  301. data/docs/Tasker/Mutations.html +117 -0
  302. data/docs/Tasker/NamedStep.html +216 -0
  303. data/docs/Tasker/NamedTask.html +910 -0
  304. data/docs/Tasker/NamedTasksNamedStep.html +435 -0
  305. data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
  306. data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
  307. data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
  308. data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
  309. data/docs/Tasker/Orchestration/Coordinator.html +641 -0
  310. data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
  311. data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
  312. data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
  313. data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
  314. data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
  315. data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
  316. data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
  317. data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
  318. data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
  319. data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
  320. data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
  321. data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
  322. data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
  323. data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
  324. data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
  325. data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
  326. data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
  327. data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
  328. data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
  329. data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
  330. data/docs/Tasker/Orchestration.html +130 -0
  331. data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
  332. data/docs/Tasker/PageSort.html +247 -0
  333. data/docs/Tasker/PermanentError.html +518 -0
  334. data/docs/Tasker/ProceduralError.html +147 -0
  335. data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
  336. data/docs/Tasker/Queries/AllTasks.html +221 -0
  337. data/docs/Tasker/Queries/BaseQuery.html +128 -0
  338. data/docs/Tasker/Queries/Helpers.html +187 -0
  339. data/docs/Tasker/Queries/OneStep.html +225 -0
  340. data/docs/Tasker/Queries/OneTask.html +217 -0
  341. data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
  342. data/docs/Tasker/Queries/TasksByStatus.html +233 -0
  343. data/docs/Tasker/Queries.html +119 -0
  344. data/docs/Tasker/Railtie.html +124 -0
  345. data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
  346. data/docs/Tasker/Registry/EventPublisher.html +667 -0
  347. data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
  348. data/docs/Tasker/Registry/RegistrationError.html +132 -0
  349. data/docs/Tasker/Registry/RegistryError.html +139 -0
  350. data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
  351. data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
  352. data/docs/Tasker/Registry/ValidationError.html +132 -0
  353. data/docs/Tasker/Registry.html +119 -0
  354. data/docs/Tasker/RetryableError.html +515 -0
  355. data/docs/Tasker/StateMachine/Compatibility.html +282 -0
  356. data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
  357. data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
  358. data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
  359. data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
  360. data/docs/Tasker/StateMachine.html +602 -0
  361. data/docs/Tasker/StepDagRelationship.html +657 -0
  362. data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
  363. data/docs/Tasker/StepHandler/Api.html +884 -0
  364. data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
  365. data/docs/Tasker/StepHandler/Base.html +970 -0
  366. data/docs/Tasker/StepHandler.html +119 -0
  367. data/docs/Tasker/StepReadinessStatus.html +836 -0
  368. data/docs/Tasker/Task.html +2575 -0
  369. data/docs/Tasker/TaskAnnotation.html +137 -0
  370. data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
  371. data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
  372. data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
  373. data/docs/Tasker/TaskBuilder.html +764 -0
  374. data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
  375. data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
  376. data/docs/Tasker/TaskDiagram.html +548 -0
  377. data/docs/Tasker/TaskDiagramsController.html +240 -0
  378. data/docs/Tasker/TaskExecutionContext.html +469 -0
  379. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
  380. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
  381. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
  382. data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
  383. data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
  384. data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
  385. data/docs/Tasker/TaskHandler.html +271 -0
  386. data/docs/Tasker/TaskNamespace.html +312 -0
  387. data/docs/Tasker/TaskRunnerJob.html +406 -0
  388. data/docs/Tasker/TaskSerializer.html +474 -0
  389. data/docs/Tasker/TaskTransition.html +1517 -0
  390. data/docs/Tasker/TaskWorkflowSummary.html +988 -0
  391. data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
  392. data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
  393. data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
  394. data/docs/Tasker/TaskerRailsSchema.html +384 -0
  395. data/docs/Tasker/TasksController.html +595 -0
  396. data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
  397. data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
  398. data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
  399. data/docs/Tasker/Telemetry/Events.html +115 -0
  400. data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
  401. data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
  402. data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
  403. data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
  404. data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
  405. data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
  406. data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
  407. data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
  408. data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
  409. data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
  410. data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
  411. data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
  412. data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
  413. data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
  414. data/docs/Tasker/Telemetry/Plugins.html +117 -0
  415. data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
  416. data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
  417. data/docs/Tasker/Telemetry.html +130 -0
  418. data/docs/Tasker/Types/AuthConfig.html +886 -0
  419. data/docs/Tasker/Types/BackoffConfig.html +1063 -0
  420. data/docs/Tasker/Types/BaseConfig.html +227 -0
  421. data/docs/Tasker/Types/CacheConfig.html +1731 -0
  422. data/docs/Tasker/Types/DatabaseConfig.html +388 -0
  423. data/docs/Tasker/Types/DependencyGraph.html +526 -0
  424. data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
  425. data/docs/Tasker/Types/EngineConfig.html +1181 -0
  426. data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
  427. data/docs/Tasker/Types/GraphEdge.html +517 -0
  428. data/docs/Tasker/Types/GraphMetadata.html +781 -0
  429. data/docs/Tasker/Types/GraphNode.html +694 -0
  430. data/docs/Tasker/Types/HealthConfig.html +784 -0
  431. data/docs/Tasker/Types/StepSequence.html +353 -0
  432. data/docs/Tasker/Types/StepTemplate.html +1193 -0
  433. data/docs/Tasker/Types/TaskRequest.html +1179 -0
  434. data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
  435. data/docs/Tasker/Types.html +154 -0
  436. data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
  437. data/docs/Tasker/WorkflowStep.html +2724 -0
  438. data/docs/Tasker/WorkflowStepEdge.html +304 -0
  439. data/docs/Tasker/WorkflowStepSerializer.html +305 -0
  440. data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
  441. data/docs/Tasker/WorkflowStepTransition.html +2201 -0
  442. data/docs/Tasker/WorkflowStepsController.html +462 -0
  443. data/docs/Tasker.html +452 -0
  444. data/docs/VISION.md +584 -0
  445. data/docs/WHY.md +21 -0
  446. data/docs/_index.html +2375 -0
  447. data/docs/class_list.html +54 -0
  448. data/docs/css/common.css +1 -0
  449. data/docs/css/full_list.css +58 -0
  450. data/docs/css/style.css +503 -0
  451. data/docs/events/migration_plan_outcomes.md +80 -0
  452. data/docs/file.README.html +541 -0
  453. data/docs/file_list.html +59 -0
  454. data/docs/frames.html +22 -0
  455. data/docs/index.html +541 -0
  456. data/docs/js/app.js +344 -0
  457. data/docs/js/full_list.js +242 -0
  458. data/docs/js/jquery.js +4 -0
  459. data/docs/method_list.html +9182 -0
  460. data/docs/top-level-namespace.html +110 -0
  461. data/lib/generators/tasker/authenticator_generator.rb +301 -0
  462. data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
  463. data/lib/generators/tasker/events_generator.rb +91 -0
  464. data/lib/generators/tasker/subscriber_generator.rb +107 -0
  465. data/lib/generators/tasker/task_handler_generator.rb +138 -0
  466. data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
  467. data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
  468. data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
  469. data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
  470. data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
  471. data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
  472. data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
  473. data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
  474. data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
  475. data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
  476. data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
  477. data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
  478. data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
  479. data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
  480. data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
  481. data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
  482. data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
  483. data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
  484. data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
  485. data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
  486. data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
  487. data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
  488. data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
  489. data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
  490. data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
  491. data/lib/tasker/authentication/coordinator.rb +78 -0
  492. data/lib/tasker/authentication/errors.rb +9 -0
  493. data/lib/tasker/authentication/interface.rb +36 -0
  494. data/lib/tasker/authentication/none_authenticator.rb +26 -0
  495. data/lib/tasker/authorization/base_coordinator.rb +112 -0
  496. data/lib/tasker/authorization/errors.rb +26 -0
  497. data/lib/tasker/authorization/resource_constants.rb +74 -0
  498. data/lib/tasker/authorization/resource_registry.rb +143 -0
  499. data/lib/tasker/authorization.rb +75 -0
  500. data/lib/tasker/cache_capabilities.rb +131 -0
  501. data/lib/tasker/cache_strategy.rb +469 -0
  502. data/lib/tasker/concerns/authenticatable.rb +41 -0
  503. data/lib/tasker/concerns/authorizable.rb +204 -0
  504. data/lib/tasker/concerns/controller_authorizable.rb +124 -0
  505. data/lib/tasker/concerns/event_publisher.rb +716 -0
  506. data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
  507. data/lib/tasker/concerns/state_machine_base.rb +218 -0
  508. data/lib/tasker/concerns/structured_logging.rb +387 -0
  509. data/lib/tasker/configuration.rb +325 -0
  510. data/lib/tasker/constants/event_definitions.rb +147 -0
  511. data/lib/tasker/constants/registry_events.rb +54 -0
  512. data/lib/tasker/constants.rb +417 -0
  513. data/lib/tasker/engine.rb +90 -0
  514. data/lib/tasker/errors.rb +90 -0
  515. data/lib/tasker/events/catalog.rb +432 -0
  516. data/lib/tasker/events/custom_registry.rb +175 -0
  517. data/lib/tasker/events/definition_loader.rb +199 -0
  518. data/lib/tasker/events/event_payload_builder.rb +461 -0
  519. data/lib/tasker/events/publisher.rb +149 -0
  520. data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
  521. data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
  522. data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
  523. data/lib/tasker/events/subscription_loader.rb +161 -0
  524. data/lib/tasker/events.rb +37 -0
  525. data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
  526. data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
  527. data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
  528. data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
  529. data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
  530. data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
  531. data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
  532. data/lib/tasker/functions/function_wrapper.rb +42 -0
  533. data/lib/tasker/functions.rb +12 -0
  534. data/lib/tasker/handler_factory.rb +322 -0
  535. data/lib/tasker/health/readiness_checker.rb +186 -0
  536. data/lib/tasker/health/status_checker.rb +203 -0
  537. data/lib/tasker/identity_strategy.rb +38 -0
  538. data/lib/tasker/logging/correlation_id_generator.rb +120 -0
  539. data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
  540. data/lib/tasker/orchestration/connection_builder.rb +122 -0
  541. data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
  542. data/lib/tasker/orchestration/coordinator.rb +119 -0
  543. data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
  544. data/lib/tasker/orchestration/plugin_integration.rb +124 -0
  545. data/lib/tasker/orchestration/response_processor.rb +168 -0
  546. data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
  547. data/lib/tasker/orchestration/step_executor.rb +941 -0
  548. data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
  549. data/lib/tasker/orchestration/task_finalizer.rb +564 -0
  550. data/lib/tasker/orchestration/task_initializer.rb +140 -0
  551. data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
  552. data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
  553. data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
  554. data/lib/tasker/orchestration.rb +45 -0
  555. data/lib/tasker/railtie.rb +9 -0
  556. data/lib/tasker/registry/base_registry.rb +177 -0
  557. data/lib/tasker/registry/event_publisher.rb +91 -0
  558. data/lib/tasker/registry/interface_validator.rb +140 -0
  559. data/lib/tasker/registry/statistics_collector.rb +381 -0
  560. data/lib/tasker/registry/subscriber_registry.rb +285 -0
  561. data/lib/tasker/registry.rb +22 -0
  562. data/lib/tasker/state_machine/step_state_machine.rb +508 -0
  563. data/lib/tasker/state_machine/task_state_machine.rb +192 -0
  564. data/lib/tasker/state_machine.rb +83 -0
  565. data/lib/tasker/step_handler/api.rb +410 -0
  566. data/lib/tasker/step_handler/base.rb +206 -0
  567. data/lib/tasker/task_builder.rb +432 -0
  568. data/lib/tasker/task_handler/class_methods.rb +324 -0
  569. data/lib/tasker/task_handler/instance_methods.rb +293 -0
  570. data/lib/tasker/task_handler/step_group.rb +182 -0
  571. data/lib/tasker/task_handler.rb +43 -0
  572. data/lib/tasker/telemetry/event_mapping.rb +126 -0
  573. data/lib/tasker/telemetry/event_router.rb +318 -0
  574. data/lib/tasker/telemetry/events/export_events.rb +38 -0
  575. data/lib/tasker/telemetry/export_coordinator.rb +497 -0
  576. data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
  577. data/lib/tasker/telemetry/log_backend.rb +224 -0
  578. data/lib/tasker/telemetry/metric_types.rb +368 -0
  579. data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
  580. data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
  581. data/lib/tasker/telemetry/plugin_registry.rb +333 -0
  582. data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
  583. data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
  584. data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
  585. data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
  586. data/lib/tasker/telemetry/trace_backend.rb +186 -0
  587. data/lib/tasker/telemetry.rb +59 -0
  588. data/lib/tasker/types/auth_config.rb +81 -0
  589. data/lib/tasker/types/backoff_config.rb +142 -0
  590. data/lib/tasker/types/cache_config.rb +257 -0
  591. data/lib/tasker/types/database_config.rb +39 -0
  592. data/lib/tasker/types/dependency_graph.rb +225 -0
  593. data/lib/tasker/types/dependency_graph_config.rb +149 -0
  594. data/lib/tasker/types/engine_config.rb +131 -0
  595. data/lib/tasker/types/execution_config.rb +289 -0
  596. data/lib/tasker/types/health_config.rb +84 -0
  597. data/lib/tasker/types/step_sequence.rb +24 -0
  598. data/lib/tasker/types/step_template.rb +63 -0
  599. data/lib/tasker/types/task_request.rb +60 -0
  600. data/lib/tasker/types/telemetry_config.rb +273 -0
  601. data/lib/tasker/types.rb +64 -0
  602. data/lib/tasker/version.rb +7 -0
  603. data/lib/tasker.rb +82 -0
  604. data/lib/tasks/tasker_tasks.rake +302 -0
  605. metadata +958 -0
@@ -0,0 +1,1227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent-ruby'
4
+ require 'socket'
5
+ require_relative 'metric_types'
6
+
7
+ module Tasker
8
+ module Telemetry
9
+ # MetricsBackend provides thread-safe, high-performance native metrics collection
10
+ #
11
+ # This class implements Tasker's core metrics storage system with thread-safe operations,
12
+ # automatic EventRouter integration, and zero-overhead metric collection. It follows
13
+ # the singleton pattern consistent with HandlerFactory and Events::Publisher.
14
+ #
15
+ # **Phase 4.2.2.3 Enhancement: Hybrid Rails Cache Architecture**
16
+ # The backend now supports cache-agnostic dual storage combining in-memory performance
17
+ # with Rails.cache persistence and cross-container coordination.
18
+ #
19
+ # The backend supports three core metric types:
20
+ # - Counter: Monotonically increasing values (requests, errors, completions)
21
+ # - Gauge: Values that can go up/down (active connections, queue depth)
22
+ # - Histogram: Statistical distributions (latencies, sizes, durations)
23
+ #
24
+ # **Cache Store Compatibility:**
25
+ # - Redis/Memcached: Full coordination with atomic operations and locking
26
+ # - File/Memory stores: Local-only mode with clear degradation messaging
27
+ # - Automatic feature detection with appropriate strategy selection
28
+ #
29
+ # @example Basic usage
30
+ # backend = MetricsBackend.instance
31
+ # backend.counter('api_requests_total').increment
32
+ # backend.gauge('active_tasks').set(42)
33
+ # backend.histogram('task_duration_seconds').observe(1.45)
34
+ #
35
+ # @example EventRouter integration
36
+ # # Automatic metric collection based on event routing
37
+ # backend.handle_event('task.completed', { duration: 2.1, status: 'success' })
38
+ #
39
+ # @example Cache synchronization
40
+ # backend.sync_to_cache! # Periodic background sync
41
+ # backend.export_distributed_metrics # Cross-container export
42
+ #
43
+ class MetricsBackend
44
+ include Singleton
45
+
46
+ # Core metric registry storing all active metrics
47
+ # Using ConcurrentHash for thread-safe operations without locks
48
+ # @return [Concurrent::Hash] Thread-safe metric storage
49
+ attr_reader :metrics
50
+
51
+ # EventRouter instance for intelligent event routing
52
+ # @return [EventRouter] The configured event router
53
+ attr_reader :event_router
54
+
55
+ # Backend creation timestamp for monitoring
56
+ # @return [Time] When this backend was initialized
57
+ attr_reader :created_at
58
+
59
+ # Cache capabilities detected at initialization
60
+ # @return [Hash] Detected Rails.cache capabilities
61
+ attr_reader :cache_capabilities
62
+
63
+ # Selected sync strategy based on cache capabilities
64
+ # @return [Symbol] One of :distributed_atomic, :distributed_basic, :local_only
65
+ attr_reader :sync_strategy
66
+
67
+ # Unique instance identifier for distributed coordination
68
+ # @return [String] Hostname-PID identifier for this instance
69
+ attr_reader :instance_id
70
+
71
+ # Configuration for cache synchronization
72
+ # @return [Hash] Sync configuration parameters
73
+ attr_reader :sync_config
74
+
75
+ # Cache strategy for this instance
76
+ # @return [Tasker::CacheStrategy] The cache strategy for this instance
77
+ attr_reader :cache_strategy
78
+
79
+ # Initialize the metrics backend
80
+ #
81
+ # Sets up thread-safe storage, integrates with EventRouter, and configures
82
+ # cache capabilities for hybrid architecture.
83
+ # Called automatically via singleton pattern.
84
+ def initialize
85
+ @metrics = Concurrent::Hash.new
86
+ @event_router = nil
87
+ @local_buffer = []
88
+ @last_sync = Time.current
89
+ @created_at = Time.current.freeze
90
+
91
+ # Thread-safe metric creation lock
92
+ @metric_creation_lock = Mutex.new
93
+
94
+ # Use unified cache strategy for capability detection
95
+ @cache_strategy = Tasker::CacheStrategy.detect
96
+ @instance_id = @cache_strategy.instance_id
97
+ @sync_strategy = @cache_strategy.coordination_mode
98
+
99
+ # Extract capabilities for backward compatibility
100
+ @cache_capabilities = @cache_strategy.export_capabilities
101
+ @sync_config = configure_sync_parameters
102
+
103
+ log_cache_strategy_selection
104
+ end
105
+
106
+ # Register the EventRouter for intelligent routing
107
+ #
108
+ # This method is called by EventRouter during configuration to enable
109
+ # automatic metric collection based on routing decisions.
110
+ #
111
+ # @param router [EventRouter] The configured event router
112
+ # @return [EventRouter] The registered router
113
+ def register_event_router(router)
114
+ @event_router = router
115
+ end
116
+
117
+ # Get or create a counter metric
118
+ #
119
+ # Counters are thread-safe and support only increment operations.
120
+ # They're ideal for counting events, requests, errors, etc.
121
+ #
122
+ # @param name [String] The metric name
123
+ # @param labels [Hash] Optional dimensional labels
124
+ # @return [MetricTypes::Counter] The counter instance
125
+ # @raise [ArgumentError] If name is invalid
126
+ #
127
+ # @example
128
+ # counter = backend.counter('http_requests_total', endpoint: '/api/tasks')
129
+ # counter.increment(5)
130
+ def counter(name, **labels)
131
+ get_or_create_metric(name, labels, :counter) do
132
+ MetricTypes::Counter.new(name, labels: labels)
133
+ end
134
+ end
135
+
136
+ # Get or create a gauge metric
137
+ #
138
+ # Gauges are thread-safe and support set, increment, and decrement operations.
139
+ # They're ideal for values that fluctuate like connections, queue depth, etc.
140
+ #
141
+ # @param name [String] The metric name
142
+ # @param labels [Hash] Optional dimensional labels
143
+ # @return [MetricTypes::Gauge] The gauge instance
144
+ # @raise [ArgumentError] If name is invalid
145
+ #
146
+ # @example
147
+ # gauge = backend.gauge('active_connections', service: 'api')
148
+ # gauge.set(100)
149
+ # gauge.increment(5)
150
+ def gauge(name, **labels)
151
+ get_or_create_metric(name, labels, :gauge) do
152
+ MetricTypes::Gauge.new(name, labels: labels)
153
+ end
154
+ end
155
+
156
+ # Get or create a histogram metric
157
+ #
158
+ # Histograms are thread-safe and provide statistical analysis of observed values.
159
+ # They're ideal for measuring durations, sizes, and distributions.
160
+ #
161
+ # @param name [String] The metric name
162
+ # @param labels [Hash] Optional dimensional labels
163
+ # @param buckets [Array<Numeric>] Optional custom bucket boundaries
164
+ # @return [MetricTypes::Histogram] The histogram instance
165
+ # @raise [ArgumentError] If name is invalid or buckets are malformed
166
+ #
167
+ # @example
168
+ # histogram = backend.histogram('request_duration_seconds', method: 'POST')
169
+ # histogram.observe(0.145)
170
+ def histogram(name, buckets: nil, **labels)
171
+ get_or_create_metric(name, labels, :histogram) do
172
+ if buckets
173
+ MetricTypes::Histogram.new(name, labels: labels, buckets: buckets)
174
+ else
175
+ MetricTypes::Histogram.new(name, labels: labels)
176
+ end
177
+ end
178
+ end
179
+
180
+ # Synchronize in-memory metrics to Rails.cache using detected strategy
181
+ #
182
+ # This method implements the core cache synchronization logic that adapts
183
+ # to the available Rails.cache store capabilities.
184
+ #
185
+ # @return [Hash] Sync result with success status and metrics count
186
+ #
187
+ # @example Periodic sync (typically called from background job)
188
+ # result = backend.sync_to_cache!
189
+ # # => { success: true, synced_metrics: 42, strategy: :distributed_atomic }
190
+ def sync_to_cache!
191
+ return { success: false, error: 'Rails.cache not available' } unless rails_cache_available?
192
+
193
+ start_time = Time.current
194
+
195
+ case @sync_strategy
196
+ when :distributed_atomic
197
+ result = sync_with_atomic_operations
198
+ when :distributed_basic
199
+ result = sync_with_read_modify_write
200
+ when :local_only
201
+ result = sync_to_local_cache
202
+ else
203
+ return { success: false, error: "Unknown sync strategy: #{@sync_strategy}" }
204
+ end
205
+
206
+ final_result = result.merge(
207
+ duration: Time.current - start_time,
208
+ timestamp: Time.current.iso8601,
209
+ instance_id: @instance_id
210
+ )
211
+
212
+ # Coordinate with export system
213
+ coordinate_cache_sync(final_result)
214
+
215
+ final_result
216
+ rescue StandardError => e
217
+ log_sync_error(e)
218
+ { success: false, error: e.message, timestamp: Time.current.iso8601 }
219
+ end
220
+
221
+ # Export metrics with distributed coordination when supported
222
+ #
223
+ # This method aggregates metrics across containers when possible,
224
+ # falls back to local export for limited cache stores.
225
+ #
226
+ # @param include_instances [Boolean] Whether to include per-instance metrics
227
+ # @return [Hash] Export data with aggregated metrics when available
228
+ def export_distributed_metrics(include_instances: false)
229
+ case @sync_strategy
230
+ when :distributed_atomic, :distributed_basic
231
+ aggregate_from_distributed_cache(include_instances: include_instances)
232
+ when :local_only
233
+ export_local_metrics_with_warning
234
+ else
235
+ export # Fallback to standard local export
236
+ end
237
+ end
238
+
239
+ # Handle an event from EventRouter and collect appropriate metrics
240
+ #
241
+ # This method is called by EventRouter when an event should be routed to
242
+ # the metrics backend. It automatically creates and updates metrics based
243
+ # on event type and payload.
244
+ #
245
+ # @param event_name [String] The lifecycle event name
246
+ # @param payload [Hash] Event payload with metric data
247
+ # @return [Boolean] True if metrics were collected successfully
248
+ #
249
+ # @example Automatic usage via EventRouter
250
+ # # EventRouter calls this automatically:
251
+ # backend.handle_event('task.completed', {
252
+ # task_id: '123',
253
+ # duration: 2.45,
254
+ # status: 'success'
255
+ # })
256
+ #
257
+ def handle_event(event_name, payload = {})
258
+ return false unless payload.is_a?(Hash)
259
+
260
+ case event_name
261
+ when /\.started$/
262
+ # Task/Step started events -> increment counter
263
+ counter("#{extract_entity_type(event_name)}_started_total", **extract_labels(payload)).increment
264
+
265
+ when /\.completed$/
266
+ # Task/Step completed events -> counter + duration histogram
267
+ entity_type = extract_entity_type(event_name)
268
+ labels = extract_labels(payload)
269
+
270
+ counter("#{entity_type}_completed_total", **labels).increment
271
+
272
+ if (duration = extract_duration(payload))
273
+ histogram("#{entity_type}_duration_seconds", **labels).observe(duration)
274
+ end
275
+
276
+ when /\.failed$/
277
+ # Task/Step failed events -> error counter + duration histogram
278
+ entity_type = extract_entity_type(event_name)
279
+ labels = extract_labels(payload)
280
+
281
+ counter("#{entity_type}_failed_total", **labels).increment
282
+
283
+ if (duration = extract_duration(payload))
284
+ histogram("#{entity_type}_duration_seconds", **labels).observe(duration)
285
+ end
286
+
287
+ when /\.cancelled$/
288
+ # Task/Step cancelled events -> cancellation counter
289
+ counter("#{extract_entity_type(event_name)}_cancelled_total", **extract_labels(payload)).increment
290
+
291
+ when /workflow\.iteration/
292
+ # Workflow iteration events -> gauge for active tasks
293
+ gauge('workflow_active_tasks').set(payload[:active_task_count]) if payload[:active_task_count]
294
+
295
+ if payload[:iteration_duration]
296
+ histogram('workflow_iteration_duration_seconds').observe(payload[:iteration_duration])
297
+ end
298
+
299
+ when /system\.health/
300
+ # System health events -> health gauges
301
+ gauge('system_healthy_tasks').set(payload[:healthy_task_count]) if payload[:healthy_task_count]
302
+
303
+ gauge('system_failed_tasks').set(payload[:failed_task_count]) if payload[:failed_task_count]
304
+ end
305
+
306
+ true
307
+ rescue StandardError => e
308
+ # Fail gracefully - metrics collection should never break the application
309
+ warn "MetricsBackend failed to handle event #{event_name}: #{e.message}"
310
+ false
311
+ end
312
+
313
+ # Get all registered metrics
314
+ #
315
+ # Returns a thread-safe snapshot of all current metrics for export
316
+ # to monitoring systems like Prometheus.
317
+ #
318
+ # @return [Hash] All metrics keyed by metric key
319
+ def all_metrics
320
+ @metrics.each_with_object({}) do |(key, metric), result|
321
+ result[key] = metric
322
+ end
323
+ end
324
+
325
+ # Export all metrics to a format suitable for monitoring systems
326
+ #
327
+ # Provides a comprehensive export of all metric data including
328
+ # metadata, current values, and statistical information.
329
+ #
330
+ # @return [Hash] Comprehensive metric export data
331
+ def export
332
+ {
333
+ timestamp: Time.current,
334
+ backend_created_at: @created_at,
335
+ total_metrics: @metrics.size,
336
+ metrics: all_metrics.transform_values(&:to_h)
337
+ }
338
+ end
339
+
340
+ # Get summary statistics about the metrics backend
341
+ #
342
+ # @return [Hash] Backend statistics and health information
343
+ def stats
344
+ metric_types = all_metrics.values.group_by { |m| m.to_h[:type] }
345
+
346
+ {
347
+ total_metrics: @metrics.size,
348
+ counter_metrics: metric_types[:counter]&.size || 0,
349
+ gauge_metrics: metric_types[:gauge]&.size || 0,
350
+ histogram_metrics: metric_types[:histogram]&.size || 0,
351
+ backend_uptime: Time.current - @created_at,
352
+ created_at: @created_at
353
+ }
354
+ end
355
+
356
+ # Clear all metrics (primarily for testing)
357
+ #
358
+ # @return [Integer] Number of metrics cleared
359
+ def clear!
360
+ cleared_count = @metrics.size
361
+ @metrics.clear
362
+ cleared_count
363
+ end
364
+
365
+ private
366
+
367
+ # Get or create a metric with thread-safe operations
368
+ #
369
+ # Uses double-checked locking pattern to minimize lock contention
370
+ # while ensuring thread safety during metric creation.
371
+ #
372
+ # @param name [String] Metric name
373
+ # @param labels [Hash] Metric labels
374
+ # @param type [Symbol] Metric type (:counter, :gauge, :histogram)
375
+ # @yield Block that creates the metric instance
376
+ # @return [Object] The metric instance
377
+ def get_or_create_metric(name, labels, _type)
378
+ raise ArgumentError, 'Metric name cannot be nil or empty' if name.nil? || name.to_s.strip.empty?
379
+
380
+ metric_key = build_metric_key(name, labels)
381
+
382
+ # Fast path: metric already exists
383
+ existing_metric = @metrics[metric_key]
384
+ return existing_metric if existing_metric
385
+
386
+ # Slow path: create new metric with lock
387
+ @metric_creation_lock.synchronize do
388
+ # Double-check pattern: another thread might have created it
389
+ existing_metric = @metrics[metric_key]
390
+ return existing_metric if existing_metric
391
+
392
+ # Create and store the new metric
393
+ new_metric = yield
394
+ @metrics[metric_key] = new_metric
395
+ new_metric
396
+ end
397
+ end
398
+
399
+ # Build a unique key for metric identification
400
+ #
401
+ # Creates a deterministic key that combines metric name and labels
402
+ # for efficient storage and retrieval.
403
+ #
404
+ # @param name [String] Metric name
405
+ # @param labels [Hash] Metric labels
406
+ # @return [String] Unique metric key
407
+ def build_metric_key(name, labels)
408
+ if labels.empty?
409
+ name.to_s
410
+ else
411
+ # Sort labels for deterministic key generation
412
+ sorted_labels = labels.sort.to_h
413
+ "#{name}#{sorted_labels.inspect}"
414
+ end
415
+ end
416
+
417
+ # Extract entity type from event name (task, step, workflow, etc.)
418
+ #
419
+ # @param event_name [String] The lifecycle event name
420
+ # @return [String] The entity type
421
+ def extract_entity_type(event_name)
422
+ # Examples: 'task.completed' -> 'task', 'step.failed' -> 'step'
423
+ event_name.split('.').first || 'unknown'
424
+ end
425
+
426
+ # Extract duration value from event payload
427
+ #
428
+ # @param payload [Hash] Event payload
429
+ # @return [Numeric, nil] Duration in seconds, or nil if not present
430
+ def extract_duration(payload)
431
+ duration = payload[:duration] || payload['duration']
432
+ return nil unless duration.is_a?(Numeric)
433
+
434
+ duration
435
+ end
436
+
437
+ # Extract labels from event payload for dimensional metrics
438
+ #
439
+ # @param payload [Hash] Event payload
440
+ # @return [Hash] Extracted labels for metric dimensions
441
+ def extract_labels(payload)
442
+ labels = {}
443
+
444
+ # Common label extractions
445
+ labels[:status] = payload[:status] || payload['status'] if payload[:status] || payload['status']
446
+ if payload[:handler_class] || payload['handler_class']
447
+ labels[:handler] =
448
+ payload[:handler_class] || payload['handler_class']
449
+ end
450
+ labels[:namespace] = payload[:namespace] || payload['namespace'] if payload[:namespace] || payload['namespace']
451
+
452
+ # Filter out nil values and ensure string keys
453
+ labels.compact.transform_keys(&:to_s)
454
+ end
455
+
456
+ # Generate unique instance identifier for distributed coordination
457
+ #
458
+ # @return [String] Hostname-PID identifier
459
+ def generate_instance_id
460
+ hostname = begin
461
+ ENV['HOSTNAME'] || Socket.gethostname
462
+ rescue StandardError
463
+ 'unknown'
464
+ end
465
+ "#{hostname}-#{Process.pid}"
466
+ end
467
+
468
+ # Configure sync parameters based on strategy and telemetry config
469
+ #
470
+ # @return [Hash] Sync configuration parameters
471
+ def configure_sync_parameters
472
+ base_config = {
473
+ retention_window: 5.minutes,
474
+ export_safety_margin: 1.minute,
475
+ sync_interval: 30.seconds
476
+ }
477
+
478
+ # Override with telemetry configuration if available
479
+ if defined?(Tasker.configuration) && Tasker.configuration.telemetry
480
+ telemetry_config = Tasker.configuration.telemetry
481
+ base_config[:retention_window] = (telemetry_config.metrics_retention_hours || 1).hours
482
+ end
483
+
484
+ base_config[:export_interval] = base_config[:retention_window] - base_config[:export_safety_margin]
485
+ base_config
486
+ end
487
+
488
+ # Synchronize metrics using atomic operations (Redis/Memcached)
489
+ # Synchronize metrics using atomic operations (Redis/advanced stores)
490
+ #
491
+ # **Phase 4.2.2.3.2**: Enhanced atomic synchronization with batch operations,
492
+ # conflict resolution, and performance optimizations for distributed coordination.
493
+ #
494
+ # @return [Hash] Detailed sync result with performance metrics
495
+ def sync_with_atomic_operations
496
+ execute_sync_operation(
497
+ strategy: :distributed_atomic,
498
+ stats_template: { counters: 0, gauges: 0, histograms: 0, conflicts: 0, batches: 0 },
499
+ logger: method(:log_atomic_sync_success)
500
+ ) do |grouped_metrics, sync_stats|
501
+ process_atomic_sync_by_type(grouped_metrics, sync_stats)
502
+ end
503
+ end
504
+
505
+ # Synchronize metrics using read-modify-write (basic distributed caches)
506
+ #
507
+ # **Phase 4.2.2.3.2**: Enhanced read-modify-write with retry logic,
508
+ # conflict detection, and optimistic concurrency control for safe updates.
509
+ #
510
+ # @return [Hash] Detailed sync result with retry statistics
511
+ def sync_with_read_modify_write
512
+ execute_sync_operation(
513
+ strategy: :distributed_basic,
514
+ stats_template: { counters: 0, gauges: 0, histograms: 0, retries: 0, conflicts: 0, batches: 0, failed: 0 },
515
+ logger: method(:log_rmw_sync_success)
516
+ ) do |grouped_metrics, sync_stats|
517
+ sync_stats.merge!(sync_with_optimistic_concurrency(grouped_metrics))
518
+ end
519
+ end
520
+
521
+ # Create local cache snapshot (memory/file stores)
522
+ #
523
+ # **Phase 4.2.2.3.2**: Enhanced local synchronization with versioned snapshots,
524
+ # efficient serialization, and proper state management for local-only deployment.
525
+ #
526
+ # @return [Hash] Detailed sync result with snapshot information
527
+ def sync_to_local_cache
528
+ execute_local_snapshot_sync
529
+ end
530
+
531
+ # Aggregate metrics from distributed cache
532
+ #
533
+ # @param include_instances [Boolean] Include per-instance breakdowns
534
+ # @return [Hash] Aggregated export data
535
+ def aggregate_from_distributed_cache(include_instances: false)
536
+ # Implementation for distributed aggregation
537
+ # This will be expanded in Phase 4.2.2.3.3
538
+
539
+ export_data = export
540
+ export_data[:distributed] = true
541
+ export_data[:sync_strategy] = @sync_strategy
542
+ export_data[:note] = 'Distributed aggregation - Phase 4.2.2.3.3'
543
+ export_data
544
+ end
545
+
546
+ # Export local metrics with cache limitation warning
547
+ #
548
+ # @return [Hash] Local export with warning
549
+ def export_local_metrics_with_warning
550
+ export_data = export
551
+ export_data[:distributed] = false
552
+ export_data[:sync_strategy] = @sync_strategy
553
+ export_data[:warning] = "Cache store doesn't support distribution - metrics are local-only"
554
+ export_data
555
+ end
556
+
557
+ # Build cache key for metric storage following Rails best practices
558
+ #
559
+ # Uses Rails-standard cache key patterns with proper namespacing
560
+ # and supports complex key structures as recommended in the Rails guide.
561
+ #
562
+ # @param metric_key [String] Internal metric key
563
+ # @return [Array] Rails.cache compatible structured key
564
+ def build_cache_key(metric_key)
565
+ # Use structured keys as recommended by Rails caching guide
566
+ # This allows Rails to handle namespacing, size limits, and transformations
567
+ [
568
+ 'tasker',
569
+ 'metrics',
570
+ @instance_id,
571
+ metric_key
572
+ ]
573
+ end
574
+
575
+ # Prepare export data for local cache storage
576
+ #
577
+ # @return [Hash] Serializable export data
578
+ def prepare_local_export_data
579
+ {
580
+ timestamp: Time.current.iso8601,
581
+ instance_id: @instance_id,
582
+ total_metrics: @metrics.size,
583
+ metrics: @metrics.transform_values(&:to_h),
584
+ cache_capabilities: @cache_capabilities
585
+ }
586
+ end
587
+
588
+ # Create default metric data for merging
589
+ #
590
+ # @param type [Symbol] Metric type
591
+ # @return [Hash] Default data structure
592
+ def default_metric_data(type)
593
+ case type
594
+ when :counter
595
+ { type: :counter, value: 0 }
596
+ when :gauge
597
+ { type: :gauge, value: 0 }
598
+ when :histogram
599
+ { type: :histogram, count: 0, sum: 0.0, buckets: {} }
600
+ else
601
+ {}
602
+ end
603
+ end
604
+
605
+ # Merge metric data for read-modify-write operations
606
+ #
607
+ # @param existing [Hash] Existing cached metric data
608
+ # @param current [Hash] Current in-memory metric data
609
+ # @return [Hash] Merged metric data
610
+ def merge_metric_data(existing, current)
611
+ return current unless existing.is_a?(Hash)
612
+
613
+ case current[:type]
614
+ when :counter
615
+ {
616
+ type: :counter,
617
+ value: (existing[:value] || 0) + current[:value],
618
+ labels: current[:labels]
619
+ }
620
+ when :gauge
621
+ # Gauges use most recent value (current instance wins)
622
+ current
623
+ when :histogram
624
+ # Histogram merging - sum the statistics
625
+ {
626
+ type: :histogram,
627
+ count: (existing[:count] || 0) + current[:count],
628
+ sum: (existing[:sum] || 0.0) + current[:sum],
629
+ buckets: merge_histogram_buckets(existing[:buckets] || {}, current[:buckets] || {}),
630
+ labels: current[:labels]
631
+ }
632
+ else
633
+ current
634
+ end
635
+ end
636
+
637
+ # Merge histogram bucket data
638
+ #
639
+ # @param existing_buckets [Hash] Existing bucket counts
640
+ # @param current_buckets [Hash] Current bucket counts
641
+ # @return [Hash] Merged bucket counts
642
+ def merge_histogram_buckets(existing_buckets, current_buckets)
643
+ all_buckets = (existing_buckets.keys + current_buckets.keys).uniq
644
+ all_buckets.index_with do |bucket|
645
+ (existing_buckets[bucket] || 0) + (current_buckets[bucket] || 0)
646
+ end
647
+ end
648
+
649
+ # **Phase 4.2.2.3.2 Supporting Methods**
650
+ # ====================================
651
+
652
+ # Execute a sync operation with common error handling and timing
653
+ #
654
+ # @param strategy [Symbol] Sync strategy identifier
655
+ # @param stats_template [Hash] Initial stats structure
656
+ # @param logger [Method] Logging method for success
657
+ # @yield [grouped_metrics, sync_stats] Block to execute sync logic
658
+ # @return [Hash] Standardized sync result
659
+ def execute_sync_operation(strategy:, stats_template:, logger:)
660
+ start_time = Time.current
661
+ sync_stats = stats_template.dup
662
+
663
+ begin
664
+ grouped_metrics = group_metrics_by_type
665
+ yield(grouped_metrics, sync_stats)
666
+
667
+ sync_duration = Time.current - start_time
668
+ total_synced = calculate_total_synced_metrics(sync_stats)
669
+
670
+ logger.call(sync_stats, sync_duration)
671
+
672
+ build_success_result(strategy, total_synced, sync_duration, sync_stats)
673
+ rescue StandardError => e
674
+ log_sync_error(e)
675
+ build_error_result(strategy, e, sync_stats)
676
+ end
677
+ end
678
+
679
+ # Process atomic sync operations by metric type
680
+ #
681
+ # @param grouped_metrics [Hash] Metrics grouped by type
682
+ # @param sync_stats [Hash] Statistics accumulator
683
+ def process_atomic_sync_by_type(grouped_metrics, sync_stats)
684
+ # Process counters with true atomic operations
685
+ sync_stats.merge!(sync_atomic_counters(grouped_metrics[:counter])) if grouped_metrics[:counter].any?
686
+
687
+ # Process gauges with last-writer-wins strategy
688
+ sync_stats.merge!(sync_distributed_gauges(grouped_metrics[:gauge])) if grouped_metrics[:gauge].any?
689
+
690
+ # Process histograms with atomic aggregation
691
+ return unless grouped_metrics[:histogram].any?
692
+
693
+ sync_stats.merge!(sync_distributed_histograms(grouped_metrics[:histogram]))
694
+ end
695
+
696
+ # Calculate total synced metrics from stats
697
+ #
698
+ # @param sync_stats [Hash] Statistics hash
699
+ # @return [Integer] Total synced metrics count
700
+ def calculate_total_synced_metrics(sync_stats)
701
+ sync_stats.values_at(:counters, :gauges, :histograms).compact.sum
702
+ end
703
+
704
+ # Build successful sync result
705
+ #
706
+ # @param strategy [Symbol] Sync strategy
707
+ # @param total_synced [Integer] Total metrics synced
708
+ # @param sync_duration [Float] Duration in seconds
709
+ # @param sync_stats [Hash] Performance statistics
710
+ # @return [Hash] Success result
711
+ def build_success_result(strategy, total_synced, sync_duration, sync_stats)
712
+ {
713
+ success: true,
714
+ strategy: strategy,
715
+ synced_metrics: total_synced,
716
+ duration_ms: (sync_duration * 1000).round(2),
717
+ performance: sync_stats,
718
+ timestamp: Time.current.iso8601
719
+ }
720
+ end
721
+
722
+ # Build error sync result
723
+ #
724
+ # @param strategy [Symbol] Sync strategy
725
+ # @param error [Exception] Error that occurred
726
+ # @param sync_stats [Hash] Partial statistics
727
+ # @return [Hash] Error result
728
+ def build_error_result(strategy, error, sync_stats)
729
+ {
730
+ success: false,
731
+ strategy: strategy,
732
+ error: error.message,
733
+ partial_results: sync_stats,
734
+ timestamp: Time.current.iso8601
735
+ }
736
+ end
737
+
738
+ # Execute local snapshot synchronization with proper error handling
739
+ #
740
+ # @return [Hash] Detailed sync result with snapshot information
741
+ def execute_local_snapshot_sync
742
+ start_time = Time.current
743
+ sync_stats = { snapshots: 0, metrics_serialized: 0, size_bytes: 0 }
744
+
745
+ begin
746
+ snapshot_data = create_versioned_snapshot
747
+ snapshot_keys = build_snapshot_keys
748
+ write_result = write_snapshot_data(snapshot_data, snapshot_keys, sync_stats)
749
+
750
+ sync_duration = Time.current - start_time
751
+ log_local_sync_success(sync_stats, sync_duration)
752
+
753
+ build_local_sync_result(write_result, sync_duration, sync_stats, snapshot_keys[:primary])
754
+ rescue StandardError => e
755
+ log_sync_error(e)
756
+ build_error_result(:local_only, e, {})
757
+ end
758
+ end
759
+
760
+ # Build snapshot cache keys for primary and history storage
761
+ #
762
+ # @return [Hash] Hash containing primary and timestamped keys
763
+ def build_snapshot_keys
764
+ {
765
+ primary: ['tasker', 'metrics', 'snapshot', @instance_id],
766
+ timestamped: ['tasker', 'metrics', 'history', @instance_id, Time.current.to_i]
767
+ }
768
+ end
769
+
770
+ # Write snapshot data to cache with redundancy
771
+ #
772
+ # @param snapshot_data [Hash] Snapshot data to write
773
+ # @param snapshot_keys [Hash] Cache keys for storage
774
+ # @param sync_stats [Hash] Statistics accumulator
775
+ # @return [Boolean] Success status of primary write
776
+ def write_snapshot_data(snapshot_data, snapshot_keys, sync_stats)
777
+ # Write primary snapshot with compression awareness
778
+ write_result = Rails.cache.write(snapshot_keys[:primary], snapshot_data,
779
+ expires_in: @sync_config[:retention_window])
780
+
781
+ if write_result
782
+ update_snapshot_stats(sync_stats, snapshot_data)
783
+ write_history_snapshot(snapshot_data, snapshot_keys[:timestamped], sync_stats)
784
+ end
785
+
786
+ write_result
787
+ end
788
+
789
+ # Update statistics after successful snapshot write
790
+ #
791
+ # @param sync_stats [Hash] Statistics accumulator
792
+ # @param snapshot_data [Hash] Snapshot data
793
+ def update_snapshot_stats(sync_stats, snapshot_data)
794
+ sync_stats[:snapshots] += 1
795
+ sync_stats[:metrics_serialized] = @metrics.size
796
+ sync_stats[:size_bytes] = estimate_snapshot_size(snapshot_data)
797
+ end
798
+
799
+ # Write optional history snapshot for redundancy
800
+ #
801
+ # @param snapshot_data [Hash] Snapshot data
802
+ # @param timestamped_key [Array] Timestamped cache key
803
+ # @param sync_stats [Hash] Statistics accumulator
804
+ def write_history_snapshot(snapshot_data, timestamped_key, sync_stats)
805
+ Rails.cache.write(timestamped_key, snapshot_data,
806
+ expires_in: @sync_config[:retention_window] / 2)
807
+ sync_stats[:snapshots] += 1
808
+ rescue StandardError
809
+ # History snapshot failure is non-critical
810
+ end
811
+
812
+ # Build result for local sync operation
813
+ #
814
+ # @param write_result [Boolean] Primary write success
815
+ # @param sync_duration [Float] Duration in seconds
816
+ # @param sync_stats [Hash] Performance statistics
817
+ # @param primary_key [Array] Primary snapshot key
818
+ # @return [Hash] Local sync result
819
+ def build_local_sync_result(write_result, sync_duration, sync_stats, primary_key)
820
+ {
821
+ success: write_result,
822
+ strategy: :local_only,
823
+ synced_metrics: @metrics.size,
824
+ duration_ms: (sync_duration * 1000).round(2),
825
+ performance: sync_stats,
826
+ snapshot_key: primary_key,
827
+ timestamp: Time.current.iso8601
828
+ }
829
+ end
830
+
831
+ # Group metrics by type for optimized batch processing
832
+ #
833
+ # @return [Hash] Metrics grouped by type (:counter, :gauge, :histogram)
834
+ def group_metrics_by_type
835
+ result = { counter: [], gauge: [], histogram: [] }
836
+
837
+ @metrics.each do |key, metric|
838
+ metric_data = metric.to_h
839
+ type = metric_data[:type]
840
+ result[type] << [key, metric_data] if result.key?(type)
841
+ end
842
+
843
+ result
844
+ end
845
+
846
+ # Synchronize counters using atomic operations
847
+ #
848
+ # @param counter_metrics [Array] Array of [key, metric_data] pairs
849
+ # @return [Hash] Sync statistics
850
+ def sync_atomic_counters(counter_metrics)
851
+ stats = { counters: 0, conflicts: 0 }
852
+
853
+ counter_metrics.each do |key, metric_data|
854
+ cache_key = build_cache_key(key)
855
+
856
+ begin
857
+ # Use atomic increment for true cross-container coordination
858
+ Rails.cache.increment(cache_key, metric_data[:value],
859
+ expires_in: @sync_config[:retention_window],
860
+ initial: 0)
861
+ stats[:counters] += 1
862
+ rescue StandardError
863
+ stats[:conflicts] += 1
864
+ # Fallback to regular write for non-atomic stores
865
+ Rails.cache.write(cache_key, metric_data,
866
+ expires_in: @sync_config[:retention_window])
867
+ end
868
+ end
869
+
870
+ stats
871
+ end
872
+
873
+ # Synchronize gauges with last-writer-wins strategy
874
+ #
875
+ # @param gauge_metrics [Array] Array of [key, metric_data] pairs
876
+ # @return [Hash] Sync statistics
877
+ def sync_distributed_gauges(gauge_metrics)
878
+ stats = { gauges: 0, conflicts: 0 }
879
+
880
+ gauge_metrics.each do |key, metric_data|
881
+ cache_key = build_cache_key(key)
882
+
883
+ # Add timestamp for conflict resolution
884
+ enhanced_data = metric_data.merge(
885
+ last_update: Time.current.to_f,
886
+ instance_id: @instance_id
887
+ )
888
+
889
+ begin
890
+ Rails.cache.write(cache_key, enhanced_data,
891
+ expires_in: @sync_config[:retention_window])
892
+ stats[:gauges] += 1
893
+ rescue StandardError
894
+ stats[:conflicts] += 1
895
+ end
896
+ end
897
+
898
+ stats
899
+ end
900
+
901
+ # Synchronize histograms with atomic aggregation
902
+ #
903
+ # @param histogram_metrics [Array] Array of [key, metric_data] pairs
904
+ # @return [Hash] Sync statistics
905
+ def sync_distributed_histograms(histogram_metrics)
906
+ stats = { histograms: 0, conflicts: 0 }
907
+
908
+ histogram_metrics.each do |key, metric_data|
909
+ cache_key = build_cache_key(key)
910
+
911
+ # Try atomic aggregation first, fallback to merge
912
+ if attempt_atomic_histogram_update(cache_key, metric_data)
913
+ stats[:histograms] += 1
914
+ else
915
+ # Fallback to read-modify-write for histograms
916
+ existing = Rails.cache.read(cache_key) || default_metric_data(:histogram)
917
+ merged = merge_metric_data(existing, metric_data)
918
+ Rails.cache.write(cache_key, merged, expires_in: @sync_config[:retention_window])
919
+ stats[:histograms] += 1
920
+ stats[:conflicts] += 1
921
+ end
922
+ end
923
+
924
+ stats
925
+ end
926
+
927
+ # Attempt atomic histogram update
928
+ #
929
+ # @param cache_key [Array] Structured cache key
930
+ # @param metric_data [Hash] Histogram metric data
931
+ # @return [Boolean] Success status
932
+ def attempt_atomic_histogram_update(cache_key, metric_data)
933
+ # For stores that support atomic operations, try to update histogram components
934
+ if @cache_capabilities[:atomic_increment]
935
+ count_key = cache_key + ['count']
936
+ sum_key = cache_key + ['sum']
937
+
938
+ begin
939
+ Rails.cache.increment(count_key, metric_data[:count], initial: 0,
940
+ expires_in: @sync_config[:retention_window])
941
+ Rails.cache.increment(sum_key, metric_data[:sum], initial: 0.0,
942
+ expires_in: @sync_config[:retention_window])
943
+
944
+ # Update buckets individually
945
+ metric_data[:buckets]&.each do |bucket, count|
946
+ bucket_key = cache_key + ['buckets', bucket.to_s]
947
+ Rails.cache.increment(bucket_key, count, initial: 0,
948
+ expires_in: @sync_config[:retention_window])
949
+ end
950
+
951
+ return true
952
+ rescue StandardError
953
+ return false
954
+ end
955
+ end
956
+ false
957
+ end
958
+
959
+ # Synchronize with optimistic concurrency control
960
+ #
961
+ # @param grouped_metrics [Hash] Metrics grouped by type
962
+ # @return [Hash] Combined sync statistics
963
+ def sync_with_optimistic_concurrency(grouped_metrics)
964
+ stats = initialize_concurrency_stats
965
+
966
+ grouped_metrics.each do |type, metrics|
967
+ process_metrics_with_concurrency_control(type, metrics, stats)
968
+ end
969
+
970
+ stats
971
+ end
972
+
973
+ # Initialize statistics for concurrency control
974
+ #
975
+ # @return [Hash] Initial statistics structure
976
+ def initialize_concurrency_stats
977
+ { counters: 0, gauges: 0, histograms: 0, retries: 0, conflicts: 0, failed: 0 }
978
+ end
979
+
980
+ # Process metrics of a specific type with concurrency control
981
+ #
982
+ # @param type [Symbol] Metric type (:counter, :gauge, :histogram)
983
+ # @param metrics [Array] Array of [key, metric_data] pairs
984
+ # @param stats [Hash] Statistics accumulator
985
+ def process_metrics_with_concurrency_control(type, metrics, stats)
986
+ metrics.each do |key, metric_data|
987
+ success = sync_single_metric_with_retry(type, key, metric_data, stats)
988
+ stats[:failed] += 1 unless success
989
+ end
990
+ end
991
+
992
+ # Sync a single metric with retry logic
993
+ #
994
+ # @param type [Symbol] Metric type
995
+ # @param key [String] Metric key
996
+ # @param metric_data [Hash] Metric data
997
+ # @param stats [Hash] Statistics accumulator
998
+ # @return [Boolean] Success status
999
+ def sync_single_metric_with_retry(type, key, metric_data, stats)
1000
+ cache_key = build_cache_key(key)
1001
+ max_retries = 3
1002
+ retry_count = 0
1003
+
1004
+ while retry_count < max_retries
1005
+ success = attempt_optimistic_write(type, cache_key, metric_data, stats)
1006
+ return true if success
1007
+
1008
+ retry_count += 1
1009
+ stats[:retries] += 1
1010
+ sleep(calculate_backoff_delay(retry_count))
1011
+ end
1012
+
1013
+ false
1014
+ end
1015
+
1016
+ # Attempt a single optimistic write operation
1017
+ #
1018
+ # @param type [Symbol] Metric type
1019
+ # @param cache_key [Array] Cache key
1020
+ # @param metric_data [Hash] Metric data
1021
+ # @param stats [Hash] Statistics accumulator
1022
+ # @return [Boolean] Success status
1023
+ def attempt_optimistic_write(type, cache_key, metric_data, stats)
1024
+ # Read current value
1025
+ existing = Rails.cache.read(cache_key) || default_metric_data(type)
1026
+
1027
+ # Merge with current data
1028
+ merged = merge_metric_data(existing, metric_data)
1029
+
1030
+ # Attempt to write (this could fail if another process updates)
1031
+ if Rails.cache.write(cache_key, merged, expires_in: @sync_config[:retention_window])
1032
+ increment_metric_type_stats(type, stats)
1033
+ true
1034
+ else
1035
+ false
1036
+ end
1037
+ rescue StandardError
1038
+ stats[:conflicts] += 1
1039
+ false
1040
+ end
1041
+
1042
+ # Increment statistics for successful metric sync
1043
+ #
1044
+ # @param type [Symbol] Metric type
1045
+ # @param stats [Hash] Statistics accumulator
1046
+ def increment_metric_type_stats(type, stats)
1047
+ plural_type = convert_type_to_plural(type)
1048
+ stats[plural_type] += 1
1049
+ end
1050
+
1051
+ # Convert singular metric type to plural stats key
1052
+ #
1053
+ # @param type [Symbol] Singular metric type
1054
+ # @return [Symbol] Plural stats key
1055
+ def convert_type_to_plural(type)
1056
+ case type
1057
+ when :counter then :counters
1058
+ when :gauge then :gauges
1059
+ when :histogram then :histograms
1060
+ else type
1061
+ end
1062
+ end
1063
+
1064
+ # Calculate exponential backoff delay
1065
+ #
1066
+ # @param retry_count [Integer] Current retry attempt
1067
+ # @return [Float] Delay in seconds
1068
+ def calculate_backoff_delay(retry_count)
1069
+ 0.001 * retry_count
1070
+ end
1071
+
1072
+ # Create versioned snapshot with enhanced metadata
1073
+ #
1074
+ # @return [Hash] Versioned snapshot data
1075
+ def create_versioned_snapshot
1076
+ {
1077
+ version: Tasker::VERSION,
1078
+ timestamp: Time.current.iso8601,
1079
+ instance_id: @instance_id,
1080
+ cache_strategy: @sync_strategy,
1081
+ cache_capabilities: @cache_capabilities,
1082
+ total_metrics: @metrics.size,
1083
+ metrics_by_type: @metrics.group_by { |_k, v| v.to_h[:type] }.transform_values(&:size),
1084
+ metrics: @metrics.transform_values(&:to_h),
1085
+ sync_config: @sync_config.slice(:retention_window, :batch_size),
1086
+ hostname: ENV['HOSTNAME'] || Socket.gethostname
1087
+ }
1088
+ end
1089
+
1090
+ # Estimate snapshot size for monitoring
1091
+ #
1092
+ # @param snapshot_data [Hash] Snapshot data
1093
+ # @return [Integer] Estimated size in bytes
1094
+ def estimate_snapshot_size(snapshot_data)
1095
+ # Rough estimation based on JSON serialization
1096
+ snapshot_data.to_json.bytesize
1097
+ rescue StandardError
1098
+ 0
1099
+ end
1100
+
1101
+ # Check if Rails.cache is available and functional
1102
+ #
1103
+ # @return [Boolean] True if Rails.cache can be used
1104
+ def rails_cache_available?
1105
+ defined?(Rails) && Rails.cache.respond_to?(:read) && Rails.cache.respond_to?(:write)
1106
+ rescue StandardError
1107
+ false
1108
+ end
1109
+
1110
+ # Default cache capabilities when detection fails
1111
+ #
1112
+ # Provides safe defaults aligned with Rails caching guide patterns
1113
+ #
1114
+ # @return [Hash] Safe default capabilities
1115
+ def default_cache_capabilities
1116
+ {
1117
+ distributed: false,
1118
+ atomic_increment: false,
1119
+ locking: false,
1120
+ ttl_inspection: false,
1121
+ store_class: 'Unknown',
1122
+ key_transformation: true, # Assume Rails key transformation
1123
+ namespace_support: false,
1124
+ compression_support: false
1125
+ }
1126
+ end
1127
+
1128
+ # Log cache strategy selection for operational visibility
1129
+ def log_cache_strategy_selection
1130
+ return unless defined?(Rails) && Rails.logger
1131
+
1132
+ Rails.logger.info "[Tasker::MetricsBackend] Cache strategy selected: #{@sync_strategy}"
1133
+ Rails.logger.info "[Tasker::MetricsBackend] Cache capabilities: #{@cache_capabilities}"
1134
+ Rails.logger.info "[Tasker::MetricsBackend] Instance ID: #{@instance_id}"
1135
+ end
1136
+
1137
+ # Log detected cache capabilities with detailed breakdown
1138
+ #
1139
+ # @param capabilities [Hash] Detected capabilities
1140
+ def log_cache_capabilities_detected(capabilities)
1141
+ return unless defined?(Rails) && Rails.logger
1142
+
1143
+ Rails.logger.debug { "[Tasker::MetricsBackend] Cache store detected: #{capabilities[:store_class]}" }
1144
+ Rails.logger.debug { "[Tasker::MetricsBackend] Distributed: #{capabilities[:distributed]}" }
1145
+ Rails.logger.debug { "[Tasker::MetricsBackend] Atomic operations: #{capabilities[:atomic_increment]}" }
1146
+ Rails.logger.debug { "[Tasker::MetricsBackend] Locking support: #{capabilities[:locking]}" }
1147
+ Rails.logger.debug { "[Tasker::MetricsBackend] Namespace support: #{capabilities[:namespace_support]}" }
1148
+ end
1149
+
1150
+ # Log cache detection errors
1151
+ #
1152
+ # @param error [Exception] Detection error
1153
+ def log_cache_detection_error(error)
1154
+ return unless defined?(Rails) && Rails.logger
1155
+
1156
+ Rails.logger.warn "[Tasker::MetricsBackend] Cache detection failed: #{error.message}"
1157
+ Rails.logger.warn '[Tasker::MetricsBackend] Falling back to local-only mode'
1158
+ end
1159
+
1160
+ # Log sync errors
1161
+ #
1162
+ # @param error [Exception] Sync error
1163
+ def log_sync_error(error)
1164
+ return unless defined?(Rails) && Rails.logger
1165
+
1166
+ Rails.logger.error "[Tasker::MetricsBackend] Cache sync failed: #{error.message}"
1167
+ end
1168
+
1169
+ # Log local-only mode usage
1170
+ def log_local_only_mode
1171
+ return unless defined?(Rails) && Rails.logger
1172
+
1173
+ Rails.logger.info "[Tasker::MetricsBackend] Cache store doesn't support distribution - using local-only mode"
1174
+ end
1175
+
1176
+ # **Phase 4.2.2.3.2 Enhanced Logging Methods**
1177
+ # =============================================
1178
+
1179
+ def log_atomic_sync_success(stats, duration)
1180
+ return unless defined?(Rails) && Rails.logger
1181
+
1182
+ total_synced = stats.values_at(:counters, :gauges, :histograms).sum
1183
+ Rails.logger.info(
1184
+ '[Tasker::Telemetry] Atomic sync completed: ' \
1185
+ "#{total_synced} metrics (#{stats[:counters]}c/#{stats[:gauges]}g/#{stats[:histograms]}h) " \
1186
+ "in #{(duration * 1000).round(2)}ms, #{stats[:conflicts]} conflicts, #{stats[:batches]} batches"
1187
+ )
1188
+ end
1189
+
1190
+ def log_rmw_sync_success(stats, duration)
1191
+ return unless defined?(Rails) && Rails.logger
1192
+
1193
+ total_synced = stats.values_at(:counters, :gauges, :histograms).sum
1194
+ Rails.logger.info(
1195
+ '[Tasker::Telemetry] Read-modify-write sync completed: ' \
1196
+ "#{total_synced} metrics (#{stats[:counters]}c/#{stats[:gauges]}g/#{stats[:histograms]}h) " \
1197
+ "in #{(duration * 1000).round(2)}ms, #{stats[:retries]} retries, #{stats[:failed]} failed"
1198
+ )
1199
+ end
1200
+
1201
+ def log_local_sync_success(stats, duration)
1202
+ return unless defined?(Rails) && Rails.logger
1203
+
1204
+ Rails.logger.info(
1205
+ '[Tasker::Telemetry] Local snapshot sync completed: ' \
1206
+ "#{stats[:metrics_serialized]} metrics in #{stats[:snapshots]} snapshots " \
1207
+ "(#{stats[:size_bytes]} bytes) in #{(duration * 1000).round(2)}ms"
1208
+ )
1209
+ end
1210
+
1211
+ # Coordinate cache sync with export system
1212
+ #
1213
+ # @param sync_result [Hash] Result from cache sync operation
1214
+ def coordinate_cache_sync(sync_result)
1215
+ return unless defined?(Tasker::Telemetry::ExportCoordinator)
1216
+
1217
+ begin
1218
+ coordinator = Tasker::Telemetry::ExportCoordinator.instance
1219
+ coordinator.coordinate_cache_sync(sync_result)
1220
+ rescue StandardError => e
1221
+ # Don't fail sync operation due to coordination errors
1222
+ Rails.logger&.warn("Export coordination failed: #{e.message}")
1223
+ end
1224
+ end
1225
+ end
1226
+ end
1227
+ end