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,941 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require_relative '../concerns/idempotent_state_transitions'
5
+ require_relative '../concerns/structured_logging'
6
+ require_relative '../concerns/event_publisher'
7
+ require_relative '../types/step_sequence'
8
+ require_relative 'future_state_analyzer'
9
+
10
+ module Tasker
11
+ module Orchestration
12
+ # StepExecutor handles the execution of workflow steps with concurrent processing
13
+ #
14
+ # This class provides the implementation for step execution while preserving
15
+ # the original concurrent processing capabilities using concurrent-ruby.
16
+ # It fires lifecycle events for observability.
17
+ #
18
+ # Enhanced with structured logging and performance monitoring for production observability.
19
+ class StepExecutor
20
+ include Tasker::Concerns::IdempotentStateTransitions
21
+ include Tasker::Concerns::EventPublisher
22
+ include Tasker::Concerns::StructuredLogging
23
+
24
+ # Configuration-driven execution settings
25
+ # These delegate to Tasker.configuration.execution for configurable values
26
+ # while maintaining architectural constants for Ruby-specific optimizations
27
+
28
+ def execution_config
29
+ @execution_config ||= Tasker.configuration.execution
30
+ end
31
+
32
+ # Execute a collection of viable steps
33
+ #
34
+ # This method preserves the original concurrent processing logic while
35
+ # adding observability through lifecycle events and structured logging.
36
+ #
37
+ # @param task [Tasker::Task] The task containing the steps
38
+ # @param sequence [Tasker::Types::StepSequence] The step sequence
39
+ # @param viable_steps [Array<Tasker::WorkflowStep>] Steps ready for execution
40
+ # @param task_handler [Object] The task handler instance
41
+ # @return [Array<Tasker::WorkflowStep>] Successfully processed steps
42
+ def execute_steps(task, sequence, viable_steps, task_handler)
43
+ return [] if viable_steps.empty?
44
+
45
+ execution_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
46
+
47
+ # Always use concurrent processing - sequential mode has been deprecated
48
+ processing_mode = 'concurrent'
49
+
50
+ log_orchestration_event('step_batch_execution', :started,
51
+ task_id: task.task_id,
52
+ step_count: viable_steps.size,
53
+ processing_mode: processing_mode,
54
+ step_names: viable_steps.map(&:name))
55
+
56
+ # Fire observability event through orchestrator
57
+ publish_steps_execution_started(
58
+ task,
59
+ step_count: viable_steps.size,
60
+ processing_mode: processing_mode
61
+ )
62
+
63
+ # Always use concurrent processing with dynamic concurrency optimization
64
+ processed_steps = execute_steps_concurrently_with_monitoring(task, sequence, viable_steps, task_handler)
65
+
66
+ execution_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - execution_start_time
67
+ successful_count = processed_steps.count do |s|
68
+ s&.status == Tasker::Constants::WorkflowStepStatuses::COMPLETE
69
+ end
70
+
71
+ # Log performance metrics
72
+ log_performance_event('step_batch_execution', execution_duration,
73
+ task_id: task.task_id,
74
+ step_count: viable_steps.size,
75
+ processed_count: processed_steps.size,
76
+ successful_count: successful_count,
77
+ failure_count: processed_steps.size - successful_count,
78
+ processing_mode: processing_mode)
79
+
80
+ # Fire completion event through orchestrator
81
+ publish_steps_execution_completed(
82
+ task,
83
+ processed_count: processed_steps.size,
84
+ successful_count: successful_count
85
+ )
86
+
87
+ log_orchestration_event('step_batch_execution', :completed,
88
+ task_id: task.task_id,
89
+ processed_count: processed_steps.size,
90
+ successful_count: successful_count,
91
+ failure_count: processed_steps.size - successful_count,
92
+ duration_ms: (execution_duration * 1000).round(2))
93
+
94
+ processed_steps.compact
95
+ end
96
+
97
+ # Calculate optimal concurrency based on system health and resources
98
+ #
99
+ # This method dynamically determines the maximum number of steps that can be
100
+ # executed concurrently based on current system load, database connections,
101
+ # and other health metrics. Now enhanced with ConnectionPoolIntelligence
102
+ # for Rails-aware connection management.
103
+ #
104
+ # @return [Integer] Optimal number of concurrent steps (between configured min and max)
105
+ def max_concurrent_steps
106
+ # Return cached value if still valid
107
+ cache_duration = execution_config.concurrency_cache_duration.seconds
108
+ if @max_concurrent_steps && @concurrency_calculated_at &&
109
+ (Time.current - @concurrency_calculated_at) < cache_duration
110
+ return @max_concurrent_steps
111
+ end
112
+
113
+ # Calculate new concurrency level using enhanced intelligence
114
+ @max_concurrent_steps = calculate_optimal_concurrency
115
+ @concurrency_calculated_at = Time.current
116
+
117
+ @max_concurrent_steps
118
+ end
119
+
120
+ # Handle viable steps discovered event
121
+ #
122
+ # Convenience method for event-driven workflows that takes an event payload
123
+ # and executes the discovered steps.
124
+ #
125
+ # @param event [Hash] Event payload with task_id, step_ids, and processing_mode
126
+ def handle_viable_steps_discovered(event)
127
+ task_id = event[:task_id]
128
+ step_ids = event[:step_ids] || []
129
+
130
+ return [] if step_ids.empty?
131
+
132
+ with_correlation_id(event[:correlation_id]) do
133
+ log_orchestration_event('event_driven_execution', :started,
134
+ task_id: task_id,
135
+ step_ids: step_ids,
136
+ trigger: 'viable_steps_discovered')
137
+
138
+ task = Tasker::Task.find(task_id)
139
+ task_handler = Tasker::HandlerFactory.instance.get(task.name)
140
+ sequence = Tasker::Orchestration::StepSequenceFactory.get_sequence(task, task_handler)
141
+ viable_steps = task.workflow_steps.where(workflow_step_id: step_ids)
142
+
143
+ execute_steps(task, sequence, viable_steps, task_handler)
144
+ end
145
+ end
146
+
147
+ # Execute a single step with state machine transitions and error handling
148
+ #
149
+ # Enhanced with structured logging and performance monitoring.
150
+ #
151
+ # @param task [Tasker::Task] The task containing the step
152
+ # @param sequence [Tasker::Types::StepSequence] The step sequence
153
+ # @param step [Tasker::WorkflowStep] The step to execute
154
+ # @param task_handler [Object] The task handler instance
155
+ # @return [Tasker::WorkflowStep, nil] The executed step or nil if failed
156
+ def execute_single_step(task, sequence, step, task_handler)
157
+ step_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
158
+
159
+ log_step_event(step, :execution_starting,
160
+ task_id: task.task_id,
161
+ step_status: step.status,
162
+ attempt_count: step.attempts)
163
+
164
+ # Guard clauses - fail fast if preconditions aren't met
165
+ return nil unless validate_step_preconditions_with_logging(step)
166
+ return nil unless ensure_step_has_initial_state_with_logging(step)
167
+ return nil unless step_ready_for_execution_with_logging?(step)
168
+
169
+ # Main execution workflow with monitoring
170
+ result = execute_step_workflow_with_monitoring(task, sequence, step, task_handler, step_start_time)
171
+
172
+ step_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
173
+
174
+ if result
175
+ log_performance_event('single_step_execution', step_duration,
176
+ task_id: task.task_id,
177
+ step_id: step.workflow_step_id,
178
+ step_name: step.name,
179
+ result: 'success',
180
+ attempt_count: step.attempts)
181
+ else
182
+ log_performance_event('single_step_execution', step_duration,
183
+ task_id: task.task_id,
184
+ step_id: step.workflow_step_id,
185
+ step_name: step.name,
186
+ result: 'failure',
187
+ attempt_count: step.attempts)
188
+ end
189
+
190
+ result
191
+ rescue StandardError => e
192
+ step_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
193
+
194
+ # Log unexpected errors that occur outside the normal workflow
195
+ step_id = step&.workflow_step_id
196
+ log_exception(e, context: {
197
+ step_id: step_id,
198
+ task_id: task&.task_id,
199
+ operation: 'single_step_execution',
200
+ duration: step_duration
201
+ })
202
+
203
+ Rails.logger.error("StepExecutor: Unexpected error in execute_single_step for step #{step_id}: #{e.message}")
204
+ nil
205
+ end
206
+
207
+ private
208
+
209
+ # Execute steps concurrently with monitoring
210
+ #
211
+ # @param task [Tasker::Task] The task containing the steps
212
+ # @param sequence [Tasker::Types::StepSequence] The step sequence
213
+ # @param viable_steps [Array<Tasker::WorkflowStep>] Steps ready for execution
214
+ # @param task_handler [Object] The task handler instance
215
+ # @return [Array<Tasker::WorkflowStep>] Successfully processed steps
216
+ def execute_steps_concurrently_with_monitoring(task, sequence, viable_steps, task_handler)
217
+ log_orchestration_event('concurrent_execution', :started,
218
+ task_id: task.task_id,
219
+ step_count: viable_steps.size,
220
+ max_concurrency: max_concurrent_steps)
221
+
222
+ concurrent_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
223
+
224
+ # Use the original concurrent execution logic without thread_pool
225
+ results = execute_steps_concurrently(task, sequence, viable_steps, task_handler)
226
+
227
+ concurrent_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - concurrent_start_time
228
+ successful_count = results.count { |r| r&.status == Tasker::Constants::WorkflowStepStatuses::COMPLETE }
229
+
230
+ log_performance_event('concurrent_execution', concurrent_duration,
231
+ task_id: task.task_id,
232
+ step_count: viable_steps.size,
233
+ successful_count: successful_count,
234
+ failure_count: results.size - successful_count)
235
+
236
+ log_orchestration_event('concurrent_execution', :completed,
237
+ task_id: task.task_id,
238
+ step_count: viable_steps.size,
239
+ successful_count: successful_count,
240
+ duration_ms: (concurrent_duration * 1000).round(2))
241
+
242
+ results
243
+ end
244
+
245
+ # Validate that the step and database connection are ready
246
+ def validate_step_preconditions_with_logging(step)
247
+ unless ActiveRecord::Base.connection.active?
248
+ log_step_event(step, :validation_failed,
249
+ reason: 'database_connection_inactive',
250
+ step_status: step&.status)
251
+ Rails.logger.error("StepExecutor: Database connection inactive for step #{step&.workflow_step_id}")
252
+ return false
253
+ end
254
+
255
+ step = step.reload if step&.persisted?
256
+ unless step
257
+ log_structured(:error, 'Step validation failed',
258
+ reason: 'step_nil_or_not_persisted',
259
+ entity_type: 'step')
260
+ Rails.logger.error('StepExecutor: Step is nil or not persisted')
261
+ return false
262
+ end
263
+
264
+ log_step_event(step, :validation_passed,
265
+ step_status: step.status,
266
+ step_attempts: step.attempts)
267
+
268
+ true
269
+ rescue ActiveRecord::ConnectionNotEstablished, PG::ConnectionBad => e
270
+ log_exception(e, context: {
271
+ step_id: step&.workflow_step_id,
272
+ operation: 'step_precondition_validation'
273
+ })
274
+ Rails.logger.error("StepExecutor: Database connection error for step #{step&.workflow_step_id}: #{e.message}")
275
+ false
276
+ rescue StandardError => e
277
+ log_exception(e, context: {
278
+ step_id: step&.workflow_step_id,
279
+ operation: 'step_precondition_validation'
280
+ })
281
+ Rails.logger.error("StepExecutor: Unexpected error checking step #{step&.workflow_step_id}: #{e.message}")
282
+ false
283
+ end
284
+
285
+ # Ensure step has an initial state, set to pending if blank
286
+ def ensure_step_has_initial_state_with_logging(step) # rubocop:disable Naming/PredicateMethod
287
+ current_state = step.state_machine.current_state
288
+ return true if current_state.present?
289
+
290
+ log_step_event(step, :state_initialization,
291
+ current_state: current_state,
292
+ target_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
293
+
294
+ Rails.logger.debug { "StepExecutor: Step #{step.workflow_step_id} has no state, setting to pending" }
295
+ unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::PENDING)
296
+ log_step_event(step, :state_initialization_failed,
297
+ current_state: current_state,
298
+ target_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
299
+ Rails.logger.error("StepExecutor: Failed to initialize step #{step.workflow_step_id} to pending state")
300
+ return false
301
+ end
302
+
303
+ step.reload
304
+ log_step_event(step, :state_initialized,
305
+ new_state: step.state_machine.current_state)
306
+ true
307
+ end
308
+
309
+ # Check if step is in the correct state for execution
310
+ def step_ready_for_execution_with_logging?(step)
311
+ current_state = step.state_machine.current_state
312
+ is_ready = current_state == Tasker::Constants::WorkflowStepStatuses::PENDING
313
+
314
+ log_step_event(step, :readiness_check,
315
+ current_state: current_state,
316
+ is_ready: is_ready,
317
+ expected_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
318
+
319
+ return true if is_ready
320
+
321
+ Rails.logger.debug do
322
+ "StepExecutor: Skipping step #{step.workflow_step_id} - not pending (current: '#{current_state}')"
323
+ end
324
+ false
325
+ end
326
+
327
+ # Execute the main step workflow with monitoring: transition -> execute -> complete
328
+ def execute_step_workflow_with_monitoring(task, sequence, step, task_handler, step_start_time)
329
+ publish_execution_started_event(task, step)
330
+
331
+ log_step_event(step, :workflow_starting,
332
+ task_id: task.task_id,
333
+ step_status: step.status,
334
+ elapsed_time_ms: ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time) * 1000).round(2))
335
+
336
+ # Execute step handler and handle both success and error cases
337
+ begin
338
+ # Transition to in_progress first - if this fails, it should be treated as an error
339
+ transition_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
340
+ transition_step_to_in_progress!(step)
341
+ transition_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - transition_start_time
342
+
343
+ log_performance_event('step_state_transition', transition_duration,
344
+ task_id: task.task_id,
345
+ step_id: step.workflow_step_id,
346
+ from_state: Tasker::Constants::WorkflowStepStatuses::PENDING,
347
+ to_state: Tasker::Constants::WorkflowStepStatuses::IN_PROGRESS)
348
+
349
+ # Execute the actual step handler with timing
350
+ handler_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
351
+ execute_step_handler(task, sequence, step, task_handler)
352
+ handler_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - handler_start_time
353
+
354
+ log_performance_event('step_handler_execution', handler_duration,
355
+ task_id: task.task_id,
356
+ step_id: step.workflow_step_id,
357
+ step_name: step.name,
358
+ result: 'success')
359
+
360
+ # Complete step execution with timing
361
+ completion_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
362
+ result = complete_step_execution(task, step)
363
+ completion_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - completion_start_time
364
+
365
+ log_performance_event('step_completion', completion_duration,
366
+ task_id: task.task_id,
367
+ step_id: step.workflow_step_id,
368
+ final_status: result&.status)
369
+
370
+ total_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
371
+ log_step_event(step, :workflow_completed,
372
+ task_id: task.task_id,
373
+ final_status: result&.status,
374
+ total_duration_ms: (total_duration * 1000).round(2),
375
+ handler_duration_ms: (handler_duration * 1000).round(2))
376
+
377
+ result
378
+ rescue StandardError => e
379
+ error_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
380
+
381
+ log_step_event(step, :workflow_failed,
382
+ task_id: task.task_id,
383
+ error: e.message,
384
+ error_class: e.class.name,
385
+ duration_ms: (error_duration * 1000).round(2))
386
+
387
+ log_performance_event('step_handler_execution', error_duration,
388
+ task_id: task.task_id,
389
+ step_id: step.workflow_step_id,
390
+ step_name: step.name,
391
+ result: 'failure',
392
+ error_class: e.class.name)
393
+
394
+ # Store error data in step.results like legacy code
395
+ store_step_error_data(step, e)
396
+
397
+ # Complete error step execution with persistence (similar to complete_step_execution but for errors)
398
+ error_completion_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
399
+ result = complete_error_step_execution(task, step)
400
+ error_completion_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - error_completion_start_time
401
+
402
+ log_performance_event('step_error_completion', error_completion_duration,
403
+ task_id: task.task_id,
404
+ step_id: step.workflow_step_id,
405
+ final_status: result&.status)
406
+
407
+ nil
408
+ end
409
+ end
410
+
411
+ # Publish event for step execution start
412
+ def publish_execution_started_event(_task, step)
413
+ # Use clean API for step execution start
414
+ publish_step_started(step)
415
+ end
416
+
417
+ # Transition step to in_progress state (bang version that raises on failure)
418
+ def transition_step_to_in_progress!(step)
419
+ unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::IN_PROGRESS)
420
+ current_state = step.state_machine.current_state
421
+ error_message = "Cannot transition step #{step.workflow_step_id} from '#{current_state}' to 'in_progress'. " \
422
+ 'Check step dependencies and current state.'
423
+
424
+ Rails.logger.warn("StepExecutor: #{error_message}")
425
+ raise Tasker::ProceduralError, error_message
426
+ end
427
+
428
+ true
429
+ end
430
+
431
+ # Execute the actual step handler logic
432
+ def execute_step_handler(task, sequence, step, task_handler)
433
+ step_handler = task_handler.get_step_handler(step)
434
+ step_handler.handle(task, sequence, step)
435
+ end
436
+
437
+ # Complete step execution and publish completion event
438
+ #
439
+ # This method ensures atomic completion by wrapping both the step save
440
+ # and state transition in a database transaction. This is critical for
441
+ # idempotency: if either operation fails, the step remains in "in_progress"
442
+ # and can be safely retried without repeating the actual work.
443
+ def complete_step_execution(_task, step)
444
+ completed_step = nil
445
+
446
+ # Update attempt tracking like legacy code (for consistency with error path)
447
+ step.attempts ||= 0
448
+ step.attempts += 1
449
+ step.last_attempted_at = Time.zone.now
450
+
451
+ # Use database transaction to ensure atomic completion
452
+ ActiveRecord::Base.transaction do
453
+ # STEP 1: Set completion flags for successful steps
454
+ # Mark step as processed and not in_process (mirrors error path logic)
455
+ step.processed = true
456
+ step.in_process = false
457
+ step.processed_at = Time.zone.now
458
+
459
+ # STEP 2: Save the step results and flags
460
+ # This persists the output of the work that has already been performed
461
+ step.save!
462
+
463
+ # STEP 3: Transition to complete state
464
+ # This marks the step as done, but only if the save succeeded
465
+ unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::COMPLETE)
466
+ Rails.logger.error("StepExecutor: Failed to transition step #{step.workflow_step_id} to complete")
467
+ # Raise exception to trigger transaction rollback
468
+ raise ActiveRecord::Rollback, "Failed to transition step #{step.workflow_step_id} to complete state"
469
+ end
470
+
471
+ completed_step = step
472
+ end
473
+
474
+ # If we got here, both save and transition succeeded
475
+ unless completed_step
476
+ Rails.logger.error("StepExecutor: Step completion transaction rolled back for step #{step.workflow_step_id}")
477
+ return nil
478
+ end
479
+
480
+ # Publish completion event outside transaction (for performance)
481
+ publish_step_completed(
482
+ step,
483
+ attempt_number: step.attempts,
484
+ execution_duration: step.processed_at&.-(step.updated_at)
485
+ )
486
+
487
+ Rails.logger.debug { "StepExecutor: Successfully completed step #{step.workflow_step_id}" }
488
+ completed_step
489
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
490
+ Rails.logger.error("StepExecutor: Failed to save step #{step.workflow_step_id}: #{e.message}")
491
+ nil
492
+ rescue StandardError => e
493
+ Rails.logger.error("StepExecutor: Unexpected error completing step #{step.workflow_step_id}: #{e.message}")
494
+ nil
495
+ end
496
+
497
+ # Store error data in step results (matching legacy pattern)
498
+ def store_step_error_data(step, error)
499
+ step.results ||= {}
500
+ step.results = step.results.merge(
501
+ error: error.to_s,
502
+ backtrace: error.backtrace.join("\n"),
503
+ error_class: error.class.name
504
+ )
505
+
506
+ # Update attempt tracking like legacy code
507
+ step.attempts ||= 0
508
+ step.attempts += 1
509
+ step.last_attempted_at = Time.zone.now
510
+ end
511
+
512
+ # Complete error step execution with persistence and state transition
513
+ #
514
+ # This mirrors complete_step_execution but handles error state persistence.
515
+ # Critical: We MUST save error steps to preserve error data and attempt tracking.
516
+ def complete_error_step_execution(_task, step)
517
+ completed_error_step = nil
518
+
519
+ # Use database transaction to ensure atomic error completion
520
+ ActiveRecord::Base.transaction do
521
+ # STEP 1: Reset step flags for retry eligibility
522
+ # Failed steps must be marked as not in_process and not processed
523
+ # so they can be picked up for retry by the step readiness view
524
+ step.in_process = false
525
+ step.processed = false
526
+
527
+ # STEP 2: Save the step with error data
528
+ # This persists the error information and attempt tracking
529
+ step.save!
530
+
531
+ # STEP 3: Transition to error state
532
+ # This marks the step as failed, but only if the save succeeded
533
+ unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::ERROR)
534
+ Rails.logger.error("StepExecutor: Failed to transition step #{step.workflow_step_id} to error")
535
+ # Raise exception to trigger transaction rollback
536
+ raise ActiveRecord::Rollback, "Failed to transition step #{step.workflow_step_id} to error state"
537
+ end
538
+
539
+ completed_error_step = step
540
+ end
541
+
542
+ # If we got here, both save and transition succeeded
543
+ unless completed_error_step
544
+ step_id = step.workflow_step_id
545
+ Rails.logger.error("StepExecutor: Error step completion transaction rolled back for step #{step_id}")
546
+ return nil
547
+ end
548
+
549
+ # Publish error event outside transaction (for performance)
550
+ publish_step_failed(
551
+ step,
552
+ error_message: step.results['error'],
553
+ error_class: step.results['error_class'],
554
+ attempt_number: step.attempts,
555
+ backtrace: step.results['backtrace']&.split("\n")&.first(10)
556
+ )
557
+
558
+ Rails.logger.debug { "StepExecutor: Successfully saved error step #{step.workflow_step_id}" }
559
+ completed_error_step
560
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
561
+ Rails.logger.error("StepExecutor: Failed to save error step #{step.workflow_step_id}: #{e.message}")
562
+ nil
563
+ rescue StandardError => e
564
+ step_id = step.workflow_step_id
565
+ Rails.logger.error("StepExecutor: Unexpected error completing error step #{step_id}: #{e.message}")
566
+ nil
567
+ end
568
+
569
+ # Calculate optimal concurrency based on system health metrics
570
+ #
571
+ # Enhanced with ConnectionPoolIntelligence for Rails-aware connection management.
572
+ # Falls back to legacy calculation if ConnectionPoolIntelligence is unavailable.
573
+ #
574
+ # @return [Integer] Calculated concurrency level
575
+ def calculate_optimal_concurrency
576
+ # Use enhanced ConnectionPoolIntelligence for Rails-aware calculation
577
+ intelligence_concurrency = Tasker::Orchestration::ConnectionPoolIntelligence
578
+ .intelligent_concurrency_for_step_executor
579
+
580
+ # Combine with system health analysis for comprehensive optimization
581
+ health_data = fetch_system_health_data
582
+ if health_data
583
+ # Apply additional system load considerations
584
+ base_concurrency = calculate_base_concurrency(health_data)
585
+
586
+ # Use the most conservative of both approaches for safety
587
+ optimal_concurrency = [intelligence_concurrency, base_concurrency].min
588
+ else
589
+ # Use ConnectionPoolIntelligence recommendation when health data unavailable
590
+ optimal_concurrency = intelligence_concurrency
591
+ end
592
+
593
+ # Ensure we stay within configured bounds
594
+ optimal_concurrency.clamp(execution_config.min_concurrent_steps, execution_config.max_concurrent_steps_limit)
595
+ rescue StandardError => e
596
+ log_structured(:warn, 'Optimal concurrency calculation failed, using fallback', {
597
+ error_class: e.class.name,
598
+ error_message: e.message,
599
+ fallback_concurrency: execution_config.min_concurrent_steps
600
+ })
601
+ execution_config.min_concurrent_steps
602
+ end
603
+
604
+ # Fetch system health data with error handling
605
+ #
606
+ # @return [HealthMetrics, nil] Health metrics or nil if unavailable
607
+ def fetch_system_health_data
608
+ Tasker::Functions::FunctionBasedSystemHealthCounts.call
609
+ rescue StandardError => e
610
+ Rails.logger.warn("StepExecutor: Failed to fetch system health data: #{e.message}")
611
+ nil
612
+ end
613
+
614
+ # Fetch connection pool size with error handling
615
+ #
616
+ # @return [Integer, nil] Connection pool size or nil if unavailable
617
+ def fetch_connection_pool_size
618
+ ActiveRecord::Base.connection_pool&.size
619
+ rescue StandardError => e
620
+ Rails.logger.warn("StepExecutor: Failed to fetch connection pool size: #{e.message}")
621
+ nil
622
+ end
623
+
624
+ # Calculate base concurrency from system health metrics
625
+ #
626
+ # @param health_data [HealthMetrics] System health metrics
627
+ # @return [Integer] Base concurrency level
628
+ def calculate_base_concurrency(health_data)
629
+ # Calculate system load factor (0.0 to 1.0+)
630
+ total_active_work = [health_data.in_progress_tasks + health_data.pending_tasks, 1].max
631
+ load_factor = total_active_work.to_f / [health_data.total_tasks, 1].max
632
+
633
+ # Calculate step processing load
634
+ total_active_steps = [health_data.in_progress_steps + health_data.pending_steps, 1].max
635
+ step_load_factor = total_active_steps.to_f / [health_data.total_steps, 1].max
636
+
637
+ # Combine load factors (weighted toward step load)
638
+ combined_load = (load_factor * 0.3) + (step_load_factor * 0.7)
639
+
640
+ # Calculate concurrency based on load (inverse relationship)
641
+ min_steps = execution_config.min_concurrent_steps
642
+ max_steps = execution_config.max_concurrent_steps_limit
643
+
644
+ if combined_load <= 0.3
645
+ # Low load: Allow higher concurrency
646
+ max_steps
647
+ elsif combined_load <= 0.6
648
+ # Moderate load: Medium concurrency
649
+ (((max_steps - min_steps) * 0.6) + min_steps).round
650
+ else
651
+ # High load: Conservative concurrency
652
+ min_steps + 1
653
+ end
654
+ rescue StandardError => e
655
+ Rails.logger.warn("StepExecutor: Error calculating base concurrency: #{e.message}")
656
+ execution_config.min_concurrent_steps
657
+ end
658
+
659
+ # Calculate connection-constrained concurrency
660
+ #
661
+ # @param health_data [HealthMetrics] System health metrics
662
+ # @param pool_size [Integer] Connection pool size
663
+ # @return [Integer] Connection-constrained concurrency level
664
+ def calculate_connection_constrained_concurrency(health_data, pool_size)
665
+ min_steps = execution_config.min_concurrent_steps
666
+ max_steps = execution_config.max_concurrent_steps_limit
667
+
668
+ return min_steps if pool_size <= 0
669
+
670
+ # Get current connection usage
671
+ active_connections = [health_data.active_connections, 0].max
672
+ connection_utilization = active_connections.to_f / pool_size
673
+
674
+ # Calculate available connections with safety margin
675
+ available_connections = pool_size - active_connections
676
+ safety_margin = [pool_size * 0.2, 2].max.round # 20% safety margin, minimum 2
677
+
678
+ safe_available = available_connections - safety_margin
679
+
680
+ # Don't allow concurrency that would exhaust connections
681
+ if connection_utilization >= 0.9 || safe_available <= 2
682
+ min_steps
683
+ elsif connection_utilization >= 0.7
684
+ [safe_available / 2, min_steps + 1].max
685
+ else
686
+ [safe_available, max_steps].min
687
+ end
688
+ rescue StandardError => e
689
+ Rails.logger.warn("StepExecutor: Error calculating connection-constrained concurrency: #{e.message}")
690
+ execution_config.min_concurrent_steps
691
+ end
692
+
693
+ # Execute steps concurrently using concurrent-ruby with enhanced memory management
694
+ #
695
+ # Enhanced with timeout protection, comprehensive future cleanup, and intelligent
696
+ # garbage collection triggers to prevent memory leaks in long-running processes.
697
+ #
698
+ # @param task [Tasker::Task] The task containing the steps
699
+ # @param sequence [Tasker::Types::StepSequence] The step sequence
700
+ # @param viable_steps [Array<Tasker::WorkflowStep>] Steps to execute
701
+ # @param task_handler [Object] The task handler instance
702
+ # @return [Array<Tasker::WorkflowStep>] Processed steps
703
+ def execute_steps_concurrently(task, sequence, viable_steps, task_handler)
704
+ results = []
705
+ current_max_concurrency = max_concurrent_steps
706
+
707
+ viable_steps.each_slice(current_max_concurrency) do |step_batch|
708
+ futures = nil
709
+ batch_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
710
+
711
+ begin
712
+ # Create futures with connection pool management
713
+ futures = step_batch.map do |step|
714
+ Concurrent::Future.execute do
715
+ # Ensure each future has its own database connection
716
+ ActiveRecord::Base.connection_pool.with_connection do
717
+ execute_single_step(task, sequence, step, task_handler)
718
+ end
719
+ end
720
+ end
721
+
722
+ # Enhanced timeout protection with graceful degradation
723
+ batch_results = collect_results_with_timeout(futures, step_batch.size, task.task_id)
724
+ results.concat(batch_results.compact)
725
+ rescue Concurrent::TimeoutError
726
+ log_structured(:warn, 'Batch execution timeout',
727
+ task_id: task.task_id,
728
+ batch_size: step_batch.size,
729
+ timeout_seconds: calculate_batch_timeout(step_batch.size),
730
+ correlation_id: current_correlation_id)
731
+
732
+ # Graceful degradation: collect any completed results
733
+ completed_results = collect_completed_results(futures)
734
+ results.concat(completed_results.compact)
735
+ rescue StandardError => e
736
+ log_structured(:error, 'Batch execution error',
737
+ task_id: task.task_id,
738
+ batch_size: step_batch.size,
739
+ error_class: e.class.name,
740
+ error_message: e.message,
741
+ correlation_id: current_correlation_id)
742
+
743
+ # Attempt to collect any completed results before cleanup
744
+ completed_results = collect_completed_results(futures) if futures
745
+ results.concat(completed_results.compact) if completed_results
746
+ ensure
747
+ # ENHANCED: Comprehensive memory cleanup with intelligent GC
748
+ cleanup_futures_with_memory_management(futures, step_batch.size, batch_start_time, task.task_id)
749
+ end
750
+ end
751
+
752
+ results
753
+ end
754
+
755
+ # Get current correlation ID for logging context
756
+ #
757
+ # Ensures we always have a correlation ID for traceability. If the StepExecutor
758
+ # doesn't have the StructuredLogging concern properly included, this will fail fast
759
+ # rather than silently returning nil and masking workflow issues.
760
+ #
761
+ # @return [String] Current correlation ID (never nil)
762
+ # @raise [RuntimeError] If StructuredLogging concern is not properly included
763
+ def current_correlation_id
764
+ unless respond_to?(:correlation_id, true)
765
+ raise 'StepExecutor must include StructuredLogging concern for correlation ID support. ' \
766
+ 'This indicates a workflow or initialization issue.'
767
+ end
768
+
769
+ # The StructuredLogging concern automatically generates correlation IDs if none exist
770
+ # This ensures we always have traceability without masking logical sequencing issues
771
+ correlation_id
772
+ end
773
+
774
+ # Collect results with configurable timeout protection
775
+ #
776
+ # @param futures [Array<Concurrent::Future>] The futures to collect from
777
+ # @param batch_size [Integer] Size of the batch for timeout calculation
778
+ # @param task_id [String] Task ID for logging context
779
+ # @return [Array] Results from completed futures
780
+ def collect_results_with_timeout(futures, batch_size, task_id)
781
+ timeout_seconds = calculate_batch_timeout(batch_size)
782
+
783
+ log_structured(:debug, 'Collecting batch results with timeout',
784
+ task_id: task_id,
785
+ batch_size: batch_size,
786
+ timeout_seconds: timeout_seconds,
787
+ correlation_id: current_correlation_id)
788
+
789
+ futures.map { |future| future.value(timeout_seconds) }
790
+ rescue Concurrent::TimeoutError
791
+ # Log timeout but let the caller handle graceful degradation
792
+ log_structured(:warn, 'Future collection timeout',
793
+ task_id: task_id,
794
+ batch_size: batch_size,
795
+ timeout_seconds: timeout_seconds,
796
+ correlation_id: current_correlation_id)
797
+ raise
798
+ end
799
+
800
+ # Calculate appropriate timeout based on batch size
801
+ #
802
+ # @param batch_size [Integer] Number of steps in the batch
803
+ # @return [Numeric] Timeout in seconds
804
+ def calculate_batch_timeout(batch_size)
805
+ # Delegate to execution config for timeout calculation
806
+ execution_config.calculate_batch_timeout(batch_size)
807
+ end
808
+
809
+ # Collect results from completed futures without waiting
810
+ #
811
+ # @param futures [Array<Concurrent::Future>] The futures to check
812
+ # @return [Array] Results from completed futures only
813
+ def collect_completed_results(futures)
814
+ return [] unless futures
815
+
816
+ completed_results = []
817
+ futures.each do |future|
818
+ if future.complete? && !future.rejected?
819
+ completed_results << future.value
820
+ elsif future.rejected?
821
+ log_structured(:warn, 'Future rejected during collection',
822
+ reason: future.reason&.message,
823
+ correlation_id: current_correlation_id)
824
+ end
825
+ end
826
+
827
+ completed_results
828
+ rescue StandardError => e
829
+ log_structured(:error, 'Error collecting completed results',
830
+ error_class: e.class.name,
831
+ error_message: e.message,
832
+ correlation_id: current_correlation_id)
833
+ []
834
+ end
835
+
836
+ # Comprehensive future cleanup with memory management
837
+ #
838
+ # @param futures [Array<Concurrent::Future>] The futures to clean up
839
+ # @param batch_size [Integer] Size of the processed batch
840
+ # @param batch_start_time [Float] When the batch started processing
841
+ # @param task_id [String] Task ID for logging context
842
+ def cleanup_futures_with_memory_management(futures, batch_size, batch_start_time, task_id)
843
+ return unless futures
844
+
845
+ cleanup_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
846
+
847
+ begin
848
+ # Step 1: Cancel any pending futures using domain-specific logic
849
+ pending_count = 0
850
+ futures.each do |future|
851
+ analyzer = FutureStateAnalyzer.new(future)
852
+ if analyzer.should_cancel?
853
+ future.cancel
854
+ pending_count += 1
855
+ end
856
+ end
857
+
858
+ # Step 2: Wait briefly for executing futures to complete gracefully
859
+ executing_count = 0
860
+ futures.each do |future|
861
+ analyzer = FutureStateAnalyzer.new(future)
862
+ if analyzer.should_wait_for_completion?
863
+ future.wait(execution_config.future_cleanup_wait_seconds)
864
+ executing_count += 1
865
+ end
866
+ end
867
+
868
+ # Step 3: Clear future references to prevent memory leaks
869
+ futures.clear
870
+
871
+ # Step 4: Intelligent GC trigger for memory pressure relief
872
+ trigger_intelligent_gc(batch_size, task_id) if should_trigger_gc?(batch_size, batch_start_time)
873
+
874
+ # Log cleanup metrics for observability
875
+ cleanup_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - cleanup_start_time
876
+ log_structured(:debug, 'Future cleanup completed',
877
+ task_id: task_id,
878
+ batch_size: batch_size,
879
+ pending_cancelled: pending_count,
880
+ executing_waited: executing_count,
881
+ cleanup_duration_ms: (cleanup_duration * 1000).round(2),
882
+ gc_triggered: should_trigger_gc?(batch_size, batch_start_time),
883
+ correlation_id: current_correlation_id)
884
+ rescue StandardError => e
885
+ log_structured(:error, 'Error during future cleanup',
886
+ task_id: task_id,
887
+ batch_size: batch_size,
888
+ error_class: e.class.name,
889
+ error_message: e.message,
890
+ correlation_id: current_correlation_id)
891
+ end
892
+ end
893
+
894
+ # Determine if garbage collection should be triggered
895
+ #
896
+ # @param batch_size [Integer] Size of the processed batch
897
+ # @param batch_start_time [Float] When the batch started processing
898
+ # @return [Boolean] Whether GC should be triggered
899
+ def should_trigger_gc?(batch_size, batch_start_time)
900
+ batch_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - batch_start_time
901
+
902
+ # Delegate to execution config for GC decision
903
+ execution_config.should_trigger_gc?(batch_size, batch_duration)
904
+ end
905
+
906
+ # Trigger intelligent garbage collection with logging
907
+ #
908
+ # @param batch_size [Integer] Size of the batch that triggered GC
909
+ # @param task_id [String] Task ID for logging context
910
+ def trigger_intelligent_gc(batch_size, task_id)
911
+ gc_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
912
+
913
+ # Record memory stats before GC
914
+ memory_before = GC.stat[:heap_live_slots] if GC.respond_to?(:stat)
915
+
916
+ # Trigger GC
917
+ GC.start
918
+
919
+ # Record memory stats after GC
920
+ memory_after = GC.stat[:heap_live_slots] if GC.respond_to?(:stat)
921
+ gc_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - gc_start_time
922
+
923
+ # Log GC metrics for memory monitoring
924
+ log_structured(:info, 'Intelligent GC triggered',
925
+ task_id: task_id,
926
+ batch_size: batch_size,
927
+ gc_duration_ms: (gc_duration * 1000).round(2),
928
+ memory_before: memory_before,
929
+ memory_after: memory_after,
930
+ memory_freed: memory_before && memory_after ? (memory_before - memory_after) : nil,
931
+ correlation_id: current_correlation_id)
932
+ rescue StandardError => e
933
+ log_structured(:error, 'Error during intelligent GC',
934
+ task_id: task_id,
935
+ error_class: e.class.name,
936
+ error_message: e.message,
937
+ correlation_id: current_correlation_id)
938
+ end
939
+ end
940
+ end
941
+ end