tasker-rb 0.1.1

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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/DEVELOPMENT.md +548 -0
  3. data/README.md +87 -0
  4. data/ext/tasker_core/Cargo.lock +4720 -0
  5. data/ext/tasker_core/Cargo.toml +76 -0
  6. data/ext/tasker_core/extconf.rb +38 -0
  7. data/ext/tasker_core/src/CLAUDE.md +7 -0
  8. data/ext/tasker_core/src/bootstrap.rs +320 -0
  9. data/ext/tasker_core/src/bridge.rs +400 -0
  10. data/ext/tasker_core/src/client_ffi.rs +173 -0
  11. data/ext/tasker_core/src/conversions.rs +131 -0
  12. data/ext/tasker_core/src/diagnostics.rs +57 -0
  13. data/ext/tasker_core/src/event_handler.rs +179 -0
  14. data/ext/tasker_core/src/event_publisher_ffi.rs +239 -0
  15. data/ext/tasker_core/src/ffi_logging.rs +245 -0
  16. data/ext/tasker_core/src/global_event_system.rs +16 -0
  17. data/ext/tasker_core/src/in_process_event_ffi.rs +319 -0
  18. data/ext/tasker_core/src/lib.rs +41 -0
  19. data/ext/tasker_core/src/observability_ffi.rs +339 -0
  20. data/lib/tasker_core/batch_processing/batch_aggregation_scenario.rb +85 -0
  21. data/lib/tasker_core/batch_processing/batch_worker_context.rb +238 -0
  22. data/lib/tasker_core/bootstrap.rb +394 -0
  23. data/lib/tasker_core/domain_events/base_publisher.rb +220 -0
  24. data/lib/tasker_core/domain_events/base_subscriber.rb +178 -0
  25. data/lib/tasker_core/domain_events/publisher_registry.rb +253 -0
  26. data/lib/tasker_core/domain_events/subscriber_registry.rb +152 -0
  27. data/lib/tasker_core/domain_events.rb +43 -0
  28. data/lib/tasker_core/errors/CLAUDE.md +7 -0
  29. data/lib/tasker_core/errors/common.rb +305 -0
  30. data/lib/tasker_core/errors/error_classifier.rb +61 -0
  31. data/lib/tasker_core/errors.rb +4 -0
  32. data/lib/tasker_core/event_bridge.rb +330 -0
  33. data/lib/tasker_core/handlers.rb +159 -0
  34. data/lib/tasker_core/internal.rb +31 -0
  35. data/lib/tasker_core/logger.rb +234 -0
  36. data/lib/tasker_core/models.rb +337 -0
  37. data/lib/tasker_core/observability/types.rb +158 -0
  38. data/lib/tasker_core/observability.rb +292 -0
  39. data/lib/tasker_core/registry/handler_registry.rb +453 -0
  40. data/lib/tasker_core/registry/resolver_chain.rb +258 -0
  41. data/lib/tasker_core/registry/resolvers/base_resolver.rb +90 -0
  42. data/lib/tasker_core/registry/resolvers/class_constant_resolver.rb +156 -0
  43. data/lib/tasker_core/registry/resolvers/explicit_mapping_resolver.rb +146 -0
  44. data/lib/tasker_core/registry/resolvers/method_dispatch_wrapper.rb +144 -0
  45. data/lib/tasker_core/registry/resolvers/registry_resolver.rb +229 -0
  46. data/lib/tasker_core/registry/resolvers.rb +42 -0
  47. data/lib/tasker_core/registry.rb +12 -0
  48. data/lib/tasker_core/step_handler/api.rb +48 -0
  49. data/lib/tasker_core/step_handler/base.rb +354 -0
  50. data/lib/tasker_core/step_handler/batchable.rb +50 -0
  51. data/lib/tasker_core/step_handler/decision.rb +53 -0
  52. data/lib/tasker_core/step_handler/mixins/api.rb +452 -0
  53. data/lib/tasker_core/step_handler/mixins/batchable.rb +465 -0
  54. data/lib/tasker_core/step_handler/mixins/decision.rb +252 -0
  55. data/lib/tasker_core/step_handler/mixins.rb +66 -0
  56. data/lib/tasker_core/subscriber.rb +212 -0
  57. data/lib/tasker_core/task_handler/base.rb +254 -0
  58. data/lib/tasker_core/tasker_rb.so +0 -0
  59. data/lib/tasker_core/template_discovery.rb +181 -0
  60. data/lib/tasker_core/tracing.rb +166 -0
  61. data/lib/tasker_core/types/batch_processing_outcome.rb +301 -0
  62. data/lib/tasker_core/types/client_types.rb +145 -0
  63. data/lib/tasker_core/types/decision_point_outcome.rb +177 -0
  64. data/lib/tasker_core/types/error_types.rb +72 -0
  65. data/lib/tasker_core/types/simple_message.rb +151 -0
  66. data/lib/tasker_core/types/step_context.rb +328 -0
  67. data/lib/tasker_core/types/step_handler_call_result.rb +307 -0
  68. data/lib/tasker_core/types/step_message.rb +112 -0
  69. data/lib/tasker_core/types/step_types.rb +207 -0
  70. data/lib/tasker_core/types/task_template.rb +240 -0
  71. data/lib/tasker_core/types/task_types.rb +148 -0
  72. data/lib/tasker_core/types.rb +132 -0
  73. data/lib/tasker_core/version.rb +13 -0
  74. data/lib/tasker_core/worker/CLAUDE.md +7 -0
  75. data/lib/tasker_core/worker/event_poller.rb +224 -0
  76. data/lib/tasker_core/worker/in_process_domain_event_poller.rb +271 -0
  77. data/lib/tasker_core.rb +160 -0
  78. metadata +322 -0
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TaskerCore
4
+ # Clean Handlers Domain API
5
+ #
6
+ # This module provides the primary public interface for working with TaskerCore handlers.
7
+ # It's organized into two main namespaces: Steps and Tasks, mirroring the Rails engine
8
+ # architecture while providing a clean Ruby interface with enhanced type safety.
9
+ #
10
+ # The Handlers namespace serves as the recommended entry point for handler operations,
11
+ # abstracting the underlying implementation details while preserving method signatures
12
+ # that developers familiar with the Rails engine will recognize.
13
+ #
14
+ # @example Creating and using a step handler
15
+ # # Define a handler class
16
+ # class ProcessPaymentHandler < TaskerCore::Handlers::Steps::Base
17
+ # def call(context)
18
+ # # Access task context
19
+ # amount = context.get_task_field('amount')
20
+ # currency = context.get_task_field('currency')
21
+ #
22
+ # # Process payment logic
23
+ # result = charge_payment(amount, currency)
24
+ #
25
+ # # Return results
26
+ # success(result: { payment_id: result.id, status: "succeeded" })
27
+ # end
28
+ # end
29
+ #
30
+ # # Create instance with configuration
31
+ # handler = TaskerCore::Handlers::Steps.create(
32
+ # ProcessPaymentHandler,
33
+ # config: { timeout: 30, max_attempts: 3 }
34
+ # )
35
+ #
36
+ # @example Validating handler implementation
37
+ # validation = TaskerCore::Handlers::Steps.validate(ProcessPaymentHandler)
38
+ # # => {
39
+ # # valid: true,
40
+ # # missing_required: [],
41
+ # # optional_implemented: [:process_results],
42
+ # # handler_class: "ProcessPaymentHandler"
43
+ # # }
44
+ #
45
+ # if validation[:valid]
46
+ # puts "Handler implements all required methods"
47
+ # else
48
+ # puts "Missing: #{validation[:missing_required].join(', ')}"
49
+ # end
50
+ #
51
+ # @example Using API handlers for HTTP operations
52
+ # class FetchUserHandler < TaskerCore::Handlers::Steps::API
53
+ # def call(context)
54
+ # user_id = context.get_task_field('user_id')
55
+ #
56
+ # # Automatic error classification and retry logic
57
+ # response = get("/users/#{user_id}")
58
+ #
59
+ # success(result: response.body)
60
+ # end
61
+ # end
62
+ #
63
+ # @example Task-level handler for workflow coordination
64
+ # # Task handlers coordinate multiple steps
65
+ # result = TaskerCore::Handlers::Tasks.handle(task_uuid: "123-456")
66
+ # # => Orchestrates all steps for the task
67
+ #
68
+ # Architecture:
69
+ # - **Steps**: Individual business logic units (payment processing, API calls, etc.)
70
+ # - **Tasks**: Workflow orchestration and step coordination
71
+ # - **API**: Specialized step handlers for HTTP operations with automatic retry
72
+ #
73
+ # Method Signature:
74
+ # Cross-language standard handler signature (TAS-96):
75
+ # - `call(context)` - Primary handler execution with unified StepContext
76
+ # - `context.task` - Task wrapper with context data
77
+ # - `context.workflow_step` - Workflow step wrapper with execution state
78
+ # - `context.dependency_results` - Results from parent steps
79
+ #
80
+ # @see TaskerCore::Handlers::Steps For step-level business logic
81
+ # @see TaskerCore::Handlers::Tasks For task-level orchestration
82
+ # @see TaskerCore::StepHandler::Base For low-level step handler implementation
83
+ # @see TaskerCore::StepHandler::Api For HTTP-based handlers
84
+ module Handlers
85
+ # Step Handler API with preserved method signatures
86
+ module Steps
87
+ # Import the existing base step handler with preserved signatures
88
+ require_relative 'step_handler/base'
89
+ require_relative 'step_handler/mixins' # TAS-112: Composition pattern mixins
90
+ require_relative 'step_handler/api'
91
+ require_relative 'step_handler/decision' # TAS-53: Decision point handlers
92
+ require_relative 'step_handler/batchable' # TAS-59: Batch processing handlers
93
+
94
+ # Re-export with clean namespace
95
+ Base = TaskerCore::StepHandler::Base
96
+
97
+ # TAS-112: Composition Pattern - Mixin modules (preferred)
98
+ # Use these with `include` in handlers that inherit from Base
99
+ Mixins = TaskerCore::StepHandler::Mixins
100
+
101
+ # Legacy class aliases (deprecated - use Mixins instead)
102
+ API = TaskerCore::StepHandler::Api
103
+ Decision = TaskerCore::StepHandler::Decision
104
+ Batchable = TaskerCore::StepHandler::Batchable
105
+
106
+ class << self
107
+ # Create a new step handler instance
108
+ # @param handler_class [Class] Handler class to instantiate
109
+ # @param config [Hash] Handler configuration
110
+ # @return [Object] Handler instance
111
+ def create(handler_class, config: {})
112
+ handler_class.new(config: config)
113
+ end
114
+
115
+ # Validate step handler implementation
116
+ # @param handler_class [Class] Handler class to validate
117
+ # @return [Hash] Validation result
118
+ def validate(handler_class)
119
+ required_methods = %i[process]
120
+ optional_methods = %i[process_results handle]
121
+
122
+ missing = required_methods.reject { |method| handler_class.method_defined?(method) }
123
+ present_optional = optional_methods.select { |method| handler_class.method_defined?(method) }
124
+
125
+ {
126
+ valid: missing.empty?,
127
+ missing_required: missing,
128
+ optional_implemented: present_optional,
129
+ handler_class: handler_class.name
130
+ }
131
+ end
132
+ end
133
+ end
134
+
135
+ # Task Handler API
136
+ module Tasks
137
+ require_relative 'task_handler/base'
138
+
139
+ # Re-export with clean namespace
140
+ Base = TaskHandler
141
+
142
+ class << self
143
+ # Handle task with preserved signature
144
+ # @param task_uuid [Integer] Task ID to handle
145
+ # @return [Object] Task handle result
146
+ def handle(task_uuid)
147
+ Base.new.handle(task_uuid)
148
+ end
149
+
150
+ # Create task handler instance
151
+ # @param config [Hash] Handler configuration
152
+ # @return [TaskHandler] Handler instance
153
+ def create(config: {})
154
+ Base.new(config: config)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module TaskerCore
6
+ module Internal
7
+ # Simple stub for OrchestrationManager to satisfy StepHandler::Base dependencies
8
+ # In the FFI worker context, we don't need the full Rails engine infrastructure
9
+ class OrchestrationManager
10
+ include Singleton
11
+
12
+ def orchestration_system
13
+ # Return a simple stub that satisfies the interface expected by handlers
14
+ NullOrchestrationSystem.new
15
+ end
16
+ end
17
+
18
+ # Minimal orchestration system stub for FFI worker context
19
+ class NullOrchestrationSystem
20
+ def method_missing(_method_name, *_args, **_kwargs)
21
+ # Log that the method was called but return nil
22
+ # This allows handlers to work without crashing
23
+ nil
24
+ end
25
+
26
+ def respond_to_missing?(_method_name, _include_private = false)
27
+ true
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+
6
+ module TaskerCore
7
+ # DEPRECATED: Legacy logger wrapper that delegates to TaskerCore::Tracing
8
+ #
9
+ # This class is maintained for backward compatibility but all new code should
10
+ # use TaskerCore::Tracing directly for unified structured logging via FFI.
11
+ #
12
+ # TAS-29 Phase 6: This logger now delegates to the Rust tracing infrastructure
13
+ # through the FFI bridge, providing unified structured logging across Ruby and Rust.
14
+ #
15
+ # Migration Guide:
16
+ # # Old:
17
+ # logger = TaskerCore::Logger.instance
18
+ # logger.info("Message")
19
+ #
20
+ # # New:
21
+ # TaskerCore::Tracing.info("Message", { operation: "example" })
22
+ #
23
+ # The logger supports two logging approaches:
24
+ # 1. **Traditional Logging**: Simple string messages (delegates to Tracing)
25
+ # 2. **Structured Logging**: Component-based logging (converts to Tracing fields)
26
+ #
27
+ # Structured logs include:
28
+ # - Emoji prefix for visual identification
29
+ # - Component label for categorization
30
+ # - Operation description
31
+ # - Structured metadata as JSON
32
+ # - ISO 8601 timestamps
33
+ #
34
+ # @example Traditional logging (backward compatibility)
35
+ # logger = TaskerCore::Logger.instance
36
+ # logger.info "Worker started successfully"
37
+ # logger.error "Failed to process step: #{error.message}"
38
+ # logger.debug "Processing step #{step_uuid}"
39
+ #
40
+ # @example Structured logging with component context
41
+ # logger.log_task(:info, "initialization",
42
+ # task_uuid: "550e8400-e29b-41d4-a716-446655440000",
43
+ # namespace: "payments",
44
+ # step_count: 4,
45
+ # priority: "high"
46
+ # )
47
+ # # Output: [2025-10-02 12:00:00] INFO TaskerCore: 📋 TASK_OPERATION: initialization | {"task_uuid":"...","namespace":"payments",...}
48
+ #
49
+ # @example Queue worker logging
50
+ # logger.log_queue_worker(:debug, "claiming_step",
51
+ # namespace: "payments",
52
+ # step_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
53
+ # retry_count: 2,
54
+ # queue_depth: 15
55
+ # )
56
+ # # Output: [2025-10-02 12:00:01] DEBUG TaskerCore: 🔄 QUEUE_WORKER: claiming_step (namespace: payments) | {...}
57
+ #
58
+ # @example Step execution logging
59
+ # logger.log_step(:info, "handler_execution",
60
+ # step_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
61
+ # handler_class: "ProcessPaymentHandler",
62
+ # execution_time_ms: 125,
63
+ # result_size_bytes: 1024
64
+ # )
65
+ #
66
+ # @example FFI operation logging
67
+ # logger.log_ffi(:info, "bootstrap_worker",
68
+ # component: "RubyWorker",
69
+ # worker_id: "ruby-worker-123",
70
+ # deployment_mode: "Hybrid"
71
+ # )
72
+ #
73
+ # @example Database operation logging
74
+ # logger.log_database(:debug, "step_transition",
75
+ # step_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
76
+ # from_state: "in_progress",
77
+ # to_state: "complete",
78
+ # duration_ms: 15
79
+ # )
80
+ #
81
+ # @example Configuration logging
82
+ # logger.log_config(:info, "environment_loaded",
83
+ # environment: "production",
84
+ # config_file: "/etc/tasker/config.yml",
85
+ # overrides_applied: 3
86
+ # )
87
+ #
88
+ # @example Handler registry logging
89
+ # logger.log_registry(:debug, "handler_registered",
90
+ # namespace: "payments",
91
+ # name: "ProcessPaymentHandler",
92
+ # handler_count: 12
93
+ # )
94
+ #
95
+ # Component Types and Their Emojis:
96
+ # - **📋 TASK_OPERATION**: Task-level operations (creation, completion, state changes)
97
+ # - **🔄 QUEUE_WORKER**: Queue processing and step claiming
98
+ # - **🚀 ORCHESTRATOR**: Orchestration coordination and workflow management
99
+ # - **🔧 STEP_OPERATION**: Step execution and handler invocation
100
+ # - **💾 DATABASE**: Database operations and state persistence
101
+ # - **🌉 FFI**: Rust FFI bridge operations and cross-language communication
102
+ # - **⚙️ CONFIG**: Configuration loading and validation
103
+ # - **📚 REGISTRY**: Handler registry operations and discovery
104
+ #
105
+ # Log Levels:
106
+ # - **:debug**: Detailed diagnostic information
107
+ # - **:info**: General informational messages
108
+ # - **:warn**: Warning messages for potential issues
109
+ # - **:error**: Error messages for failures
110
+ # - **:fatal**: Critical failures requiring immediate attention
111
+ #
112
+ # @see #log_task For task-level operations
113
+ # @see #log_queue_worker For queue processing
114
+ # @see #log_orchestrator For orchestration operations
115
+ # @see #log_step For step execution
116
+ # @see #log_database For database operations
117
+ # @see #log_ffi For FFI bridge operations
118
+ # @see #log_config For configuration operations
119
+ # @see #log_registry For handler registry operations
120
+ class Logger
121
+ include Singleton
122
+
123
+ # Traditional string-based logging methods (delegates to Tracing)
124
+ def info(message, &)
125
+ Tracing.info(message.to_s, extract_context_fields)
126
+ end
127
+
128
+ def warn(message, &)
129
+ Tracing.warn(message.to_s, extract_context_fields)
130
+ end
131
+
132
+ def error(message, &)
133
+ Tracing.error(message.to_s, extract_context_fields)
134
+ end
135
+
136
+ def fatal(message, &)
137
+ Tracing.error(message.to_s, extract_context_fields.merge(severity: 'fatal'))
138
+ end
139
+
140
+ def debug(message, &)
141
+ Tracing.debug(message.to_s, extract_context_fields)
142
+ end
143
+
144
+ # Enhanced structured logging methods (delegates to Tracing with appropriate fields)
145
+
146
+ def log_task(level, operation, **data)
147
+ fields = extract_context_fields.merge(
148
+ operation: operation,
149
+ component: 'task_operation'
150
+ ).merge(data)
151
+ Tracing.send(level, operation, fields)
152
+ end
153
+
154
+ def log_queue_worker(level, operation, namespace: nil, **data)
155
+ fields = extract_context_fields.merge(
156
+ operation: operation,
157
+ component: 'queue_worker'
158
+ ).merge(data)
159
+ fields[:namespace] = namespace if namespace
160
+ Tracing.send(level, operation, fields)
161
+ end
162
+
163
+ def log_orchestrator(level, operation, **data)
164
+ fields = extract_context_fields.merge(
165
+ operation: operation,
166
+ component: 'orchestrator'
167
+ ).merge(data)
168
+ Tracing.send(level, operation, fields)
169
+ end
170
+
171
+ def log_step(level, operation, **data)
172
+ fields = extract_context_fields.merge(
173
+ operation: operation,
174
+ component: 'step_operation'
175
+ ).merge(data)
176
+ Tracing.send(level, operation, fields)
177
+ end
178
+
179
+ def log_database(level, operation, **data)
180
+ fields = extract_context_fields.merge(
181
+ operation: operation,
182
+ component: 'database'
183
+ ).merge(data)
184
+ Tracing.send(level, operation, fields)
185
+ end
186
+
187
+ def log_ffi(level, operation, component: nil, **data)
188
+ fields = extract_context_fields.merge(
189
+ operation: operation,
190
+ component: component || 'ffi'
191
+ ).merge(data)
192
+ Tracing.send(level, operation, fields)
193
+ end
194
+
195
+ def log_config(level, operation, **data)
196
+ fields = extract_context_fields.merge(
197
+ operation: operation,
198
+ component: 'config'
199
+ ).merge(data)
200
+ Tracing.send(level, operation, fields)
201
+ end
202
+
203
+ def log_registry(level, operation, namespace: nil, name: nil, **data)
204
+ fields = extract_context_fields.merge(
205
+ operation: operation,
206
+ component: 'registry'
207
+ ).merge(data)
208
+ fields[:namespace] = namespace if namespace
209
+ fields[:handler_name] = name if name
210
+ Tracing.send(level, operation, fields)
211
+ end
212
+
213
+ def initialize
214
+ # No-op - everything delegates to Tracing now
215
+ end
216
+
217
+ private
218
+
219
+ # Extract context fields from thread-local or instance variables if available
220
+ def extract_context_fields
221
+ fields = {}
222
+
223
+ # Try to extract correlation_id from thread-local storage if available
224
+ fields[:correlation_id] = Thread.current[:correlation_id] if Thread.current[:correlation_id]
225
+
226
+ # Try to extract task/step UUIDs if available
227
+ fields[:task_uuid] = Thread.current[:task_uuid] if Thread.current[:task_uuid]
228
+
229
+ fields[:step_uuid] = Thread.current[:step_uuid] if Thread.current[:step_uuid]
230
+
231
+ fields
232
+ end
233
+ end
234
+ end