tasker-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
@@ -0,0 +1,508 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'statesman'
|
4
|
+
require_relative '../constants'
|
5
|
+
require 'tasker/events/event_payload_builder'
|
6
|
+
require_relative '../concerns/event_publisher'
|
7
|
+
|
8
|
+
module Tasker
|
9
|
+
module StateMachine
|
10
|
+
# StepStateMachine defines state transitions for workflow steps using Statesman
|
11
|
+
#
|
12
|
+
# This state machine manages workflow step lifecycle states and integrates with
|
13
|
+
# the existing event system to provide declarative state management.
|
14
|
+
class StepStateMachine
|
15
|
+
include Statesman::Machine
|
16
|
+
extend Tasker::Concerns::EventPublisher
|
17
|
+
|
18
|
+
# Define all step states using existing constants
|
19
|
+
state Constants::WorkflowStepStatuses::PENDING, initial: true
|
20
|
+
state Constants::WorkflowStepStatuses::IN_PROGRESS
|
21
|
+
state Constants::WorkflowStepStatuses::COMPLETE
|
22
|
+
state Constants::WorkflowStepStatuses::ERROR
|
23
|
+
state Constants::WorkflowStepStatuses::CANCELLED
|
24
|
+
state Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
|
25
|
+
|
26
|
+
# Define state transitions based on existing StateTransition definitions
|
27
|
+
transition from: Constants::WorkflowStepStatuses::PENDING,
|
28
|
+
to: [Constants::WorkflowStepStatuses::IN_PROGRESS,
|
29
|
+
Constants::WorkflowStepStatuses::ERROR,
|
30
|
+
Constants::WorkflowStepStatuses::CANCELLED,
|
31
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] # Allow manual resolution
|
32
|
+
|
33
|
+
transition from: Constants::WorkflowStepStatuses::IN_PROGRESS,
|
34
|
+
to: [Constants::WorkflowStepStatuses::COMPLETE,
|
35
|
+
Constants::WorkflowStepStatuses::ERROR,
|
36
|
+
Constants::WorkflowStepStatuses::CANCELLED]
|
37
|
+
|
38
|
+
transition from: Constants::WorkflowStepStatuses::ERROR,
|
39
|
+
to: [Constants::WorkflowStepStatuses::PENDING,
|
40
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY]
|
41
|
+
|
42
|
+
# Callbacks for state transitions
|
43
|
+
before_transition do |step, transition|
|
44
|
+
# Handle idempotent transitions using existing helper method
|
45
|
+
if StepStateMachine.idempotent_transition?(step, transition.to_state)
|
46
|
+
# Abort the transition by raising GuardFailedError
|
47
|
+
raise Statesman::GuardFailedError, "Already in target state #{transition.to_state}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Log the transition for debugging
|
51
|
+
effective_current_state = StepStateMachine.effective_current_state(step)
|
52
|
+
Rails.logger.debug do
|
53
|
+
"Step #{step.workflow_step_id} transitioning from #{effective_current_state} to #{transition.to_state}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
after_transition do |step, transition|
|
58
|
+
# Determine the appropriate event name based on the transition
|
59
|
+
event_name = determine_transition_event_name(transition.from_state, transition.to_state)
|
60
|
+
|
61
|
+
# Only fire the event if we have a valid event name
|
62
|
+
if event_name
|
63
|
+
# Fire the lifecycle event with step context
|
64
|
+
StepStateMachine.safe_fire_event(
|
65
|
+
event_name,
|
66
|
+
{
|
67
|
+
task_id: step.task_id,
|
68
|
+
step_id: step.workflow_step_id,
|
69
|
+
step_name: step.name,
|
70
|
+
from_state: transition.from_state,
|
71
|
+
to_state: transition.to_state,
|
72
|
+
transitioned_at: Time.zone.now
|
73
|
+
}
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Guard clauses for business logic only
|
79
|
+
# Let Statesman handle state transition validation and idempotent calls
|
80
|
+
|
81
|
+
guard_transition(to: Constants::WorkflowStepStatuses::IN_PROGRESS) do |step, _transition|
|
82
|
+
# Only business rule: check dependencies are met
|
83
|
+
StepStateMachine.step_dependencies_met?(step)
|
84
|
+
end
|
85
|
+
|
86
|
+
# No other guard clauses needed!
|
87
|
+
# - State transition validation is handled by the transition definitions above
|
88
|
+
# - Idempotent transitions are handled by Statesman automatically
|
89
|
+
# - Simple state changes (PENDING->ERROR, IN_PROGRESS->COMPLETE, etc.) don't need guards
|
90
|
+
|
91
|
+
# Frozen constant mapping state transitions to event names
|
92
|
+
# This provides O(1) lookup performance and ensures consistency
|
93
|
+
TRANSITION_EVENT_MAP = {
|
94
|
+
# Initial state transitions (from nil/initial)
|
95
|
+
[nil, Constants::WorkflowStepStatuses::PENDING] => Constants::StepEvents::INITIALIZE_REQUESTED,
|
96
|
+
[nil, Constants::WorkflowStepStatuses::IN_PROGRESS] => Constants::StepEvents::EXECUTION_REQUESTED,
|
97
|
+
[nil, Constants::WorkflowStepStatuses::COMPLETE] => Constants::StepEvents::COMPLETED,
|
98
|
+
[nil, Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
|
99
|
+
[nil, Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
|
100
|
+
[nil, Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY,
|
101
|
+
|
102
|
+
# Normal state transitions
|
103
|
+
[Constants::WorkflowStepStatuses::PENDING,
|
104
|
+
Constants::WorkflowStepStatuses::IN_PROGRESS] => Constants::StepEvents::EXECUTION_REQUESTED,
|
105
|
+
[Constants::WorkflowStepStatuses::PENDING,
|
106
|
+
Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
|
107
|
+
[Constants::WorkflowStepStatuses::PENDING,
|
108
|
+
Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
|
109
|
+
[Constants::WorkflowStepStatuses::PENDING,
|
110
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY,
|
111
|
+
|
112
|
+
[Constants::WorkflowStepStatuses::IN_PROGRESS,
|
113
|
+
Constants::WorkflowStepStatuses::COMPLETE] => Constants::StepEvents::COMPLETED,
|
114
|
+
[Constants::WorkflowStepStatuses::IN_PROGRESS,
|
115
|
+
Constants::WorkflowStepStatuses::ERROR] => Constants::StepEvents::FAILED,
|
116
|
+
[Constants::WorkflowStepStatuses::IN_PROGRESS,
|
117
|
+
Constants::WorkflowStepStatuses::CANCELLED] => Constants::StepEvents::CANCELLED,
|
118
|
+
|
119
|
+
[Constants::WorkflowStepStatuses::ERROR,
|
120
|
+
Constants::WorkflowStepStatuses::PENDING] => Constants::StepEvents::RETRY_REQUESTED,
|
121
|
+
[Constants::WorkflowStepStatuses::ERROR,
|
122
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY] => Constants::StepEvents::RESOLVED_MANUALLY
|
123
|
+
}.freeze
|
124
|
+
|
125
|
+
# Override current_state to work with custom transition model
|
126
|
+
# Since WorkflowStepTransition doesn't include Statesman::Adapters::ActiveRecordTransition,
|
127
|
+
# we need to implement our own current_state logic using the most_recent column
|
128
|
+
def current_state
|
129
|
+
most_recent_transition = object.workflow_step_transitions.where(most_recent: true).first
|
130
|
+
|
131
|
+
if most_recent_transition
|
132
|
+
# Ensure we never return empty strings or nil - always return a valid state
|
133
|
+
state = most_recent_transition.to_state
|
134
|
+
state.presence || Constants::WorkflowStepStatuses::PENDING
|
135
|
+
else
|
136
|
+
# Return initial state if no transitions exist
|
137
|
+
Constants::WorkflowStepStatuses::PENDING
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Override Statesman's transition building to ensure proper from_state handling
|
142
|
+
# This is called by Statesman when creating new transitions
|
143
|
+
def create_transition(from_state, to_state, metadata = {})
|
144
|
+
# Ensure from_state is properly set - never allow empty strings
|
145
|
+
effective_from_state = case from_state
|
146
|
+
when nil, ''
|
147
|
+
# For initial transitions or empty strings, use nil
|
148
|
+
nil
|
149
|
+
else
|
150
|
+
# For existing states, ensure it's a valid state
|
151
|
+
from_state.presence
|
152
|
+
end
|
153
|
+
|
154
|
+
# Log transition creation for debugging
|
155
|
+
Rails.logger.debug do
|
156
|
+
"StepStateMachine: Creating transition for step #{object.workflow_step_id}: " \
|
157
|
+
"'#{effective_from_state}' → '#{to_state}'"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Get the next sort key
|
161
|
+
next_sort_key = next_sort_key_value
|
162
|
+
|
163
|
+
# Create the transition with proper from_state handling
|
164
|
+
transition = Tasker::WorkflowStepTransition.create!(
|
165
|
+
workflow_step_id: object.workflow_step_id,
|
166
|
+
to_state: to_state,
|
167
|
+
from_state: effective_from_state, # Use nil instead of empty string
|
168
|
+
most_recent: true,
|
169
|
+
sort_key: next_sort_key,
|
170
|
+
metadata: metadata || {},
|
171
|
+
created_at: Time.current,
|
172
|
+
updated_at: Time.current
|
173
|
+
)
|
174
|
+
|
175
|
+
# Update previous transitions to not be most recent
|
176
|
+
object.workflow_step_transitions
|
177
|
+
.where(most_recent: true)
|
178
|
+
.where.not(id: transition.id)
|
179
|
+
.update_all(most_recent: false)
|
180
|
+
|
181
|
+
transition
|
182
|
+
end
|
183
|
+
|
184
|
+
# Get the next sort key for transitions
|
185
|
+
def next_sort_key_value
|
186
|
+
max_sort_key = object.workflow_step_transitions.maximum(:sort_key) || -1
|
187
|
+
max_sort_key + 10 # Use increments of 10 for flexibility
|
188
|
+
end
|
189
|
+
|
190
|
+
# Initialize the state machine with the initial state
|
191
|
+
# This ensures the state machine is properly initialized when called explicitly
|
192
|
+
# DEFENSIVE: Only creates transitions when explicitly needed
|
193
|
+
def initialize_state_machine!
|
194
|
+
# Check if state machine is already initialized
|
195
|
+
return current_state if Tasker::WorkflowStepTransition.exists?(workflow_step_id: object.workflow_step_id)
|
196
|
+
|
197
|
+
# DEFENSIVE: Use a rescue block instead of transaction to handle race conditions gracefully
|
198
|
+
begin
|
199
|
+
# Create the initial transition only if none exists
|
200
|
+
initial_transition = Tasker::WorkflowStepTransition.create!(
|
201
|
+
workflow_step_id: object.workflow_step_id,
|
202
|
+
to_state: Constants::WorkflowStepStatuses::PENDING,
|
203
|
+
from_state: nil, # Explicitly set to nil for initial transition
|
204
|
+
most_recent: true,
|
205
|
+
sort_key: 0,
|
206
|
+
metadata: { initialized_by: 'state_machine' },
|
207
|
+
created_at: Time.current,
|
208
|
+
updated_at: Time.current
|
209
|
+
)
|
210
|
+
|
211
|
+
Rails.logger.debug do
|
212
|
+
"StepStateMachine: Initialized state machine for step #{object.workflow_step_id} with initial transition to PENDING"
|
213
|
+
end
|
214
|
+
|
215
|
+
initial_transition.to_state
|
216
|
+
rescue ActiveRecord::RecordNotUnique => e
|
217
|
+
# Handle duplicate key violations gracefully - another thread may have initialized the state machine
|
218
|
+
Rails.logger.debug do
|
219
|
+
"StepStateMachine: State machine for step #{object.workflow_step_id} already initialized by another process: #{e.message}"
|
220
|
+
end
|
221
|
+
|
222
|
+
# Return the current state since we know it's initialized
|
223
|
+
current_state
|
224
|
+
rescue ActiveRecord::StatementInvalid => e
|
225
|
+
# Handle transaction issues gracefully
|
226
|
+
Rails.logger.warn do
|
227
|
+
"StepStateMachine: Transaction issue initializing state machine for step #{object.workflow_step_id}: #{e.message}"
|
228
|
+
end
|
229
|
+
|
230
|
+
# Check if the step actually has transitions now (another process may have created them)
|
231
|
+
if Tasker::WorkflowStepTransition.exists?(workflow_step_id: object.workflow_step_id)
|
232
|
+
current_state
|
233
|
+
else
|
234
|
+
# If still no transitions, return the default state without creating a transition
|
235
|
+
Constants::WorkflowStepStatuses::PENDING
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# Class methods for state machine management
|
241
|
+
class << self
|
242
|
+
# Class-level wrapper methods for guard clause context
|
243
|
+
# These delegate to instance methods to provide clean access from guard clauses
|
244
|
+
|
245
|
+
# Check if a transition is idempotent (current state == target state)
|
246
|
+
#
|
247
|
+
# @param step [WorkflowStep] The step to check
|
248
|
+
# @param target_state [String] The target state
|
249
|
+
# @return [Boolean] True if this is an idempotent transition
|
250
|
+
def idempotent_transition?(step, target_state)
|
251
|
+
current_state = step.state_machine.current_state
|
252
|
+
effective_current_state = current_state.presence || Constants::WorkflowStepStatuses::PENDING
|
253
|
+
is_idempotent = effective_current_state == target_state
|
254
|
+
|
255
|
+
if is_idempotent
|
256
|
+
Rails.logger.debug do
|
257
|
+
"StepStateMachine: Allowing idempotent transition to #{target_state} for step #{step.workflow_step_id}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
is_idempotent
|
262
|
+
end
|
263
|
+
|
264
|
+
# Get the effective current state, handling blank/empty states
|
265
|
+
#
|
266
|
+
# @param step [WorkflowStep] The step to check
|
267
|
+
# @return [String] The effective current state (blank states become PENDING)
|
268
|
+
def effective_current_state(step)
|
269
|
+
current_state = step.state_machine.current_state
|
270
|
+
current_state.presence || Constants::WorkflowStepStatuses::PENDING
|
271
|
+
end
|
272
|
+
|
273
|
+
# Log an invalid from-state transition
|
274
|
+
#
|
275
|
+
# @param step [WorkflowStep] The step
|
276
|
+
# @param current_state [String] The current state
|
277
|
+
# @param target_state [String] The target state
|
278
|
+
# @param reason [String] The reason for the restriction
|
279
|
+
def log_invalid_from_state(step, current_state, target_state, reason)
|
280
|
+
Rails.logger.debug do
|
281
|
+
"StepStateMachine: Cannot transition to #{target_state} from '#{current_state}' " \
|
282
|
+
"(step #{step.workflow_step_id}). #{reason}."
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Log when dependencies are not met
|
287
|
+
#
|
288
|
+
# @param step [WorkflowStep] The step
|
289
|
+
# @param target_state [String] The target state
|
290
|
+
def log_dependencies_not_met(step, target_state)
|
291
|
+
Rails.logger.debug do
|
292
|
+
"StepStateMachine: Cannot transition step #{step.workflow_step_id} to #{target_state} - " \
|
293
|
+
'dependencies not satisfied. Check parent step completion status.'
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Log the result of a transition check
|
298
|
+
#
|
299
|
+
# @param step [WorkflowStep] The step
|
300
|
+
# @param target_state [String] The target state
|
301
|
+
# @param result [Boolean] Whether the transition is allowed
|
302
|
+
# @param reason [String] The reason for the result
|
303
|
+
def log_transition_result(step, target_state, result, reason)
|
304
|
+
if result
|
305
|
+
Rails.logger.debug do
|
306
|
+
"StepStateMachine: Allowing transition to #{target_state} for step #{step.workflow_step_id} (#{reason})"
|
307
|
+
end
|
308
|
+
else
|
309
|
+
Rails.logger.debug do
|
310
|
+
"StepStateMachine: Blocking transition to #{target_state} for step #{step.workflow_step_id} (#{reason} failed)"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Check if step dependencies are met
|
316
|
+
#
|
317
|
+
# @param step [WorkflowStep] The step to check
|
318
|
+
# @return [Boolean] True if all dependencies are satisfied
|
319
|
+
def step_dependencies_met?(step)
|
320
|
+
# Handle cases where step doesn't have parents association or it's not loaded
|
321
|
+
|
322
|
+
# If step doesn't respond to parents, assume no dependencies
|
323
|
+
return true unless step.respond_to?(:parents)
|
324
|
+
|
325
|
+
# If parents association exists but is empty, no dependencies to check
|
326
|
+
parents = step.parents
|
327
|
+
return true if parents.blank?
|
328
|
+
|
329
|
+
# Check if all parent steps are complete
|
330
|
+
parents.all? do |parent|
|
331
|
+
completion_states = [
|
332
|
+
Constants::WorkflowStepStatuses::COMPLETE,
|
333
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
|
334
|
+
]
|
335
|
+
# Use state_machine.current_state to avoid circular reference with parent.status
|
336
|
+
current_state = parent.state_machine.current_state
|
337
|
+
parent_status = current_state.presence || Constants::WorkflowStepStatuses::PENDING
|
338
|
+
is_complete = completion_states.include?(parent_status)
|
339
|
+
|
340
|
+
unless is_complete
|
341
|
+
Rails.logger.debug do
|
342
|
+
"StepStateMachine: Step #{step.workflow_step_id} dependency not met - " \
|
343
|
+
"parent step #{parent.workflow_step_id} is '#{parent_status}', needs to be complete"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
is_complete
|
348
|
+
end
|
349
|
+
rescue StandardError => e
|
350
|
+
# If there's an error checking dependencies, log it and assume dependencies are met
|
351
|
+
# This prevents dependency checking from blocking execution in edge cases
|
352
|
+
Rails.logger.warn do
|
353
|
+
"StepStateMachine: Error checking dependencies for step #{step.workflow_step_id}: #{e.message}. " \
|
354
|
+
'Assuming dependencies are met.'
|
355
|
+
end
|
356
|
+
true
|
357
|
+
end
|
358
|
+
|
359
|
+
# Safely fire a lifecycle event using dry-events bus
|
360
|
+
#
|
361
|
+
# @param event_name [String] The event name
|
362
|
+
# @param context [Hash] The event context
|
363
|
+
# @return [void]
|
364
|
+
def safe_fire_event(event_name, context = {})
|
365
|
+
# Use EventPayloadBuilder for consistent payload structure
|
366
|
+
step = extract_step_from_context(context)
|
367
|
+
task = step&.task
|
368
|
+
|
369
|
+
if step && task
|
370
|
+
# Determine event type from event name
|
371
|
+
event_type = determine_event_type_from_name(event_name)
|
372
|
+
|
373
|
+
# Use EventPayloadBuilder for standardized payload
|
374
|
+
enhanced_context = Tasker::Events::EventPayloadBuilder.build_step_payload(
|
375
|
+
step,
|
376
|
+
task,
|
377
|
+
event_type: event_type,
|
378
|
+
additional_context: context
|
379
|
+
)
|
380
|
+
else
|
381
|
+
# Fallback to enhanced context if step/task not available
|
382
|
+
enhanced_context = build_standardized_payload(event_name, context)
|
383
|
+
end
|
384
|
+
|
385
|
+
publish_event(event_name, enhanced_context)
|
386
|
+
end
|
387
|
+
|
388
|
+
# Extract step object from context for EventPayloadBuilder
|
389
|
+
#
|
390
|
+
# @param context [Hash] The event context
|
391
|
+
# @return [WorkflowStep, nil] The step object if available
|
392
|
+
def extract_step_from_context(context)
|
393
|
+
step_id = context[:step_id]
|
394
|
+
return nil unless step_id
|
395
|
+
|
396
|
+
# Try to find the step - handle both string and numeric IDs
|
397
|
+
Tasker::WorkflowStep.find_by(workflow_step_id: step_id) ||
|
398
|
+
Tasker::WorkflowStep.find_by(id: step_id)
|
399
|
+
rescue StandardError => e
|
400
|
+
Rails.logger.warn { "Could not find step with ID #{step_id}: #{e.message}" }
|
401
|
+
nil
|
402
|
+
end
|
403
|
+
|
404
|
+
# Determine event type from event name for EventPayloadBuilder
|
405
|
+
#
|
406
|
+
# @param event_name [String] The event name
|
407
|
+
# @return [Symbol] The event type
|
408
|
+
def determine_event_type_from_name(event_name)
|
409
|
+
case event_name
|
410
|
+
when /completed/i
|
411
|
+
:completed
|
412
|
+
when /failed/i, /error/i
|
413
|
+
:failed
|
414
|
+
when /execution_requested/i, /started/i
|
415
|
+
:started
|
416
|
+
when /retry/i
|
417
|
+
:retry
|
418
|
+
when /backoff/i
|
419
|
+
:backoff
|
420
|
+
else
|
421
|
+
:unknown
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# Build standardized event payload with all expected keys (legacy fallback)
|
426
|
+
#
|
427
|
+
# @param event_name [String] The event name
|
428
|
+
# @param context [Hash] The base context
|
429
|
+
# @return [Hash] Enhanced context with standardized payload structure
|
430
|
+
def build_standardized_payload(_event_name, context)
|
431
|
+
# Base payload with core identifiers
|
432
|
+
enhanced_context = {
|
433
|
+
# Core identifiers (always present)
|
434
|
+
task_id: context[:task_id],
|
435
|
+
step_id: context[:step_id],
|
436
|
+
step_name: context[:step_name],
|
437
|
+
|
438
|
+
# State transition information
|
439
|
+
from_state: context[:from_state],
|
440
|
+
to_state: context[:to_state],
|
441
|
+
|
442
|
+
# Timing information (provide defaults for missing keys)
|
443
|
+
started_at: context[:started_at] || context[:transitioned_at],
|
444
|
+
completed_at: context[:completed_at] || context[:transitioned_at],
|
445
|
+
execution_duration: context[:execution_duration] || 0.0,
|
446
|
+
|
447
|
+
# Error information (for error events)
|
448
|
+
error_message: context[:error_message] || context[:error] || 'Unknown error',
|
449
|
+
exception_class: context[:exception_class] || 'StandardError',
|
450
|
+
attempt_number: context[:attempt_number] || 1,
|
451
|
+
|
452
|
+
# Additional context
|
453
|
+
transitioned_at: context[:transitioned_at] || Time.zone.now
|
454
|
+
}
|
455
|
+
|
456
|
+
# Merge in any additional context provided
|
457
|
+
enhanced_context.merge!(context.except(
|
458
|
+
:task_id, :step_id, :step_name, :from_state, :to_state,
|
459
|
+
:started_at, :completed_at, :execution_duration,
|
460
|
+
:error_message, :exception_class, :attempt_number, :transitioned_at
|
461
|
+
))
|
462
|
+
|
463
|
+
enhanced_context
|
464
|
+
end
|
465
|
+
|
466
|
+
# Determine the appropriate event name for a state transition using constant lookup
|
467
|
+
#
|
468
|
+
# @param from_state [String, nil] The source state
|
469
|
+
# @param to_state [String] The target state
|
470
|
+
# @return [String, nil] The event name or nil if no mapping exists
|
471
|
+
def determine_transition_event_name(from_state, to_state)
|
472
|
+
transition_key = [from_state, to_state]
|
473
|
+
event_name = TRANSITION_EVENT_MAP[transition_key]
|
474
|
+
|
475
|
+
if event_name.nil?
|
476
|
+
# For unexpected transitions, log a warning and return nil to skip event firing
|
477
|
+
Rails.logger.warn do
|
478
|
+
"Unexpected step state transition: #{from_state || 'initial'} → #{to_state}. " \
|
479
|
+
'No event will be fired for this transition.'
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
event_name
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
private
|
488
|
+
|
489
|
+
# Safely fire a lifecycle event
|
490
|
+
#
|
491
|
+
# @param event_name [String] The event name
|
492
|
+
# @param context [Hash] The event context
|
493
|
+
# @return [void]
|
494
|
+
def safe_fire_event(event_name, context = {})
|
495
|
+
self.class.safe_fire_event(event_name, context)
|
496
|
+
end
|
497
|
+
|
498
|
+
# Determine the appropriate event name for a state transition
|
499
|
+
#
|
500
|
+
# @param from_state [String, nil] The source state
|
501
|
+
# @param to_state [String] The target state
|
502
|
+
# @return [String] The event name
|
503
|
+
def determine_transition_event_name(from_state, to_state)
|
504
|
+
self.class.determine_transition_event_name(from_state, to_state)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'statesman'
|
4
|
+
require_relative '../constants'
|
5
|
+
require 'tasker/events/event_payload_builder'
|
6
|
+
require_relative '../concerns/event_publisher'
|
7
|
+
|
8
|
+
module Tasker
|
9
|
+
module StateMachine
|
10
|
+
# TaskStateMachine defines state transitions for tasks using Statesman
|
11
|
+
#
|
12
|
+
# This state machine manages task lifecycle states and integrates with
|
13
|
+
# the existing event system to provide declarative state management.
|
14
|
+
class TaskStateMachine
|
15
|
+
include Statesman::Machine
|
16
|
+
extend Tasker::Concerns::EventPublisher
|
17
|
+
|
18
|
+
# Define all task states using existing constants
|
19
|
+
state Constants::TaskStatuses::PENDING, initial: true
|
20
|
+
state Constants::TaskStatuses::IN_PROGRESS
|
21
|
+
state Constants::TaskStatuses::COMPLETE
|
22
|
+
state Constants::TaskStatuses::ERROR
|
23
|
+
state Constants::TaskStatuses::CANCELLED
|
24
|
+
state Constants::TaskStatuses::RESOLVED_MANUALLY
|
25
|
+
|
26
|
+
# Define state transitions based on existing StateTransition definitions
|
27
|
+
# Fixed: Added missing transitions that guard clauses were handling
|
28
|
+
transition from: Constants::TaskStatuses::PENDING,
|
29
|
+
to: [Constants::TaskStatuses::IN_PROGRESS,
|
30
|
+
Constants::TaskStatuses::CANCELLED,
|
31
|
+
Constants::TaskStatuses::ERROR] # Allow direct error from pending
|
32
|
+
|
33
|
+
transition from: Constants::TaskStatuses::IN_PROGRESS,
|
34
|
+
to: [Constants::TaskStatuses::COMPLETE,
|
35
|
+
Constants::TaskStatuses::ERROR,
|
36
|
+
Constants::TaskStatuses::CANCELLED,
|
37
|
+
Constants::TaskStatuses::PENDING] # Allow reset to pending
|
38
|
+
|
39
|
+
transition from: Constants::TaskStatuses::ERROR,
|
40
|
+
to: [Constants::TaskStatuses::PENDING,
|
41
|
+
Constants::TaskStatuses::RESOLVED_MANUALLY]
|
42
|
+
|
43
|
+
# Allow cancellation from complete/error states (admin override scenarios)
|
44
|
+
transition from: Constants::TaskStatuses::COMPLETE,
|
45
|
+
to: Constants::TaskStatuses::CANCELLED
|
46
|
+
|
47
|
+
transition from: Constants::TaskStatuses::RESOLVED_MANUALLY,
|
48
|
+
to: Constants::TaskStatuses::CANCELLED
|
49
|
+
|
50
|
+
# Callbacks for lifecycle event integration
|
51
|
+
before_transition do |task, transition|
|
52
|
+
# Handle idempotent transitions using existing helper method
|
53
|
+
if TaskStateMachine.idempotent_transition?(task, transition.to_state)
|
54
|
+
# Abort the transition by raising GuardFailedError
|
55
|
+
raise Statesman::GuardFailedError, "Already in target state #{transition.to_state}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Log the transition for debugging
|
59
|
+
effective_current_state = TaskStateMachine.effective_current_state(task)
|
60
|
+
Rails.logger.debug do
|
61
|
+
"Task #{task.task_id} transitioning from #{effective_current_state} to #{transition.to_state}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fire before transition event
|
65
|
+
TaskStateMachine.safe_fire_event(
|
66
|
+
Constants::TaskEvents::BEFORE_TRANSITION,
|
67
|
+
{
|
68
|
+
task_id: task.task_id,
|
69
|
+
from_state: transition.from_state,
|
70
|
+
to_state: transition.to_state,
|
71
|
+
transition_event: TaskStateMachine.determine_transition_event_name(transition.from_state,
|
72
|
+
transition.to_state)
|
73
|
+
}
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
after_transition do |task, transition|
|
78
|
+
# Determine the appropriate event name based on the transition
|
79
|
+
event_name = determine_transition_event_name(transition.from_state, transition.to_state)
|
80
|
+
|
81
|
+
# Only fire the event if we have a valid event name
|
82
|
+
if event_name
|
83
|
+
# Fire the lifecycle event with task context
|
84
|
+
TaskStateMachine.safe_fire_event(
|
85
|
+
event_name,
|
86
|
+
{
|
87
|
+
task_id: task.task_id,
|
88
|
+
task_name: task.name,
|
89
|
+
task_context: task.context,
|
90
|
+
from_state: transition.from_state,
|
91
|
+
to_state: transition.to_state,
|
92
|
+
transitioned_at: Time.zone.now
|
93
|
+
}
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# We do not transition to complete unless the steps are also complete
|
99
|
+
guard_transition(to: Constants::TaskStatuses::COMPLETE) do |task, _transition|
|
100
|
+
task.all_steps_complete?
|
101
|
+
end
|
102
|
+
|
103
|
+
# No other guard clauses needed!
|
104
|
+
# - State transition validation is handled by the transition definitions above
|
105
|
+
# - Idempotent transitions are handled by Statesman automatically
|
106
|
+
# - Simple state changes don't need business logic validation
|
107
|
+
|
108
|
+
# Override current_state to work with custom transition model
|
109
|
+
# Since TaskTransition doesn't include Statesman::Adapters::ActiveRecordTransition,
|
110
|
+
# we need to implement our own current_state logic using the most_recent column
|
111
|
+
def current_state
|
112
|
+
most_recent_transition = object.task_transitions.where(most_recent: true).first
|
113
|
+
|
114
|
+
if most_recent_transition
|
115
|
+
most_recent_transition.to_state
|
116
|
+
else
|
117
|
+
# Return initial state if no transitions exist
|
118
|
+
Constants::TaskStatuses::PENDING
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Class methods for state machine management
|
123
|
+
class << self
|
124
|
+
# Check if a transition is idempotent (current state == target state)
|
125
|
+
#
|
126
|
+
# @param task [Task] The task to check
|
127
|
+
# @param target_state [String] The target state
|
128
|
+
# @return [Boolean] True if this is an idempotent transition
|
129
|
+
def idempotent_transition?(task, target_state)
|
130
|
+
task.state_machine.current_state == target_state
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get the effective current state, handling blank/empty states
|
134
|
+
#
|
135
|
+
# @param task [Task] The task to check
|
136
|
+
# @return [String] The effective current state (blank states become PENDING)
|
137
|
+
def effective_current_state(task)
|
138
|
+
current_state = task.state_machine.current_state
|
139
|
+
current_state.presence || Constants::TaskStatuses::PENDING
|
140
|
+
end
|
141
|
+
|
142
|
+
# Safely fire a lifecycle event using dry-events bus
|
143
|
+
#
|
144
|
+
# @param event_name [String] The event name
|
145
|
+
# @param context [Hash] The event context
|
146
|
+
# @return [void]
|
147
|
+
def safe_fire_event(event_name, context = {})
|
148
|
+
publish_event(event_name, context)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Determine the transition event name based on states using hashmap lookup
|
152
|
+
#
|
153
|
+
# @param from_state [String] The from state
|
154
|
+
# @param to_state [String] The to state
|
155
|
+
# @return [String, nil] The event name or nil if no mapping exists
|
156
|
+
def determine_transition_event_name(from_state, to_state)
|
157
|
+
transition_key = [from_state, to_state]
|
158
|
+
event_name = Constants::TASK_TRANSITION_EVENT_MAP[transition_key]
|
159
|
+
|
160
|
+
if event_name.nil?
|
161
|
+
Rails.logger.warn do
|
162
|
+
"Unexpected task state transition: #{from_state || 'initial'} → #{to_state}. " \
|
163
|
+
'No event will be fired for this transition.'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
event_name
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# Safely fire a lifecycle event
|
174
|
+
#
|
175
|
+
# @param event_name [String] The event name
|
176
|
+
# @param context [Hash] The event context
|
177
|
+
# @return [void]
|
178
|
+
def safe_fire_event(event_name, context = {})
|
179
|
+
self.class.safe_fire_event(event_name, context)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Determine the appropriate event name for a state transition
|
183
|
+
#
|
184
|
+
# @param from_state [String, nil] The source state
|
185
|
+
# @param to_state [String] The target state
|
186
|
+
# @return [String] The event name
|
187
|
+
def determine_transition_event_name(from_state, to_state)
|
188
|
+
self.class.determine_transition_event_name(from_state, to_state)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|