From 1f7dd1443df42d917747ca7f2a442317d62cc827 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Fri, 10 Apr 2026 15:30:42 -0400 Subject: [PATCH 1/3] otel regression test --- .../test/contrib/open_telemetry_test.rb | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/temporalio/test/contrib/open_telemetry_test.rb b/temporalio/test/contrib/open_telemetry_test.rb index 17f8980f..c99c94cc 100644 --- a/temporalio/test/contrib/open_telemetry_test.rb +++ b/temporalio/test/contrib/open_telemetry_test.rb @@ -612,6 +612,43 @@ def test_illegal_calls_on_context ContextCurrentPatch.do_illegal_thing = false end + def test_signal_with_start + exp_root = ExpectedSpan.new(name: 'root') + act_root = trace do |client| + outer_context = OpenTelemetry::Context.current + attach_token = nil + task_queue = "tq-#{SecureRandom.uuid}" + worker = Temporalio::Worker.new( + client:, + task_queue:, + workflows: [TestWorkflow] + ) + worker.run do + attach_token = OpenTelemetry::Context.attach(outer_context) + id = "wf-#{SecureRandom.uuid}" + start_workflow_operation = Temporalio::Client::WithStartWorkflowOperation.new( + TestWorkflow, :wait_on_signal, + id:, task_queue: + ) + handle = client.signal_with_start_workflow( + TestWorkflow.signal, :mark_finished, start_workflow_operation: + ) + + exp_cl_attrs = { 'temporalWorkflowID' => id } + exp_run_attrs = exp_cl_attrs.merge({ 'temporalRunID' => handle.result_run_id }) + exp_sig_start = exp_root.add_child(name: 'SignalWithStartWorkflow:TestWorkflow', attributes: exp_cl_attrs) + exp_sig_start.add_child(name: 'HandleSignal:signal', attributes: exp_run_attrs, links: [exp_sig_start]) + exp_run_wf = exp_sig_start.add_child(name: 'RunWorkflow:TestWorkflow', attributes: exp_run_attrs) + exp_run_wf.add_child(name: 'CompleteWorkflow:TestWorkflow', attributes: exp_run_attrs) + + assert_equal 'workflow-done', handle.result + ensure + OpenTelemetry::Context.detach(attach_token) if attach_token + end + end + assert_equal exp_root.to_s_indented, act_root.to_s_indented + end + def test_otel_context_cleared traced_wf_trace_id = nil traced_act = trace_workflow(:activity_success_return_trace_id) do |handle| From 156ec3f3dbeb1be4d1c783552c80b011f80f46d3 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Fri, 10 Apr 2026 10:56:24 -0400 Subject: [PATCH 2/3] fix: address hidden steep issues --- temporalio/Steepfile | 4 +- temporalio/lib/temporalio/client.rb | 2 +- .../lib/temporalio/contrib/open_telemetry.rb | 14 +++--- temporalio/sig/open_telemetry.rbs | 47 +++++++++++++++++++ .../sig/temporalio/contrib/open_telemetry.rbs | 8 ++++ .../payload_converter/binary_null.rbs | 3 +- .../payload_converter/binary_plain.rbs | 3 +- .../payload_converter/binary_protobuf.rbs | 3 +- .../payload_converter/json_plain.rbs | 10 ++-- .../payload_converter/json_protobuf.rbs | 5 +- temporalio/sig/temporalio/error.rbs | 4 +- temporalio/sig/temporalio/internal/bridge.rbs | 21 ++++++++- .../internal/worker/activity_worker.rbs | 4 +- .../internal/worker/multi_runner.rbs | 5 +- .../testing/activity_environment.rbs | 29 +++++++++++- .../sig/temporalio/worker/poller_behavior.rbs | 24 +++++----- .../sig/temporalio/workflow/definition.rbs | 11 +++++ temporalio/test/api/payload_visitor_test.rb | 3 +- 18 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 temporalio/sig/open_telemetry.rbs diff --git a/temporalio/Steepfile b/temporalio/Steepfile index 9c1ab004..bb118daa 100644 --- a/temporalio/Steepfile +++ b/temporalio/Steepfile @@ -15,7 +15,7 @@ target :lib do signature 'sig' check 'lib' ignore 'lib/temporalio/api', 'lib/temporalio/internal/bridge/api' - library 'uri', 'objspace' + library 'uri', 'objspace', 'etc' configure_code_diagnostics do |hash| hash.update(common_diagnostics) end @@ -24,7 +24,7 @@ end target :test do signature 'sig', 'test/sig' check 'test' - library 'uri', 'objspace' + library 'uri', 'objspace', 'etc' configure_code_diagnostics do |hash| hash.update(common_diagnostics) # Steep cannot infer some things, so we can ignore them in tests diff --git a/temporalio/lib/temporalio/client.rb b/temporalio/lib/temporalio/client.rb index 653468b9..5c8f148e 100644 --- a/temporalio/lib/temporalio/client.rb +++ b/temporalio/lib/temporalio/client.rb @@ -216,7 +216,7 @@ def self._validate_plugins!(plugins) def initialize( connection:, namespace:, - data_converter: DataConverter.default, + data_converter: Converters::DataConverter.default, plugins: [], interceptors: [], logger: Logger.new($stdout, level: Logger::WARN), diff --git a/temporalio/lib/temporalio/contrib/open_telemetry.rb b/temporalio/lib/temporalio/contrib/open_telemetry.rb index 1de3b9a5..9f2e3f8b 100644 --- a/temporalio/lib/temporalio/contrib/open_telemetry.rb +++ b/temporalio/lib/temporalio/contrib/open_telemetry.rb @@ -158,15 +158,15 @@ def start_update_with_start_workflow(input) # @!visibility private def signal_with_start_workflow(input) @root._with_started_span( - name: "SignalWithStartWorkflow:#{input.workflow}", + name: "SignalWithStartWorkflow:#{input.start_workflow_operation.options.workflow}", kind: :client, - attributes: { 'temporalWorkflowID' => input.start_workflow_operation.options.id }, - outbound_input: input + attributes: { 'temporalWorkflowID' => input.start_workflow_operation.options.id } ) do - # Also add to start headers - if input.headers[@header_key] - input.start_workflow_operation.options.headers[@header_key] = input.headers[@header_key] - end + # SignalWithStartWorkflowInput has no top-level headers field and instead uses + # start_workflow_operation.options.headers. + # So tracing context must be injected explicitly instead of relying + # on _with_started_span. + @root._apply_context_to_headers(input.start_workflow_operation.options.headers) super end end diff --git a/temporalio/sig/open_telemetry.rbs b/temporalio/sig/open_telemetry.rbs new file mode 100644 index 00000000..44f4a8f1 --- /dev/null +++ b/temporalio/sig/open_telemetry.rbs @@ -0,0 +1,47 @@ +module OpenTelemetry + module Context + def self.current: -> untyped + def self.attach: (untyped context) -> untyped + def self.detach: (untyped token) -> void + + module Propagation + class CompositeTextMapPropagator + def self.compose_propagators: (Array[untyped] propagators) -> untyped + end + end + end + + module Baggage + module Propagation + class TextMapPropagator + def initialize: -> void + end + end + end + + module Trace + def self.with_span: [T] (untyped span) { -> T } -> T + def self.current_span: (?untyped context) -> untyped + def self.context_with_span: (untyped span) -> untyped + + class Link + def initialize: (untyped context) -> void + end + + module Status + def self.error: (String description) -> untyped + end + + module Propagation + module TraceContext + class TextMapPropagator + def initialize: -> void + end + end + end + + class Span + INVALID: untyped + end + end +end diff --git a/temporalio/sig/temporalio/contrib/open_telemetry.rbs b/temporalio/sig/temporalio/contrib/open_telemetry.rbs index 9cf512b4..f5481a59 100644 --- a/temporalio/sig/temporalio/contrib/open_telemetry.rbs +++ b/temporalio/sig/temporalio/contrib/open_telemetry.rbs @@ -27,6 +27,14 @@ module Temporalio ) { () -> T } -> T def _always_create_workflow_spans: -> bool + class ClientOutbound < Client::Interceptor::Outbound + def initialize: (TracingInterceptor root, Client::Interceptor::Outbound next_interceptor) -> void + end + + class ActivityInbound < Worker::Interceptor::Activity::Inbound + def initialize: (TracingInterceptor root, Worker::Interceptor::Activity::Inbound next_interceptor) -> void + end + class WorkflowInbound < Worker::Interceptor::Workflow::Inbound def initialize: (TracingInterceptor root, Worker::Interceptor::Workflow::Inbound next_interceptor) -> void diff --git a/temporalio/sig/temporalio/converters/payload_converter/binary_null.rbs b/temporalio/sig/temporalio/converters/payload_converter/binary_null.rbs index 86f5176f..231ae25e 100644 --- a/temporalio/sig/temporalio/converters/payload_converter/binary_null.rbs +++ b/temporalio/sig/temporalio/converters/payload_converter/binary_null.rbs @@ -2,7 +2,8 @@ module Temporalio module Converters class PayloadConverter class BinaryNull < Encoding + ENCODING: String end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/converters/payload_converter/binary_plain.rbs b/temporalio/sig/temporalio/converters/payload_converter/binary_plain.rbs index 90f69eac..78b786bc 100644 --- a/temporalio/sig/temporalio/converters/payload_converter/binary_plain.rbs +++ b/temporalio/sig/temporalio/converters/payload_converter/binary_plain.rbs @@ -2,7 +2,8 @@ module Temporalio module Converters class PayloadConverter class BinaryPlain < Encoding + ENCODING: String end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/converters/payload_converter/binary_protobuf.rbs b/temporalio/sig/temporalio/converters/payload_converter/binary_protobuf.rbs index 79f391cd..1170088a 100644 --- a/temporalio/sig/temporalio/converters/payload_converter/binary_protobuf.rbs +++ b/temporalio/sig/temporalio/converters/payload_converter/binary_protobuf.rbs @@ -2,7 +2,8 @@ module Temporalio module Converters class PayloadConverter class BinaryProtobuf < Encoding + ENCODING: String end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/converters/payload_converter/json_plain.rbs b/temporalio/sig/temporalio/converters/payload_converter/json_plain.rbs index 196f28e3..ab2d6cbc 100644 --- a/temporalio/sig/temporalio/converters/payload_converter/json_plain.rbs +++ b/temporalio/sig/temporalio/converters/payload_converter/json_plain.rbs @@ -1,12 +1,14 @@ module Temporalio module Converters class PayloadConverter - class JsonPlain < Encoding + class JSONPlain < Encoding + ENCODING: String + def initialize: ( - ?Hash[Symbol, untyped] parse_options, - ?Hash[Symbol, untyped] generate_options + ?parse_options: Hash[Symbol, untyped], + ?generate_options: Hash[Symbol, untyped] ) -> void end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/converters/payload_converter/json_protobuf.rbs b/temporalio/sig/temporalio/converters/payload_converter/json_protobuf.rbs index 99ad7354..bbfe2d71 100644 --- a/temporalio/sig/temporalio/converters/payload_converter/json_protobuf.rbs +++ b/temporalio/sig/temporalio/converters/payload_converter/json_protobuf.rbs @@ -1,8 +1,9 @@ module Temporalio module Converters class PayloadConverter - class JsonProtobuf < Encoding + class JSONProtobuf < Encoding + ENCODING: String end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/error.rbs b/temporalio/sig/temporalio/error.rbs index c067e436..6ad0cd57 100644 --- a/temporalio/sig/temporalio/error.rbs +++ b/temporalio/sig/temporalio/error.rbs @@ -60,7 +60,7 @@ module Temporalio module Code OK: 0 - CANCELLED: 1 + CANCELED: 1 UNKNOWN: 2 INVALID_ARGUMENT: 3 DEADLINE_EXCEEDED: 4 @@ -81,4 +81,4 @@ module Temporalio end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/internal/bridge.rbs b/temporalio/sig/temporalio/internal/bridge.rbs index 2d95bd07..a8a3ede2 100644 --- a/temporalio/sig/temporalio/internal/bridge.rbs +++ b/temporalio/sig/temporalio/internal/bridge.rbs @@ -4,10 +4,29 @@ module Temporalio def self.assert_fiber_compatibility!: -> void def self.fibers_supported: -> bool + module EnvConfig + def self.load_client_connect_config: ( + String? profile, + String? path, + String? data, + bool disable_file, + bool disable_env, + bool config_file_strict, + Hash[String, String]? override_env_vars + ) -> Hash[Symbol, untyped] + + def self.load_client_config: ( + String? path, + String? data, + bool config_file_strict, + Hash[String, String]? override_env_vars + ) -> Hash[String, Hash[Symbol, untyped]] + end + # Defined in Rust class Error < StandardError end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/internal/worker/activity_worker.rbs b/temporalio/sig/temporalio/internal/worker/activity_worker.rbs index 20bdcea7..71365b90 100644 --- a/temporalio/sig/temporalio/internal/worker/activity_worker.rbs +++ b/temporalio/sig/temporalio/internal/worker/activity_worker.rbs @@ -2,6 +2,8 @@ module Temporalio module Internal module Worker class ActivityWorker + LOG_TASKS: bool + attr_reader worker: Temporalio::Worker attr_reader bridge_worker: Bridge::Worker @@ -59,4 +61,4 @@ module Temporalio end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/internal/worker/multi_runner.rbs b/temporalio/sig/temporalio/internal/worker/multi_runner.rbs index ddfa2cee..78de9b16 100644 --- a/temporalio/sig/temporalio/internal/worker/multi_runner.rbs +++ b/temporalio/sig/temporalio/internal/worker/multi_runner.rbs @@ -116,6 +116,9 @@ module Temporalio def initialize: (error: Exception) -> void end + + class ShutdownSignalReceived < Event + end end class InjectEventForTesting < Temporalio::Error @@ -126,4 +129,4 @@ module Temporalio end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/testing/activity_environment.rbs b/temporalio/sig/temporalio/testing/activity_environment.rbs index bf91368b..3ea3acfb 100644 --- a/temporalio/sig/temporalio/testing/activity_environment.rbs +++ b/temporalio/sig/temporalio/testing/activity_environment.rbs @@ -20,6 +20,33 @@ module Temporalio Activity::Definition | singleton(Activity::Definition) | Activity::Definition::Info activity, *Object? args ) -> untyped + + class Context < Activity::Context + attr_reader info: Activity::Info + attr_reader instance: untyped + attr_reader cancellation: Cancellation + attr_reader worker_shutdown_cancellation: Cancellation + attr_reader payload_converter: Converters::PayloadConverter + attr_reader logger: Logger + + def initialize: ( + info: Activity::Info, + instance: untyped, + on_heartbeat: Proc?, + cancellation: Cancellation, + on_cancellation_details: Proc, + worker_shutdown_cancellation: Cancellation, + payload_converter: Converters::PayloadConverter, + logger: Logger, + metric_meter: Metric::Meter?, + client: Client? + ) -> void + + def heartbeat: (*untyped details) -> untyped + def metric_meter: -> Metric::Meter + def client: -> Client + def cancellation_details: -> Activity::CancellationDetails? + end end end -end \ No newline at end of file +end diff --git a/temporalio/sig/temporalio/worker/poller_behavior.rbs b/temporalio/sig/temporalio/worker/poller_behavior.rbs index b46ef35f..8d0761f4 100644 --- a/temporalio/sig/temporalio/worker/poller_behavior.rbs +++ b/temporalio/sig/temporalio/worker/poller_behavior.rbs @@ -2,22 +2,22 @@ module Temporalio class Worker class PollerBehavior def _to_bridge_options: -> untyped - end - class SimpleMaximum < PollerBehavior - attr_reader maximum: Integer + class SimpleMaximum < PollerBehavior + attr_reader maximum: Integer - def initialize: (Integer) -> void - def _to_bridge_options: -> Internal::Bridge::Worker::PollerBehaviorSimpleMaximum - end + def initialize: (Integer) -> void + def _to_bridge_options: -> Internal::Bridge::Worker::PollerBehaviorSimpleMaximum + end - class Autoscaling < PollerBehavior - attr_reader minimum: Integer - attr_reader maximum: Integer - attr_reader initial: Integer + class Autoscaling < PollerBehavior + attr_reader minimum: Integer + attr_reader maximum: Integer + attr_reader initial: Integer - def initialize: (?minimum: Integer, ?maximum: Integer, ?initial: Integer) -> void - def _to_bridge_options: -> Internal::Bridge::Worker::PollerBehaviorAutoscaling + def initialize: (?minimum: Integer, ?maximum: Integer, ?initial: Integer) -> void + def _to_bridge_options: -> Internal::Bridge::Worker::PollerBehaviorAutoscaling + end end end end diff --git a/temporalio/sig/temporalio/workflow/definition.rbs b/temporalio/sig/temporalio/workflow/definition.rbs index 09268c4e..47737b88 100644 --- a/temporalio/sig/temporalio/workflow/definition.rbs +++ b/temporalio/sig/temporalio/workflow/definition.rbs @@ -165,6 +165,17 @@ module Temporalio def _with_validator_to_invoke: (Symbol | Proc | nil validator_to_invoke) -> Update end + + end + + class DefinitionOptions + attr_reader failure_exception_types: Array[singleton(Exception)]? + attr_reader versioning_behavior: VersioningBehavior::enum? + + def initialize: ( + ?failure_exception_types: Array[singleton(Exception)]?, + ?versioning_behavior: VersioningBehavior::enum? + ) -> void end end end diff --git a/temporalio/test/api/payload_visitor_test.rb b/temporalio/test/api/payload_visitor_test.rb index bc5114fa..8e363d1c 100644 --- a/temporalio/test/api/payload_visitor_test.rb +++ b/temporalio/test/api/payload_visitor_test.rb @@ -69,7 +69,7 @@ def test_basics end # Basic check including search attributes - visitor = Temporalio::Api::PayloadVisitor.new(&mutator) + visitor = Temporalio::Api::PayloadVisitor.new(&mutator) # steep:ignore BlockTypeMismatch mutated_act = act.class.decode(act.class.encode(act)) mutated_succ = succ.class.decode(succ.class.encode(succ)) visitor.run(mutated_act) @@ -84,6 +84,7 @@ def test_basics assert_equal 'samap-single', upsert_sa.indexed_fields['sakey'].data # Skip search attributes + # steep:ignore BlockTypeMismatch visitor = Temporalio::Api::PayloadVisitor.new(skip_search_attributes: true, &mutator) mutated_act = act.class.decode(act.class.encode(act)) mutated_succ = succ.class.decode(succ.class.encode(succ)) From 5ca7b4bad87482b5a74836110d320eef4d573480 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Sat, 11 Apr 2026 11:30:22 -0400 Subject: [PATCH 3/3] test: place steep ignore on block pass --- temporalio/test/api/payload_visitor_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/temporalio/test/api/payload_visitor_test.rb b/temporalio/test/api/payload_visitor_test.rb index 8e363d1c..7e34da24 100644 --- a/temporalio/test/api/payload_visitor_test.rb +++ b/temporalio/test/api/payload_visitor_test.rb @@ -84,8 +84,10 @@ def test_basics assert_equal 'samap-single', upsert_sa.indexed_fields['sakey'].data # Skip search attributes - # steep:ignore BlockTypeMismatch - visitor = Temporalio::Api::PayloadVisitor.new(skip_search_attributes: true, &mutator) + visitor = Temporalio::Api::PayloadVisitor.new( + skip_search_attributes: true, + &mutator # steep:ignore BlockTypeMismatch + ) mutated_act = act.class.decode(act.class.encode(act)) mutated_succ = succ.class.decode(succ.class.encode(succ)) visitor.run(mutated_act)