tasker-rb 0.1.7 → 0.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8c5ed3385bea52ede9378bb4cd2f35c4fb03e285a0b811698fa34042b2a0c8a
4
- data.tar.gz: a9ce3c0d4d390ae8f0263ca60794c8a0997d99ca6272c52ccd7a8cea579de6ae
3
+ metadata.gz: efbbc9f01809d4ac82732ea8b7d7f906f57fb939879c78f3ca448447211abd21
4
+ data.tar.gz: 7b7b1e5a38af3a78d5f090a725c5db85c49e4193e12440ed9a9b802972718112
5
5
  SHA512:
6
- metadata.gz: d7957c47e7f5403a0adc3cbfc917b49196e44e21438276f806809662b0bd3abaa2ff14408e69233dd06e1521fece83cc0f320d3601d152e8ee69730212f3ca58
7
- data.tar.gz: e7844fe1ee414c7f3887db916b084c15ac44768885516792964bce9afff6cd807a8b847335428cb367fb7e0042730b8bc95a098e89cc7a24851ca0b96bc8b5db
6
+ metadata.gz: 05f12f661859b22dd1446bd6c5b162523c74f5640cbeeff1e4027416c07bca1d05160fd6700405d54453b34bc0acab5bad0b4e156e89bb7e1510462593b00ff1
7
+ data.tar.gz: 418b0a95010e8b500630769702357c63fcbba996918b35aace2b65364b73d6126a7b34cd7282e97c82e4b802be31b30ea2dac3f92f1faa08560d2cf6e208df26
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tasker-rb"
3
- version = "0.1.5"
3
+ version = "0.1.6"
4
4
  edition = "2021"
5
5
  description = "Ruby bindings for tasker-core: High-performance workflow orchestration"
6
6
  readme = "../../../../README.md"
