tasker-engine 0.1.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 (601) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +440 -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/tasks_controller.rb +123 -0
  13. data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
  14. data/app/graphql/examples/all_tasks.graphql +22 -0
  15. data/app/graphql/examples/pending_tasks.graphql +23 -0
  16. data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
  17. data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
  18. data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
  19. data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
  20. data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
  21. data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
  22. data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
  23. data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
  24. data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
  25. data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
  26. data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
  27. data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
  28. data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
  29. data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
  30. data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
  31. data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
  32. data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
  33. data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
  34. data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
  35. data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
  36. data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
  37. data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
  38. data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
  39. data/app/graphql/tasker/graph_ql_types.rb +42 -0
  40. data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
  41. data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
  42. data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
  43. data/app/graphql/tasker/mutations/create_task.rb +52 -0
  44. data/app/graphql/tasker/mutations/update_step.rb +36 -0
  45. data/app/graphql/tasker/mutations/update_task.rb +41 -0
  46. data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
  47. data/app/graphql/tasker/queries/all_tasks.rb +23 -0
  48. data/app/graphql/tasker/queries/base_query.rb +9 -0
  49. data/app/graphql/tasker/queries/helpers.rb +16 -0
  50. data/app/graphql/tasker/queries/one_step.rb +24 -0
  51. data/app/graphql/tasker/queries/one_task.rb +18 -0
  52. data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
  53. data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
  54. data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
  55. data/app/jobs/tasker/application_job.rb +8 -0
  56. data/app/jobs/tasker/metrics_export_job.rb +252 -0
  57. data/app/jobs/tasker/task_runner_job.rb +224 -0
  58. data/app/models/tasker/annotation_type.rb +26 -0
  59. data/app/models/tasker/application_record.rb +70 -0
  60. data/app/models/tasker/dependent_system.rb +26 -0
  61. data/app/models/tasker/dependent_system_object_map.rb +64 -0
  62. data/app/models/tasker/named_step.rb +41 -0
  63. data/app/models/tasker/named_task.rb +121 -0
  64. data/app/models/tasker/named_tasks_named_step.rb +82 -0
  65. data/app/models/tasker/step_dag_relationship.rb +65 -0
  66. data/app/models/tasker/step_readiness_status.rb +59 -0
  67. data/app/models/tasker/task.rb +414 -0
  68. data/app/models/tasker/task_annotation.rb +36 -0
  69. data/app/models/tasker/task_execution_context.rb +29 -0
  70. data/app/models/tasker/task_namespace.rb +41 -0
  71. data/app/models/tasker/task_transition.rb +235 -0
  72. data/app/models/tasker/workflow_step.rb +461 -0
  73. data/app/models/tasker/workflow_step_edge.rb +95 -0
  74. data/app/models/tasker/workflow_step_transition.rb +434 -0
  75. data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
  76. data/app/serializers/tasker/handler_serializer.rb +109 -0
  77. data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
  78. data/app/serializers/tasker/task_serializer.rb +168 -0
  79. data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
  80. data/app/services/tasker/analytics_service.rb +409 -0
  81. data/config/initializers/dry_struct.rb +11 -0
  82. data/config/initializers/statesman.rb +6 -0
  83. data/config/initializers/tasker_orchestration.rb +17 -0
  84. data/config/initializers/time_formats.rb +4 -0
  85. data/config/routes.rb +34 -0
  86. data/config/tasker/subscriptions/example_integrations.yml +67 -0
  87. data/config/tasker/system_events.yml +305 -0
  88. data/db/functions/calculate_dependency_levels_v01.sql +45 -0
  89. data/db/functions/get_analytics_metrics_v01.sql +137 -0
  90. data/db/functions/get_slowest_steps_v01.sql +82 -0
  91. data/db/functions/get_slowest_tasks_v01.sql +96 -0
  92. data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
  93. data/db/functions/get_step_readiness_status_single_and_batch_v02.sql +223 -0
  94. data/db/functions/get_step_readiness_status_v01.sql +139 -0
  95. data/db/functions/get_system_health_counts_v01.sql +108 -0
  96. data/db/functions/get_task_execution_context_v01.sql +108 -0
  97. data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
  98. data/db/init/schema.sql +2254 -0
  99. data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
  100. data/db/migrate/20250710110830_step_readiness_sql_functions_v02.rb +39 -0
  101. data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
  102. data/docs/APPLICATION_GENERATOR.md +384 -0
  103. data/docs/AUTH.md +1741 -0
  104. data/docs/CIRCUIT_BREAKER.md +224 -0
  105. data/docs/DEVELOPER_GUIDE.md +2664 -0
  106. data/docs/EVENT_SYSTEM.md +637 -0
  107. data/docs/EXECUTION_CONFIGURATION.md +341 -0
  108. data/docs/FLOW_CHART.md +149 -0
  109. data/docs/HEALTH.md +542 -0
  110. data/docs/METRICS.md +731 -0
  111. data/docs/OPTIMIZATION_PLAN.md +1479 -0
  112. data/docs/OVERVIEW.md +548 -0
  113. data/docs/QUICK_START.md +270 -0
  114. data/docs/REGISTRY_SYSTEMS.md +373 -0
  115. data/docs/REST_API.md +632 -0
  116. data/docs/REVERSIONING.md +404 -0
  117. data/docs/ROADMAP.md +221 -0
  118. data/docs/SQL_FUNCTIONS.md +1408 -0
  119. data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
  120. data/docs/TELEMETRY.md +795 -0
  121. data/docs/TROUBLESHOOTING.md +756 -0
  122. data/docs/TaskHandlerGenerator.html +255 -0
  123. data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
  124. data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
  125. data/docs/Tasker/Analysis.html +117 -0
  126. data/docs/Tasker/AnalyticsController.html +450 -0
  127. data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
  128. data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
  129. data/docs/Tasker/AnalyticsService.html +2221 -0
  130. data/docs/Tasker/AnnotationType.html +137 -0
  131. data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
  132. data/docs/Tasker/ApplicationController.html +147 -0
  133. data/docs/Tasker/ApplicationJob.html +128 -0
  134. data/docs/Tasker/ApplicationRecord.html +378 -0
  135. data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
  136. data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
  137. data/docs/Tasker/Authentication/Coordinator.html +242 -0
  138. data/docs/Tasker/Authentication/Interface.html +560 -0
  139. data/docs/Tasker/Authentication/InterfaceError.html +124 -0
  140. data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
  141. data/docs/Tasker/Authentication.html +119 -0
  142. data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
  143. data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
  144. data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
  145. data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
  146. data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +360 -0
  147. data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
  148. data/docs/Tasker/Authorization/ResourceRegistry.html +875 -0
  149. data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
  150. data/docs/Tasker/Authorization.html +582 -0
  151. data/docs/Tasker/CacheCapabilities.html +167 -0
  152. data/docs/Tasker/CacheStrategy.html +1297 -0
  153. data/docs/Tasker/Concerns/Authenticatable.html +116 -0
  154. data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
  155. data/docs/Tasker/Concerns/Authorizable.html +816 -0
  156. data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
  157. data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
  158. data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
  159. data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
  160. data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
  161. data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
  162. data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
  163. data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
  164. data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
  165. data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
  166. data/docs/Tasker/Concerns.html +117 -0
  167. data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
  168. data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
  169. data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
  170. data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
  171. data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
  172. data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
  173. data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
  174. data/docs/Tasker/Configuration.html +1669 -0
  175. data/docs/Tasker/ConfigurationError.html +143 -0
  176. data/docs/Tasker/ConfiguredTask.html +514 -0
  177. data/docs/Tasker/Constants/EventDefinitions.html +590 -0
  178. data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
  179. data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
  180. data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
  181. data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
  182. data/docs/Tasker/Constants/RegistryEvents.html +285 -0
  183. data/docs/Tasker/Constants/StepEvents.html +177 -0
  184. data/docs/Tasker/Constants/TaskEvents.html +167 -0
  185. data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
  186. data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
  187. data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
  188. data/docs/Tasker/Constants/TaskExecution.html +126 -0
  189. data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
  190. data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
  191. data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
  192. data/docs/Tasker/Constants/TaskFinalization.html +126 -0
  193. data/docs/Tasker/Constants/TaskStatuses.html +223 -0
  194. data/docs/Tasker/Constants/TestEvents.html +163 -0
  195. data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
  196. data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
  197. data/docs/Tasker/Constants.html +561 -0
  198. data/docs/Tasker/DependentSystem.html +137 -0
  199. data/docs/Tasker/DependentSystemObjectMap.html +250 -0
  200. data/docs/Tasker/DetectorRegistry.html +598 -0
  201. data/docs/Tasker/Diagram/Edge.html +1191 -0
  202. data/docs/Tasker/Diagram/Flowchart.html +1539 -0
  203. data/docs/Tasker/Diagram/Node.html +1165 -0
  204. data/docs/Tasker/Diagram.html +117 -0
  205. data/docs/Tasker/Engine.html +215 -0
  206. data/docs/Tasker/Error.html +139 -0
  207. data/docs/Tasker/Events/Bus.html +1226 -0
  208. data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
  209. data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
  210. data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
  211. data/docs/Tasker/Events/Catalog.html +1291 -0
  212. data/docs/Tasker/Events/CustomRegistry.html +943 -0
  213. data/docs/Tasker/Events/DefinitionLoader.html +575 -0
  214. data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
  215. data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
  216. data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
  217. data/docs/Tasker/Events/Publisher.html +365 -0
  218. data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
  219. data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
  220. data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
  221. data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
  222. data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
  223. data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
  224. data/docs/Tasker/Events/Subscribers.html +117 -0
  225. data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
  226. data/docs/Tasker/Events.html +294 -0
  227. data/docs/Tasker/EventsGenerator.html +459 -0
  228. data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
  229. data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
  230. data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
  231. data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
  232. data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
  233. data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
  234. data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
  235. data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
  236. data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
  237. data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
  238. data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
  239. data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
  240. data/docs/Tasker/Functions.html +117 -0
  241. data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
  242. data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
  243. data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
  244. data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
  245. data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
  246. data/docs/Tasker/Generators.html +117 -0
  247. data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
  248. data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
  249. data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
  250. data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
  251. data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
  252. data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
  253. data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
  254. data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
  255. data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
  256. data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
  257. data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
  258. data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
  259. data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
  260. data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
  261. data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
  262. data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
  263. data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
  264. data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
  265. data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
  266. data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
  267. data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
  268. data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
  269. data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
  270. data/docs/Tasker/GraphQLTypes.html +130 -0
  271. data/docs/Tasker/GraphqlController.html +251 -0
  272. data/docs/Tasker/HandlerFactory.html +1528 -0
  273. data/docs/Tasker/HandlerSerializer.html +682 -0
  274. data/docs/Tasker/HandlersController.html +574 -0
  275. data/docs/Tasker/HashIdentityStrategy.html +278 -0
  276. data/docs/Tasker/Health/ReadinessChecker.html +712 -0
  277. data/docs/Tasker/Health/StatusChecker.html +653 -0
  278. data/docs/Tasker/Health.html +117 -0
  279. data/docs/Tasker/HealthController.html +523 -0
  280. data/docs/Tasker/IdentityStrategy.html +276 -0
  281. data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
  282. data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
  283. data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
  284. data/docs/Tasker/LifecycleEvents/Events.html +204 -0
  285. data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
  286. data/docs/Tasker/LifecycleEvents.html +799 -0
  287. data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
  288. data/docs/Tasker/Logging.html +115 -0
  289. data/docs/Tasker/MetricsController.html +293 -0
  290. data/docs/Tasker/MetricsExportJob.html +414 -0
  291. data/docs/Tasker/Mutations/BaseMutation.html +128 -0
  292. data/docs/Tasker/Mutations/CancelStep.html +219 -0
  293. data/docs/Tasker/Mutations/CancelTask.html +221 -0
  294. data/docs/Tasker/Mutations/CreateTask.html +243 -0
  295. data/docs/Tasker/Mutations/UpdateStep.html +243 -0
  296. data/docs/Tasker/Mutations/UpdateTask.html +243 -0
  297. data/docs/Tasker/Mutations.html +117 -0
  298. data/docs/Tasker/NamedStep.html +216 -0
  299. data/docs/Tasker/NamedTask.html +910 -0
  300. data/docs/Tasker/NamedTasksNamedStep.html +435 -0
  301. data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
  302. data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
  303. data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
  304. data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
  305. data/docs/Tasker/Orchestration/Coordinator.html +641 -0
  306. data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
  307. data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
  308. data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
  309. data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
  310. data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
  311. data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
  312. data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
  313. data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
  314. data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
  315. data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
  316. data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
  317. data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
  318. data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
  319. data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
  320. data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
  321. data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
  322. data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
  323. data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
  324. data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
  325. data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
  326. data/docs/Tasker/Orchestration.html +130 -0
  327. data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
  328. data/docs/Tasker/PageSort.html +247 -0
  329. data/docs/Tasker/PermanentError.html +518 -0
  330. data/docs/Tasker/ProceduralError.html +147 -0
  331. data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
  332. data/docs/Tasker/Queries/AllTasks.html +221 -0
  333. data/docs/Tasker/Queries/BaseQuery.html +128 -0
  334. data/docs/Tasker/Queries/Helpers.html +187 -0
  335. data/docs/Tasker/Queries/OneStep.html +225 -0
  336. data/docs/Tasker/Queries/OneTask.html +217 -0
  337. data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
  338. data/docs/Tasker/Queries/TasksByStatus.html +233 -0
  339. data/docs/Tasker/Queries.html +119 -0
  340. data/docs/Tasker/Railtie.html +124 -0
  341. data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
  342. data/docs/Tasker/Registry/EventPublisher.html +667 -0
  343. data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
  344. data/docs/Tasker/Registry/RegistrationError.html +132 -0
  345. data/docs/Tasker/Registry/RegistryError.html +139 -0
  346. data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
  347. data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
  348. data/docs/Tasker/Registry/ValidationError.html +132 -0
  349. data/docs/Tasker/Registry.html +119 -0
  350. data/docs/Tasker/RetryableError.html +515 -0
  351. data/docs/Tasker/StateMachine/Compatibility.html +282 -0
  352. data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
  353. data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
  354. data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
  355. data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
  356. data/docs/Tasker/StateMachine.html +602 -0
  357. data/docs/Tasker/StepDagRelationship.html +657 -0
  358. data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
  359. data/docs/Tasker/StepHandler/Api.html +884 -0
  360. data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
  361. data/docs/Tasker/StepHandler/Base.html +970 -0
  362. data/docs/Tasker/StepHandler.html +119 -0
  363. data/docs/Tasker/StepReadinessStatus.html +836 -0
  364. data/docs/Tasker/Task.html +2478 -0
  365. data/docs/Tasker/TaskAnnotation.html +137 -0
  366. data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
  367. data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
  368. data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
  369. data/docs/Tasker/TaskBuilder.html +764 -0
  370. data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
  371. data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
  372. data/docs/Tasker/TaskDiagram.html +548 -0
  373. data/docs/Tasker/TaskDiagramsController.html +240 -0
  374. data/docs/Tasker/TaskExecutionContext.html +469 -0
  375. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
  376. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
  377. data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
  378. data/docs/Tasker/TaskHandler/ClassMethods.html +395 -0
  379. data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
  380. data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
  381. data/docs/Tasker/TaskHandler.html +271 -0
  382. data/docs/Tasker/TaskNamespace.html +312 -0
  383. data/docs/Tasker/TaskRunnerJob.html +406 -0
  384. data/docs/Tasker/TaskSerializer.html +474 -0
  385. data/docs/Tasker/TaskTransition.html +1517 -0
  386. data/docs/Tasker/TaskWorkflowSummary.html +988 -0
  387. data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
  388. data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
  389. data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
  390. data/docs/Tasker/TaskerRailsSchema.html +384 -0
  391. data/docs/Tasker/TasksController.html +595 -0
  392. data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
  393. data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
  394. data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
  395. data/docs/Tasker/Telemetry/Events.html +115 -0
  396. data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
  397. data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
  398. data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
  399. data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
  400. data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
  401. data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
  402. data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
  403. data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
  404. data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
  405. data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
  406. data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
  407. data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
  408. data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
  409. data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
  410. data/docs/Tasker/Telemetry/Plugins.html +117 -0
  411. data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
  412. data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
  413. data/docs/Tasker/Telemetry.html +130 -0
  414. data/docs/Tasker/Types/AuthConfig.html +886 -0
  415. data/docs/Tasker/Types/BackoffConfig.html +1063 -0
  416. data/docs/Tasker/Types/BaseConfig.html +227 -0
  417. data/docs/Tasker/Types/CacheConfig.html +1731 -0
  418. data/docs/Tasker/Types/DatabaseConfig.html +388 -0
  419. data/docs/Tasker/Types/DependencyGraph.html +526 -0
  420. data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
  421. data/docs/Tasker/Types/EngineConfig.html +1181 -0
  422. data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
  423. data/docs/Tasker/Types/GraphEdge.html +517 -0
  424. data/docs/Tasker/Types/GraphMetadata.html +781 -0
  425. data/docs/Tasker/Types/GraphNode.html +694 -0
  426. data/docs/Tasker/Types/HealthConfig.html +784 -0
  427. data/docs/Tasker/Types/StepSequence.html +353 -0
  428. data/docs/Tasker/Types/StepTemplate.html +1193 -0
  429. data/docs/Tasker/Types/TaskRequest.html +1179 -0
  430. data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
  431. data/docs/Tasker/Types.html +154 -0
  432. data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
  433. data/docs/Tasker/WorkflowStep.html +2724 -0
  434. data/docs/Tasker/WorkflowStepEdge.html +306 -0
  435. data/docs/Tasker/WorkflowStepSerializer.html +305 -0
  436. data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
  437. data/docs/Tasker/WorkflowStepTransition.html +2201 -0
  438. data/docs/Tasker/WorkflowStepsController.html +462 -0
  439. data/docs/Tasker.html +468 -0
  440. data/docs/VISION.md +584 -0
  441. data/docs/WHY.md +21 -0
  442. data/docs/_index.html +2319 -0
  443. data/docs/class_list.html +54 -0
  444. data/docs/css/common.css +1 -0
  445. data/docs/css/full_list.css +58 -0
  446. data/docs/css/style.css +503 -0
  447. data/docs/events/migration_plan_outcomes.md +80 -0
  448. data/docs/file.README.html +537 -0
  449. data/docs/file_list.html +59 -0
  450. data/docs/frames.html +22 -0
  451. data/docs/index.html +537 -0
  452. data/docs/js/app.js +344 -0
  453. data/docs/js/full_list.js +242 -0
  454. data/docs/js/jquery.js +4 -0
  455. data/docs/method_list.html +8854 -0
  456. data/docs/top-level-namespace.html +110 -0
  457. data/lib/generators/tasker/authenticator_generator.rb +301 -0
  458. data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
  459. data/lib/generators/tasker/events_generator.rb +91 -0
  460. data/lib/generators/tasker/subscriber_generator.rb +107 -0
  461. data/lib/generators/tasker/task_handler_generator.rb +138 -0
  462. data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
  463. data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
  464. data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +82 -0
  465. data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +136 -0
  466. data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
  467. data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
  468. data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
  469. data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
  470. data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
  471. data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
  472. data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
  473. data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
  474. data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
  475. data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
  476. data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
  477. data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
  478. data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
  479. data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
  480. data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
  481. data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
  482. data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
  483. data/lib/generators/tasker/templates/task_handler.rb.erb +60 -0
  484. data/lib/generators/tasker/templates/task_handler_spec.rb.erb +165 -0
  485. data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
  486. data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
  487. data/lib/tasker/authentication/coordinator.rb +78 -0
  488. data/lib/tasker/authentication/errors.rb +9 -0
  489. data/lib/tasker/authentication/interface.rb +36 -0
  490. data/lib/tasker/authentication/none_authenticator.rb +26 -0
  491. data/lib/tasker/authorization/base_coordinator.rb +112 -0
  492. data/lib/tasker/authorization/errors.rb +26 -0
  493. data/lib/tasker/authorization/resource_constants.rb +73 -0
  494. data/lib/tasker/authorization/resource_registry.rb +136 -0
  495. data/lib/tasker/authorization.rb +75 -0
  496. data/lib/tasker/cache_capabilities.rb +131 -0
  497. data/lib/tasker/cache_strategy.rb +469 -0
  498. data/lib/tasker/concerns/authenticatable.rb +41 -0
  499. data/lib/tasker/concerns/authorizable.rb +204 -0
  500. data/lib/tasker/concerns/controller_authorizable.rb +124 -0
  501. data/lib/tasker/concerns/event_publisher.rb +716 -0
  502. data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
  503. data/lib/tasker/concerns/state_machine_base.rb +218 -0
  504. data/lib/tasker/concerns/structured_logging.rb +387 -0
  505. data/lib/tasker/configuration.rb +325 -0
  506. data/lib/tasker/constants/event_definitions.rb +147 -0
  507. data/lib/tasker/constants/registry_events.rb +54 -0
  508. data/lib/tasker/constants.rb +417 -0
  509. data/lib/tasker/engine.rb +90 -0
  510. data/lib/tasker/errors.rb +90 -0
  511. data/lib/tasker/events/catalog.rb +432 -0
  512. data/lib/tasker/events/custom_registry.rb +175 -0
  513. data/lib/tasker/events/definition_loader.rb +199 -0
  514. data/lib/tasker/events/event_payload_builder.rb +461 -0
  515. data/lib/tasker/events/publisher.rb +149 -0
  516. data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
  517. data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
  518. data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
  519. data/lib/tasker/events/subscription_loader.rb +161 -0
  520. data/lib/tasker/events.rb +37 -0
  521. data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
  522. data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
  523. data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
  524. data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
  525. data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
  526. data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
  527. data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
  528. data/lib/tasker/functions/function_wrapper.rb +42 -0
  529. data/lib/tasker/functions.rb +12 -0
  530. data/lib/tasker/handler_factory.rb +327 -0
  531. data/lib/tasker/health/readiness_checker.rb +186 -0
  532. data/lib/tasker/health/status_checker.rb +203 -0
  533. data/lib/tasker/identity_strategy.rb +38 -0
  534. data/lib/tasker/logging/correlation_id_generator.rb +120 -0
  535. data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
  536. data/lib/tasker/orchestration/connection_builder.rb +122 -0
  537. data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
  538. data/lib/tasker/orchestration/coordinator.rb +119 -0
  539. data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
  540. data/lib/tasker/orchestration/plugin_integration.rb +124 -0
  541. data/lib/tasker/orchestration/response_processor.rb +168 -0
  542. data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
  543. data/lib/tasker/orchestration/step_executor.rb +941 -0
  544. data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
  545. data/lib/tasker/orchestration/task_finalizer.rb +564 -0
  546. data/lib/tasker/orchestration/task_initializer.rb +140 -0
  547. data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
  548. data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
  549. data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
  550. data/lib/tasker/orchestration.rb +45 -0
  551. data/lib/tasker/railtie.rb +9 -0
  552. data/lib/tasker/registry/base_registry.rb +177 -0
  553. data/lib/tasker/registry/event_publisher.rb +91 -0
  554. data/lib/tasker/registry/interface_validator.rb +140 -0
  555. data/lib/tasker/registry/statistics_collector.rb +381 -0
  556. data/lib/tasker/registry/subscriber_registry.rb +285 -0
  557. data/lib/tasker/registry.rb +22 -0
  558. data/lib/tasker/state_machine/step_state_machine.rb +508 -0
  559. data/lib/tasker/state_machine/task_state_machine.rb +192 -0
  560. data/lib/tasker/state_machine.rb +83 -0
  561. data/lib/tasker/step_handler/api.rb +410 -0
  562. data/lib/tasker/step_handler/base.rb +206 -0
  563. data/lib/tasker/task_builder.rb +432 -0
  564. data/lib/tasker/task_handler/class_methods.rb +327 -0
  565. data/lib/tasker/task_handler/instance_methods.rb +293 -0
  566. data/lib/tasker/task_handler/step_group.rb +182 -0
  567. data/lib/tasker/task_handler.rb +43 -0
  568. data/lib/tasker/telemetry/event_mapping.rb +126 -0
  569. data/lib/tasker/telemetry/event_router.rb +318 -0
  570. data/lib/tasker/telemetry/events/export_events.rb +38 -0
  571. data/lib/tasker/telemetry/export_coordinator.rb +497 -0
  572. data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
  573. data/lib/tasker/telemetry/log_backend.rb +224 -0
  574. data/lib/tasker/telemetry/metric_types.rb +416 -0
  575. data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
  576. data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
  577. data/lib/tasker/telemetry/plugin_registry.rb +333 -0
  578. data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
  579. data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
  580. data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
  581. data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
  582. data/lib/tasker/telemetry/trace_backend.rb +186 -0
  583. data/lib/tasker/telemetry.rb +59 -0
  584. data/lib/tasker/types/auth_config.rb +81 -0
  585. data/lib/tasker/types/backoff_config.rb +142 -0
  586. data/lib/tasker/types/cache_config.rb +257 -0
  587. data/lib/tasker/types/database_config.rb +39 -0
  588. data/lib/tasker/types/dependency_graph.rb +225 -0
  589. data/lib/tasker/types/dependency_graph_config.rb +149 -0
  590. data/lib/tasker/types/engine_config.rb +131 -0
  591. data/lib/tasker/types/execution_config.rb +289 -0
  592. data/lib/tasker/types/health_config.rb +84 -0
  593. data/lib/tasker/types/step_sequence.rb +24 -0
  594. data/lib/tasker/types/step_template.rb +63 -0
  595. data/lib/tasker/types/task_request.rb +60 -0
  596. data/lib/tasker/types/telemetry_config.rb +273 -0
  597. data/lib/tasker/types.rb +64 -0
  598. data/lib/tasker/version.rb +8 -0
  599. data/lib/tasker.rb +82 -0
  600. data/lib/tasks/tasker_tasks.rake +383 -0
  601. metadata +954 -0
