tasker-rb 0.1.3-x86_64-linux

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +55 -0
  3. data/lib/tasker_core/batch_processing/batch_aggregation_scenario.rb +85 -0
  4. data/lib/tasker_core/batch_processing/batch_worker_context.rb +238 -0
  5. data/lib/tasker_core/bootstrap.rb +394 -0
  6. data/lib/tasker_core/client.rb +165 -0
  7. data/lib/tasker_core/domain_events/base_publisher.rb +220 -0
  8. data/lib/tasker_core/domain_events/base_subscriber.rb +178 -0
  9. data/lib/tasker_core/domain_events/publisher_registry.rb +253 -0
  10. data/lib/tasker_core/domain_events/subscriber_registry.rb +152 -0
  11. data/lib/tasker_core/domain_events.rb +43 -0
  12. data/lib/tasker_core/errors/CLAUDE.md +7 -0
  13. data/lib/tasker_core/errors/common.rb +305 -0
  14. data/lib/tasker_core/errors/error_classifier.rb +61 -0
  15. data/lib/tasker_core/errors.rb +4 -0
  16. data/lib/tasker_core/event_bridge.rb +330 -0
  17. data/lib/tasker_core/handlers.rb +159 -0
  18. data/lib/tasker_core/internal.rb +31 -0
  19. data/lib/tasker_core/logger.rb +234 -0
  20. data/lib/tasker_core/models.rb +337 -0
  21. data/lib/tasker_core/observability/types.rb +158 -0
  22. data/lib/tasker_core/observability.rb +292 -0
  23. data/lib/tasker_core/registry/handler_registry.rb +453 -0
  24. data/lib/tasker_core/registry/resolver_chain.rb +258 -0
  25. data/lib/tasker_core/registry/resolvers/base_resolver.rb +90 -0
  26. data/lib/tasker_core/registry/resolvers/class_constant_resolver.rb +156 -0
  27. data/lib/tasker_core/registry/resolvers/explicit_mapping_resolver.rb +146 -0
  28. data/lib/tasker_core/registry/resolvers/method_dispatch_wrapper.rb +144 -0
  29. data/lib/tasker_core/registry/resolvers/registry_resolver.rb +229 -0
  30. data/lib/tasker_core/registry/resolvers.rb +42 -0
  31. data/lib/tasker_core/registry.rb +12 -0
  32. data/lib/tasker_core/step_handler/api.rb +48 -0
  33. data/lib/tasker_core/step_handler/base.rb +354 -0
  34. data/lib/tasker_core/step_handler/batchable.rb +50 -0
  35. data/lib/tasker_core/step_handler/decision.rb +53 -0
  36. data/lib/tasker_core/step_handler/mixins/api.rb +452 -0
  37. data/lib/tasker_core/step_handler/mixins/batchable.rb +465 -0
  38. data/lib/tasker_core/step_handler/mixins/decision.rb +252 -0
  39. data/lib/tasker_core/step_handler/mixins.rb +66 -0
  40. data/lib/tasker_core/subscriber.rb +212 -0
  41. data/lib/tasker_core/task_handler/base.rb +254 -0
  42. data/lib/tasker_core/tasker_rb.so +0 -0
  43. data/lib/tasker_core/template_discovery.rb +181 -0
  44. data/lib/tasker_core/test_environment.rb +313 -0
  45. data/lib/tasker_core/tracing.rb +166 -0
  46. data/lib/tasker_core/types/batch_processing_outcome.rb +301 -0
  47. data/lib/tasker_core/types/client_types.rb +145 -0
  48. data/lib/tasker_core/types/decision_point_outcome.rb +177 -0
  49. data/lib/tasker_core/types/error_types.rb +72 -0
  50. data/lib/tasker_core/types/simple_message.rb +151 -0
  51. data/lib/tasker_core/types/step_context.rb +328 -0
  52. data/lib/tasker_core/types/step_handler_call_result.rb +307 -0
  53. data/lib/tasker_core/types/step_message.rb +112 -0
  54. data/lib/tasker_core/types/step_types.rb +207 -0
  55. data/lib/tasker_core/types/task_template.rb +240 -0
  56. data/lib/tasker_core/types/task_types.rb +148 -0
  57. data/lib/tasker_core/types.rb +132 -0
  58. data/lib/tasker_core/version.rb +13 -0
  59. data/lib/tasker_core/worker/CLAUDE.md +7 -0
  60. data/lib/tasker_core/worker/event_poller.rb +224 -0
  61. data/lib/tasker_core/worker/in_process_domain_event_poller.rb +271 -0
  62. data/lib/tasker_core.rb +161 -0
  63. metadata +292 -0