@@ -66,8 +66,8 @@ sqlx = { version = "0.8", features = [
66
66
  # Both path (for workspace builds) and version (for standalone/published builds).
67
67
  # cargo publish strips the path field, leaving only the version for crates.io resolution.
68
68
  # Version pins are updated by scripts/release/update-versions.sh during release-prepare.
69
- tasker-shared = { path = "../../../../tasker-shared", version = "=0.1.5" }
70
- tasker-worker = { path = "../../../../tasker-worker", version = "=0.1.5" }
69
+ tasker-shared = { path = "../../../../tasker-shared", version = "=0.1.6" }
70
+ tasker-worker = { path = "../../../../tasker-worker", version = "=0.1.6" }
71
71
  # Error handling
72
72
  thiserror = "2.0"
73
73
  # Async runtime for blocking on futures in FFI
@@ -80,7 +80,7 @@ uuid = { version = "1.11", features = ["serde", "v4", "v7"] }
80
80
  workspace_tools = { version = "0.11.0", features = ["full"] }
81
81
 
82
82
  [dev-dependencies]
83
- tasker-core = { package = "tasker-core", path = "../../../../", version = "=0.1.5" }
83
+ tasker-core = { package = "tasker-core", path = "../../../../", version = "=0.1.6" }
84
84
 
85
85
  [features]
86
86
  default = []
@@ -194,21 +194,54 @@ module TaskerCore
194
194
 
195
195
  # Send completion event back to Rust
196
196
  # Called by StepExecutionSubscriber after handler execution
197
+ #
198
+ # serde_magnus requires string keys for deserialization into Rust structs.
199
+ # Ruby hashes built with symbol literals (e.g. { success: true }) must be
200
+ # deep-stringified before crossing the FFI boundary.
201
+ #
202
+ # If the FFI call still fails (e.g. unexpected nil, type mismatch), the
203
+ # fallback path constructs a guaranteed-safe failure result so the step is
204
+ # marked as permanently failed rather than silently lost.
197
205
  def publish_step_completion(completion_data)
198
206
  return unless active?
199
207
 
200
- logger.debug "Sending step completion to Rust: #{completion_data[:event_id]}"
208
+ event_id_str = completion_data[:event_id].to_s
209
+ logger.debug "Sending step completion to Rust: #{event_id_str}"
201
210
 
202
211
  # Validate completion data
203
212
  validate_completion!(completion_data)
204
213
 
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'
214
+ # serde_magnus expects string keys convert symbol keys at the FFI boundary
215
+ stringified = completion_data.deep_stringify_keys
216
+
217
+ begin
218
+ TaskerCore::FFI.complete_step_event(event_id_str, stringified)
219
+ # Only publish monitoring event after successful primary FFI
220
+ publish('step.completion.sent', completion_data)
221
+ logger.debug 'Step completion sent to Rust'
222
+ rescue StandardError => e
223
+ logger.error "FFI serialization failed for event #{event_id_str}: #{e.message}"
224
+ logger.error e.backtrace&.first(5)&.join("\n")
225
+
226
+ # Submit a minimal failure result that is guaranteed to deserialize
227
+ fallback = build_ffi_safe_failure(completion_data, e)
228
+ begin
229
+ TaskerCore::FFI.complete_step_event(event_id_str, fallback)
230
+ logger.warn "FFI fallback failure submitted for event #{event_id_str}"
231
+ begin
232
+ publish('step.completion.sent', fallback)
233
+ rescue StandardError => pub_err
234
+ logger.warn "Monitoring publish failed after fallback: #{pub_err.message}"
235
+ end
236
+ rescue StandardError => fallback_error
237
+ logger.error "FFI fallback also failed for event #{event_id_str}: " \
238
+ "#{fallback_error.message} (original error: #{e.message})"
239
+ raise fallback_error
240
+ end
241
+ end
242
+ rescue ArgumentError
243
+ # Validation errors from validate_completion! should propagate
244
+ raise
212
245
  rescue StandardError => e
213
246
  logger.error "Failed to send step completion: #{e.message}"
214
247
  logger.error e.backtrace.join("\n")
@@ -246,10 +279,13 @@ module TaskerCore
246
279
  # Validate checkpoint data
247
280
  validate_checkpoint_yield!(checkpoint_data)
248
281
 
282
+ # serde_magnus expects string keys — convert symbol keys at the FFI boundary
283
+ stringified = checkpoint_data.deep_stringify_keys
284
+
249
285
  # Send to Rust via FFI (TAS-125)
250
286
  success = TaskerCore::FFI.checkpoint_yield_step_event(
251
287
  checkpoint_data[:event_id].to_s,
252
- checkpoint_data
288
+ stringified
253
289
  )
254
290
 
255
291
  if success
@@ -311,6 +347,34 @@ module TaskerCore
311
347
  completion_data[:completed_at] ||= Time.now.utc.iso8601
312
348
  end
313
349
 
350
+ # Build a minimal StepExecutionResult-shaped hash that is guaranteed to
351
+ # deserialize through serde_magnus. Uses only string keys and primitive
352
+ # values so there is zero chance of a secondary serialization failure.
353
+ def build_ffi_safe_failure(original_data, error)
354
+ {
355
+ 'step_uuid' => original_data[:step_uuid].to_s,
356
+ 'task_uuid' => original_data[:task_uuid].to_s,
357
+ 'success' => false,
358
+ 'result' => {},
359
+ 'status' => 'error',
360
+ 'metadata' => {
361
+ 'execution_time_ms' => 0,
362
+ 'retryable' => false,
363
+ 'completed_at' => Time.now.utc.iso8601,
364
+ 'worker_id' => 'ruby_worker',
365
+ 'custom' => {
366
+ 'ffi_serialization_error' => error.message.to_s[0, 500],
367
+ 'original_success' => original_data[:success].to_s
368
+ }
369
+ },
370
+ 'error' => {
371
+ 'message' => "FFI serialization failed: #{error.message}"[0, 500],
372
+ 'error_type' => 'FFI_SERIALIZATION_ERROR',
373
+ 'retryable' => false
374
+ }
375
+ }
376
+ end
377
+
314
378
  # TAS-125: Validate checkpoint yield data before sending to Rust
315
379
  def validate_checkpoint_yield!(checkpoint_data)
316
380
  required_fields = %i[event_id step_uuid cursor items_processed]
@@ -3,7 +3,7 @@
3
3
  module TaskerCore
4
4
  # Version synchronization with the core Rust crate
5
5
  # This should be kept in sync with the Cargo.toml version
6
- VERSION = '0.1.7'
6
+ VERSION = '0.1.8'
7
7
 
8
8
  def self.version_info
9
9
  {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tasker-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Taylor
@@ -201,7 +201,7 @@ description: |
201
201
  Ruby FFI bindings for tasker-core, providing 10-100x performance improvements
202
202
  for workflow orchestration, dependency resolution, and state management.
203
203
 
204
- This gem enables Rails applications using the Tasker engine to leverage
204
+ This gem enables Ruby applications using the Tasker engine to leverage
205
205
  Rust's performance for computationally intensive orchestration operations
206
206
  while maintaining Ruby's flexibility for business logic.
207
207
  email:
@@ -293,15 +293,14 @@ licenses:
293
293
  metadata:
294
294
  homepage_uri: https://github.com/tasker-systems/tasker-core
295
295
  source_code_uri: https://github.com/tasker-systems/tasker-core/tree/main/workers/ruby
296
- changelog_uri: https://github.com/tasker-systems/tasker-core/blob/main/workers/ruby/CHANGELOG.md
297
- documentation_uri: https://github.com/tasker-systems/tasker-core/blob/main/docs/RUBY.md
296
+ changelog_uri: https://github.com/tasker-systems/tasker-core/blob/main/CHANGELOG.md
297
+ documentation_uri: https://docs.tasker.systems
298
298
  bug_tracker_uri: https://github.com/tasker-systems/tasker-core/issues
299
299
  allowed_push_host: https://rubygems.org
300
300
  rubygems_mfa_required: 'true'
301
301
  post_install_message: "\n\U0001F980 tasker-rb successfully installed!\n\nThis gem
302
302
  provides high-performance Rust-powered workflow orchestration.\n\nDocumentation:
303
- https://github.com/tasker-systems/tasker-core/blob/main/docs/workers/ruby.md\n\nFor
304
- Rails integration, see the tasker-engine gem documentation.\n\n"
303
+ https://docs.tasker.systems\n\n"
305
304
  rdoc_options: []
306
305
  require_paths:
307
306
  - lib