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.
- checksums.yaml +7 -0
- data/README.md +55 -0
- data/lib/tasker_core/batch_processing/batch_aggregation_scenario.rb +85 -0
- data/lib/tasker_core/batch_processing/batch_worker_context.rb +238 -0
- data/lib/tasker_core/bootstrap.rb +394 -0
- data/lib/tasker_core/client.rb +165 -0
- data/lib/tasker_core/domain_events/base_publisher.rb +220 -0
- data/lib/tasker_core/domain_events/base_subscriber.rb +178 -0
- data/lib/tasker_core/domain_events/publisher_registry.rb +253 -0
- data/lib/tasker_core/domain_events/subscriber_registry.rb +152 -0
- data/lib/tasker_core/domain_events.rb +43 -0
- data/lib/tasker_core/errors/CLAUDE.md +7 -0
- data/lib/tasker_core/errors/common.rb +305 -0
- data/lib/tasker_core/errors/error_classifier.rb +61 -0
- data/lib/tasker_core/errors.rb +4 -0
- data/lib/tasker_core/event_bridge.rb +330 -0
- data/lib/tasker_core/handlers.rb +159 -0
- data/lib/tasker_core/internal.rb +31 -0
- data/lib/tasker_core/logger.rb +234 -0
- data/lib/tasker_core/models.rb +337 -0
- data/lib/tasker_core/observability/types.rb +158 -0
- data/lib/tasker_core/observability.rb +292 -0
- data/lib/tasker_core/registry/handler_registry.rb +453 -0
- data/lib/tasker_core/registry/resolver_chain.rb +258 -0
- data/lib/tasker_core/registry/resolvers/base_resolver.rb +90 -0
- data/lib/tasker_core/registry/resolvers/class_constant_resolver.rb +156 -0
- data/lib/tasker_core/registry/resolvers/explicit_mapping_resolver.rb +146 -0
- data/lib/tasker_core/registry/resolvers/method_dispatch_wrapper.rb +144 -0
- data/lib/tasker_core/registry/resolvers/registry_resolver.rb +229 -0
- data/lib/tasker_core/registry/resolvers.rb +42 -0
- data/lib/tasker_core/registry.rb +12 -0
- data/lib/tasker_core/step_handler/api.rb +48 -0
- data/lib/tasker_core/step_handler/base.rb +354 -0
- data/lib/tasker_core/step_handler/batchable.rb +50 -0
- data/lib/tasker_core/step_handler/decision.rb +53 -0
- data/lib/tasker_core/step_handler/mixins/api.rb +452 -0
- data/lib/tasker_core/step_handler/mixins/batchable.rb +465 -0
- data/lib/tasker_core/step_handler/mixins/decision.rb +252 -0
- data/lib/tasker_core/step_handler/mixins.rb +66 -0
- data/lib/tasker_core/subscriber.rb +212 -0
- data/lib/tasker_core/task_handler/base.rb +254 -0
- data/lib/tasker_core/tasker_rb.so +0 -0
- data/lib/tasker_core/template_discovery.rb +181 -0
- data/lib/tasker_core/test_environment.rb +313 -0
- data/lib/tasker_core/tracing.rb +166 -0
- data/lib/tasker_core/types/batch_processing_outcome.rb +301 -0
- data/lib/tasker_core/types/client_types.rb +145 -0
- data/lib/tasker_core/types/decision_point_outcome.rb +177 -0
- data/lib/tasker_core/types/error_types.rb +72 -0
- data/lib/tasker_core/types/simple_message.rb +151 -0
- data/lib/tasker_core/types/step_context.rb +328 -0
- data/lib/tasker_core/types/step_handler_call_result.rb +307 -0
- data/lib/tasker_core/types/step_message.rb +112 -0
- data/lib/tasker_core/types/step_types.rb +207 -0
- data/lib/tasker_core/types/task_template.rb +240 -0
- data/lib/tasker_core/types/task_types.rb +148 -0
- data/lib/tasker_core/types.rb +132 -0
- data/lib/tasker_core/version.rb +13 -0
- data/lib/tasker_core/worker/CLAUDE.md +7 -0
- data/lib/tasker_core/worker/event_poller.rb +224 -0
- data/lib/tasker_core/worker/in_process_domain_event_poller.rb +271 -0
- data/lib/tasker_core.rb +161 -0
- metadata +292 -0
|
@@ -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
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TaskerCore
|
|
4
|
+
module Models
|
|
5
|
+
# Wrapper for TaskSequenceStep data received from Rust FFI.
|
|
6
|
+
#
|
|
7
|
+
# This class provides a Ruby-friendly interface to the complete step execution context,
|
|
8
|
+
# including task metadata, workflow step state, dependency results, and step definition.
|
|
9
|
+
# The data structure is provided by the Rust orchestration system via FFI when a step
|
|
10
|
+
# is ready for execution.
|
|
11
|
+
#
|
|
12
|
+
# @example Accessing task context in a handler
|
|
13
|
+
# def call(context)
|
|
14
|
+
# even_number = context.task.context['even_number']
|
|
15
|
+
# # or use the convenience method
|
|
16
|
+
# even_number = context.get_task_field('even_number')
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Accessing dependency results
|
|
20
|
+
# def call(context)
|
|
21
|
+
# previous_result = context.get_dependency_result('previous_step_name')
|
|
22
|
+
# end
|
|
23
|
+
class TaskSequenceStepWrapper
|
|
24
|
+
# @return [TaskWrapper] Wrapped task with context and metadata
|
|
25
|
+
# @return [WorkflowStepWrapper] Wrapped workflow step with execution state
|
|
26
|
+
# @return [DependencyResultsWrapper] Results from parent steps
|
|
27
|
+
# @return [StepDefinitionWrapper] Step definition from task template
|
|
28
|
+
attr_reader :task, :workflow_step, :dependency_results, :step_definition
|
|
29
|
+
|
|
30
|
+
# Creates a new TaskSequenceStepWrapper from FFI data
|
|
31
|
+
#
|
|
32
|
+
# @param step_data [Hash] The complete step execution data from Rust
|
|
33
|
+
# @option step_data [Hash] :task Task and namespace metadata
|
|
34
|
+
# @option step_data [Hash] :workflow_step Step execution state and results
|
|
35
|
+
# @option step_data [Hash] :dependency_results Results from parent steps
|
|
36
|
+
# @option step_data [Hash] :step_definition Step configuration from template
|
|
37
|
+
def initialize(step_data)
|
|
38
|
+
@task = TaskWrapper.new(step_data[:task])
|
|
39
|
+
@workflow_step = WorkflowStepWrapper.new(step_data[:workflow_step])
|
|
40
|
+
@dependency_results = DependencyResultsWrapper.new(step_data[:dependency_results])
|
|
41
|
+
@step_definition = StepDefinitionWrapper.new(step_data[:step_definition])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convenience method to access task context fields
|
|
45
|
+
#
|
|
46
|
+
# @param field_name [String, Symbol] The field name in task context
|
|
47
|
+
# @return [Object, nil] The field value or nil if not found
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# sequence.get_task_field('even_number') # => 2
|
|
51
|
+
def get_task_field(field_name)
|
|
52
|
+
@task.context[field_name.to_s]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Convenience method to access dependency results
|
|
56
|
+
#
|
|
57
|
+
# @param step_name [String, Symbol] The name of the parent step
|
|
58
|
+
# @return [Object, nil] The result from the parent step or nil
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# result = sequence.get_dependency_result('linear_step_1') # => 36
|
|
62
|
+
def get_dependency_result(step_name)
|
|
63
|
+
@dependency_results.get_result(step_name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Convenience method to access dependency results (alias for compatibility)
|
|
67
|
+
#
|
|
68
|
+
# @param step_name [String, Symbol] The name of the parent step
|
|
69
|
+
# @return [Object, nil] The result from the parent step or nil
|
|
70
|
+
#
|
|
71
|
+
# @example
|
|
72
|
+
# result = sequence.get_results('linear_step_1') # => 36
|
|
73
|
+
def get_results(step_name)
|
|
74
|
+
@dependency_results.get_results(step_name)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Wrapper for task metadata and context.
|
|
79
|
+
#
|
|
80
|
+
# Handles the nested task structure from Rust FFI where the actual task data
|
|
81
|
+
# is nested under a :task key within the parent task hash.
|
|
82
|
+
#
|
|
83
|
+
# @example Accessing task properties
|
|
84
|
+
# task.task_uuid # => "0199a46a-11a8-7d53-83da-0b13513dab49"
|
|
85
|
+
# task.context # => { "even_number" => 2 }
|
|
86
|
+
# task.namespace_name # => "linear_workflow"
|
|
87
|
+
class TaskWrapper
|
|
88
|
+
# @return [String] UUID v7 of the task instance
|
|
89
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Task context with input data and accumulated state
|
|
90
|
+
# @return [String] Namespace name from task template
|
|
91
|
+
# @return [String] Task template name
|
|
92
|
+
# @return [String] Task template version
|
|
93
|
+
attr_reader :task_uuid, :context, :namespace_name, :task_name, :task_version
|
|
94
|
+
|
|
95
|
+
# Creates a new TaskWrapper from FFI data
|
|
96
|
+
#
|
|
97
|
+
# @param task_data [Hash] Task data with nested structure from Rust
|
|
98
|
+
# @option task_data [Hash] :task Nested task object with context
|
|
99
|
+
# @option task_data [String] :task_name Template name
|
|
100
|
+
# @option task_data [String] :namespace_name Namespace from template
|
|
101
|
+
# @option task_data [String] :task_version Template version
|
|
102
|
+
def initialize(task_data)
|
|
103
|
+
# Handle nested task structure from Rust FFI
|
|
104
|
+
# Data comes as: { task: { task_uuid: ..., context: ... }, task_name: "...", namespace_name: "..." }
|
|
105
|
+
# The actual task fields are nested under :task key
|
|
106
|
+
inner_task = task_data[:task] || task_data
|
|
107
|
+
|
|
108
|
+
@task_uuid = inner_task[:task_uuid]
|
|
109
|
+
# Use HashWithIndifferentAccess for developer-friendly context access
|
|
110
|
+
@context = (inner_task[:context] || {}).with_indifferent_access
|
|
111
|
+
@namespace_name = task_data[:namespace_name] || inner_task[:namespace_name]
|
|
112
|
+
@task_name = task_data[:task_name] || inner_task[:task_name]
|
|
113
|
+
@task_version = task_data[:task_version] || inner_task[:task_version]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Wrapper for workflow step execution state and metadata.
|
|
118
|
+
#
|
|
119
|
+
# Provides access to step execution tracking, retry configuration, and results.
|
|
120
|
+
#
|
|
121
|
+
# @example Checking step state
|
|
122
|
+
# step.name # => "linear_step_1"
|
|
123
|
+
# step.attempts # => 1
|
|
124
|
+
# step.max_attempts # => 3
|
|
125
|
+
# step.in_process # => false
|
|
126
|
+
class WorkflowStepWrapper
|
|
127
|
+
# @return [String] UUID v7 of the workflow step instance
|
|
128
|
+
# @return [String] UUID v7 of the task this step belongs to
|
|
129
|
+
# @return [String] UUID v7 of the named step definition
|
|
130
|
+
# @return [String] Step name from template
|
|
131
|
+
# @return [Boolean] Whether step can be retried
|
|
132
|
+
# @return [Integer] Maximum retry attempts
|
|
133
|
+
# @return [Boolean] Whether step is currently being processed
|
|
134
|
+
# @return [Boolean] Whether step has been processed
|
|
135
|
+
# @return [Time, nil] When step was last processed
|
|
136
|
+
# @return [Integer] Number of execution attempts
|
|
137
|
+
# @return [Time, nil] When step was last attempted
|
|
138
|
+
# @return [Integer] Backoff delay in seconds for retry
|
|
139
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Step inputs from template
|
|
140
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Step execution results
|
|
141
|
+
|
|
142
|
+
# @return [Time] When step was created
|
|
143
|
+
# @return [Time] When step was last updated
|
|
144
|
+
# @return [ActiveSupport::HashWithIndifferentAccess, nil] TAS-125: Checkpoint data for batch processing resumption
|
|
145
|
+
attr_reader :workflow_step_uuid, :task_uuid, :named_step_uuid, :name,
|
|
146
|
+
:retryable, :max_attempts, :in_process, :processed, :processed_at,
|
|
147
|
+
:attempts, :last_attempted_at, :backoff_request_seconds,
|
|
148
|
+
:inputs, :results, :created_at, :updated_at, :checkpoint
|
|
149
|
+
|
|
150
|
+
# Creates a new WorkflowStepWrapper from FFI data
|
|
151
|
+
#
|
|
152
|
+
# @param step_data [Hash] Workflow step data from Rust
|
|
153
|
+
def initialize(step_data)
|
|
154
|
+
@workflow_step_uuid = step_data[:workflow_step_uuid]
|
|
155
|
+
@task_uuid = step_data[:task_uuid]
|
|
156
|
+
@named_step_uuid = step_data[:named_step_uuid]
|
|
157
|
+
@name = step_data[:name]
|
|
158
|
+
@retryable = step_data[:retryable]
|
|
159
|
+
@max_attempts = step_data[:max_attempts]
|
|
160
|
+
@in_process = step_data[:in_process]
|
|
161
|
+
@processed = step_data[:processed]
|
|
162
|
+
@processed_at = step_data[:processed_at]
|
|
163
|
+
@attempts = step_data[:attempts]
|
|
164
|
+
@last_attempted_at = step_data[:last_attempted_at]
|
|
165
|
+
@backoff_request_seconds = step_data[:backoff_request_seconds]
|
|
166
|
+
# Use HashWithIndifferentAccess for nested hashes
|
|
167
|
+
@inputs = (step_data[:inputs] || {}).with_indifferent_access
|
|
168
|
+
@results = (step_data[:results] || {}).with_indifferent_access
|
|
169
|
+
|
|
170
|
+
@created_at = step_data[:created_at]
|
|
171
|
+
@updated_at = step_data[:updated_at]
|
|
172
|
+
# TAS-125: Checkpoint data for batch processing resumption
|
|
173
|
+
@checkpoint = step_data[:checkpoint]&.with_indifferent_access
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Wrapper for dependency results from parent steps.
|
|
178
|
+
#
|
|
179
|
+
# Provides access to results from steps that this step depends on.
|
|
180
|
+
# Results are keyed by step name.
|
|
181
|
+
#
|
|
182
|
+
# @note Two methods for accessing results:
|
|
183
|
+
# - `get_result(name)` returns the full result hash with metadata
|
|
184
|
+
# - `get_results(name)` returns just the computed value (recommended for handlers)
|
|
185
|
+
#
|
|
186
|
+
# @example Accessing dependency results
|
|
187
|
+
# deps.get_results('previous_step') # => 36 (just the value)
|
|
188
|
+
# deps.get_result('previous_step') # => { result: 36, metadata: {...} }
|
|
189
|
+
# deps['previous_step'] # => { result: 36, metadata: {...} }
|
|
190
|
+
class DependencyResultsWrapper
|
|
191
|
+
# Creates a new DependencyResultsWrapper
|
|
192
|
+
#
|
|
193
|
+
# @param results_data [Hash] Hash of step names to their results
|
|
194
|
+
def initialize(results_data)
|
|
195
|
+
# Use HashWithIndifferentAccess for developer-friendly key access
|
|
196
|
+
@results = (results_data || {}).with_indifferent_access
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Get result from a parent step (returns full result hash)
|
|
200
|
+
#
|
|
201
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
202
|
+
# @return [Hash, nil] The full result hash or nil if not found
|
|
203
|
+
def get_result(step_name)
|
|
204
|
+
@results[step_name] # HashWithIndifferentAccess handles string/symbol automatically
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Get the actual result value from a parent step (extracts 'result' field)
|
|
208
|
+
#
|
|
209
|
+
# This is the typical method handlers use to get the actual computed value
|
|
210
|
+
# from a parent step, rather than the full result metadata hash.
|
|
211
|
+
#
|
|
212
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
213
|
+
# @return [Object, nil] The result value or nil if not found
|
|
214
|
+
#
|
|
215
|
+
# @example
|
|
216
|
+
# deps.get_results('linear_step_1') # => 36 (the actual value)
|
|
217
|
+
# deps.get_result('linear_step_1') # => { result: 36, metadata: {...} }
|
|
218
|
+
def get_results(step_name)
|
|
219
|
+
result_hash = @results[step_name]
|
|
220
|
+
return nil unless result_hash
|
|
221
|
+
|
|
222
|
+
# If it's a hash with a 'result' key, extract that value
|
|
223
|
+
# Otherwise return the whole thing (might be a primitive value)
|
|
224
|
+
if result_hash.is_a?(Hash) && result_hash.key?('result')
|
|
225
|
+
result_hash['result']
|
|
226
|
+
else
|
|
227
|
+
result_hash
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Array-style access to dependency results
|
|
232
|
+
#
|
|
233
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
234
|
+
# @return [Hash, nil] The full result hash or nil if not found
|
|
235
|
+
def [](step_name)
|
|
236
|
+
get_result(step_name)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Get all dependency result step names
|
|
240
|
+
#
|
|
241
|
+
# @return [Array<String>] List of step names that have results
|
|
242
|
+
def keys
|
|
243
|
+
@results.keys
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Check if a dependency result exists
|
|
247
|
+
#
|
|
248
|
+
# @param step_name [String, Symbol] Name of the parent step
|
|
249
|
+
# @return [Boolean] True if result exists
|
|
250
|
+
def key?(step_name)
|
|
251
|
+
@results.key?(step_name) # HashWithIndifferentAccess handles string/symbol automatically
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def each_key(&)
|
|
255
|
+
@results.keys.each(&)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Wrapper for step definition from task template.
|
|
260
|
+
#
|
|
261
|
+
# Provides access to step configuration including handler specification,
|
|
262
|
+
# dependencies, retry policy, and timeout settings.
|
|
263
|
+
#
|
|
264
|
+
# @example Accessing step definition
|
|
265
|
+
# step_def.name # => "linear_step_1"
|
|
266
|
+
# step_def.description # => "Square the initial even number..."
|
|
267
|
+
# step_def.handler.callable # => "LinearWorkflow::StepHandlers::LinearStep1Handler"
|
|
268
|
+
# step_def.dependencies # => ["previous_step"]
|
|
269
|
+
# step_def.timeout_seconds # => 30
|
|
270
|
+
class StepDefinitionWrapper
|
|
271
|
+
# @return [String] Step name
|
|
272
|
+
# @return [String] Human-readable description
|
|
273
|
+
# @return [HandlerWrapper] Handler configuration
|
|
274
|
+
|
|
275
|
+
# @return [Array<String>] Names of parent steps this depends on
|
|
276
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Retry configuration
|
|
277
|
+
# @return [Integer] Timeout in seconds
|
|
278
|
+
# @return [Array<String>] Events this step publishes
|
|
279
|
+
attr_reader :name, :description, :handler,
|
|
280
|
+
:dependencies, :retry, :timeout_seconds, :publishes_events
|
|
281
|
+
|
|
282
|
+
# Creates a new StepDefinitionWrapper from template data
|
|
283
|
+
#
|
|
284
|
+
# @param definition_data [Hash] Step definition from task template
|
|
285
|
+
def initialize(definition_data)
|
|
286
|
+
@name = definition_data[:name]
|
|
287
|
+
@description = definition_data[:description]
|
|
288
|
+
@handler = HandlerWrapper.new(definition_data[:handler])
|
|
289
|
+
|
|
290
|
+
@dependencies = definition_data[:dependencies] || []
|
|
291
|
+
# Use HashWithIndifferentAccess for retry configuration
|
|
292
|
+
@retry = (definition_data[:retry] || {}).with_indifferent_access
|
|
293
|
+
@timeout_seconds = definition_data[:timeout_seconds]
|
|
294
|
+
@publishes_events = definition_data[:publishes_events] || []
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Wrapper for handler configuration.
|
|
299
|
+
#
|
|
300
|
+
# Provides access to handler class name, method dispatch, resolver hints,
|
|
301
|
+
# and initialization parameters from the task template.
|
|
302
|
+
#
|
|
303
|
+
# TAS-93: Extended to support method dispatch and resolver hints from
|
|
304
|
+
# the Rust HandlerDefinition struct.
|
|
305
|
+
#
|
|
306
|
+
# @example Accessing handler configuration
|
|
307
|
+
# handler.callable # => "LinearWorkflow::StepHandlers::LinearStep1Handler"
|
|
308
|
+
# handler.initialization # => { operation: "square", step_number: 1 }
|
|
309
|
+
# handler.handler_method # => "refund" (or nil for default .call())
|
|
310
|
+
# handler.resolver # => "explicit" (or nil for chain traversal)
|
|
311
|
+
class HandlerWrapper
|
|
312
|
+
# @return [String] Fully-qualified handler class name
|
|
313
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Initialization parameters for the handler
|
|
314
|
+
# @return [String, nil] TAS-93: Method to invoke instead of default .call()
|
|
315
|
+
# @return [String, nil] TAS-93: Specific resolver to use (bypasses chain)
|
|
316
|
+
attr_reader :callable, :initialization, :handler_method, :resolver
|
|
317
|
+
|
|
318
|
+
# Creates a new HandlerWrapper from template data
|
|
319
|
+
#
|
|
320
|
+
# @param handler_data [Hash] Handler configuration from template
|
|
321
|
+
# @option handler_data [String] :callable Handler class name
|
|
322
|
+
# @option handler_data [Hash] :initialization Handler init params
|
|
323
|
+
# @option handler_data [String] :method TAS-93: Method to invoke (from Rust FFI)
|
|
324
|
+
# @option handler_data [String] :resolver TAS-93: Resolver hint (from Rust FFI)
|
|
325
|
+
def initialize(handler_data)
|
|
326
|
+
@callable = handler_data[:callable]
|
|
327
|
+
# Use HashWithIndifferentAccess for initialization parameters
|
|
328
|
+
@initialization = (handler_data[:initialization] || {}).with_indifferent_access
|
|
329
|
+
# TAS-93: Method dispatch - note Rust field is 'method' but we use 'handler_method'
|
|
330
|
+
# to avoid conflict with Ruby's Object#method
|
|
331
|
+
@handler_method = handler_data[:method]
|
|
332
|
+
# TAS-93: Resolver hint for direct resolver routing
|
|
333
|
+
@resolver = handler_data[:resolver]
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|