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,508 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'statesman'
4
+ require_relative '../constants'
5
+ require 'tasker/events/event_payload_builder'
6
+ require_relative '../concerns/event_publisher'
7
+
8
+ module Tasker
9
+ module StateMachine
10
+ # StepStateMachine defines state transitions for workflow steps using Statesman
11
+ #
12
+ # This state machine manages workflow step lifecycle states and integrates with
13
+ # the existing event system to provide declarative state management.
14
+ class StepStateMachine
15
+ include Statesman::Machine
16
+ extend Tasker::Concerns::EventPublisher
17
+
18
+ # Define all step states using existing constants
19
+ state Constants::WorkflowStepStatuses::PENDING, initial: true
20
+ state Constants::WorkflowStepStatuses::IN_PROGRESS
21
+ state Constants::WorkflowStepStatuses::COMPLETE
22
+ state Constants::WorkflowStepStatuses::ERROR
23
+ state Constants::WorkflowStepStatuses::CANCELLED
24
+ state Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
25
+
26
+ # Define state transitions based on existing StateTransition definitions
27
+ transition from: Constants::WorkflowStepStatuses::PENDING,
28
+ to: [Constants::WorkflowStepStatuses::IN_PROGRESS,
29
+ Constants::WorkflowStepStatuses::ERROR,
30
+ Constants::WorkflowStepStatuses::CANCELLED,
31
+ Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] # Allow manual resolution
32
+
33
+ transition from: Constants::WorkflowStepStatuses::IN_PROGRESS,
34
+ to: [Constants::WorkflowStepStatuses::COMPLETE,
35
+ Constants::WorkflowStepStatuses::ERROR,
36
+ Constants::WorkflowStepStatuses::CANCELLED]
37
+
38
+ transition from: Constants::WorkflowStepStatuses::ERROR,
39
+ to: [Constants::WorkflowStepStatuses::PENDING,
40
+ Constants::WorkflowStepStatuses::RESOLVED_MANUALLY]
41
+
42
+ # Callbacks for state transitions
43
+ before_transition do |step, transition|
44
+ # Handle idempotent transitions using existing helper method
45
+ if StepStateMachine.idempotent_transition?(step, transition.to_state)
46
+ # Abort the transition by raising GuardFailedError
47
+ raise Statesman::GuardFailedError, "Already in target state #{transition.to_state}"
48
+ end
49
+
50
+ # Log the transition for debugging
51
+ effective_current_state = StepStateMachine.effective_current_state(step)
52
+ Rails.logger.debug do
53
+ "Step #{step.workflow_step_id} transitioning from #{effective_current_state} to #{transition.to_state}"
54
+ end
55
+ end
56
+
57
+ after_transition do |step, transition|
58
+ # Determine the appropriate event name based on the transition
59
+ event_name = determine_transition_event_name(transition.from_state, transition.to_state)
60
+
61
+ # Only fire the event if we have a valid event name
62
+ if event_name
63
+ # Fire the lifecycle event with step context
64
+ StepStateMachine.safe_fire_event(
65
+ event_name,
66
+ {
67
+ task_id: step.task_id,
68
+ step_id: step.workflow_step_id,
69
+ step_name: step.name,
70
+ from_state: transition.from_state,
71
+ to_state: transition.to_state,
72
+ transitioned_at: Time.zone.now
73
+ }
74
+ )
75
+ end
76
+ end
77
+
78
+ # Guard clauses for business logic only
79
+ # Let Statesman handle state transition validation and idempotent calls
80
+
81
+ guard_transition(to: Constants::WorkflowStepStatuses::IN_PROGRESS) do |step, _transition|
82
+ # Only business rule: check dependencies are met
83
+ StepStateMachine.step_dependencies_met?(step)
84
+ end
85
+
86
+ # No other guard clauses needed!
87
+ # - State transition validation is handled by the transition definitions above
88
+ # - Idempotent transitions are handled by Statesman automatically
89
+ # - Simple state changes (PENDING->ERROR, IN_PROGRESS->COMPLETE, etc.) don't need guards
90
+
91
+ # Frozen constant mapping state transitions to event names
92
+ # This provides O(1) lookup performance and ensures consistency
93
+ TRANSITION_EVENT_MAP = {
94
+ # Initial state transitions (from nil/initial)
95
+ [nil, Constants::WorkflowStepStatuses::PENDING] => Constants::StepEvents::INITIALIZE_REQUESTED,
96
+ [nil, Constants::WorkflowStepStatuses::IN_PROGRESS] => Constants::StepEvents::EXECUTION_REQUESTED,
97
+ [nil, Constants::WorkflowStepStatuses::COMPLETE] => Constants::StepEvents::COMPLETED,
98
+ [nil, Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
99
+ [nil, Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
100
+ [nil, Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY,
101
+
102
+ # Normal state transitions
103
+ [Constants::WorkflowStepStatuses::PENDING,
104
+ Constants::WorkflowStepStatuses::IN_PROGRESS] => Constants::StepEvents::EXECUTION_REQUESTED,
105
+ [Constants::WorkflowStepStatuses::PENDING,
106
+ Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
107
+ [Constants::WorkflowStepStatuses::PENDING,
108
+ Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
109
+ [Constants::WorkflowStepStatuses::PENDING,
110
+ Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY,
111
+
112
+ [Constants::WorkflowStepStatuses::IN_PROGRESS,
113
+ Constants::WorkflowStepStatuses::COMPLETE] => Constants::StepEvents::COMPLETED,
114
+ [Constants::WorkflowStepStatuses::IN_PROGRESS,
115
+ Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
116
+ [Constants::WorkflowStepStatuses::IN_PROGRESS,
117
+ Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
118
+
119
+ [Constants::WorkflowStepStatuses::ERROR,
120
+ Constants::WorkflowStepStatuses::PENDING] => Constants::StepEvents::RETRY_REQUESTED,
121
+ [Constants::WorkflowStepStatuses::ERROR,
122
+ Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY
123
+ }.freeze
124
+
125
+ # Override current_state to work with custom transition model
126
+ # Since WorkflowStepTransition doesn't include Statesman::Adapters::ActiveRecordTransition,
127
+ # we need to implement our own current_state logic using the most_recent column
128
+ def current_state
129
+ most_recent_transition = object.workflow_step_transitions.where(most_recent: true).first
130
+
131
+ if most_recent_transition
132
+ # Ensure we never return empty strings or nil - always return a valid state
133
+ state = most_recent_transition.to_state
134
+ state.presence || Constants::WorkflowStepStatuses::PENDING
135
+ else
136
+ # Return initial state if no transitions exist
137
+ Constants::WorkflowStepStatuses::PENDING
138
+ end
139
+ end
140
+
141
+ # Override Statesman's transition building to ensure proper from_state handling
142
+ # This is called by Statesman when creating new transitions
143
+ def create_transition(from_state, to_state, metadata = {})
144
+ # Ensure from_state is properly set - never allow empty strings
145
+ effective_from_state = case from_state
146
+ when nil, ''
147
+ # For initial transitions or empty strings, use nil
148
+ nil
149
+ else
150
+ # For existing states, ensure it's a valid state
151
+ from_state.presence
152
+ end
153
+
154
+ # Log transition creation for debugging
155
+ Rails.logger.debug do
156
+ "StepStateMachine: Creating transition for step #{object.workflow_step_id}: " \
157
+ "'#{effective_from_state}' → '#{to_state}'"
158
+ end
159
+
160
+ # Get the next sort key
161
+ next_sort_key = next_sort_key_value
162
+
163
+ # Create the transition with proper from_state handling
164
+ transition = Tasker::WorkflowStepTransition.create!(
165
+ workflow_step_id: object.workflow_step_id,
166
+ to_state: to_state,
167
+ from_state: effective_from_state, # Use nil instead of empty string
168
+ most_recent: true,
169
+ sort_key: next_sort_key,
170
+ metadata: metadata || {},
171
+ created_at: Time.current,
172
+ updated_at: Time.current
173
+ )
174
+
175
+ # Update previous transitions to not be most recent
176
+ object.workflow_step_transitions
177
+ .where(most_recent: true)
178
+ .where.not(id: transition.id)
179
+ .update_all(most_recent: false)
180
+
181
+ transition
182
+ end
183
+
184
+ # Get the next sort key for transitions
185
+ def next_sort_key_value
186
+ max_sort_key = object.workflow_step_transitions.maximum(:sort_key) || -1
187
+ max_sort_key + 10 # Use increments of 10 for flexibility
188
+ end
189
+
190
+ # Initialize the state machine with the initial state
191
+ # This ensures the state machine is properly initialized when called explicitly
192
+ # DEFENSIVE: Only creates transitions when explicitly needed
193
+ def initialize_state_machine!
194
+ # Check if state machine is already initialized
195
+ return current_state if Tasker::WorkflowStepTransition.exists?(workflow_step_id: object.workflow_step_id)
196
+
197
+ # DEFENSIVE: Use a rescue block instead of transaction to handle race conditions gracefully
198
+ begin
199
+ # Create the initial transition only if none exists
200
+ initial_transition = Tasker::WorkflowStepTransition.create!(
201
+ workflow_step_id: object.workflow_step_id,
202
+ to_state: Constants::WorkflowStepStatuses::PENDING,
203
+ from_state: nil, # Explicitly set to nil for initial transition
204
+ most_recent: true,
205
+ sort_key: 0,
206
+ metadata: { initialized_by: 'state_machine' },
207
+ created_at: Time.current,
208
+ updated_at: Time.current
209
+ )
210
+
211
+ Rails.logger.debug do
212
+ "StepStateMachine: Initialized state machine for step #{object.workflow_step_id} with initial transition to PENDING"
213
+ end
214
+
215
+ initial_transition.to_state
216
+ rescue ActiveRecord::RecordNotUnique => e
217
+ # Handle duplicate key violations gracefully - another thread may have initialized the state machine
218
+ Rails.logger.debug do
219
+ "StepStateMachine: State machine for step #{object.workflow_step_id} already initialized by another process: #{e.message}"
220
+ end
221
+
222
+ # Return the current state since we know it's initialized
223
+ current_state
224
+ rescue ActiveRecord::StatementInvalid => e
225
+ # Handle transaction issues gracefully
226
+ Rails.logger.warn do
227
+ "StepStateMachine: Transaction issue initializing state machine for step #{object.workflow_step_id}: #{e.message}"
228
+ end
229
+
230
+ # Check if the step actually has transitions now (another process may have created them)
231
+ if Tasker::WorkflowStepTransition.exists?(workflow_step_id: object.workflow_step_id)
232
+ current_state
233
+ else
234
+ # If still no transitions, return the default state without creating a transition
235
+ Constants::WorkflowStepStatuses::PENDING
236
+ end
237
+ end
238
+ end
239
+
240
+ # Class methods for state machine management
241
+ class << self
242
+ # Class-level wrapper methods for guard clause context
243
+ # These delegate to instance methods to provide clean access from guard clauses
244
+
245
+ # Check if a transition is idempotent (current state == target state)
246
+ #
247
+ # @param step [WorkflowStep] The step to check
248
+ # @param target_state [String] The target state
249
+ # @return [Boolean] True if this is an idempotent transition
250
+ def idempotent_transition?(step, target_state)
251
+ current_state = step.state_machine.current_state
252
+ effective_current_state = current_state.presence || Constants::WorkflowStepStatuses::PENDING
253
+ is_idempotent = effective_current_state == target_state
254
+
255
+ if is_idempotent
256
+ Rails.logger.debug do
257
+ "StepStateMachine: Allowing idempotent transition to #{target_state} for step #{step.workflow_step_id}"
258
+ end
259
+ end
260
+
261
+ is_idempotent
262
+ end
263
+
264
+ # Get the effective current state, handling blank/empty states
265
+ #
266
+ # @param step [WorkflowStep] The step to check
267
+ # @return [String] The effective current state (blank states become PENDING)
268
+ def effective_current_state(step)
269
+ current_state = step.state_machine.current_state
270
+ current_state.presence || Constants::WorkflowStepStatuses::PENDING
271
+ end
272
+
273
+ # Log an invalid from-state transition
274
+ #
275
+ # @param step [WorkflowStep] The step
276
+ # @param current_state [String] The current state
277
+ # @param target_state [String] The target state
278
+ # @param reason [String] The reason for the restriction
279
+ def log_invalid_from_state(step, current_state, target_state, reason)
280
+ Rails.logger.debug do
281
+ "StepStateMachine: Cannot transition to #{target_state} from '#{current_state}' " \
282
+ "(step #{step.workflow_step_id}). #{reason}."
283
+ end
284
+ end
285
+
286
+ # Log when dependencies are not met
287
+ #
288
+ # @param step [WorkflowStep] The step
289
+ # @param target_state [String] The target state
290
+ def log_dependencies_not_met(step, target_state)
291
+ Rails.logger.debug do
292
+ "StepStateMachine: Cannot transition step #{step.workflow_step_id} to #{target_state} - " \
293
+ 'dependencies not satisfied. Check parent step completion status.'
294
+ end
295
+ end
296
+
297
+ # Log the result of a transition check
298
+ #
299
+ # @param step [WorkflowStep] The step
300
+ # @param target_state [String] The target state
301
+ # @param result [Boolean] Whether the transition is allowed
302
+ # @param reason [String] The reason for the result
303
+ def log_transition_result(step, target_state, result, reason)
304
+ if result
305
+ Rails.logger.debug do
306
+ "StepStateMachine: Allowing transition to #{target_state} for step #{step.workflow_step_id} (#{reason})"
307
+ end
308
+ else
309
+ Rails.logger.debug do
310
+ "StepStateMachine: Blocking transition to #{target_state} for step #{step.workflow_step_id} (#{reason} failed)"
311
+ end
312
+ end
313
+ end
314
+
315
+ # Check if step dependencies are met
316
+ #
317
+ # @param step [WorkflowStep] The step to check
318
+ # @return [Boolean] True if all dependencies are satisfied
319
+ def step_dependencies_met?(step)
320
+ # Handle cases where step doesn't have parents association or it's not loaded
321
+
322
+ # If step doesn't respond to parents, assume no dependencies
323
+ return true unless step.respond_to?(:parents)
324
+
325
+ # If parents association exists but is empty, no dependencies to check
326
+ parents = step.parents
327
+ return true if parents.blank?
328
+
329
+ # Check if all parent steps are complete
330
+ parents.all? do |parent|
331
+ completion_states = [
332
+ Constants::WorkflowStepStatuses::COMPLETE,
333
+ Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
334
+ ]
335
+ # Use state_machine.current_state to avoid circular reference with parent.status
336
+ current_state = parent.state_machine.current_state
337
+ parent_status = current_state.presence || Constants::WorkflowStepStatuses::PENDING
338
+ is_complete = completion_states.include?(parent_status)
339
+
340
+ unless is_complete
341
+ Rails.logger.debug do
342
+ "StepStateMachine: Step #{step.workflow_step_id} dependency not met - " \
343
+ "parent step #{parent.workflow_step_id} is '#{parent_status}', needs to be complete"
344
+ end
345
+ end
346
+
347
+ is_complete
348
+ end
349
+ rescue StandardError => e
350
+ # If there's an error checking dependencies, log it and assume dependencies are met
351
+ # This prevents dependency checking from blocking execution in edge cases
352
+ Rails.logger.warn do
353
+ "StepStateMachine: Error checking dependencies for step #{step.workflow_step_id}: #{e.message}. " \
354
+ 'Assuming dependencies are met.'
355
+ end
356
+ true
357
+ end
358
+
359
+ # Safely fire a lifecycle event using dry-events bus
360
+ #
361
+ # @param event_name [String] The event name
362
+ # @param context [Hash] The event context
363
+ # @return [void]
364
+ def safe_fire_event(event_name, context = {})
365
+ # Use EventPayloadBuilder for consistent payload structure
366
+ step = extract_step_from_context(context)
367
+ task = step&.task
368
+
369
+ if step && task
370
+ # Determine event type from event name
371
+ event_type = determine_event_type_from_name(event_name)
372
+
373
+ # Use EventPayloadBuilder for standardized payload
374
+ enhanced_context = Tasker::Events::EventPayloadBuilder.build_step_payload(
375
+ step,
376
+ task,
377
+ event_type: event_type,
378
+ additional_context: context
379
+ )
380
+ else
381
+ # Fallback to enhanced context if step/task not available
382
+ enhanced_context = build_standardized_payload(event_name, context)
383
+ end
384
+
385
+ publish_event(event_name, enhanced_context)
386
+ end
387
+
388
+ # Extract step object from context for EventPayloadBuilder
389
+ #
390
+ # @param context [Hash] The event context
391
+ # @return [WorkflowStep, nil] The step object if available
392
+ def extract_step_from_context(context)
393
+ step_id = context[:step_id]
394
+ return nil unless step_id
395
+
396
+ # Try to find the step - handle both string and numeric IDs
397
+ Tasker::WorkflowStep.find_by(workflow_step_id: step_id) ||
398
+ Tasker::WorkflowStep.find_by(id: step_id)
399
+ rescue StandardError => e
400
+ Rails.logger.warn { "Could not find step with ID #{step_id}: #{e.message}" }
401
+ nil
402
+ end
403
+
404
+ # Determine event type from event name for EventPayloadBuilder
405
+ #
406
+ # @param event_name [String] The event name
407
+ # @return [Symbol] The event type
408
+ def determine_event_type_from_name(event_name)
409
+ case event_name
410
+ when /completed/i
411
+ :completed
412
+ when /failed/i, /error/i
413
+ :failed
414
+ when /execution_requested/i, /started/i
415
+ :started
416
+ when /retry/i
417
+ :retry
418
+ when /backoff/i
419
+ :backoff
420
+ else
421
+ :unknown
422
+ end
423
+ end
424
+
425
+ # Build standardized event payload with all expected keys (legacy fallback)
426
+ #
427
+ # @param event_name [String] The event name
428
+ # @param context [Hash] The base context
429
+ # @return [Hash] Enhanced context with standardized payload structure
430
+ def build_standardized_payload(_event_name, context)
431
+ # Base payload with core identifiers
432
+ enhanced_context = {
433
+ # Core identifiers (always present)
434
+ task_id: context[:task_id],
435
+ step_id: context[:step_id],
436
+ step_name: context[:step_name],
437
+
438
+ # State transition information
439
+ from_state: context[:from_state],
440
+ to_state: context[:to_state],
441
+
442
+ # Timing information (provide defaults for missing keys)
443
+ started_at: context[:started_at] || context[:transitioned_at],
444
+ completed_at: context[:completed_at] || context[:transitioned_at],
445
+ execution_duration: context[:execution_duration] || 0.0,
446
+
447
+ # Error information (for error events)
448
+ error_message: context[:error_message] || context[:error] || 'Unknown error',
449
+ exception_class: context[:exception_class] || 'StandardError',
450
+ attempt_number: context[:attempt_number] || 1,
451
+
452
+ # Additional context
453
+ transitioned_at: context[:transitioned_at] || Time.zone.now
454
+ }
455
+
456
+ # Merge in any additional context provided
457
+ enhanced_context.merge!(context.except(
458
+ :task_id, :step_id, :step_name, :from_state, :to_state,
459
+ :started_at, :completed_at, :execution_duration,
460
+ :error_message, :exception_class, :attempt_number, :transitioned_at
461
+ ))
462
+
463
+ enhanced_context
464
+ end
465
+
466
+ # Determine the appropriate event name for a state transition using constant lookup
467
+ #
468
+ # @param from_state [String, nil] The source state
469
+ # @param to_state [String] The target state
470
+ # @return [String, nil] The event name or nil if no mapping exists
471
+ def determine_transition_event_name(from_state, to_state)
472
+ transition_key = [from_state, to_state]
473
+ event_name = TRANSITION_EVENT_MAP[transition_key]
474
+
475
+ if event_name.nil?
476
+ # For unexpected transitions, log a warning and return nil to skip event firing
477
+ Rails.logger.warn do
478
+ "Unexpected step state transition: #{from_state || 'initial'} → #{to_state}. " \
479
+ 'No event will be fired for this transition.'
480
+ end
481
+ end
482
+
483
+ event_name
484
+ end
485
+ end
486
+
487
+ private
488
+
489
+ # Safely fire a lifecycle event
490
+ #
491
+ # @param event_name [String] The event name
492
+ # @param context [Hash] The event context
493
+ # @return [void]
494
+ def safe_fire_event(event_name, context = {})
495
+ self.class.safe_fire_event(event_name, context)
496
+ end
497
+
498
+ # Determine the appropriate event name for a state transition
499
+ #
500
+ # @param from_state [String, nil] The source state
501
+ # @param to_state [String] The target state
502
+ # @return [String] The event name
503
+ def determine_transition_event_name(from_state, to_state)
504
+ self.class.determine_transition_event_name(from_state, to_state)
505
+ end
506
+ end
507
+ end
508
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'statesman'
4
+ require_relative '../constants'
5
+ require 'tasker/events/event_payload_builder'
6
+ require_relative '../concerns/event_publisher'
7
+
8
+ module Tasker
9
+ module StateMachine
10
+ # TaskStateMachine defines state transitions for tasks using Statesman
11
+ #
12
+ # This state machine manages task lifecycle states and integrates with
13
+ # the existing event system to provide declarative state management.
14
+ class TaskStateMachine
15
+ include Statesman::Machine
16
+ extend Tasker::Concerns::EventPublisher
17
+
18
+ # Define all task states using existing constants
19
+ state Constants::TaskStatuses::PENDING, initial: true
20
+ state Constants::TaskStatuses::IN_PROGRESS
21
+ state Constants::TaskStatuses::COMPLETE
22
+ state Constants::TaskStatuses::ERROR
23
+ state Constants::TaskStatuses::CANCELLED
24
+ state Constants::TaskStatuses::RESOLVED_MANUALLY
25
+
26
+ # Define state transitions based on existing StateTransition definitions
27
+ # Fixed: Added missing transitions that guard clauses were handling
28
+ transition from: Constants::TaskStatuses::PENDING,
29
+ to: [Constants::TaskStatuses::IN_PROGRESS,
30
+ Constants::TaskStatuses::CANCELLED,
31
+ Constants::TaskStatuses::ERROR] # Allow direct error from pending
32
+
33
+ transition from: Constants::TaskStatuses::IN_PROGRESS,
34
+ to: [Constants::TaskStatuses::COMPLETE,
35
+ Constants::TaskStatuses::ERROR,
36
+ Constants::TaskStatuses::CANCELLED,
37
+ Constants::TaskStatuses::PENDING] # Allow reset to pending
38
+
39
+ transition from: Constants::TaskStatuses::ERROR,
40
+ to: [Constants::TaskStatuses::PENDING,
41
+ Constants::TaskStatuses::RESOLVED_MANUALLY]
42
+
43
+ # Allow cancellation from complete/error states (admin override scenarios)
44
+ transition from: Constants::TaskStatuses::COMPLETE,
45
+ to: Constants::TaskStatuses::CANCELLED
46
+
47
+ transition from: Constants::TaskStatuses::RESOLVED_MANUALLY,
48
+ to: Constants::TaskStatuses::CANCELLED
49
+
50
+ # Callbacks for lifecycle event integration
51
+ before_transition do |task, transition|
52
+ # Handle idempotent transitions using existing helper method
53
+ if TaskStateMachine.idempotent_transition?(task, transition.to_state)
54
+ # Abort the transition by raising GuardFailedError
55
+ raise Statesman::GuardFailedError, "Already in target state #{transition.to_state}"
56
+ end
57
+
58
+ # Log the transition for debugging
59
+ effective_current_state = TaskStateMachine.effective_current_state(task)
60
+ Rails.logger.debug do
61
+ "Task #{task.task_id} transitioning from #{effective_current_state} to #{transition.to_state}"
62
+ end
63
+
64
+ # Fire before transition event
65
+ TaskStateMachine.safe_fire_event(
66
+ Constants::TaskEvents::BEFORE_TRANSITION,
67
+ {
68
+ task_id: task.task_id,
69
+ from_state: transition.from_state,
70
+ to_state: transition.to_state,
71
+ transition_event: TaskStateMachine.determine_transition_event_name(transition.from_state,
72
+ transition.to_state)
73
+ }
74
+ )
75
+ end
76
+
77
+ after_transition do |task, transition|
78
+ # Determine the appropriate event name based on the transition
79
+ event_name = determine_transition_event_name(transition.from_state, transition.to_state)
80
+
81
+ # Only fire the event if we have a valid event name
82
+ if event_name
83
+ # Fire the lifecycle event with task context
84
+ TaskStateMachine.safe_fire_event(
85
+ event_name,
86
+ {
87
+ task_id: task.task_id,
88
+ task_name: task.name,
89
+ task_context: task.context,
90
+ from_state: transition.from_state,
91
+ to_state: transition.to_state,
92
+ transitioned_at: Time.zone.now
93
+ }
94
+ )
95
+ end
96
+ end
97
+
98
+ # We do not transition to complete unless the steps are also complete
99
+ guard_transition(to: Constants::TaskStatuses::COMPLETE) do |task, _transition|
100
+ task.all_steps_complete?
101
+ end
102
+
103
+ # No other guard clauses needed!
104
+ # - State transition validation is handled by the transition definitions above
105
+ # - Idempotent transitions are handled by Statesman automatically
106
+ # - Simple state changes don't need business logic validation
107
+
108
+ # Override current_state to work with custom transition model
109
+ # Since TaskTransition doesn't include Statesman::Adapters::ActiveRecordTransition,
110
+ # we need to implement our own current_state logic using the most_recent column
111
+ def current_state
112
+ most_recent_transition = object.task_transitions.where(most_recent: true).first
113
+
114
+ if most_recent_transition
115
+ most_recent_transition.to_state
116
+ else
117
+ # Return initial state if no transitions exist
118
+ Constants::TaskStatuses::PENDING
119
+ end
120
+ end
121
+
122
+ # Class methods for state machine management
123
+ class << self
124
+ # Check if a transition is idempotent (current state == target state)
125
+ #
126
+ # @param task [Task] The task to check
127
+ # @param target_state [String] The target state
128
+ # @return [Boolean] True if this is an idempotent transition
129
+ def idempotent_transition?(task, target_state)
130
+ task.state_machine.current_state == target_state
131
+ end
132
+
133
+ # Get the effective current state, handling blank/empty states
134
+ #
135
+ # @param task [Task] The task to check
136
+ # @return [String] The effective current state (blank states become PENDING)
137
+ def effective_current_state(task)
138
+ current_state = task.state_machine.current_state
139
+ current_state.presence || Constants::TaskStatuses::PENDING
140
+ end
141
+
142
+ # Safely fire a lifecycle event using dry-events bus
143
+ #
144
+ # @param event_name [String] The event name
145
+ # @param context [Hash] The event context
146
+ # @return [void]
147
+ def safe_fire_event(event_name, context = {})
148
+ publish_event(event_name, context)
149
+ end
150
+
151
+ # Determine the transition event name based on states using hashmap lookup
152
+ #
153
+ # @param from_state [String] The from state
154
+ # @param to_state [String] The to state
155
+ # @return [String, nil] The event name or nil if no mapping exists
156
+ def determine_transition_event_name(from_state, to_state)
157
+ transition_key = [from_state, to_state]
158
+ event_name = Constants::TASK_TRANSITION_EVENT_MAP[transition_key]
159
+
160
+ if event_name.nil?
161
+ Rails.logger.warn do
162
+ "Unexpected task state transition: #{from_state || 'initial'} → #{to_state}. " \
163
+ 'No event will be fired for this transition.'
164
+ end
165
+ end
166
+
167
+ event_name
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ # Safely fire a lifecycle event
174
+ #
175
+ # @param event_name [String] The event name
176
+ # @param context [Hash] The event context
177
+ # @return [void]
178
+ def safe_fire_event(event_name, context = {})
179
+ self.class.safe_fire_event(event_name, context)
180
+ end
181
+
182
+ # Determine the appropriate event name for a state transition
183
+ #
184
+ # @param from_state [String, nil] The source state
185
+ # @param to_state [String] The target state
186
+ # @return [String] The event name
187
+ def determine_transition_event_name(from_state, to_state)
188
+ self.class.determine_transition_event_name(from_state, to_state)
189
+ end
190
+ end
191
+ end
192
+ end