@@ -0,0 +1,2664 @@
1
+ # Tasker Developer Guide
2
+
3
+ ## Overview
4
+
5
+ This guide provides a comprehensive overview of developing with Tasker, covering all the key components that make up the workflow engine. Tasker is designed around six main developer-facing components:
6
+
7
+ 1. **TaskNamespaces & Versioning** - Organize and version task handlers for scalable workflow management
8
+ 2. **Task Handlers** - Define and coordinate multi-step workflows
9
+ 3. **Step Handlers** - Implement the business logic for individual workflow steps
10
+ 4. **Event Subscribers** - Create integrations with external services and monitoring systems
11
+ 5. **YAML Configuration** - Declarative workflow and step configuration with namespace support
12
+ 6. **Authentication & Authorization** - Secure your workflows with flexible authentication strategies
13
+
14
+ ## New in Tasker 2.3.0: TaskNamespace + Versioning Architecture
15
+
16
+ Tasker now supports **hierarchical task organization** and **semantic versioning** for enterprise-scale workflow management:
17
+
18
+ ### Key Benefits
19
+ - **Namespace Isolation**: Organize tasks by domain (`payments`, `inventory`, `notifications`)
20
+ - **Version Coexistence**: Multiple versions of the same task can run simultaneously
21
+ - **Zero Breaking Changes**: Existing tasks continue working with automatic defaults
22
+ - **Enterprise Scalability**: Clean separation of concerns for large organizations
23
+
24
+ ### Quick Example
25
+ ```ruby
26
+ # Create namespaced task handlers
27
+ task_request = Tasker::Types::TaskRequest.new(
28
+ name: 'process_order',
29
+ namespace: 'payments', # NEW: Namespace organization
30
+ version: '2.1.0', # NEW: Semantic versioning
31
+ context: { order_id: 123 }
32
+ )
33
+
34
+ # Handler lookup with namespace + version
35
+ handler = Tasker::HandlerFactory.instance.get(
36
+ 'process_order',
37
+ namespace_name: 'payments',
38
+ version: '2.1.0'
39
+ )
40
+ ```
41
+
42
+ ### Namespace Organization Patterns
43
+ - **`payments`** - Payment processing, billing, refunds
44
+ - **`inventory`** - Stock management, fulfillment, warehouse operations
45
+ - **`notifications`** - Email, SMS, push notifications, alerts
46
+ - **`integrations`** - Third-party APIs, webhooks, data synchronization
47
+ - **`data_processing`** - ETL, data transformation, analytics pipelines
48
+ - **`default`** - General workflows (automatic fallback when unspecified)
49
+
50
+ ## Architecture Overview
51
+
52
+ ```mermaid
53
+ flowchart TB
54
+ subgraph Config["YAML Configuration"]
55
+ TaskYAML["Task Handler YAML<br/>config/tasker/tasks/"]
56
+ StepConfig["Step Configuration<br/>Dependencies & Settings"]
57
+ end
58
+
59
+ subgraph Handlers["Handler Classes"]
60
+ TaskHandler["Task Handler<br/>app/tasks/"]
61
+ StepHandler["Step Handler<br/>app/tasks/*/step_handler/"]
62
+ end
63
+
64
+ subgraph Events["Event System"]
65
+ EventCatalog["Event Catalog<br/>Tasker::Events.catalog"]
66
+ Subscribers["Event Subscribers<br/>app/subscribers/"]
67
+ end
68
+
69
+ subgraph External["External Integrations"]
70
+ Sentry["Error Tracking<br/>(Sentry)"]
71
+ Metrics["Metrics<br/>(DataDog/StatsD)"]
72
+ Alerts["Alerting<br/>(PagerDuty)"]
73
+ Notifications["Notifications<br/>(Slack/Email)"]
74
+ end
75
+
76
+ TaskYAML --> TaskHandler
77
+ StepConfig --> StepHandler
78
+ TaskHandler --> Events
79
+ StepHandler --> Events
80
+ EventCatalog --> Subscribers
81
+ Subscribers --> Sentry
82
+ Subscribers --> Metrics
83
+ Subscribers --> Alerts
84
+ Subscribers --> Notifications
85
+
86
+ classDef config fill:#e1f5fe,stroke:#01579b
87
+ classDef handlers fill:#f3e5f5,stroke:#4a148c
88
+ classDef events fill:#fff3e0,stroke:#e65100
89
+ classDef external fill:#e8f5e8,stroke:#2e7d32
90
+
91
+ class TaskYAML,StepConfig config
92
+ class TaskHandler,StepHandler handlers
93
+ class EventCatalog,Subscribers events
94
+ class Sentry,Metrics,Alerts,Notifications external
95
+ ```
96
+
97
+ ## 1. TaskNamespaces & Versioning
98
+
99
+ TaskNamespaces provide organizational hierarchy for task handlers, while versioning enables multiple versions of the same task to coexist. This is essential for enterprise environments with complex workflows and deployment strategies.
100
+
101
+ ### TaskNamespace Management
102
+
103
+ #### Creating TaskNamespaces
104
+
105
+ TaskNamespaces are automatically created when referenced, but you can also create them explicitly:
106
+
107
+ ```ruby
108
+ # Automatic creation during task creation
109
+ task_request = Tasker::Types::TaskRequest.new(
110
+ name: 'process_payment',
111
+ namespace: 'payments', # Automatically creates 'payments' namespace if needed
112
+ version: '1.0.0',
113
+ context: { payment_id: 123 }
114
+ )
115
+
116
+ # Manual creation with description
117
+ namespace = Tasker::TaskNamespace.create!(
118
+ name: 'payments',
119
+ description: 'Payment processing and billing workflows'
120
+ )
121
+ ```
122
+
123
+ #### Namespace Patterns & Best Practices
124
+
125
+ **Domain-Based Organization**:
126
+ ```yaml
127
+ # config/tasker/tasks/payments/process_order.yaml
128
+ ---
129
+ name: process_order
130
+ namespace_name: payments
131
+ version: 2.1.0
132
+ task_handler_class: Payments::ProcessOrderHandler
133
+
134
+ # config/tasker/tasks/inventory/process_order.yaml
135
+ ---
136
+ name: process_order
137
+ namespace_name: inventory
138
+ version: 1.5.0
139
+ task_handler_class: Inventory::ProcessOrderHandler
140
+ ```
141
+
142
+ **Version Management Strategies**:
143
+ ```ruby
144
+ # Gradual rollout - start with specific version
145
+ handler_v2 = Tasker::HandlerFactory.instance.get(
146
+ 'process_payment',
147
+ namespace_name: 'payments',
148
+ version: '2.0.0'
149
+ )
150
+
151
+ # Legacy support - maintain old version
152
+ handler_v1 = Tasker::HandlerFactory.instance.get(
153
+ 'process_payment',
154
+ namespace_name: 'payments',
155
+ version: '1.5.0'
156
+ )
157
+
158
+ # Default behavior - uses latest registered version
159
+ handler_default = Tasker::HandlerFactory.instance.get('process_payment')
160
+ ```
161
+
162
+ #### HandlerFactory Registry Architecture
163
+
164
+ The HandlerFactory now uses a **thread-safe 3-level registry** with enterprise-grade capabilities:
165
+
166
+ ```ruby
167
+ # Registry Structure: namespace_name → handler_name → version → handler_class
168
+ # Thread-safe storage: Concurrent::Hash for all levels
169
+ # Example internal structure:
170
+ {
171
+ payments: {
172
+ process_order: {
173
+ '1.0.0' => Payments::ProcessOrderV1,
174
+ '2.0.0' => Payments::ProcessOrderV2
175
+ }
176
+ },
177
+ inventory: {
178
+ process_order: {
179
+ '1.5.0' => Inventory::ProcessOrder
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ **Enterprise Features**:
186
+ - **Thread-Safe Operations**: `Concurrent::Hash` storage eliminates race conditions
187
+ - **Structured Logging**: Every operation logged with correlation IDs
188
+ - **Interface Validation**: Fail-fast validation with detailed error messages
189
+ - **Conflict Resolution**: `replace: true` parameter for graceful updates
190
+ - **Health Monitoring**: Built-in statistics and health checks
191
+ - **Event Integration**: Registry operations trigger 56-event system
192
+
193
+ **Registration Examples**:
194
+ ```ruby
195
+ # Class-based registration with namespace + version
196
+ class PaymentHandler < Tasker::TaskHandler
197
+ register_handler(
198
+ 'process_payment',
199
+ namespace_name: 'payments',
200
+ version: '2.0.0'
201
+ )
202
+ end
203
+
204
+ # Thread-safe manual registration with conflict resolution
205
+ Tasker::HandlerFactory.instance.register(
206
+ 'payment_processor',
207
+ PaymentHandler,
208
+ namespace_name: 'payments',
209
+ version: '2.1.0',
210
+ replace: true # Gracefully handles conflicts
211
+ )
212
+
213
+ # Automatic structured logging output:
214
+ # {"correlation_id":"tsk_abc123","component":"handler_factory","message":"Registry item registered","entity_id":"payments/payment_processor/2.1.0","event_type":"registered"}
215
+
216
+ # YAML-based registration
217
+ # config/tasker/tasks/payments/process_payment.yaml
218
+ ---
219
+ name: process_payment
220
+ namespace_name: payments
221
+ version: 2.0.0
222
+ task_handler_class: PaymentHandler
223
+ ```
224
+
225
+ ### Versioning Best Practices
226
+
227
+ #### Semantic Versioning
228
+ Follow [semver.org](https://semver.org) conventions:
229
+ - **Major version** (`2.0.0`): Breaking changes, incompatible API changes
230
+ - **Minor version** (`1.1.0`): New features, backward compatible
231
+ - **Patch version** (`1.0.1`): Bug fixes, backward compatible
232
+
233
+ #### Version Lifecycle Management
234
+ ```ruby
235
+ # Development workflow
236
+ class PaymentHandler < Tasker::TaskHandler
237
+ register_handler('process_payment',
238
+ namespace_name: 'payments',
239
+ version: '2.0.0-beta.1') # Pre-release
240
+ end
241
+
242
+ # Production deployment
243
+ class PaymentHandler < Tasker::TaskHandler
244
+ register_handler('process_payment',
245
+ namespace_name: 'payments',
246
+ version: '2.0.0') # Stable release
247
+ end
248
+
249
+ # Maintenance mode
250
+ class PaymentHandler < Tasker::TaskHandler
251
+ register_handler('process_payment',
252
+ namespace_name: 'payments',
253
+ version: '1.5.2') # Patch release for legacy
254
+ end
255
+ ```
256
+
257
+ ## 2. Task Handlers
258
+
259
+ Task handlers define the overall workflow structure and coordinate step execution. They are the entry point for creating and managing multi-step processes.
260
+
261
+ ### Creating Task Handlers
262
+
263
+ Use the generator to create a complete task handler structure:
264
+
265
+ ```bash
266
+ rails generate tasker:task_handler OrderHandler --module_namespace OrderProcess
267
+ ```
268
+
269
+ This creates:
270
+ - **Handler Class**: `app/tasks/order_process/order_handler.rb`
271
+ - **YAML Configuration**: `config/tasker/tasks/order_process/order_handler.yaml`
272
+ - **Test File**: `spec/tasks/order_process/order_handler_spec.rb`
273
+
274
+ ### Task Handler Class Structure
275
+
276
+ ```ruby
277
+ # app/tasks/order_process/order_handler.rb
278
+ module OrderProcess
279
+ class OrderHandler < Tasker::ConfiguredTask
280
+ # The task handler class is primarily configuration-driven
281
+ # Most behavior is defined in the YAML file
282
+
283
+ # Optional: Override the default task name (defaults to class name underscored)
284
+ def self.task_name
285
+ 'custom_order_process'
286
+ end
287
+
288
+ # Optional: Override the default YAML path
289
+ def self.yaml_path
290
+ Rails.root.join('config/custom_tasks/order_handler.yaml')
291
+ end
292
+
293
+ # Optional: Establish custom step dependencies beyond YAML configuration
294
+ def establish_step_dependencies_and_defaults(task, steps)
295
+ # Add runtime dependencies based on task context
296
+ if task.context['priority'] == 'expedited'
297
+ # Modify step configuration for expedited orders
298
+ payment_step = steps.find { |s| s.name == 'process_payment' }
299
+ payment_step&.update(retry_limit: 1) # Faster failure for expedited orders
300
+ end
301
+ end
302
+
303
+ # Optional: Update annotations after steps complete
304
+ def update_annotations(task, sequence, steps)
305
+ # Add custom annotations based on step results
306
+ total_amount = steps.find { |s| s.name == 'calculate_total' }&.results&.dig('amount')
307
+ if total_amount && total_amount > 1000
308
+ task.annotations.create!(
309
+ annotation_type: 'high_value_order',
310
+ content: { amount: total_amount, flagged_at: Time.current }
311
+ )
312
+ end
313
+ end
314
+
315
+ # Optional: Custom validation schema (beyond YAML schema)
316
+ def schema
317
+ # This overrides any schema defined in YAML
318
+ {
319
+ type: 'object',
320
+ required: ['order_id', 'customer_id'],
321
+ properties: {
322
+ order_id: { type: 'integer', minimum: 1 },
323
+ customer_id: { type: 'integer', minimum: 1 },
324
+ priority: { type: 'string', enum: ['standard', 'expedited', 'rush'] }
325
+ }
326
+ }
327
+ end
328
+ end
329
+ end
330
+ ```
331
+
332
+ ### Task Handler Capabilities
333
+
334
+ - **Workflow Orchestration**: Manages step dependencies and execution order
335
+ - **Parallel Processing**: Supports concurrent execution of independent steps
336
+ - **Error Handling**: Comprehensive retry logic with exponential backoff
337
+ - **Context Management**: Passes data between steps through task context
338
+ - **Validation**: JSON schema validation plus custom business rules
339
+ - **Event Publishing**: Automatic lifecycle event generation
340
+
341
+ ### Available Override Methods
342
+
343
+ **ConfiguredTask Class Methods** (for customizing configuration):
344
+ - `self.task_name` - Override the default task name (defaults to class name underscored)
345
+ - `self.yaml_path` - Override the default YAML configuration file path
346
+ - `self.config` - Override how configuration is loaded (defaults to YAML.load_file)
347
+
348
+ **TaskHandler Instance Methods** (for customizing behavior):
349
+ - `establish_step_dependencies_and_defaults(task, steps)` - Modify step configuration at runtime
350
+ - `update_annotations(task, sequence, steps)` - Add custom annotations after step completion
351
+ - `schema` - Define custom validation schema for task context (overrides YAML schema)
352
+
353
+ ## 2. Step Handlers
354
+
355
+ Step handlers implement the specific business logic for individual workflow steps. They are the workhorses that perform the actual operations.
356
+
357
+ ### Step Handler Types
358
+
359
+ **Base Step Handler** - For general business logic with custom events:
360
+ ```ruby
361
+ module OrderProcess
362
+ module StepHandler
363
+ class ProcessPaymentHandler < Tasker::StepHandler::Base
364
+ # Define custom events that this handler can publish
365
+ # These are automatically registered when the task handler is loaded
366
+ def self.custom_event_configuration
367
+ [
368
+ {
369
+ name: 'payment.processed',
370
+ description: 'Published when payment processing completes successfully'
371
+ },
372
+ {
373
+ name: 'payment.risk_flagged',
374
+ description: 'Published when payment is flagged for manual review'
375
+ }
376
+ ]
377
+ end
378
+
379
+ def process(task, sequence, step)
380
+ order_id = task.context[:order_id]
381
+ payment_amount = task.context[:payment_amount]
382
+
383
+ # Perform risk assessment
384
+ risk_score = assess_payment_risk(order_id, payment_amount)
385
+
386
+ if risk_score > 0.8
387
+ # Publish custom event for high-risk payments
388
+ publish_custom_event('payment.risk_flagged', {
389
+ order_id: order_id,
390
+ risk_score: risk_score,
391
+ requires_manual_review: true,
392
+ flagged_at: Time.current
393
+ })
394
+
395
+ { status: 'risk_review', risk_score: risk_score }
396
+ else
397
+ # Process payment normally
398
+ payment_result = process_payment_transaction(order_id, payment_amount)
399
+
400
+ # Publish custom event for successful payments
401
+ publish_custom_event('payment.processed', {
402
+ order_id: order_id,
403
+ payment_amount: payment_amount,
404
+ transaction_id: payment_result[:transaction_id],
405
+ processed_at: Time.current
406
+ })
407
+
408
+ { status: 'completed', transaction_id: payment_result[:transaction_id] }
409
+ end
410
+ end
411
+
412
+ private
413
+
414
+ def assess_payment_risk(order_id, amount)
415
+ # Risk assessment logic here
416
+ # Returns a score between 0.0 and 1.0
417
+ rand(0.0..1.0)
418
+ end
419
+
420
+ def process_payment_transaction(order_id, amount)
421
+ # Payment processing logic here
422
+ { transaction_id: "txn_#{SecureRandom.hex(8)}" }
423
+ end
424
+ end
425
+ end
426
+ end
427
+ ```
428
+
429
+ **API Step Handler** - For external API integrations:
430
+ ```ruby
431
+ module OrderProcess
432
+ module StepHandler
433
+ class FetchInventoryHandler < Tasker::StepHandler::Api
434
+ include OrderProcess::ApiUtils
435
+
436
+ def process(task, sequence, step)
437
+ product_ids = get_previous_step_data(sequence, 'fetch_products', 'product_ids')
438
+
439
+ # Make HTTP request (automatic retry, timeout, error handling)
440
+ connection.get('/inventory/check', { product_ids: product_ids })
441
+ end
442
+
443
+ def process_results(step, process_output, initial_results)
444
+ # Custom response processing
445
+ inventory_data = JSON.parse(process_output.body)
446
+ step.results = {
447
+ inventory_levels: inventory_data['levels'],
448
+ availability: inventory_data['available'],
449
+ last_updated: inventory_data['timestamp']
450
+ }
451
+ end
452
+ end
453
+ end
454
+ end
455
+ ```
456
+
457
+ ### API Step Handler Implementation
458
+
459
+ **The `process` Method - Your Extension Point**:
460
+
461
+ API step handlers use the `process` method as the developer extension point. The framework handles all orchestration, error handling, retries, and event publishing automatically:
462
+
463
+ ```ruby
464
+ module OrderProcess
465
+ module StepHandler
466
+ class FetchUserProfileHandler < Tasker::StepHandler::Api
467
+ include OrderProcess::ApiUtils
468
+
469
+ def process(task, sequence, step)
470
+ # Simply implement your HTTP request logic
471
+ # Framework handles retries, timeouts, and error handling automatically
472
+ user_id = task.context['user_id']
473
+ connection.get("/users/#{user_id}/profile")
474
+ end
475
+ end
476
+ end
477
+ end
478
+ ```
479
+
480
+ **Making Different Types of API Requests**:
481
+
482
+ ```ruby
483
+ module OrderProcess
484
+ module StepHandler
485
+ # GET request example
486
+ class FetchOrderHandler < Tasker::StepHandler::Api
487
+ def process(task, sequence, step)
488
+ order_id = task.context['order_id']
489
+ connection.get("/orders/#{order_id}")
490
+ end
491
+ end
492
+
493
+ # POST request example
494
+ class CreatePaymentHandler < Tasker::StepHandler::Api
495
+ def process(task, sequence, step)
496
+ payment_data = {
497
+ amount: task.context['amount'],
498
+ currency: task.context['currency'],
499
+ customer_id: task.context['customer_id']
500
+ }
501
+ connection.post('/payments', payment_data)
502
+ end
503
+ end
504
+
505
+ # PUT request with data from previous steps
506
+ class UpdateInventoryHandler < Tasker::StepHandler::Api
507
+ def process(task, sequence, step)
508
+ # Get data from previous steps
509
+ order_items = get_previous_step_data(sequence, 'fetch_order', 'items')
510
+
511
+ inventory_updates = order_items.map do |item|
512
+ { product_id: item['product_id'], quantity: -item['quantity'] }
513
+ end
514
+
515
+ connection.put('/inventory/bulk-update', { updates: inventory_updates })
516
+ end
517
+ end
518
+ end
519
+ end
520
+ ```
521
+
522
+ **Custom Response Processing**:
523
+
524
+ Override the `process_results` method to customize how API responses are processed and stored:
525
+
526
+ ```ruby
527
+ module OrderProcess
528
+ module StepHandler
529
+ class FetchUserDataHandler < Tasker::StepHandler::Api
530
+ def process(task, sequence, step)
531
+ user_id = task.context['user_id']
532
+ connection.get("/users/#{user_id}/profile")
533
+ end
534
+
535
+ # Override to customize response processing
536
+ def process_results(step, process_output, initial_results)
537
+ # process_output is the Faraday::Response from your process method
538
+ if process_output.status == 200
539
+ data = JSON.parse(process_output.body)
540
+ step.results = {
541
+ profile: data['user_profile'],
542
+ preferences: data['preferences'],
543
+ last_login: data['last_login_at'],
544
+ api_response_time: process_output.headers['x-response-time']
545
+ }
546
+ else
547
+ # Let framework handle error - this will trigger retries if configured
548
+ raise "API error: #{process_output.status} - #{process_output.body}"
549
+ end
550
+ end
551
+ end
552
+ end
553
+ end
554
+ ```
555
+
556
+ **Setting Results Directly in `process`**:
557
+
558
+ You can also set `step.results` directly in your `process` method if you prefer:
559
+
560
+ ```ruby
561
+ module OrderProcess
562
+ module StepHandler
563
+ class ProcessOrderHandler < Tasker::StepHandler::Api
564
+ def process(task, sequence, step)
565
+ response = connection.post('/orders', task.context)
566
+
567
+ # Set results directly - framework will respect this
568
+ if response.status == 201
569
+ order_data = JSON.parse(response.body)
570
+ step.results = {
571
+ order_id: order_data['id'],
572
+ status: order_data['status'],
573
+ created_at: order_data['created_at']
574
+ }
575
+ end
576
+
577
+ response # Return response for framework processing
578
+ end
579
+ end
580
+ end
581
+ end
582
+ ```
583
+
584
+ **Key API Step Handler Principles**:
585
+
586
+ 1. **Implement `process` method** - This is your extension point for HTTP request logic
587
+ 2. **Use the `connection` object** - Pre-configured Faraday connection with retry/timeout handling
588
+ 3. **Return the response** - Let the framework handle response processing and error detection
589
+ 4. **Override `process_results` for custom processing** - Transform responses while preserving framework behavior
590
+ 5. **Set `step.results` for custom data** - Either in `process` or `process_results`
591
+ 6. **Raise exceptions for failures** - Framework handles error states and retry orchestration
592
+ 7. **Never override `handle`** - This is framework-only coordination code
593
+
594
+ ### Step Handler Features
595
+
596
+ - **Automatic Result Storage**: Return values automatically stored in `step.results`
597
+ - **Context Access**: Full access to task context and previous step results
598
+ - **Error Handling**: Framework determines success/failure based on exceptions (see Error Handling Patterns in Best Practices)
599
+ - **Custom Processing**: Override `process_results` for custom result handling
600
+ - **Event Integration**: Automatic event publishing for observability
601
+
602
+ ### Accessing Previous Step Data
603
+
604
+ ```ruby
605
+ def process(task, sequence, step)
606
+ # Access task context
607
+ order_id = task.context['order_id']
608
+
609
+ # Find specific step by name
610
+ payment_step = sequence.find_step_by_name('process_payment')
611
+ payment_id = payment_step.results['payment_id']
612
+
613
+ # Get data from multiple steps
614
+ product_data = get_previous_step_data(sequence, 'fetch_products', 'products')
615
+ inventory_data = get_previous_step_data(sequence, 'check_inventory', 'levels')
616
+
617
+ # Your business logic here
618
+ process_order_fulfillment(order_id, payment_id, product_data, inventory_data)
619
+ end
620
+ ```
621
+
622
+ ## 3. Event Subscribers
623
+
624
+ Event subscribers handle **"collateral" or "secondary" logic** - operations that support observability, monitoring, and alerting but are not core business requirements. They respond to workflow events and provide operational visibility into system behavior.
625
+
626
+ ### Architectural Distinction: Subscribers vs Steps
627
+
628
+ **Event Subscribers** are for collateral concerns:
629
+ - **Operational Observability**: Logging, metrics, telemetry, traces
630
+ - **Alerting & Monitoring**: Sentry errors, PagerDuty alerts, operational notifications
631
+ - **Analytics**: Business intelligence, usage tracking, performance monitoring
632
+ - **External Integrations**: Non-critical third-party service notifications
633
+
634
+ **Workflow Steps** are for business-critical operations requiring:
635
+ - **Idempotency**: Can be safely retried without side effects
636
+ - **Retryability**: Built-in retry logic with exponential backoff
637
+ - **Explicit Lifecycle Tracking**: Success/failure states that matter to the business
638
+ - **Transactional Integrity**: Operations that need to be rolled back on failure
639
+
640
+ **Rule of Thumb**: If the operation must succeed for the workflow to be considered complete, it should be a workflow step. If it's supporting infrastructure (logging, monitoring, analytics), it should be an event subscriber.
641
+
642
+ ### Creating Event Subscribers
643
+
644
+ Use the generator to create subscribers with automatic method routing:
645
+
646
+ ```bash
647
+ # Generate a subscriber for specific events
648
+ rails generate tasker:subscriber notification --events task.completed task.failed step.failed
649
+
650
+ # Generate a metrics collector
651
+ rails generate tasker:subscriber metrics --events task.completed step.completed task.failed step.failed
652
+ ```
653
+
654
+ ### Event Subscriber Structure
655
+
656
+ ```ruby
657
+ # app/subscribers/observability_subscriber.rb
658
+ class ObservabilitySubscriber < Tasker::Events::Subscribers::BaseSubscriber
659
+ # Subscribe to specific events for operational monitoring
660
+ subscribe_to 'task.completed', 'task.failed', 'step.failed', 'order.fulfilled'
661
+
662
+ # Automatic method routing: task.completed -> handle_task_completed
663
+ def handle_task_completed(event)
664
+ task_id = safe_get(event, :task_id)
665
+ task_name = safe_get(event, :task_name, 'unknown')
666
+ execution_duration = safe_get(event, :execution_duration, 0)
667
+
668
+ # Operational logging and metrics (collateral concerns)
669
+ Rails.logger.info "Task completed: #{task_name} (#{task_id}) in #{execution_duration}s"
670
+ StatsD.histogram('tasker.task.duration', execution_duration, tags: ["task:#{task_name}"])
671
+ end
672
+
673
+ def handle_task_failed(event)
674
+ task_id = safe_get(event, :task_id)
675
+ error_message = safe_get(event, :error_message, 'Unknown error')
676
+
677
+ # Send alerts to operational tools (collateral concerns)
678
+ Sentry.capture_message("Task failed: #{task_id}", level: 'error', extra: { error: error_message })
679
+ PagerDutyService.trigger_alert(
680
+ summary: "Tasker workflow failed",
681
+ severity: 'error',
682
+ details: { task_id: task_id, error: error_message }
683
+ )
684
+ end
685
+
686
+ def handle_step_failed(event)
687
+ step_id = safe_get(event, :step_id)
688
+ step_name = safe_get(event, :step_name, 'unknown')
689
+ task_id = safe_get(event, :task_id)
690
+
691
+ # Operational logging for debugging (collateral concern)
692
+ Rails.logger.error "Step failure in task #{task_id}: #{step_name} (#{step_id})"
693
+ end
694
+
695
+ def handle_order_fulfilled(event)
696
+ order_id = safe_get(event, :order_id)
697
+ customer_id = safe_get(event, :customer_id)
698
+
699
+ # Analytics and monitoring (collateral concerns)
700
+ AnalyticsService.track_order_fulfillment(order_id, customer_id)
701
+ Rails.logger.info "Order fulfilled: #{order_id} for customer #{customer_id}"
702
+ end
703
+ end
704
+ ```
705
+
706
+ ### Available Events
707
+
708
+ Discover all available events using the event catalog:
709
+
710
+ ```ruby
711
+ # In Rails console or your code
712
+ Tasker::Events.catalog.keys
713
+ # => ["task.started", "task.completed", "task.failed", "step.started", ...]
714
+
715
+ # Get detailed event information
716
+ Tasker::Events.event_info('task.completed')
717
+ # => { name: "task.completed", category: "task", description: "...", ... }
718
+
719
+ # Browse by category
720
+ Tasker::Events.task_events.keys # Task lifecycle events
721
+ Tasker::Events.step_events.keys # Step execution events
722
+ Tasker::Events.workflow_events.keys # Orchestration events
723
+ ```
724
+
725
+ ### Real-World Integration Examples
726
+
727
+ **Metrics Collection (DataDog)**:
728
+ ```ruby
729
+ class MetricsSubscriber < Tasker::Events::Subscribers::BaseSubscriber
730
+ subscribe_to 'task.completed', 'task.failed', 'step.completed'
731
+
732
+ def handle_task_completed(event)
733
+ task_name = safe_get(event, :task_name, 'unknown')
734
+ execution_duration = safe_get(event, :execution_duration, 0)
735
+
736
+ StatsD.histogram('tasker.task.duration', execution_duration, tags: ["task:#{task_name}"])
737
+ StatsD.increment('tasker.task.completed', tags: ["task:#{task_name}"])
738
+ end
739
+
740
+ def handle_task_failed(event)
741
+ task_name = safe_get(event, :task_name, 'unknown')
742
+ error_class = safe_get(event, :exception_class, 'unknown')
743
+
744
+ StatsD.increment('tasker.task.failed', tags: ["task:#{task_name}", "error:#{error_class}"])
745
+ end
746
+ end
747
+ ```
748
+
749
+ **Error Tracking (Sentry)**:
750
+ ```ruby
751
+ class SentrySubscriber < Tasker::Events::Subscribers::BaseSubscriber
752
+ subscribe_to 'task.failed', 'step.failed'
753
+
754
+ def handle_task_failed(event)
755
+ task_id = safe_get(event, :task_id)
756
+ error_message = safe_get(event, :error_message, 'Unknown error')
757
+
758
+ Sentry.capture_message(error_message,
759
+ level: 'error',
760
+ fingerprint: ['tasker', 'task_failed', task_id],
761
+ tags: { task_id: task_id, component: 'tasker' }
762
+ )
763
+ end
764
+ end
765
+ ```
766
+
767
+ ## 4. YAML Configuration
768
+
769
+ YAML files provide declarative configuration for task handlers, defining workflows, dependencies, and step settings without requiring code changes.
770
+
771
+ ### Task Handler YAML Structure
772
+
773
+ ```yaml
774
+ # config/tasker/tasks/payments/order_process.yaml
775
+ ---
776
+ name: order_process
777
+ namespace_name: payments # NEW: TaskNamespace organization
778
+ version: 1.2.0 # NEW: Semantic versioning
779
+ module_namespace: OrderProcess # Ruby module namespace
780
+ task_handler_class: OrderHandler
781
+
782
+ # JSON Schema validation for task context
783
+ schema:
784
+ type: object
785
+ required:
786
+ - order_id
787
+ - customer_id
788
+ properties:
789
+ order_id:
790
+ type: integer
791
+ customer_id:
792
+ type: integer
793
+ priority:
794
+ type: string
795
+ enum: [low, normal, high, critical]
796
+
797
+ # Step definitions with dependencies
798
+ step_templates:
799
+ - name: fetch_order
800
+ description: Retrieve order details from database
801
+ handler_class: OrderProcess::StepHandler::FetchOrderHandler
802
+
803
+ - name: validate_inventory
804
+ description: Check product availability
805
+ depends_on_step: fetch_order
806
+ handler_class: OrderProcess::StepHandler::ValidateInventoryHandler
807
+ default_retryable: true
808
+ default_retry_limit: 3
809
+
810
+ - name: process_payment
811
+ description: Charge customer payment method
812
+ depends_on_step: validate_inventory
813
+ handler_class: OrderProcess::StepHandler::ProcessPaymentHandler
814
+ default_retryable: true
815
+ default_retry_limit: 2
816
+
817
+ - name: update_inventory
818
+ description: Decrement inventory levels
819
+ depends_on_step: process_payment
820
+ handler_class: OrderProcess::StepHandler::UpdateInventoryHandler
821
+
822
+ - name: send_confirmation
823
+ description: Send order confirmation email
824
+ depends_on_step: update_inventory
825
+ handler_class: OrderProcess::StepHandler::SendConfirmationHandler
826
+ default_retryable: true
827
+ default_retry_limit: 5
828
+
829
+ # Environment-specific overrides
830
+ environments:
831
+ development:
832
+ step_templates:
833
+ - name: process_payment
834
+ # Use test payment processor in development
835
+ handler_config:
836
+ payment_processor: test
837
+
838
+ production:
839
+ step_templates:
840
+ - name: process_payment
841
+ # Production payment configuration
842
+ handler_config:
843
+ payment_processor: stripe
844
+ timeout: 30
845
+ ```
846
+
847
+ ### Advanced YAML Features
848
+
849
+ **Multiple Dependencies**:
850
+ ```yaml
851
+ - name: finalize_order
852
+ description: Complete order processing
853
+ depends_on_steps: # Multiple dependencies
854
+ - process_payment
855
+ - update_inventory
856
+ - reserve_shipping
857
+ handler_class: OrderProcess::StepHandler::FinalizeOrderHandler
858
+ ```
859
+
860
+ **Environment-Specific Configuration**:
861
+ ```yaml
862
+ environments:
863
+ test:
864
+ step_templates:
865
+ - name: send_email
866
+ handler_config:
867
+ email_service: mock
868
+
869
+ staging:
870
+ step_templates:
871
+ - name: send_email
872
+ handler_config:
873
+ email_service: sendgrid_test
874
+
875
+ production:
876
+ step_templates:
877
+ - name: send_email
878
+ handler_config:
879
+ email_service: sendgrid_production
880
+ api_key: ${SENDGRID_API_KEY}
881
+ ```
882
+
883
+ **API Step Configuration**:
884
+ ```yaml
885
+ - name: fetch_external_data
886
+ handler_class: OrderProcess::StepHandler::FetchExternalDataHandler
887
+ handler_config:
888
+ type: api
889
+ url: https://api.external-service.com/data
890
+ method: GET
891
+ headers:
892
+ Authorization: "Bearer ${API_TOKEN}"
893
+ timeout: 15
894
+ retries: 3
895
+ ```
896
+
897
+ ### TaskNamespace + Versioning in YAML
898
+
899
+ The new namespace and versioning system provides enterprise-scale organization:
900
+
901
+ #### Namespace-Based File Organization
902
+
903
+ **Recommended File Structure**:
904
+ ```
905
+ config/tasker/tasks/
906
+ ├── payments/
907
+ │ ├── process_order.yaml # version: 1.0.0
908
+ │ ├── process_refund.yaml # version: 1.1.0
909
+ │ └── validate_payment.yaml # version: 2.0.0
910
+ ├── inventory/
911
+ │ ├── process_order.yaml # version: 1.5.0 (same name, different namespace)
912
+ │ ├── stock_check.yaml # version: 1.0.0
913
+ │ └── reorder_items.yaml # version: 1.2.0
914
+ └── notifications/
915
+ ├── send_email.yaml # version: 1.0.0
916
+ ├── send_sms.yaml # version: 1.1.0
917
+ └── push_notification.yaml # version: 2.0.0
918
+ ```
919
+
920
+ #### Version Coexistence Examples
921
+
922
+ **Multiple Versions of Same Task**:
923
+ ```yaml
924
+ # config/tasker/tasks/payments/process_order_v1.yaml
925
+ ---
926
+ name: process_order
927
+ namespace_name: payments
928
+ version: 1.0.0 # Legacy version
929
+ task_handler_class: Payments::ProcessOrderV1
930
+
931
+ # config/tasker/tasks/payments/process_order_v2.yaml
932
+ ---
933
+ name: process_order
934
+ namespace_name: payments
935
+ version: 2.0.0 # Current version
936
+ task_handler_class: Payments::ProcessOrderV2
937
+ ```
938
+
939
+ #### Namespace-Specific Configuration
940
+
941
+ **Domain-Specific Settings**:
942
+ ```yaml
943
+ # config/tasker/tasks/integrations/api_sync.yaml
944
+ ---
945
+ name: api_sync
946
+ namespace_name: integrations
947
+ version: 1.3.0
948
+ task_handler_class: Integrations::ApiSyncHandler
949
+
950
+ # Integration-specific settings
951
+ handler_config:
952
+ max_concurrent_requests: 5
953
+ rate_limit_per_second: 10
954
+ dependent_systems: # External systems this task interacts with
955
+ - salesforce_api
956
+ - inventory_service
957
+ - notification_queue
958
+
959
+ step_templates:
960
+ - name: fetch_data
961
+ handler_class: Integrations::FetchDataHandler
962
+ dependent_system: salesforce_api # Step-level system identification
963
+
964
+ - name: transform_data
965
+ handler_class: Integrations::TransformDataHandler
966
+
967
+ - name: upload_data
968
+ handler_class: Integrations::UploadDataHandler
969
+ dependent_system: inventory_service
970
+ ```
971
+
972
+ #### Configuration Validation & Defaults
973
+
974
+ **YAML Configuration Schema**:
975
+ ```yaml
976
+ # Full configuration with all optional fields
977
+ ---
978
+ name: process_payment # Required
979
+ namespace_name: payments # Optional - defaults to 'default'
980
+ version: 2.1.0 # Optional - defaults to '0.1.0'
981
+ module_namespace: Payments # Optional - Ruby module namespace
982
+ task_handler_class: ProcessPaymentHandler # Required
983
+ description: "Advanced payment processing" # Optional - for documentation
984
+
985
+ # Database configuration (optional)
986
+ configuration:
987
+ priority: high
988
+ max_execution_time: 300
989
+ custom_metadata:
990
+ team: payments
991
+ owner: alice@company.com
992
+ documentation_url: https://wiki.company.com/payments
993
+ ```
994
+
995
+ #### Migration Strategy for Existing Configurations
996
+
997
+ **Backward Compatibility**: Existing YAML files continue working unchanged:
998
+ ```yaml
999
+ # Existing file - continues working with defaults
1000
+ ---
1001
+ name: legacy_task
1002
+ task_handler_class: LegacyHandler
1003
+ # Automatically gets: namespace_name: 'default', version: '0.1.0'
1004
+
1005
+ # Enhanced file - new capabilities
1006
+ ---
1007
+ name: legacy_task
1008
+ namespace_name: default # Explicit default
1009
+ version: 0.1.0 # Explicit version
1010
+ task_handler_class: LegacyHandler
1011
+ ```
1012
+
1013
+ ## 5. Authentication & Authorization
1014
+
1015
+ Tasker provides a comprehensive, production-ready authentication and authorization system that works with any Rails authentication solution. The system uses **dependency injection** and **resource-based authorization** to provide enterprise-grade security for both REST APIs and GraphQL endpoints.
1016
+
1017
+ ### Key Features
1018
+
1019
+ - **Provider Agnostic**: Works with Devise, JWT, OmniAuth, custom authentication, or no authentication
1020
+ - **Resource-Based Authorization**: Granular permissions using resource:action patterns (e.g., `tasker.task:create`)
1021
+ - **GraphQL Operation-Level Authorization**: Revolutionary security for GraphQL that maps operations to resource permissions
1022
+ - **Automatic Controller Integration**: Authentication and authorization work seamlessly across REST and GraphQL
1023
+ - **Comprehensive Generators**: Create production-ready authenticators and authorization coordinators
1024
+ - **Zero Breaking Changes**: All features are opt-in and backward compatible
1025
+
1026
+ ### Quick Configuration Example
1027
+
1028
+ ```ruby
1029
+ # config/initializers/tasker.rb
1030
+ Tasker::Configuration.configuration do |config|
1031
+ config.auth do |auth|
1032
+ # Authentication
1033
+ auth.authentication_enabled = true
1034
+ auth.authenticator_class = 'YourCustomAuthenticator'
1035
+
1036
+ # Authorization
1037
+ auth.authorization_enabled = true
1038
+ auth.authorization_coordinator_class = 'YourAuthorizationCoordinator'
1039
+ auth.user_class = 'User'
1040
+ end
1041
+ end
1042
+ ```
1043
+
1044
+ ### Available Generators
1045
+
1046
+ ```bash
1047
+ # Generate authenticators for different systems
1048
+ rails generate tasker:authenticator CompanyJWT --type=jwt
1049
+ rails generate tasker:authenticator AdminAuth --type=devise --user-class=Admin
1050
+ rails generate tasker:authenticator ApiAuth --type=api_token
1051
+ rails generate tasker:authenticator SocialAuth --type=omniauth
1052
+
1053
+ # Generate authorization coordinator
1054
+ rails generate tasker:authorization_coordinator CompanyAuth
1055
+ ```
1056
+
1057
+ ### GraphQL Authorization Example
1058
+
1059
+ The system automatically maps GraphQL operations to resource permissions:
1060
+
1061
+ ```ruby
1062
+ # This GraphQL query:
1063
+ query { tasks { taskId status } }
1064
+
1065
+ # Automatically requires: tasker.task:index permission
1066
+
1067
+ # This mutation:
1068
+ mutation { createTask(input: {...}) { taskId } }
1069
+
1070
+ # Automatically requires: tasker.task:create permission
1071
+ ```
1072
+
1073
+ ### Resource-Based Permissions
1074
+
1075
+ Authorization uses a simple resource:action permission model:
1076
+
1077
+ ```ruby
1078
+ # Available permissions:
1079
+ 'tasker.task:index' # List all tasks
1080
+ 'tasker.task:create' # Create new tasks
1081
+ 'tasker.workflow_step:show' # View individual workflow steps
1082
+ ```
1083
+
1084
+ ### Complete Documentation
1085
+
1086
+ For comprehensive documentation including:
1087
+ - **Quick Start Guides** - Get authentication working in minutes
1088
+ - **Custom Authenticator Examples** - JWT, Devise, API tokens, and more
1089
+ - **Authorization Coordinator Patterns** - Role-based, context-aware, and time-based authorization
1090
+ - **GraphQL Authorization Details** - Operation mapping and context handling
1091
+ - **Production Best Practices** - Security, performance, and monitoring guidelines
1092
+ - **Testing Strategies** - Complete test examples and isolation techniques
1093
+
1094
+ **See [Authentication & Authorization Guide](AUTH.md)** for complete documentation.
1095
+
1096
+ ## 6. Multi-Database Support
1097
+
1098
+ Tasker provides optional multi-database support using Rails' standard multi-database conventions. This allows Tasker models to use a separate database from the host application for data isolation, performance, or compliance requirements.
1099
+
1100
+ ### Key Features
1101
+
1102
+ - **Rails Multi-Database Integration**: Uses Rails' `connects_to` API following official conventions
1103
+ - **Standard Configuration**: Leverages Rails database.yml patterns with named databases
1104
+ - **Automatic Model Support**: All Tasker models inherit multi-database capability automatically
1105
+ - **Zero Breaking Changes**: Fully backward compatible with existing installations
1106
+ - **Environment-Specific**: Supports different database configurations per environment
1107
+
1108
+ ### Configuration
1109
+
1110
+ ```ruby
1111
+ # config/initializers/tasker.rb
1112
+
1113
+ # Default: Use host application database (shared)
1114
+ Tasker::Configuration.configuration do |config|
1115
+ config.database.enable_secondary_database = false
1116
+ end
1117
+
1118
+ # Use dedicated Tasker database
1119
+ Tasker::Configuration.configuration do |config|
1120
+ config.database.enable_secondary_database = true
1121
+ config.database.name = :tasker
1122
+ end
1123
+
1124
+ # Environment-specific configuration
1125
+ Tasker::Configuration.configuration do |config|
1126
+ config.database.enable_secondary_database = Rails.env.production?
1127
+ config.database.name = Rails.env.production? ? :tasker : nil
1128
+ end
1129
+ ```
1130
+
1131
+ ### Database Configuration
1132
+
1133
+ ```yaml
1134
+ # config/database.yml
1135
+ production:
1136
+ primary:
1137
+ database: my_primary_database
1138
+ adapter: postgresql
1139
+ username: app_user
1140
+ password: <%= ENV['DATABASE_PASSWORD'] %>
1141
+
1142
+ tasker:
1143
+ database: my_tasker_database
1144
+ adapter: postgresql
1145
+ username: tasker_user
1146
+ password: <%= ENV['TASKER_DATABASE_PASSWORD'] %>
1147
+ ```
1148
+
1149
+ ### Benefits of Multi-Database Setup
1150
+
1151
+ **Data Isolation**: Separate Tasker data from application data for security or compliance
1152
+
1153
+ **Performance**: Dedicated database resources for workflow processing
1154
+
1155
+ **Scaling**: Independent scaling of workflow database based on usage patterns
1156
+
1157
+ **Backup Strategy**: Separate backup and recovery policies for workflow data
1158
+
1159
+ **Development**: Easier testing and development with isolated workflow data
1160
+
1161
+ ### Migration Support
1162
+
1163
+ When using a secondary database, Tasker migrations automatically target the correct database:
1164
+
1165
+ ```bash
1166
+ # Migrations run against the configured Tasker database
1167
+ bundle exec rails tasker:install:migrations
1168
+ bundle exec rails tasker:install:database_objects
1169
+ bundle exec rails db:migrate
1170
+ ```
1171
+
1172
+ ### Production Considerations
1173
+
1174
+ - **Connection Pooling**: Configure appropriate connection pool sizes for both databases
1175
+ - **Monitoring**: Monitor connection usage and performance for both databases
1176
+ - **Backup Strategy**: Implement coordinated backup strategies if data consistency across databases is required
1177
+ - **Network Latency**: Consider network latency if databases are on different servers
1178
+
1179
+ ## 7. Dependency Graph & Bottleneck Analysis Configuration
1180
+
1181
+ Tasker provides advanced dependency graph analysis and bottleneck detection capabilities that can be fine-tuned for your specific workflow patterns. The dependency graph configuration controls how Tasker analyzes workflow dependencies, identifies bottlenecks, and calculates impact scores for optimization recommendations.
1182
+
1183
+ ### Key Features
1184
+
1185
+ - **Configurable Impact Scoring**: Customize how different factors influence bottleneck calculations
1186
+ - **Adaptive Severity Classification**: Define custom thresholds for Critical/High/Medium/Low priority bottlenecks
1187
+ - **Dynamic Duration Estimation**: Configure time estimates for path analysis and planning
1188
+ - **State-Based Multipliers**: Adjust scoring based on step states and execution conditions
1189
+ - **Retry Pattern Analysis**: Configure penalties for instability and failure patterns
1190
+
1191
+ ### Quick Configuration Example
1192
+
1193
+ ```ruby
1194
+ # config/initializers/tasker.rb
1195
+ Tasker::Configuration.configuration do |config|
1196
+ config.dependency_graph do |graph|
1197
+ # Prioritize blocked steps more heavily
1198
+ graph.impact_multipliers = {
1199
+ blocked_weight: 20, # Increase from default 15
1200
+ error_penalty: 40 # Increase from default 30
1201
+ }
1202
+
1203
+ # Lower thresholds for faster bottleneck detection
1204
+ graph.severity_thresholds = {
1205
+ critical: 80, # Decrease from default 100
1206
+ high: 40, # Decrease from default 50
1207
+ medium: 15 # Decrease from default 20
1208
+ }
1209
+ end
1210
+ end
1211
+ ```
1212
+
1213
+ ### Configuration Options
1214
+
1215
+ #### Impact Multipliers
1216
+
1217
+ Control how different factors contribute to the overall bottleneck impact score:
1218
+
1219
+ ```ruby
1220
+ config.dependency_graph do |graph|
1221
+ graph.impact_multipliers = {
1222
+ downstream_weight: 5, # Weight for downstream step count
1223
+ blocked_weight: 15, # Weight for blocked step count (most critical)
1224
+ path_length_weight: 10, # Weight for critical path length
1225
+ completed_penalty: 15, # Penalty to reduce priority of completed work
1226
+ blocked_penalty: 25, # Penalty to increase priority of blocked work
1227
+ error_penalty: 30, # Penalty for steps in error state (highest)
1228
+ retry_penalty: 10 # Penalty for steps requiring retries
1229
+ }
1230
+ end
1231
+ ```
1232
+
1233
+ **Impact Score Calculation:**
1234
+ ```
1235
+ base_score = (downstream_count * downstream_weight) + (blocked_count * blocked_weight)
1236
+ path_score = path_length * path_length_weight
1237
+ penalties = (completed_steps * completed_penalty) + (blocked_steps * blocked_penalty) +
1238
+ (error_steps * error_penalty) + (retry_steps * retry_penalty)
1239
+ final_score = (base_score + path_score + penalties) * severity_multiplier
1240
+ ```
1241
+
1242
+ #### Severity Multipliers
1243
+
1244
+ Adjust impact scores based on step states and execution conditions:
1245
+
1246
+ ```ruby
1247
+ config.dependency_graph do |graph|
1248
+ graph.severity_multipliers = {
1249
+ error_state: 2.0, # Multiply score by 2.0 for steps in error state
1250
+ exhausted_retry_bonus: 0.5, # Additional 0.5x multiplier for exhausted retries
1251
+ dependency_issue: 1.2 # 1.2x multiplier for dependency-related issues
1252
+ }
1253
+ end
1254
+ ```
1255
+
1256
+ **State-Based Scoring Examples:**
1257
+ - Normal step: `base_score * 1.0`
1258
+ - Error step: `base_score * 2.0`
1259
+ - Error step with exhausted retries: `base_score * (2.0 + 0.5) = base_score * 2.5`
1260
+ - Dependency blocked step: `base_score * 1.2`
1261
+
1262
+ #### Penalty Constants
1263
+
1264
+ Add fixed penalty points for specific problematic conditions:
1265
+
1266
+ ```ruby
1267
+ config.dependency_graph do |graph|
1268
+ graph.penalty_constants = {
1269
+ retry_instability: 3, # +3 points per retry attempt
1270
+ non_retryable: 10, # +10 points for non-retryable failures
1271
+ exhausted_retry: 20 # +20 points for exhausted retry attempts
1272
+ }
1273
+ end
1274
+ ```
1275
+
1276
+ **Penalty Application:**
1277
+ - Step with 2 retry attempts: `+6 penalty points`
1278
+ - Non-retryable failure: `+10 penalty points`
1279
+ - Exhausted retries (3+ attempts): `+20 penalty points`
1280
+
1281
+ #### Severity Thresholds
1282
+
1283
+ Define score ranges for bottleneck classification:
1284
+
1285
+ ```ruby
1286
+ config.dependency_graph do |graph|
1287
+ graph.severity_thresholds = {
1288
+ critical: 100, # Score >= 100: Requires immediate attention
1289
+ high: 50, # Score >= 50: High priority for optimization
1290
+ medium: 20 # Score >= 20: Monitor and plan improvements
1291
+ } # Score < 20: Low priority
1292
+ end
1293
+ ```
1294
+
1295
+ **Classification Examples:**
1296
+ - Score 150: **Critical** - Blocking multiple workflows, immediate intervention required
1297
+ - Score 75: **High** - Significant impact, should be addressed in current sprint
1298
+ - Score 35: **Medium** - Moderate impact, address in upcoming planning cycle
1299
+ - Score 10: **Low** - Minor impact, monitor for trends
1300
+
1301
+ #### Duration Estimates
1302
+
1303
+ Configure time estimates for path analysis and planning:
1304
+
1305
+ ```ruby
1306
+ config.dependency_graph do |graph|
1307
+ graph.duration_estimates = {
1308
+ base_step_seconds: 30, # Default time estimate per step
1309
+ error_penalty_seconds: 60, # Additional time for error recovery
1310
+ retry_penalty_seconds: 30 # Additional time per retry attempt
1311
+ }
1312
+ end
1313
+ ```
1314
+
1315
+ **Duration Calculation:**
1316
+ ```
1317
+ estimated_duration = (step_count * base_step_seconds) +
1318
+ (error_steps * error_penalty_seconds) +
1319
+ (total_retry_attempts * retry_penalty_seconds)
1320
+ ```
1321
+
1322
+ ### Use Cases & Recommendations
1323
+
1324
+ #### High-Volume Transaction Processing
1325
+
1326
+ For workflows processing thousands of transactions per hour:
1327
+
1328
+ ```ruby
1329
+ config.dependency_graph do |graph|
1330
+ # Emphasize blocking issues more heavily
1331
+ graph.impact_multipliers = {
1332
+ blocked_weight: 25, # Increase sensitivity to blocked steps
1333
+ error_penalty: 40 # Prioritize error resolution
1334
+ }
1335
+
1336
+ # Lower thresholds for faster detection
1337
+ graph.severity_thresholds = {
1338
+ critical: 75, # Faster escalation
1339
+ high: 35,
1340
+ medium: 15
1341
+ }
1342
+
1343
+ # Faster execution estimates for high-volume patterns
1344
+ graph.duration_estimates = {
1345
+ base_step_seconds: 15, # Optimized processes run faster
1346
+ error_penalty_seconds: 45 # Reduced error recovery time
1347
+ }
1348
+ end
1349
+ ```
1350
+
1351
+ #### Long-Running Batch Processes
1352
+
1353
+ For workflows that run for hours or days:
1354
+
1355
+ ```ruby
1356
+ config.dependency_graph do |graph|
1357
+ # Focus on path length and completion tracking
1358
+ graph.impact_multipliers = {
1359
+ path_length_weight: 20, # Longer paths have higher impact
1360
+ completed_penalty: 5 # Reduce penalty for completed work
1361
+ }
1362
+
1363
+ # Higher thresholds due to expected longer execution
1364
+ graph.severity_thresholds = {
1365
+ critical: 200,
1366
+ high: 100,
1367
+ medium: 50
1368
+ }
1369
+
1370
+ # Longer execution estimates
1371
+ graph.duration_estimates = {
1372
+ base_step_seconds: 120, # Steps take longer in batch processes
1373
+ error_penalty_seconds: 300 # Error recovery is more expensive
1374
+ }
1375
+ end
1376
+ ```
1377
+
1378
+ #### Real-Time Processing Systems
1379
+
1380
+ For workflows requiring sub-second response times:
1381
+
1382
+ ```ruby
1383
+ config.dependency_graph do |graph|
1384
+ # Maximize sensitivity to any delays
1385
+ graph.impact_multipliers = {
1386
+ retry_penalty: 20, # Retries are very costly
1387
+ error_penalty: 50 # Errors must be resolved immediately
1388
+ }
1389
+
1390
+ # Very low thresholds for immediate response
1391
+ graph.severity_thresholds = {
1392
+ critical: 30,
1393
+ high: 15,
1394
+ medium: 5
1395
+ }
1396
+
1397
+ # Tight time estimates
1398
+ graph.duration_estimates = {
1399
+ base_step_seconds: 1, # Sub-second execution expected
1400
+ error_penalty_seconds: 10, # Errors add significant delay
1401
+ retry_penalty_seconds: 5 # Each retry is expensive
1402
+ }
1403
+ end
1404
+ ```
1405
+
1406
+ ### Monitoring & Observability
1407
+
1408
+ The dependency graph analysis integrates with Tasker's observability system to provide actionable insights:
1409
+
1410
+ ```ruby
1411
+ # Published events include bottleneck severity levels
1412
+ Tasker::Events.subscribe('task.bottleneck_detected') do |event|
1413
+ severity = event.payload['severity'] # 'Critical', 'High', 'Medium', 'Low'
1414
+ impact_score = event.payload['impact_score']
1415
+
1416
+ case severity
1417
+ when 'Critical'
1418
+ PagerDuty.alert("Critical workflow bottleneck detected: #{impact_score}")
1419
+ when 'High'
1420
+ Slack.notify("#ops", "High priority bottleneck: #{impact_score}")
1421
+ end
1422
+ end
1423
+ ```
1424
+
1425
+ ### Production Best Practices
1426
+
1427
+ 1. **Start with Defaults**: Begin with default values and adjust based on observed patterns
1428
+ 2. **Monitor Thresholds**: Track how often each severity level is triggered
1429
+ 3. **A/B Testing**: Test configuration changes on a subset of workflows first
1430
+ 4. **Gradual Tuning**: Make small adjustments and measure impact over time
1431
+ 5. **Documentation**: Document your configuration rationale for team knowledge
1432
+
1433
+ The dependency graph configuration provides powerful tools for optimizing workflow performance while maintaining the flexibility to adapt to your specific operational requirements.
1434
+
1435
+ ## 8. Cache Strategy & Custom Store Capabilities
1436
+
1437
+ Tasker includes a **Hybrid Cache Detection System** that provides intelligent cache strategy selection and supports both built-in Rails cache stores and custom cache implementations. This system automatically adapts coordination strategies based on cache store capabilities, ensuring optimal performance across different deployment environments.
1438
+
1439
+ ### Key Features
1440
+
1441
+ - **🎯 Developer-Friendly API**: Declarative capability system for custom cache stores
1442
+ - **🚀 Performance Optimized**: Frozen constants provide O(1) lookup for built-in stores
1443
+ - **🔄 Hybrid Detection**: Priority-based detection system (declared → constants → custom → runtime)
1444
+ - **📊 Production-Ready**: Comprehensive structured logging and error handling
1445
+ - **⚡ Rails Integration**: Always uses `Rails.cache` as the source of truth
1446
+
1447
+ ### Quick Example
1448
+
1449
+ ```ruby
1450
+ # For custom cache stores - declare capabilities explicitly
1451
+ class MyAwesomeCacheStore < ActiveSupport::Cache::Store
1452
+ include Tasker::CacheCapabilities
1453
+
1454
+ # Use convenience methods for common capabilities
1455
+ supports_distributed_caching!
1456
+ supports_atomic_increment!
1457
+ supports_locking!
1458
+
1459
+ # Or declare specific capabilities
1460
+ declare_cache_capability(:advanced_analytics, true)
1461
+ declare_cache_capability(:compression_support, false)
1462
+ end
1463
+
1464
+ # Configure Rails to use your custom store
1465
+ Rails.application.configure do
1466
+ config.cache_store = MyAwesomeCacheStore.new
1467
+ end
1468
+
1469
+ # Tasker automatically detects capabilities and selects optimal strategy
1470
+ strategy = Tasker::CacheStrategy.detect
1471
+ puts strategy.coordination_mode # => :distributed_atomic
1472
+ puts strategy.supports?(:distributed) # => true
1473
+ ```
1474
+
1475
+ ### Architecture Overview
1476
+
1477
+ The cache detection system uses a **4-level priority hierarchy**:
1478
+
1479
+ ```mermaid
1480
+ flowchart TD
1481
+ A[Cache Store Detection] --> B{Priority 1: Declared?}
1482
+ B -->|Yes| C[Use Declared Capabilities]
1483
+ B -->|No| D{Priority 2: Built-in Store?}
1484
+ D -->|Yes| E[Use Frozen Constants]
1485
+ D -->|No| F{Priority 3: Custom Detector?}
1486
+ F -->|Yes| G[Apply Custom Detector]
1487
+ F -->|No| H[Priority 4: Runtime Detection]
1488
+
1489
+ C --> I[Select Coordination Strategy]
1490
+ E --> I
1491
+ G --> I
1492
+ H --> I
1493
+
1494
+ I --> J{Strategy Selection}
1495
+ J --> K[distributed_atomic]
1496
+ J --> L[distributed_basic]
1497
+ J --> M[local_only]
1498
+
1499
+ classDef priority fill:#e1f5fe,stroke:#01579b
1500
+ classDef strategy fill:#f3e5f5,stroke:#4a148c
1501
+
1502
+ class B,D,F priority
1503
+ class K,L,M strategy
1504
+ ```
1505
+
1506
+ ### Built-in Store Support
1507
+
1508
+ Tasker includes **frozen constants** for fast, reliable detection of official Rails cache stores:
1509
+
1510
+ ```ruby
1511
+ # Automatically detected with O(1) lookup
1512
+ DISTRIBUTED_CACHE_STORES = %w[
1513
+ ActiveSupport::Cache::RedisCacheStore
1514
+ ActiveSupport::Cache::MemCacheStore
1515
+ SolidCache::Store
1516
+ ].freeze
1517
+
1518
+ ATOMIC_INCREMENT_STORES = %w[
1519
+ ActiveSupport::Cache::RedisCacheStore
1520
+ ActiveSupport::Cache::MemCacheStore
1521
+ SolidCache::Store
1522
+ ].freeze
1523
+
1524
+ LOCKING_CAPABLE_STORES = %w[
1525
+ ActiveSupport::Cache::RedisCacheStore
1526
+ SolidCache::Store
1527
+ ].freeze
1528
+
1529
+ LOCAL_CACHE_STORES = %w[
1530
+ ActiveSupport::Cache::MemoryStore
1531
+ ActiveSupport::Cache::FileStore
1532
+ ActiveSupport::Cache::NullStore
1533
+ ].freeze
1534
+ ```
1535
+
1536
+ **Coordination Strategies by Store Type**:
1537
+ - **Redis/SolidCache**: `distributed_atomic` (full distributed coordination with locking)
1538
+ - **Memcached**: `distributed_basic` (distributed coordination without locking)
1539
+ - **Memory/File/Null**: `local_only` (single-process coordination)
1540
+
1541
+ ### Custom Cache Store Integration
1542
+
1543
+ #### Using CacheCapabilities Module
1544
+
1545
+ The `Tasker::CacheCapabilities` module provides a clean API for declaring your cache store's capabilities:
1546
+
1547
+ ```ruby
1548
+ class MyRedisCluster < ActiveSupport::Cache::Store
1549
+ include Tasker::CacheCapabilities
1550
+
1551
+ # Convenience methods for common capabilities
1552
+ supports_distributed_caching! # Sets distributed: true
1553
+ supports_atomic_increment! # Sets atomic_increment: true
1554
+ supports_locking! # Sets locking: true
1555
+ supports_ttl_inspection! # Sets ttl_inspection: true
1556
+ supports_namespace_isolation! # Sets namespace_support: true
1557
+ supports_compression! # Sets compression_support: true
1558
+
1559
+ # Custom capabilities specific to your implementation
1560
+ declare_cache_capability(:cluster_failover, true)
1561
+ declare_cache_capability(:read_replicas, true)
1562
+ declare_cache_capability(:geo_replication, false)
1563
+
1564
+ # Your cache store implementation...
1565
+ def read(name, options = nil)
1566
+ # Implementation
1567
+ end
1568
+
1569
+ def write(name, value, options = nil)
1570
+ # Implementation
1571
+ end
1572
+
1573
+ # Optional: Implement advanced features
1574
+ def increment(name, amount = 1, options = nil)
1575
+ # Atomic increment implementation
1576
+ end
1577
+
1578
+ def with_lock(name, options = {}, &block)
1579
+ # Distributed locking implementation
1580
+ end
1581
+ end
1582
+ ```
1583
+
1584
+ #### Capability Reference
1585
+
1586
+ | Capability | Description | Impact |
1587
+ |------------|-------------|---------|
1588
+ | `distributed` | Cache is shared across processes/containers | Enables distributed coordination |
1589
+ | `atomic_increment` | Supports atomic increment/decrement operations | Enables optimistic locking patterns |
1590
+ | `locking` | Supports distributed locking (with_lock) | Enables `distributed_atomic` strategy |
1591
+ | `ttl_inspection` | Can inspect/extend TTL of cached entries | Enables advanced TTL management |
1592
+ | `namespace_support` | Supports cache key namespacing | Enables namespace isolation |
1593
+ | `compression_support` | Supports automatic value compression | Enables bandwidth optimization |
1594
+
1595
+ #### Chaining and Inheritance
1596
+
1597
+ ```ruby
1598
+ class BaseDistributedStore < ActiveSupport::Cache::Store
1599
+ include Tasker::CacheCapabilities
1600
+
1601
+ supports_distributed_caching!
1602
+ supports_ttl_inspection!
1603
+ end
1604
+
1605
+ class RedisClusterStore < BaseDistributedStore
1606
+ # Inherits distributed and ttl_inspection capabilities
1607
+ supports_atomic_increment!
1608
+ supports_locking!
1609
+
1610
+ # Override inherited capabilities if needed
1611
+ declare_cache_capability(:ttl_inspection, false) # Override parent
1612
+ end
1613
+
1614
+ class MemcachedClusterStore < BaseDistributedStore
1615
+ # Different capabilities for different technology
1616
+ supports_atomic_increment!
1617
+ # Note: Memcached doesn't support distributed locking
1618
+ end
1619
+ ```
1620
+
1621
+ ### Advanced Usage Patterns
1622
+
1623
+ #### Custom Detector Registration
1624
+
1625
+ For complex detection logic that can't be expressed through simple capability declarations:
1626
+
1627
+ ```ruby
1628
+ # Register a custom detector for pattern-based detection
1629
+ Tasker::CacheStrategy.register_detector(/MyCustomCache/, lambda do |store|
1630
+ {
1631
+ distributed: store.respond_to?(:cluster_nodes) && store.cluster_nodes.size > 1,
1632
+ atomic_increment: store.respond_to?(:atomic_ops),
1633
+ locking: store.respond_to?(:distributed_lock),
1634
+ custom_feature: store.respond_to?(:advanced_query)
1635
+ }
1636
+ end)
1637
+
1638
+ # The detector will be applied automatically during detection
1639
+ strategy = Tasker::CacheStrategy.detect
1640
+ ```
1641
+
1642
+ #### Runtime Strategy Inspection
1643
+
1644
+ ```ruby
1645
+ strategy = Tasker::CacheStrategy.detect
1646
+
1647
+ # Check coordination mode
1648
+ puts strategy.coordination_mode # => :distributed_atomic
1649
+
1650
+ # Check specific capabilities
1651
+ puts strategy.supports?(:distributed) # => true
1652
+ puts strategy.supports?(:locking) # => true
1653
+ puts strategy.supports?(:custom_feature) # => true
1654
+
1655
+ # Export all capabilities (useful for debugging)
1656
+ puts strategy.export_capabilities
1657
+ # => {
1658
+ # distributed: true,
1659
+ # atomic_increment: true,
1660
+ # locking: true,
1661
+ # ttl_inspection: true,
1662
+ # namespace_support: true,
1663
+ # compression_support: false,
1664
+ # key_transformation: true,
1665
+ # store_class: "MyAwesomeCacheStore"
1666
+ # }
1667
+ ```
1668
+
1669
+ #### Production Monitoring
1670
+
1671
+ The cache strategy system includes comprehensive structured logging:
1672
+
1673
+ ```ruby
1674
+ # Automatic logging when strategy is detected
1675
+ strategy = Tasker::CacheStrategy.detect
1676
+
1677
+ # Logs output (JSON format):
1678
+ {
1679
+ "timestamp": "2025-06-28T17:15:53.386Z",
1680
+ "correlation_id": "tsk_mcgi5my9_a24eXc",
1681
+ "component": "cache_strategy",
1682
+ "message": "Cache strategy detected",
1683
+ "environment": "production",
1684
+ "tasker_version": "2.5.0",
1685
+ "store_class": "MyAwesomeCacheStore",
1686
+ "coordination_strategy": "distributed_atomic",
1687
+ "capabilities": {
1688
+ "distributed": true,
1689
+ "atomic_increment": true,
1690
+ "locking": true,
1691
+ "ttl_inspection": true,
1692
+ "namespace_support": true,
1693
+ "compression_support": false,
1694
+ "key_transformation": true,
1695
+ "store_class": "MyAwesomeCacheStore"
1696
+ },
1697
+ "instance_id": "web-01-12345"
1698
+ }
1699
+ ```
1700
+
1701
+ ### Best Practices
1702
+
1703
+ #### Cache Store Selection
1704
+
1705
+ **For High-Performance Applications**:
1706
+ ```ruby
1707
+ # Redis with full capabilities
1708
+ class ProductionRedisStore < ActiveSupport::Cache::RedisCacheStore
1709
+ include Tasker::CacheCapabilities
1710
+
1711
+ supports_distributed_caching!
1712
+ supports_atomic_increment!
1713
+ supports_locking!
1714
+ supports_ttl_inspection!
1715
+ supports_namespace_isolation!
1716
+ supports_compression!
1717
+ end
1718
+ ```
1719
+
1720
+ **For Development/Testing**:
1721
+ ```ruby
1722
+ # Memory store with local-only coordination
1723
+ # No additional configuration needed - automatically detected
1724
+ Rails.application.configure do
1725
+ config.cache_store = :memory_store
1726
+ end
1727
+ ```
1728
+
1729
+ **For Legacy Memcached**:
1730
+ ```ruby
1731
+ class LegacyMemcachedStore < ActiveSupport::Cache::MemCacheStore
1732
+ include Tasker::CacheCapabilities
1733
+
1734
+ supports_distributed_caching!
1735
+ supports_atomic_increment!
1736
+ # Note: Don't declare locking support - Memcached doesn't support it
1737
+ supports_ttl_inspection!
1738
+ end
1739
+ ```
1740
+
1741
+ #### Testing Custom Stores
1742
+
1743
+ ```ruby
1744
+ # spec/lib/my_awesome_cache_store_spec.rb
1745
+ RSpec.describe MyAwesomeCacheStore do
1746
+ let(:store) { described_class.new }
1747
+
1748
+ describe 'capability declarations' do
1749
+ it 'declares expected capabilities' do
1750
+ capabilities = store.class.declared_cache_capabilities
1751
+
1752
+ expect(capabilities[:distributed]).to be(true)
1753
+ expect(capabilities[:atomic_increment]).to be(true)
1754
+ expect(capabilities[:locking]).to be(true)
1755
+ expect(capabilities[:advanced_analytics]).to be(true)
1756
+ expect(capabilities[:compression_support]).to be(false)
1757
+ end
1758
+ end
1759
+
1760
+ describe 'integration with CacheStrategy' do
1761
+ before do
1762
+ allow(Rails).to receive(:cache).and_return(store)
1763
+ end
1764
+
1765
+ it 'is detected correctly by CacheStrategy' do
1766
+ strategy = Tasker::CacheStrategy.detect
1767
+
1768
+ expect(strategy.coordination_mode).to eq(:distributed_atomic)
1769
+ expect(strategy.supports?(:distributed)).to be(true)
1770
+ expect(strategy.supports?(:advanced_analytics)).to be(true)
1771
+ end
1772
+ end
1773
+ end
1774
+ ```
1775
+
1776
+ ### Migration Guide
1777
+
1778
+ #### Upgrading Existing Custom Stores
1779
+
1780
+ If you have existing custom cache stores, add capability declarations:
1781
+
1782
+ ```ruby
1783
+ # Before: No capability declarations
1784
+ class MyLegacyStore < ActiveSupport::Cache::Store
1785
+ # Implementation...
1786
+ end
1787
+
1788
+ # After: With capability declarations
1789
+ class MyLegacyStore < ActiveSupport::Cache::Store
1790
+ include Tasker::CacheCapabilities
1791
+
1792
+ # Declare what your store actually supports
1793
+ supports_distributed_caching! if respond_to?(:cluster_mode?)
1794
+ supports_atomic_increment! if respond_to?(:increment)
1795
+ # etc.
1796
+ end
1797
+ ```
1798
+
1799
+ #### Backwards Compatibility
1800
+
1801
+ The system maintains full backwards compatibility:
1802
+ - **Undeclared stores**: Fall back to runtime pattern detection
1803
+ - **Existing configurations**: Continue working without changes
1804
+ - **Built-in stores**: Automatically detected with frozen constants
1805
+
1806
+ The cache strategy system provides a robust foundation for building cache-aware applications that adapt intelligently to different deployment environments while maintaining optimal performance characteristics.
1807
+
1808
+ ## Extensibility & Advanced Patterns
1809
+
1810
+ ### Custom Step Handler Types
1811
+
1812
+ Beyond the built-in `Base` and `Api` step handlers, you can create specialized step handler types for common patterns in your application:
1813
+
1814
+ ```ruby
1815
+ # Base class for database-heavy operations
1816
+ module YourApp
1817
+ module StepHandler
1818
+ class DatabaseBase < Tasker::StepHandler::Base
1819
+ protected
1820
+
1821
+ def with_transaction(&block)
1822
+ ActiveRecord::Base.transaction(&block)
1823
+ rescue ActiveRecord::StatementInvalid => e
1824
+ # Transform database errors for consistent handling
1825
+ raise Tasker::RetryableError, "Database operation failed: #{e.message}"
1826
+ end
1827
+
1828
+ def bulk_insert(model_class, records, batch_size: 1000)
1829
+ records.each_slice(batch_size) do |batch|
1830
+ model_class.insert_all(batch)
1831
+ end
1832
+ end
1833
+ end
1834
+ end
1835
+ end
1836
+
1837
+ # Usage in your step handlers
1838
+ class ProcessBulkOrdersHandler < YourApp::StepHandler::DatabaseBase
1839
+ def process(task, sequence, step)
1840
+ orders_data = get_previous_step_data(sequence, 'fetch_orders', 'orders')
1841
+
1842
+ with_transaction do
1843
+ bulk_insert(Order, orders_data, batch_size: 500)
1844
+ end
1845
+
1846
+ { processed_count: orders_data.size }
1847
+ end
1848
+ end
1849
+ ```
1850
+
1851
+ ### Custom Event Publishing Patterns
1852
+
1853
+ Create reusable event publishing patterns for consistent observability:
1854
+
1855
+ ```ruby
1856
+ # Mixin for consistent business event publishing
1857
+ module BusinessEventPublisher
1858
+ extend ActiveSupport::Concern
1859
+
1860
+ def publish_business_event(domain, action, entity_id, data = {})
1861
+ event_name = "#{domain}.#{action}"
1862
+ event_data = {
1863
+ entity_id: entity_id,
1864
+ domain: domain,
1865
+ action: action,
1866
+ timestamp: Time.current.iso8601,
1867
+ **data
1868
+ }
1869
+
1870
+ publish_custom_event(event_name, event_data)
1871
+ end
1872
+ end
1873
+
1874
+ # Usage in step handlers
1875
+ class ProcessOrderHandler < Tasker::StepHandler::Base
1876
+ include BusinessEventPublisher
1877
+
1878
+ def process(task, sequence, step)
1879
+ order = Order.find(task.context['order_id'])
1880
+
1881
+ # Process order logic here...
1882
+ order.update!(status: 'processed')
1883
+
1884
+ # Publish consistent business event
1885
+ publish_business_event('order', 'processed', order.id, {
1886
+ customer_id: order.customer_id,
1887
+ total_amount: order.total,
1888
+ processing_duration: Time.current - order.created_at
1889
+ })
1890
+
1891
+ { order_id: order.id, status: 'processed' }
1892
+ end
1893
+ end
1894
+ ```
1895
+
1896
+ ### Advanced Configuration Patterns
1897
+
1898
+ **Environment-Specific Step Configuration**:
1899
+ ```yaml
1900
+ # config/tasker/tasks/order_process/order_handler.yaml
1901
+ step_templates:
1902
+ - name: send_notification
1903
+ handler_class: OrderProcess::SendNotificationHandler
1904
+ handler_config:
1905
+ <% if Rails.env.production? %>
1906
+ notification_service: sms_gateway
1907
+ priority: high
1908
+ <% else %>
1909
+ notification_service: email_only
1910
+ priority: low
1911
+ <% end %>
1912
+ ```
1913
+
1914
+ **Dynamic Step Generation**:
1915
+ ```ruby
1916
+ # Support for dynamically generated workflows
1917
+ class DynamicOrderProcess < Tasker::TaskHandler::Base
1918
+ def self.generate_step_templates(product_types)
1919
+ base_steps = [
1920
+ { name: 'validate_order', handler_class: 'OrderProcess::ValidateOrderHandler' }
1921
+ ]
1922
+
1923
+ # Generate product-specific steps
1924
+ product_steps = product_types.map do |type|
1925
+ {
1926
+ name: "process_#{type}",
1927
+ depends_on_step: 'validate_order',
1928
+ handler_class: "OrderProcess::Process#{type.camelize}Handler"
1929
+ }
1930
+ end
1931
+
1932
+ merge_step = {
1933
+ name: 'finalize_order',
1934
+ depends_on_steps: product_steps.map { |step| step[:name] },
1935
+ handler_class: 'OrderProcess::FinalizeOrderHandler'
1936
+ }
1937
+
1938
+ base_steps + product_steps + [merge_step]
1939
+ end
1940
+ end
1941
+ ```
1942
+
1943
+ ### Custom Workflow Coordination
1944
+
1945
+ **Priority-Based Step Execution**:
1946
+ ```ruby
1947
+ module PriorityWorkflow
1948
+ class Coordinator < Tasker::Orchestration::Coordinator
1949
+ private
1950
+
1951
+ def prioritize_ready_steps(ready_steps)
1952
+ # Custom sorting based on step configuration
1953
+ ready_steps.sort_by do |step|
1954
+ priority = step.handler_config['priority'] || 'normal'
1955
+ case priority
1956
+ when 'critical' then 0
1957
+ when 'high' then 1
1958
+ when 'normal' then 2
1959
+ when 'low' then 3
1960
+ else 2
1961
+ end
1962
+ end
1963
+ end
1964
+ end
1965
+ end
1966
+
1967
+ # Configure in initializer
1968
+ Tasker::Configuration.configuration do |config|
1969
+ config.orchestration.coordinator_class = 'PriorityWorkflow::Coordinator'
1970
+ end
1971
+ ```
1972
+
1973
+ **Resource-Aware Execution**:
1974
+ ```ruby
1975
+ class ResourceAwareStepHandler < Tasker::StepHandler::Base
1976
+ def process(task, sequence, step)
1977
+ # Check system resources before proceeding
1978
+ if system_under_load?
1979
+ # Delay execution by raising a retryable error
1980
+ raise Tasker::RetryableError, "System under load, retrying later"
1981
+ end
1982
+
1983
+ # Normal processing
1984
+ perform_resource_intensive_operation
1985
+ end
1986
+
1987
+ private
1988
+
1989
+ def system_under_load?
1990
+ # Check CPU, memory, or external service health
1991
+ cpu_usage > 80 || memory_usage > 90 || external_service_degraded?
1992
+ end
1993
+ end
1994
+ ```
1995
+
1996
+ ### Custom Result Processing
1997
+
1998
+ **Structured Result Schemas**:
1999
+ ```ruby
2000
+ class SchemaValidatedStepHandler < Tasker::StepHandler::Base
2001
+ RESULT_SCHEMA = {
2002
+ type: 'object',
2003
+ required: ['status', 'data'],
2004
+ properties: {
2005
+ status: { type: 'string', enum: ['success', 'partial', 'warning'] },
2006
+ data: { type: 'object' },
2007
+ metadata: { type: 'object' }
2008
+ }
2009
+ }.freeze
2010
+
2011
+ def process(task, sequence, step)
2012
+ result = perform_business_logic
2013
+
2014
+ # Validate result structure
2015
+ validate_result_schema(result)
2016
+
2017
+ result
2018
+ end
2019
+
2020
+ private
2021
+
2022
+ def validate_result_schema(result)
2023
+ errors = JSON::Validator.fully_validate(RESULT_SCHEMA, result)
2024
+ raise ArgumentError, "Invalid result schema: #{errors.join(', ')}" if errors.any?
2025
+ end
2026
+ end
2027
+ ```
2028
+
2029
+ **Result Transformation Pipelines**:
2030
+ ```ruby
2031
+ module ResultTransformers
2032
+ class SensitiveDataFilter
2033
+ def self.transform(results)
2034
+ # Remove sensitive data from results before storage
2035
+ results.except('password', 'api_key', 'secret_token')
2036
+ end
2037
+ end
2038
+
2039
+ class MetricsExtractor
2040
+ def self.transform(results)
2041
+ # Extract metrics for monitoring
2042
+ {
2043
+ **results,
2044
+ performance_metrics: {
2045
+ execution_time: results['duration'],
2046
+ memory_used: results['memory_peak'],
2047
+ records_processed: results['record_count']
2048
+ }
2049
+ }
2050
+ end
2051
+ end
2052
+ end
2053
+
2054
+ class TransformingStepHandler < Tasker::StepHandler::Base
2055
+ def process_results(step, process_output, initial_results)
2056
+ # Apply transformation pipeline
2057
+ transformed = process_output
2058
+ transformed = ResultTransformers::SensitiveDataFilter.transform(transformed)
2059
+ transformed = ResultTransformers::MetricsExtractor.transform(transformed)
2060
+
2061
+ step.results = transformed
2062
+ end
2063
+ end
2064
+ ```
2065
+
2066
+ ## Task Execution & Orchestration
2067
+
2068
+ For detailed understanding of Tasker's sophisticated orchestration patterns, including the dual finalization strategy and coordinated vs autonomous execution contexts, see:
2069
+
2070
+ **📖 [Task Execution Control Flow](TASK_EXECUTION_CONTROL_FLOW.md)** - Comprehensive documentation of workflow orchestration patterns, synchronous vs asynchronous finalization, and the coordination between WorkflowCoordinator and TaskFinalizer components.
2071
+
2072
+ ## Best Practices
2073
+
2074
+ ### Task Handler Design
2075
+
2076
+ 1. **Single Responsibility**: Each task handler should represent one business process
2077
+ 2. **Meaningful Names**: Use descriptive names that clearly indicate the workflow purpose
2078
+ 3. **Proper Dependencies**: Define clear step dependencies that reflect business logic
2079
+ 4. **Error Handling**: Configure appropriate retry limits based on operation reliability
2080
+ 5. **Context Design**: Include all necessary data in task context for step execution
2081
+ 6. **Extensibility**: Design task handlers to support configuration-driven customization
2082
+
2083
+ ### Step Handler Implementation
2084
+
2085
+ 1. **Focused Logic**: Each step should do one thing well
2086
+ 2. **Clear Results**: Return meaningful data structure for dependent steps
2087
+ 3. **Error Handling**: Understand how the framework determines step success/failure (see Error Handling Patterns below)
2088
+ 4. **Idempotency**: Design steps to be safely retryable
2089
+ 5. **Resource Cleanup**: Clean up resources in error scenarios
2090
+
2091
+ #### Error Handling Patterns
2092
+
2093
+ The framework determines step success/failure based on whether the `process` method raises an exception:
2094
+
2095
+ - **Exception raised** → Step marked as FAILED, retry logic triggered, workflow stops
2096
+ - **No exception raised** → Step marked as COMPLETED, workflow continues
2097
+
2098
+ **Pattern 1: Let exceptions bubble up (Recommended)**
2099
+ ```ruby
2100
+ def process(task, sequence, step)
2101
+ # Attempt the operation - let exceptions propagate naturally
2102
+ result = perform_complex_operation(task.context)
2103
+ { success: true, data: result }
2104
+
2105
+ # Framework automatically handles exceptions:
2106
+ # - Publishes step_failed event with error details
2107
+ # - Stores error information in step.results
2108
+ # - Transitions step to error state
2109
+ # - Triggers retry logic if configured
2110
+ end
2111
+ ```
2112
+
2113
+ **Pattern 2: Catch, record error details, then re-raise**
2114
+ ```ruby
2115
+ def process(task, sequence, step)
2116
+ begin
2117
+ result = perform_complex_operation(task.context)
2118
+ { success: true, data: result }
2119
+ rescue StandardError => e
2120
+ # Add custom error context to step.results
2121
+ step.results = {
2122
+ error: e.message,
2123
+ error_type: e.class.name,
2124
+ custom_context: "Additional business context",
2125
+ retry_recommended: should_retry?(e)
2126
+ }
2127
+ # Re-raise so framework knows this step failed
2128
+ raise
2129
+ end
2130
+ end
2131
+ ```
2132
+
2133
+ **Pattern 3: Treat handled exceptions as success**
2134
+ ```ruby
2135
+ def process(task, sequence, step)
2136
+ begin
2137
+ result = perform_complex_operation(task.context)
2138
+ { success: true, data: result }
2139
+ rescue RecoverableError => e
2140
+ # This exception is handled and considered a success case
2141
+ # Step will be marked as COMPLETED, not failed
2142
+ {
2143
+ success: true,
2144
+ data: get_fallback_data(task.context),
2145
+ recovered_from_error: e.message,
2146
+ used_fallback: true
2147
+ }
2148
+ end
2149
+ end
2150
+ ```
2151
+
2152
+ ⚠️ **Important**: Only catch exceptions in your `process` method if you intend to either:
2153
+ - Add custom error context to `step.results` and re-raise (Pattern 2)
2154
+ - Treat the exception as a recoverable success case (Pattern 3)
2155
+
2156
+ **Never** catch an exception, return error data, and expect the framework to treat it as a failure - it will be marked as successful.
2157
+
2158
+ ### Event Subscriber Guidelines
2159
+
2160
+ 1. **Extend BaseSubscriber**: Always use `Tasker::Events::Subscribers::BaseSubscriber`
2161
+ 2. **Safe Data Access**: Use `safe_get(event, :key, default)` for robust data access
2162
+ 3. **Error Isolation**: Don't let subscriber errors break task execution
2163
+ 4. **Async Operations**: Move heavy operations to background jobs
2164
+ 5. **Monitoring**: Monitor subscriber performance and error rates
2165
+
2166
+ ### YAML Configuration Management
2167
+
2168
+ 1. **Environment Separation**: Use environment-specific configurations appropriately
2169
+ 2. **Sensitive Data**: Use environment variables for secrets and API keys
2170
+ 3. **Documentation**: Include clear descriptions for all steps
2171
+ 4. **Validation**: Use JSON schema validation for task context requirements
2172
+ 5. **Versioning**: Version control all YAML configuration files
2173
+
2174
+ ## Development Workflow
2175
+
2176
+ ### 1. Plan Your Workflow
2177
+ ```bash
2178
+ # Define your business process steps and dependencies
2179
+ # Example: Order Processing
2180
+ # 1. Validate Order → 2. Process Payment → 3. Ship Order
2181
+ ```
2182
+
2183
+ ### 2. Generate Task Handler
2184
+ ```bash
2185
+ rails generate tasker:task_handler OrderHandler --module_namespace OrderProcess --steps="validate_order,process_payment,ship_order"
2186
+ ```
2187
+
2188
+ ### 3. Implement Step Handlers
2189
+ ```ruby
2190
+ # app/tasks/order_process/validate_order_handler.rb
2191
+ class ValidateOrderHandler < Tasker::StepHandler::Base
2192
+ def self.custom_event_configuration
2193
+ [
2194
+ { name: 'order.validation_failed', description: 'Order validation failed' },
2195
+ { name: 'order.validated', description: 'Order validation successful' }
2196
+ ]
2197
+ end
2198
+
2199
+ def process(task, sequence, step)
2200
+ # Implementation here
2201
+ # Publish custom events as needed
2202
+ end
2203
+ end
2204
+ ```
2205
+
2206
+ ### 4. Configure Dependencies
2207
+ ```yaml
2208
+ # config/tasker/tasks/order_process/order_handler.yaml
2209
+ step_templates:
2210
+ - name: validate_order
2211
+ handler_class: OrderProcess::ValidateOrderHandler
2212
+ - name: process_payment
2213
+ depends_on_step: validate_order
2214
+ handler_class: OrderProcess::ProcessPaymentHandler
2215
+ custom_events:
2216
+ - name: payment.gateway_error
2217
+ description: Payment gateway returned an error
2218
+ - name: ship_order
2219
+ depends_on_step: process_payment
2220
+ handler_class: OrderProcess::ShipOrderHandler
2221
+ ```
2222
+
2223
+ ### 5. Test Your Workflow
2224
+ ```ruby
2225
+ # spec/tasks/order_process/order_handler_spec.rb
2226
+ RSpec.describe OrderProcess::OrderHandler do
2227
+ it 'processes orders successfully' do
2228
+ # Test implementation
2229
+ end
2230
+ end
2231
+ ```
2232
+
2233
+ ### 6. Deploy and Monitor
2234
+ - Custom events are automatically registered and discoverable
2235
+ - External systems can subscribe to your custom events
2236
+ - Monitor workflow execution through event subscriptions
2237
+
2238
+ ## Testing Strategies
2239
+
2240
+ ### Testing Task Handlers
2241
+ ```ruby
2242
+ RSpec.describe OrderProcess::OrderHandler do
2243
+ describe '#initialize_task!' do
2244
+ let(:task_request) do
2245
+ Tasker::Types::TaskRequest.new(
2246
+ name: 'order_process',
2247
+ context: { order_id: 12345, customer_id: 67890 }
2248
+ )
2249
+ end
2250
+
2251
+ it 'creates task with proper step configuration' do
2252
+ handler = described_class.new
2253
+ task = handler.initialize_task!(task_request)
2254
+
2255
+ expect(task.workflow_steps.count).to eq(5)
2256
+ expect(task.workflow_steps.pluck(:name)).to include('fetch_order', 'process_payment')
2257
+ end
2258
+ end
2259
+ end
2260
+ ```
2261
+
2262
+ ### Testing Step Handlers
2263
+ ```ruby
2264
+ RSpec.describe OrderProcess::StepHandler::ProcessPaymentHandler do
2265
+ describe '#process' do
2266
+ let(:task) { create(:task, context: { order_id: 123, payment_method: 'credit_card' }) }
2267
+ let(:sequence) { build_sequence_for(task) }
2268
+ let(:step) { sequence.find_step_by_name('process_payment') }
2269
+
2270
+ it 'processes payment and returns payment details' do
2271
+ handler = described_class.new
2272
+ result = handler.process(task, sequence, step)
2273
+
2274
+ expect(result).to include(:payment_id, :amount_charged, :transaction_id)
2275
+ end
2276
+ end
2277
+ end
2278
+ ```
2279
+
2280
+ ### Testing Event Subscribers
2281
+ ```ruby
2282
+ RSpec.describe NotificationSubscriber do
2283
+ describe '#handle_task_completed' do
2284
+ let(:event) { { task_id: 'task_123', task_name: 'order_process', execution_duration: 45.2 } }
2285
+
2286
+ it 'sends completion notification' do
2287
+ subscriber = described_class.new
2288
+
2289
+ expect(NotificationService).to receive(:send_email)
2290
+ .with(hash_including(subject: /Task Completed/))
2291
+
2292
+ subscriber.handle_task_completed(event)
2293
+ end
2294
+ end
2295
+ end
2296
+ ```
2297
+
2298
+ ## Integration Validation & Production Readiness
2299
+
2300
+ Tasker includes comprehensive integration validation scripts that prove production readiness through real-world testing of the complete observability stack. These scripts are essential for validating enterprise deployments and ensuring reliable metrics collection and distributed tracing.
2301
+
2302
+ ### Validation Script Architecture
2303
+
2304
+ The validation scripts (`scripts/validate_*.rb`) provide **enterprise-grade validation** of Tasker's integration with observability stack components:
2305
+
2306
+ #### **Jaeger Integration Validator** (`validate_jaeger_integration.rb`)
2307
+ Validates OpenTelemetry distributed tracing integration:
2308
+
2309
+ ```bash
2310
+ # Run comprehensive Jaeger validation
2311
+ ./scripts/validate_jaeger_integration.rb
2312
+ ```
2313
+
2314
+ **Validation Categories**:
2315
+ - **Connection Testing**: Verifies Jaeger HTTP API connectivity
2316
+ - **Workflow Execution**: Creates and executes real workflow patterns (linear, diamond, parallel)
2317
+ - **Trace Collection**: Validates trace export and collection in Jaeger
2318
+ - **Span Hierarchy**: Analyzes parent-child relationships across workflow steps
2319
+ - **Trace Correlation**: Ensures proper trace correlation across distributed operations
2320
+
2321
+ **Sample Results**:
2322
+ ```
2323
+ 🎯 Tasker 2.5.0 - Jaeger Integration Validator
2324
+ ✅ Jaeger Connection: PASS - Successfully connected to Jaeger
2325
+ ✅ Workflow Execution: PASS - Created and executed 3 workflows
2326
+ ✅ Trace Collection: PASS - Successfully collected 13 spans
2327
+ ✅ Span Hierarchy: PASS - Validated 10 parent-child relationships
2328
+ ✅ Trace Correlation: PASS - All spans properly correlated
2329
+
2330
+ 📊 Span Analysis Results:
2331
+ Linear Workflow: 4 spans, 3 parent-child relationships
2332
+ Diamond Workflow: 5 spans, 4 parent-child relationships
2333
+ Parallel Workflow: 4 spans, 3 parent-child relationships
2334
+ ```
2335
+
2336
+ #### **Prometheus Integration Validator** (`validate_prometheus_integration.rb`)
2337
+ Validates metrics collection and Prometheus integration:
2338
+
2339
+ ```bash
2340
+ # Run comprehensive Prometheus validation
2341
+ ./scripts/validate_prometheus_integration.rb
2342
+ ```
2343
+
2344
+ **Validation Categories**:
2345
+ - **Prometheus Connection**: Verifies Prometheus server connectivity
2346
+ - **Metrics Endpoint**: Tests Tasker's `/tasker/metrics` endpoint
2347
+ - **Workflow Execution**: Executes workflows to generate authentic metrics
2348
+ - **Metrics Collection**: Validates event-driven metrics collection via EventRouter
2349
+ - **Query Validation**: Tests PromQL queries for dashboard compatibility
2350
+ - **Performance Analysis**: Analyzes TSDB integration and performance
2351
+
2352
+ **Sample Results**:
2353
+ ```
2354
+ 🎯 Tasker 2.5.0 - Prometheus Integration Validator
2355
+ ✅ MetricsSubscriber registered successfully
2356
+ ✅ Prometheus Connection: PASS - Successfully connected to Prometheus
2357
+ ✅ Metrics Endpoint: PASS - Tasker metrics endpoint accessible
2358
+ ✅ Workflow Execution: PASS - Created and executed 3 workflows
2359
+ ✅ Metrics Collection: PASS - Successfully collected 3 total metrics
2360
+ ✅ Query Validation: PASS - All 4 PromQL queries successful
2361
+
2362
+ 📊 Metrics Analysis Results:
2363
+ Counter metrics: 2 (step_completed_total: 22, task_completed_total: 3)
2364
+ Histogram metrics: 1 (step_duration_seconds)
2365
+ Total workflow activity: 22 steps completed across 3 tasks
2366
+ ```
2367
+
2368
+ ### Event-Driven Architecture Validation
2369
+
2370
+ The validation scripts prove Tasker's sophisticated event-driven architecture:
2371
+
2372
+ ```mermaid
2373
+ flowchart LR
2374
+ subgraph Validation["Integration Validation"]
2375
+ WF["Workflow<br/>Execution"]
2376
+ EP["Event<br/>Publishing"]
2377
+ end
2378
+
2379
+ subgraph EventSystem["Event System"]
2380
+ PUB["Events::<br/>Publisher"]
2381
+ TS["Telemetry<br/>Subscriber"]
2382
+ MS["Metrics<br/>Subscriber"]
2383
+ end
2384
+
2385
+ subgraph Observability["Observability Stack"]
2386
+ OT["OpenTelemetry<br/>Spans"]
2387
+ ER["EventRouter<br/>Metrics"]
2388
+ J["Jaeger<br/>Tracing"]
2389
+ P["Prometheus<br/>Metrics"]
2390
+ end
2391
+
2392
+ WF --> EP
2393
+ EP --> PUB
2394
+ PUB --> TS
2395
+ PUB --> MS
2396
+ TS --> OT
2397
+ MS --> ER
2398
+ OT --> J
2399
+ ER --> P
2400
+
2401
+ classDef validation fill:#e1f5fe,stroke:#01579b
2402
+ classDef events fill:#fff3e0,stroke:#e65100
2403
+ classDef observability fill:#e8f5e8,stroke:#2e7d32
2404
+
2405
+ class WF,EP validation
2406
+ class PUB,TS,MS events
2407
+ class OT,ER,J,P observability
2408
+ ```
2409
+
2410
+ ### Critical Technical Breakthrough: MetricsSubscriber
2411
+
2412
+ The Prometheus validator discovered and resolved a **critical missing component** in Tasker's metrics architecture:
2413
+
2414
+ **Problem**: Events were being published via `Events::Publisher` and creating OpenTelemetry spans via `TelemetrySubscriber`, but **no metrics were being collected** because there was no bridge between the event system and the `EventRouter` → `MetricsBackend` system.
2415
+
2416
+ **Solution**: Created `MetricsSubscriber` that bridges events to the EventRouter:
2417
+
2418
+ ```ruby
2419
+ # lib/tasker/events/subscribers/metrics_subscriber.rb
2420
+ class MetricsSubscriber < BaseSubscriber
2421
+ def handle_event(event_name, payload)
2422
+ # Bridge events to EventRouter for automatic metrics collection
2423
+ Tasker::Telemetry::EventRouter.instance.route_event(event_name, payload)
2424
+ rescue StandardError => e
2425
+ Rails.logger.error("MetricsSubscriber failed to route event: #{e.message}")
2426
+ end
2427
+ end
2428
+ ```
2429
+
2430
+ **Integration**: The `MetricsSubscriber` is now automatically registered in the `Orchestration::Coordinator` alongside the `TelemetrySubscriber`, ensuring complete observability coverage.
2431
+
2432
+ ### Production Deployment Validation
2433
+
2434
+ #### Prerequisites for Validation
2435
+ ```bash
2436
+ # 1. Start observability stack
2437
+ docker-compose up -d jaeger prometheus
2438
+
2439
+ # 2. Ensure Tasker Rails application is running
2440
+ bundle exec rails server
2441
+
2442
+ # 3. Run validation scripts
2443
+ ./scripts/validate_jaeger_integration.rb
2444
+ ./scripts/validate_prometheus_integration.rb
2445
+ ```
2446
+
2447
+ #### Continuous Integration Integration
2448
+ Both scripts are designed for CI/CD pipelines:
2449
+
2450
+ ```yaml
2451
+ # .github/workflows/integration-validation.yml
2452
+ name: Integration Validation
2453
+ on: [push, pull_request]
2454
+
2455
+ jobs:
2456
+ validate-integrations:
2457
+ runs-on: ubuntu-latest
2458
+ services:
2459
+ jaeger:
2460
+ image: jaegertracing/all-in-one:latest
2461
+ ports:
2462
+ - 14268:14268
2463
+ prometheus:
2464
+ image: prom/prometheus:latest
2465
+ ports:
2466
+ - 9090:9090
2467
+
2468
+ steps:
2469
+ - uses: actions/checkout@v2
2470
+ - name: Setup Ruby
2471
+ uses: ruby/setup-ruby@v1
2472
+ with:
2473
+ bundler-cache: true
2474
+
2475
+ - name: Start Rails Application
2476
+ run: bundle exec rails server &
2477
+
2478
+ - name: Validate Jaeger Integration
2479
+ run: ./scripts/validate_jaeger_integration.rb
2480
+
2481
+ - name: Validate Prometheus Integration
2482
+ run: ./scripts/validate_prometheus_integration.rb
2483
+ ```
2484
+
2485
+ #### Enterprise Deployment Checklist
2486
+
2487
+ **Pre-Deployment Validation**:
2488
+ - [ ] ✅ Jaeger integration validator passes (100% success rate)
2489
+ - [ ] ✅ Prometheus integration validator passes (100% success rate)
2490
+ - [ ] ✅ All observability stack components responding
2491
+ - [ ] ✅ Metrics collection functioning (non-zero metrics count)
2492
+ - [ ] ✅ Distributed tracing working (proper span hierarchies)
2493
+ - [ ] ✅ Dashboard queries validated (PromQL compatibility)
2494
+
2495
+ **Post-Deployment Monitoring**:
2496
+ ```bash
2497
+ # Validate production observability stack
2498
+ PROMETHEUS_URL=https://prometheus.production.com \
2499
+ JAEGER_URL=https://jaeger.production.com \
2500
+ ./scripts/validate_prometheus_integration.rb
2501
+
2502
+ # Monitor metrics collection in production
2503
+ bundle exec rake app:tasker:metrics_status
2504
+ ```
2505
+
2506
+ ### Development Workflow Integration
2507
+
2508
+ #### Local Development Setup
2509
+ ```bash
2510
+ # 1. Start local observability stack
2511
+ docker-compose up -d
2512
+
2513
+ # 2. Run Tasker with observability enabled
2514
+ RAILS_ENV=development bundle exec rails server
2515
+
2516
+ # 3. Validate integrations during development
2517
+ ./scripts/validate_jaeger_integration.rb
2518
+ ./scripts/validate_prometheus_integration.rb
2519
+ ```
2520
+
2521
+ #### Debugging Integration Issues
2522
+ The validation scripts provide comprehensive diagnostics:
2523
+
2524
+ ```bash
2525
+ # Detailed diagnostic output for troubleshooting
2526
+ ./scripts/validate_jaeger_integration.rb --verbose
2527
+ ./scripts/validate_prometheus_integration.rb --verbose
2528
+ ```
2529
+
2530
+ **Common Issues & Solutions**:
2531
+ - **No Metrics Collected**: Ensure `MetricsSubscriber` is registered (automatic in Tasker 2.5.0+)
2532
+ - **Missing Spans**: Verify OpenTelemetry exporter configuration
2533
+ - **Connection Failures**: Check service ports and network connectivity
2534
+ - **Query Failures**: Validate Prometheus data retention and configuration
2535
+
2536
+ ### Strategic Value
2537
+
2538
+ The integration validation scripts provide:
2539
+
2540
+ - **🎯 Production Confidence**: Comprehensive proof of enterprise readiness
2541
+ - **🔧 Developer Experience**: Clear validation results with actionable feedback
2542
+ - **📊 Integration Foundation**: Proven patterns for observability stack integration
2543
+ - **📈 Content Creation**: Technical excellence provides foundation for documentation and presentations
2544
+ - **🚀 Enterprise Adoption**: Validated observability enables confident production deployment
2545
+
2546
+ **Status**: **COMPLETE** - Both Jaeger and Prometheus integration validators fully implemented and tested with 100% success rates, proving Tasker's production readiness through comprehensive observability stack validation.
2547
+
2548
+ ## Application Template Validation (v2.6.0)
2549
+
2550
+ Tasker includes a comprehensive dry-run validation system for application template generation that ensures template consistency and correctness without generating files. This system is essential for maintaining template quality and CI/CD pipeline integration.
2551
+
2552
+ ### Dry-Run Validation Overview
2553
+
2554
+ The validation system tests five critical categories of template correctness:
2555
+
2556
+ ```bash
2557
+ # Run comprehensive validation
2558
+ ruby scripts/create_tasker_app.rb dry_run
2559
+
2560
+ # Run specific validation categories
2561
+ ruby scripts/create_tasker_app.rb dry_run --mode=templates # File existence
2562
+ ruby scripts/create_tasker_app.rb dry_run --mode=syntax # ERB/Ruby/YAML syntax
2563
+ ruby scripts/create_tasker_app.rb dry_run --mode=cli # CLI option mapping
2564
+ ruby scripts/create_tasker_app.rb dry_run --mode=bindings # Template variables
2565
+
2566
+ # Validate Docker templates
2567
+ ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
2568
+ ```
2569
+
2570
+ ### Validation Categories
2571
+
2572
+ **1. Template File Existence Validation**
2573
+ - Verifies all required ERB templates exist for selected task types
2574
+ - Adapts template requirements based on Docker/observability modes
2575
+ - Reports missing templates with specific file paths
2576
+
2577
+ **2. ERB Template Syntax Validation**
2578
+ - Parses all ERB templates for syntax errors using `ERB.new(template).check_syntax`
2579
+ - Validates template structure without executing Ruby code
2580
+ - Catches malformed ERB blocks and Ruby expressions
2581
+
2582
+ **3. Generated Code Syntax Validation**
2583
+ - Renders templates with test data to validate actual output
2584
+ - Uses `RubyVM::InstructionSequence.compile` for Ruby syntax validation
2585
+ - Uses `YAML.safe_load` for YAML syntax validation
2586
+ - Tests real generated output, not just template correctness
2587
+
2588
+ **4. CLI Options Mapping Validation**
2589
+ - Ensures all Thor CLI options have corresponding instance variables
2590
+ - Validates option-to-variable mapping (e.g., `--docker` → `@docker_mode`)
2591
+ - Checks for missing or incorrectly mapped CLI options
2592
+
2593
+ **5. Template Variable Bindings Validation**
2594
+ - Tests template rendering with expected binding contexts
2595
+ - Validates all required variables are available during rendering
2596
+ - Tests step handlers, configuration files, and Docker-specific bindings
2597
+
2598
+ ### CI/CD Integration
2599
+
2600
+ The dry-run validation system is designed for continuous integration:
2601
+
2602
+ ```yaml
2603
+ # .github/workflows/template-validation.yml
2604
+ name: Template Validation
2605
+ on: [push, pull_request]
2606
+
2607
+ jobs:
2608
+ validate-templates:
2609
+ runs-on: ubuntu-latest
2610
+ steps:
2611
+ - uses: actions/checkout@v2
2612
+ - name: Setup Ruby
2613
+ uses: ruby/setup-ruby@v1
2614
+ with:
2615
+ bundler-cache: true
2616
+
2617
+ - name: Validate Application Templates
2618
+ run: |
2619
+ # Test all template combinations
2620
+ ruby scripts/create_tasker_app.rb dry_run --mode=all
2621
+ ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
2622
+
2623
+ - name: Validate Specific Categories
2624
+ run: |
2625
+ ruby scripts/create_tasker_app.rb dry_run --mode=templates
2626
+ ruby scripts/create_tasker_app.rb dry_run --mode=syntax
2627
+ ruby scripts/create_tasker_app.rb dry_run --mode=cli
2628
+ ruby scripts/create_tasker_app.rb dry_run --mode=bindings
2629
+ ```
2630
+
2631
+ ### Development Workflow Integration
2632
+
2633
+ **Template Development Process**:
2634
+ 1. Modify ERB templates in `scripts/templates/`
2635
+ 2. Run dry-run validation to catch issues early
2636
+ 3. Fix validation errors before committing
2637
+ 4. CI/CD pipeline validates all template combinations
2638
+
2639
+ **Local Development Validation**:
2640
+ ```bash
2641
+ # Quick validation during template development
2642
+ ruby scripts/create_tasker_app.rb dry_run --mode=syntax
2643
+
2644
+ # Full validation before committing
2645
+ ruby scripts/create_tasker_app.rb dry_run --mode=all
2646
+
2647
+ # Test Docker-specific templates
2648
+ ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
2649
+ ```
2650
+
2651
+ ### Performance Characteristics
2652
+
2653
+ - **Zero File System Impact**: No files created during validation
2654
+ - **Sub-second Execution**: Validation completes in under 1 second
2655
+ - **Clear Exit Codes**: 0 for success, 1 for failure (CI/CD friendly)
2656
+ - **Detailed Error Reporting**: Specific file paths and line numbers for errors
2657
+
2658
+ ### Strategic Benefits
2659
+
2660
+ - **Template Quality Assurance**: Prevents broken templates from reaching production
2661
+ - **Developer Confidence**: Comprehensive validation reduces template-related issues
2662
+ - **CI/CD Integration**: Automated validation in deployment pipelines
2663
+ - **Rapid Feedback**: Immediate validation results during development
2664
+ - **Enterprise Reliability**: Ensures consistent template generation across environments