@@ -0,0 +1,330 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models'
4
+
5
+ module TaskerCore
6
+ module Worker
7
+ # Event bridge between Rust and Ruby using dry-events
8
+ #
9
+ # Handles bidirectional event communication between the Rust orchestration
10
+ # layer and Ruby business logic handlers. The bridge uses dry-events for
11
+ # Ruby-side pub/sub and FFI for cross-language communication.
12
+ #
13
+ # Event Flow:
14
+ # 1. **Rust → Ruby**: StepExecutionEvent indicates step is ready for processing
15
+ # 2. **Ruby Processing**: Handler executes business logic
16
+ # 3. **Ruby → Rust**: StepExecutionCompletionEvent returns results
17
+ #
18
+ # The EventBridge automatically wraps raw FFI data in accessor objects for
19
+ # developer convenience and maintains event schema validation.
20
+ #
21
+ # @example Publishing step execution (from Rust FFI)
22
+ # # This is called automatically by Rust via FFI when a step is ready
23
+ # bridge = TaskerCore::Worker::EventBridge.instance
24
+ # bridge.publish_step_execution({
25
+ # event_id: "550e8400-e29b-41d4-a716-446655440000",
26
+ # task_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
27
+ # step_uuid: "123e4567-e89b-12d3-a456-426614174000",
28
+ # task_sequence_step: {
29
+ # task: { context: { amount: 100.00, currency: "USD" } },
30
+ # step: { name: "process_payment", handler_class: "ProcessPaymentHandler" },
31
+ # workflow_step: { state: "in_progress", attempts: 1 }
32
+ # }
33
+ # })
34
+ # # => Publishes 'step.execution.received' event to Ruby subscribers
35
+ #
36
+ # @example Subscribing to step execution events
37
+ # # This is typically done in StepExecutionSubscriber
38
+ # bridge.subscribe_to_step_execution do |event|
39
+ # # Resolve handler
40
+ # handler_class = event[:task_sequence_step].handler_class
41
+ # handler = registry.resolve_handler(handler_class)
42
+ #
43
+ # # Create context and execute handler
44
+ # context = TaskerCore::Types::StepContext.new(event[:task_sequence_step])
45
+ # result = handler.call(context)
46
+ #
47
+ # # Send completion back to Rust
48
+ # bridge.publish_step_completion({
49
+ # event_id: event[:event_id],
50
+ # task_uuid: event[:task_uuid],
51
+ # step_uuid: event[:step_uuid],
52
+ # success: true,
53
+ # result: result
54
+ # })
55
+ # end
56
+ #
57
+ # @example Sending completion back to Rust
58
+ # bridge.publish_step_completion({
59
+ # event_id: "550e8400-e29b-41d4-a716-446655440000",
60
+ # task_uuid: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
61
+ # step_uuid: "123e4567-e89b-12d3-a456-426614174000",
62
+ # success: true,
63
+ # result: { payment_id: "pay_123", status: "succeeded" },
64
+ # metadata: {
65
+ # handler_class: "ProcessPaymentHandler",
66
+ # execution_time_ms: 125
67
+ # }
68
+ # })
69
+ # # => Sends completion to Rust via FFI and publishes 'step.completion.sent'
70
+ #
71
+ # @example Handling errors in step execution
72
+ # begin
73
+ # result = handler.call(context)
74
+ # bridge.publish_step_completion({
75
+ # event_id: event_id,
76
+ # task_uuid: task_uuid,
77
+ # step_uuid: step_uuid,
78
+ # success: true,
79
+ # result: result
80
+ # })
81
+ # rescue TaskerCore::Errors::RetryableError => e
82
+ # bridge.publish_step_completion({
83
+ # event_id: event_id,
84
+ # task_uuid: task_uuid,
85
+ # step_uuid: step_uuid,
86
+ # success: false,
87
+ # error_message: e.message,
88
+ # error_class: e.class.name,
89
+ # retryable: true,
90
+ # retry_after: e.retry_after
91
+ # })
92
+ # rescue TaskerCore::Errors::PermanentError => e
93
+ # bridge.publish_step_completion({
94
+ # event_id: event_id,
95
+ # task_uuid: task_uuid,
96
+ # step_uuid: step_uuid,
97
+ # success: false,
98
+ # error_message: e.message,
99
+ # error_class: e.class.name,
100
+ # retryable: false
101
+ # })
102
+ # end
103
+ #
104
+ # Event Flow Diagram:
105
+ #
106
+ # ```
107
+ # Rust Orchestration EventBridge Ruby Handler
108
+ # ----------------- ----------- ------------
109
+ # | | |
110
+ # | 1. Step ready for execution | |
111
+ # |------------------------------->| |
112
+ # | publish_step_execution | |
113
+ # | | 2. Publish event |
114
+ # | |-------------------------->|
115
+ # | | step.execution.received|
116
+ # | | |
117
+ # | | 3. Execute handler
118
+ # | | |
119
+ # | | 4. Completion |
120
+ # | |<--------------------------|
121
+ # | 5. FFI completion | publish_step_completion|
122
+ # |<-------------------------------| |
123
+ # | send_step_completion_event | |
124
+ # ```
125
+ #
126
+ # Registered Events:
127
+ # - **step.execution.received**: Step ready for execution (Rust → Ruby)
128
+ # - **step.completion.sent**: Step execution completed (Ruby → Rust)
129
+ # - **bridge.error**: Error in event processing
130
+ #
131
+ # Completion Data Validation:
132
+ # The bridge validates completion data before sending to Rust:
133
+ # - **event_id**: Required, UUID of the original execution event
134
+ # - **task_uuid**: Required, UUID of the task
135
+ # - **step_uuid**: Required, UUID of the step
136
+ # - **success**: Required, boolean indicating success/failure
137
+ # - **metadata**: Optional, hash with additional context
138
+ # - **completed_at**: Optional, ISO 8601 timestamp (auto-generated if missing)
139
+ #
140
+ # @see TaskerCore::Subscriber For event subscription implementation
141
+ # @see TaskerCore::Worker::EventPoller For polling mechanism
142
+ # @see TaskerCore::FFI For Rust FFI operations
143
+ # @see TaskerCore::Models::TaskSequenceStepWrapper For data wrappers
144
+ class EventBridge
145
+ include Singleton
146
+ include Dry::Events::Publisher[:tasker_core]
147
+
148
+ attr_reader :logger, :active
149
+
150
+ def initialize
151
+ @logger = TaskerCore::Logger.instance
152
+ @active = true
153
+
154
+ setup_event_schema!
155
+ end
156
+
157
+ # Check if bridge is active
158
+ def active?
159
+ @active
160
+ end
161
+
162
+ # Stop the event bridge
163
+ def stop!
164
+ @active = false
165
+ logger.info 'Event bridge stopped'
166
+ end
167
+
168
+ # Called by Rust FFI when StepExecutionEvent is received
169
+ # This is the entry point for events from Rust
170
+ def publish_step_execution(event_data)
171
+ return unless active?
172
+
173
+ event_data = event_data.to_h.deep_symbolize_keys
174
+ logger.debug "Publishing step execution event: #{event_data[:event_id]}"
175
+
176
+ # Wrap the raw data in accessor objects for easier use
177
+ wrapped_event = wrap_step_execution_event(event_data)
178
+
179
+ # Publish to dry-events subscribers (Ruby handlers)
180
+ publish('step.execution.received', wrapped_event)
181
+
182
+ logger.debug 'Step execution event published successfully'
183
+ true
184
+ rescue StandardError => e
185
+ logger.error "Failed to publish step execution: #{e.message}"
186
+ logger.error e.backtrace.join("\n")
187
+ raise
188
+ end
189
+
190
+ # Subscribe to step execution events (used by StepExecutionSubscriber)
191
+ def subscribe_to_step_execution(&)
192
+ subscribe('step.execution.received', &)
193
+ end
194
+
195
+ # Send completion event back to Rust
196
+ # Called by StepExecutionSubscriber after handler execution
197
+ def publish_step_completion(completion_data)
198
+ return unless active?
199
+
200
+ logger.debug "Sending step completion to Rust: #{completion_data[:event_id]}"
201
+
202
+ # Validate completion data
203
+ validate_completion!(completion_data)
204
+
205
+ # Send to Rust via FFI (TAS-67: complete_step_event takes event_id and completion_data)
206
+ TaskerCore::FFI.complete_step_event(completion_data[:event_id].to_s, completion_data)
207
+
208
+ # Also publish locally for monitoring/debugging
209
+ publish('step.completion.sent', completion_data)
210
+
211
+ logger.debug 'Step completion sent to Rust'
212
+ rescue StandardError => e
213
+ logger.error "Failed to send step completion: #{e.message}"
214
+ logger.error e.backtrace.join("\n")
215
+ raise
216
+ end
217
+
218
+ # TAS-125: Send checkpoint yield back to Rust for batch processing
219
+ #
220
+ # Called by batch processing handlers when they want to persist progress
221
+ # and be re-dispatched for continuation. Unlike publish_step_completion,
222
+ # this does NOT complete the step - instead it persists checkpoint data
223
+ # and causes the step to be re-dispatched for continued processing.
224
+ #
225
+ # @param checkpoint_data [Hash] Checkpoint data to persist:
226
+ # - :event_id [String] Required, UUID of the original execution event
227
+ # - :step_uuid [String] Required, UUID of the step
228
+ # - :cursor [Object] Required, position to resume from (Integer, String, or Hash)
229
+ # - :items_processed [Integer] Required, count of items processed so far
230
+ # - :accumulated_results [Hash] Optional, partial results to carry forward
231
+ # @return [Boolean] true if checkpoint was persisted and step re-dispatched
232
+ #
233
+ # @example Yield checkpoint in batch processing
234
+ # bridge.publish_step_checkpoint_yield({
235
+ # event_id: "550e8400-e29b-41d4-a716-446655440000",
236
+ # step_uuid: "123e4567-e89b-12d3-a456-426614174000",
237
+ # cursor: 1000,
238
+ # items_processed: 1000,
239
+ # accumulated_results: { total_amount: 50000.00, processed_count: 1000 }
240
+ # })
241
+ def publish_step_checkpoint_yield(checkpoint_data)
242
+ return false unless active?
243
+
244
+ logger.debug "Sending checkpoint yield to Rust: #{checkpoint_data[:event_id]}"
245
+
246
+ # Validate checkpoint data
247
+ validate_checkpoint_yield!(checkpoint_data)
248
+
249
+ # Send to Rust via FFI (TAS-125)
250
+ success = TaskerCore::FFI.checkpoint_yield_step_event(
251
+ checkpoint_data[:event_id].to_s,
252
+ checkpoint_data
253
+ )
254
+
255
+ if success
256
+ # Publish locally for monitoring/debugging
257
+ publish('step.checkpoint_yield.sent', checkpoint_data)
258
+ logger.debug 'Checkpoint yield sent to Rust - step will be re-dispatched'
259
+ else
260
+ logger.warn 'Checkpoint yield failed - checkpoint support may not be configured'
261
+ end
262
+
263
+ success
264
+ rescue StandardError => e
265
+ logger.error "Failed to send checkpoint yield: #{e.message}"
266
+ logger.error e.backtrace.join("\n")
267
+ raise
268
+ end
269
+
270
+ private
271
+
272
+ def setup_event_schema!
273
+ # Register event types
274
+ register_event('step.execution.received')
275
+ register_event('step.completion.sent')
276
+ register_event('step.checkpoint_yield.sent') # TAS-125
277
+ register_event('bridge.error')
278
+ end
279
+
280
+ def wrap_step_execution_event(event_data)
281
+ wrapped = {
282
+ event_id: event_data[:event_id],
283
+ task_uuid: event_data[:task_uuid],
284
+ step_uuid: event_data[:step_uuid],
285
+ task_sequence_step: TaskerCore::Models::TaskSequenceStepWrapper.new(event_data[:task_sequence_step])
286
+ }
287
+
288
+ # TAS-29: Expose correlation_id at top level for easy access
289
+ wrapped[:correlation_id] = event_data[:correlation_id] if event_data[:correlation_id]
290
+ wrapped[:parent_correlation_id] = event_data[:parent_correlation_id] if event_data[:parent_correlation_id]
291
+
292
+ # TAS-65 Phase 1.5b: Expose trace_id and span_id for distributed tracing
293
+ wrapped[:trace_id] = event_data[:trace_id] if event_data[:trace_id]
294
+ wrapped[:span_id] = event_data[:span_id] if event_data[:span_id]
295
+
296
+ wrapped
297
+ end
298
+
299
+ def validate_completion!(completion_data)
300
+ required_fields = %i[event_id task_uuid step_uuid success]
301
+ missing_fields = required_fields - completion_data.keys
302
+
303
+ if missing_fields.any?
304
+ raise ArgumentError, "Missing required fields in completion: #{missing_fields.join(', ')}"
305
+ end
306
+
307
+ # Ensure metadata is a hash
308
+ completion_data[:metadata] ||= {}
309
+
310
+ # Ensure timestamps
311
+ completion_data[:completed_at] ||= Time.now.utc.iso8601
312
+ end
313
+
314
+ # TAS-125: Validate checkpoint yield data before sending to Rust
315
+ def validate_checkpoint_yield!(checkpoint_data)
316
+ required_fields = %i[event_id step_uuid cursor items_processed]
317
+ missing_fields = required_fields - checkpoint_data.keys
318
+
319
+ if missing_fields.any?
320
+ raise ArgumentError, "Missing required fields in checkpoint yield: #{missing_fields.join(', ')}"
321
+ end
322
+
323
+ # Validate items_processed is a non-negative integer
324
+ return if checkpoint_data[:items_processed].is_a?(Integer) && checkpoint_data[:items_processed] >= 0
325
+
326
+ raise ArgumentError, 'items_processed must be a non-negative integer'
327
+ end
328
+ end
329
+ end
330
+ end
@@ -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