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,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
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-struct'
4
+ require 'dry-types'
5
+
6
+ module TaskerCore
7
+ module Observability
8
+ # Type definitions for observability data structures
9
+ #
10
+ # These types use dry-struct for automatic validation and provide
11
+ # structured access to health, metrics, and configuration data.
12
+ module Types
13
+ include Dry.Types()
14
+
15
+ # ==========================================================================
16
+ # Health Types
17
+ # ==========================================================================
18
+
19
+ # Basic health check response
20
+ class BasicHealth < Dry::Struct
21
+ attribute :status, Types::Strict::String
22
+ attribute :worker_id, Types::Strict::String
23
+ attribute :timestamp, Types::Strict::String
24
+ end
25
+
26
+ # Individual health check result
27
+ class HealthCheck < Dry::Struct
28
+ attribute :status, Types::Strict::String
29
+ attribute :message, Types::Strict::String.optional
30
+ attribute :duration_ms, Types::Strict::Integer
31
+ attribute :last_checked, Types::Strict::String
32
+ end
33
+
34
+ # Worker system information
35
+ class WorkerSystemInfo < Dry::Struct
36
+ attribute :version, Types::Strict::String
37
+ attribute :environment, Types::Strict::String
38
+ attribute :uptime_seconds, Types::Strict::Integer
39
+ attribute :worker_type, Types::Strict::String
40
+ attribute :database_pool_size, Types::Strict::Integer
41
+ attribute :command_processor_active, Types::Strict::Bool
42
+ attribute :supported_namespaces, Types::Strict::Array.of(Types::Strict::String)
43
+ end
44
+
45
+ # Detailed health check response
46
+ class DetailedHealth < Dry::Struct
47
+ attribute :status, Types::Strict::String
48
+ attribute :timestamp, Types::Strict::String
49
+ attribute :worker_id, Types::Strict::String
50
+ attribute :checks, Types::Strict::Hash.map(Types::Strict::String, HealthCheck)
51
+ attribute :system_info, WorkerSystemInfo
52
+ end
53
+
54
+ # ==========================================================================
55
+ # Metrics Types
56
+ # ==========================================================================
57
+
58
+ # Event router statistics
59
+ class EventRouterStats < Dry::Struct
60
+ attribute :total_routed, Types::Strict::Integer
61
+ attribute :durable_routed, Types::Strict::Integer
62
+ attribute :fast_routed, Types::Strict::Integer
63
+ attribute :broadcast_routed, Types::Strict::Integer
64
+ attribute :fast_delivery_errors, Types::Strict::Integer
65
+ attribute :routing_errors, Types::Strict::Integer
66
+ end
67
+
68
+ # In-process event bus statistics
69
+ class InProcessEventBusStats < Dry::Struct
70
+ attribute :total_events_dispatched, Types::Strict::Integer
71
+ attribute :rust_handler_dispatches, Types::Strict::Integer
72
+ attribute :ffi_channel_dispatches, Types::Strict::Integer
73
+ attribute :rust_handler_errors, Types::Strict::Integer
74
+ attribute :ffi_channel_drops, Types::Strict::Integer
75
+ attribute :rust_subscriber_patterns, Types::Strict::Integer
76
+ attribute :rust_handler_count, Types::Strict::Integer
77
+ attribute :ffi_subscriber_count, Types::Strict::Integer
78
+ end
79
+
80
+ # Domain event statistics
81
+ class DomainEventStats < Dry::Struct
82
+ attribute :router, EventRouterStats
83
+ attribute :in_process_bus, InProcessEventBusStats
84
+ attribute :captured_at, Types::Strict::String
85
+ attribute :worker_id, Types::Strict::String
86
+ end
87
+
88
+ # ==========================================================================
89
+ # Template Types
90
+ # ==========================================================================
91
+
92
+ # Template cache statistics
93
+ class CacheStats < Dry::Struct
94
+ attribute :total_entries, Types::Strict::Integer
95
+ attribute :hits, Types::Strict::Integer
96
+ attribute :misses, Types::Strict::Integer
97
+ attribute :evictions, Types::Strict::Integer
98
+ attribute :last_maintenance, Types::Strict::String.optional
99
+ end
100
+
101
+ # Cache operation result
102
+ class CacheOperationResult < Dry::Struct
103
+ attribute :success, Types::Strict::Bool
104
+ attribute :message, Types::Strict::String
105
+ attribute :timestamp, Types::Strict::String
106
+ end
107
+
108
+ # Template validation result
109
+ class TemplateValidation < Dry::Struct
110
+ attribute :valid, Types::Strict::Bool
111
+ attribute :namespace, Types::Strict::String
112
+ attribute :name, Types::Strict::String
113
+ attribute :version, Types::Strict::String
114
+ attribute :handler_count, Types::Strict::Integer
115
+ attribute :issues, Types::Strict::Array.of(Types::Strict::String)
116
+ attribute :handler_metadata, Types::Strict::Hash.optional
117
+ end
118
+
119
+ # ==========================================================================
120
+ # Config Types (TAS-150: whitelist-only safe config exposure)
121
+ # ==========================================================================
122
+
123
+ # Configuration metadata (non-sensitive system info)
124
+ class ConfigMetadata < Dry::Struct
125
+ attribute :timestamp, Types::Strict::String
126
+ attribute :environment, Types::Strict::String
127
+ attribute :version, Types::Strict::String
128
+ end
129
+
130
+ # Non-sensitive auth configuration summary
131
+ class SafeAuthConfig < Dry::Struct
132
+ attribute :enabled, Types::Strict::Bool
133
+ attribute :verification_method, Types::Strict::String
134
+ attribute :jwt_issuer, Types::Strict::String
135
+ attribute :jwt_audience, Types::Strict::String
136
+ attribute :api_key_header, Types::Strict::String
137
+ attribute :api_key_count, Types::Strict::Integer
138
+ attribute :strict_validation, Types::Strict::Bool
139
+ attribute :allowed_algorithms, Types::Strict::Array.of(Types::Strict::String)
140
+ end
141
+
142
+ # Non-sensitive messaging configuration
143
+ class SafeMessagingConfig < Dry::Struct
144
+ attribute :backend, Types::Strict::String
145
+ attribute :queues, Types::Strict::Array.of(Types::Strict::String)
146
+ end
147
+
148
+ # Worker configuration response (safe fields only)
149
+ class RuntimeConfig < Dry::Struct
150
+ attribute :metadata, ConfigMetadata
151
+ attribute :worker_id, Types::Strict::String
152
+ attribute :worker_type, Types::Strict::String
153
+ attribute :auth, SafeAuthConfig
154
+ attribute :messaging, SafeMessagingConfig
155
+ end
156
+ end
157
+ end
158
+ end