From 23a221c1bb1e3d4cded4c42fae042401306843f1 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 18 Mar 2026 05:28:17 -0700 Subject: [PATCH 1/5] chore: add Steep type checking infrastructure Add RBS/Steep type checking tooling: - Add steep gem to development dependencies - Create Steepfile with lenient diagnostics for lib/ - Create rbs_collection.yaml for stdlib type dependencies - Add Steep::RakeTask to Rakefile - Add .gem_rbs_collection/ to .gitignore Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jose Colella --- .gitignore | 3 + Gemfile | 1 + Gemfile.lock | 59 ++++++++ Rakefile | 7 + Steepfile | 7 + rbs_collection.lock.yaml | 292 +++++++++++++++++++++++++++++++++++++++ rbs_collection.yaml | 14 ++ 7 files changed, 383 insertions(+) create mode 100644 Steepfile create mode 100644 rbs_collection.lock.yaml create mode 100644 rbs_collection.yaml diff --git a/.gitignore b/.gitignore index a1815f7a..7c90e525 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Gemfile.lock # Claude plans docs/plans/ + +# RBS collection cache +.gem_rbs_collection/ diff --git a/Gemfile b/Gemfile index 96bf22b5..b3b15fba 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ group :development, :test do gem "standard-performance" gem "simplecov", "~> 0.22.0" gem "simplecov-cobertura", "~> 3.0" + gem "steep", "~> 1.9" gem "timecop", "~> 0.9.10" end diff --git a/Gemfile.lock b/Gemfile.lock index 76bbdfdf..3092c189 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,10 +6,26 @@ PATH GEM remote: https://rubygems.org/ specs: + activesupport (8.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) ast (2.4.3) base64 (0.3.0) bigdecimal (4.0.1) builder (3.3.0) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + csv (3.3.5) cucumber (10.2.0) base64 (~> 0.2) builder (~> 3.2) @@ -41,8 +57,12 @@ GEM reline (>= 0.3.8) diff-lcs (1.6.2) docile (1.4.1) + drb (2.2.3) erb (6.0.2) ffi (1.17.3) + fileutils (1.8.0) + i18n (1.14.8) + concurrent-ruby (~> 1.0) io-console (0.8.2) irb (1.17.0) pp (>= 0.6.0) @@ -52,11 +72,19 @@ GEM json (2.18.1) language_server-protocol (3.17.0.5) lint_roller (1.1.0) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) markly (0.15.2) memoist3 (1.0.0) mini_mime (1.1.5) + minitest (6.0.2) + drb (~> 2.0) + prism (~> 1.5) multi_test (1.1.0) + mutex_m (0.3.0) parallel (1.27.0) parser (3.3.10.2) ast (~> 2.4.1) @@ -71,6 +99,12 @@ GEM racc (1.8.1) rainbow (3.1.1) rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.10.3) + logger + tsort rdoc (7.2.0) erb psych (>= 4.0.0) @@ -111,6 +145,7 @@ GEM rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) + securerandom (0.4.1) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -132,15 +167,38 @@ GEM standard-performance (1.9.0) lint_roller (~> 1.1) rubocop-performance (~> 1.26.0) + steep (1.10.0) + activesupport (>= 5.1) + concurrent-ruby (>= 1.1.10) + csv (>= 3.0.9) + fileutils (>= 1.1.0) + json (>= 2.1.0) + language_server-protocol (>= 3.17.0.4, < 4.0) + listen (~> 3.0) + logger (>= 1.3.0) + mutex_m (>= 0.3.0) + parser (>= 3.1) + rainbow (>= 2.2.2, < 4.0) + rbs (~> 3.9) + securerandom (>= 0.1) + strscan (>= 1.0.0) + terminal-table (>= 2, < 5) + uri (>= 0.12.0) stringio (3.2.0) + strscan (3.1.7) sys-uname (1.5.0) ffi (~> 1.1) memoist3 (~> 1.0.0) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) timecop (0.9.10) tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) + uri (1.1.1) PLATFORMS arm64-darwin-21 @@ -167,6 +225,7 @@ DEPENDENCIES simplecov-cobertura (~> 3.0) standard standard-performance + steep (~> 1.9) timecop (~> 0.9.10) BUNDLED WITH diff --git a/Rakefile b/Rakefile index ddeff3cd..4cc66c04 100644 --- a/Rakefile +++ b/Rakefile @@ -19,4 +19,11 @@ task :cucumber do sh "bundle exec cucumber" end +begin + require "steep/rake_task" + Steep::RakeTask.new +rescue LoadError + # Steep not available +end + task default: %i[spec standard] diff --git a/Steepfile b/Steepfile new file mode 100644 index 00000000..e76dee3d --- /dev/null +++ b/Steepfile @@ -0,0 +1,7 @@ +D = Steep::Diagnostic + +target :lib do + signature "sig" + check "lib" + configure_code_diagnostics(D::Ruby.lenient) +end diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml new file mode 100644 index 00000000..983eb2d0 --- /dev/null +++ b/rbs_collection.lock.yaml @@ -0,0 +1,292 @@ +--- +path: ".gem_rbs_collection" +gems: +- name: activesupport + version: '7.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: ast + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: base64 + version: 0.3.0 + source: + type: rubygems +- name: bigdecimal + version: '4.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: concurrent-ruby + version: '1.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: connection_pool + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: csv + version: '3.3' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: date + version: '0' + source: + type: stdlib +- name: dbm + version: '0' + source: + type: stdlib +- name: delegate + version: '0' + source: + type: stdlib +- name: diff-lcs + version: '1.5' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: digest + version: '0' + source: + type: stdlib +- name: erb + version: '0' + source: + type: stdlib +- name: ffi + version: 1.17.3 + source: + type: rubygems +- name: fileutils + version: '0' + source: + type: stdlib +- name: forwardable + version: '0' + source: + type: stdlib +- name: i18n + version: '1.10' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: io-console + version: '0' + source: + type: stdlib +- name: json + version: '0' + source: + type: stdlib +- name: listen + version: '3.9' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: logger + version: '0' + source: + type: stdlib +- name: mini_mime + version: '0.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: minitest + version: '5.25' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: monitor + version: '0' + source: + type: stdlib +- name: mutex_m + version: 0.3.0 + source: + type: rubygems +- name: openssl + version: '0' + source: + type: stdlib +- name: optparse + version: '0' + source: + type: stdlib +- name: parallel + version: '1.20' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: parser + version: '3.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: pp + version: '0' + source: + type: stdlib +- name: prettyprint + version: '0' + source: + type: stdlib +- name: prism + version: 1.9.0 + source: + type: rubygems +- name: pstore + version: '0' + source: + type: stdlib +- name: psych + version: '0' + source: + type: stdlib +- name: rainbow + version: '3.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rake + version: '13.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rbs + version: 3.10.3 + source: + type: rubygems +- name: rdoc + version: '0' + source: + type: stdlib +- name: regexp_parser + version: '2.8' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rubocop + version: '1.57' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rubocop-ast + version: '1.46' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: securerandom + version: '0' + source: + type: stdlib +- name: simplecov + version: '0.22' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: singleton + version: '0' + source: + type: stdlib +- name: socket + version: '0' + source: + type: stdlib +- name: stringio + version: '0' + source: + type: stdlib +- name: strscan + version: '0' + source: + type: stdlib +- name: time + version: '0' + source: + type: stdlib +- name: timeout + version: '0' + source: + type: stdlib +- name: tsort + version: '0' + source: + type: stdlib +- name: tzinfo + version: '2.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 9bf2eebb1c54b5d6f23f2acb65d4c36f195b4783 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: uri + version: '0' + source: + type: stdlib +gemfile_lock_path: Gemfile.lock diff --git a/rbs_collection.yaml b/rbs_collection.yaml new file mode 100644 index 00000000..dc98c8c5 --- /dev/null +++ b/rbs_collection.yaml @@ -0,0 +1,14 @@ +sources: + - type: git + name: ruby/gem_rbs_collection + remote: https://github.com/ruby/gem_rbs_collection.git + revision: main + repo_dir: gems + +path: .gem_rbs_collection + +gems: + - name: singleton + - name: forwardable + - name: delegate + - name: timeout From dc1cc3a653741d86109af4d8d659b9a738b7f307 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 18 Mar 2026 05:28:30 -0700 Subject: [PATCH 2/5] feat: add RBS type signatures for all lib/ modules Add 31 RBS signature files under sig/ mirroring the lib/ structure: - Provider duck-type contract via _Provider interface - Hook lifecycle contract via _Hook interface - TransactionContextPropagator interface - All 12 dynamically generated Client methods declared explicitly - SDK module method_missing delegates declared as explicit self.* methods - Struct types (ResolutionDetails, EvaluationDetails, ClientMetadata, etc.) - Constants modules (ErrorCode, Reason, ProviderEvent, ProviderState) - Core classes (Configuration, API, EventDispatcher, ProviderStateRegistry) - Hints declared as plain class (DelegateClass unsupported in RBS) Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jose Colella --- sig/open_feature/sdk.rbs | 22 +++++++++ sig/open_feature/sdk/api.rbs | 35 +++++++++++++ sig/open_feature/sdk/client.rbs | 49 +++++++++++++++++++ sig/open_feature/sdk/client_metadata.rbs | 11 +++++ sig/open_feature/sdk/configuration.rbs | 43 ++++++++++++++++ sig/open_feature/sdk/evaluation_context.rbs | 19 +++++++ .../sdk/evaluation_context_builder.rbs | 7 +++ sig/open_feature/sdk/evaluation_details.rbs | 19 +++++++ sig/open_feature/sdk/event_dispatcher.rbs | 24 +++++++++ sig/open_feature/sdk/hooks/hints.rbs | 30 ++++++++++++ sig/open_feature/sdk/hooks/hook.rbs | 19 +++++++ sig/open_feature/sdk/hooks/hook_context.rbs | 18 +++++++ sig/open_feature/sdk/hooks/hook_executor.rbs | 20 ++++++++ sig/open_feature/sdk/hooks/logging_hook.rbs | 25 ++++++++++ sig/open_feature/sdk/provider.rbs | 24 +++++++++ sig/open_feature/sdk/provider/error_code.rbs | 16 ++++++ .../sdk/provider/event_emitter.rbs | 16 ++++++ .../sdk/provider/in_memory_provider.rbs | 35 +++++++++++++ .../sdk/provider/no_op_provider.rbs | 25 ++++++++++ .../sdk/provider/provider_metadata.rbs | 11 +++++ sig/open_feature/sdk/provider/reason.rbs | 17 +++++++ .../sdk/provider/resolution_details.rbs | 19 +++++++ sig/open_feature/sdk/provider_event.rbs | 12 +++++ .../sdk/provider_initialization_error.rbs | 11 +++++ sig/open_feature/sdk/provider_state.rbs | 13 +++++ .../sdk/provider_state_registry.rbs | 22 +++++++++ sig/open_feature/sdk/telemetry.rbs | 29 +++++++++++ ...d_local_transaction_context_propagator.rbs | 12 +++++ .../sdk/tracking_event_details.rbs | 10 ++++ .../sdk/transaction_context_propagator.rbs | 11 +++++ sig/open_feature/sdk/version.rbs | 5 ++ 31 files changed, 629 insertions(+) create mode 100644 sig/open_feature/sdk.rbs create mode 100644 sig/open_feature/sdk/api.rbs create mode 100644 sig/open_feature/sdk/client.rbs create mode 100644 sig/open_feature/sdk/client_metadata.rbs create mode 100644 sig/open_feature/sdk/configuration.rbs create mode 100644 sig/open_feature/sdk/evaluation_context.rbs create mode 100644 sig/open_feature/sdk/evaluation_context_builder.rbs create mode 100644 sig/open_feature/sdk/evaluation_details.rbs create mode 100644 sig/open_feature/sdk/event_dispatcher.rbs create mode 100644 sig/open_feature/sdk/hooks/hints.rbs create mode 100644 sig/open_feature/sdk/hooks/hook.rbs create mode 100644 sig/open_feature/sdk/hooks/hook_context.rbs create mode 100644 sig/open_feature/sdk/hooks/hook_executor.rbs create mode 100644 sig/open_feature/sdk/hooks/logging_hook.rbs create mode 100644 sig/open_feature/sdk/provider.rbs create mode 100644 sig/open_feature/sdk/provider/error_code.rbs create mode 100644 sig/open_feature/sdk/provider/event_emitter.rbs create mode 100644 sig/open_feature/sdk/provider/in_memory_provider.rbs create mode 100644 sig/open_feature/sdk/provider/no_op_provider.rbs create mode 100644 sig/open_feature/sdk/provider/provider_metadata.rbs create mode 100644 sig/open_feature/sdk/provider/reason.rbs create mode 100644 sig/open_feature/sdk/provider/resolution_details.rbs create mode 100644 sig/open_feature/sdk/provider_event.rbs create mode 100644 sig/open_feature/sdk/provider_initialization_error.rbs create mode 100644 sig/open_feature/sdk/provider_state.rbs create mode 100644 sig/open_feature/sdk/provider_state_registry.rbs create mode 100644 sig/open_feature/sdk/telemetry.rbs create mode 100644 sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs create mode 100644 sig/open_feature/sdk/tracking_event_details.rbs create mode 100644 sig/open_feature/sdk/transaction_context_propagator.rbs create mode 100644 sig/open_feature/sdk/version.rbs diff --git a/sig/open_feature/sdk.rbs b/sig/open_feature/sdk.rbs new file mode 100644 index 00000000..00762a19 --- /dev/null +++ b/sig/open_feature/sdk.rbs @@ -0,0 +1,22 @@ +module OpenFeature + module SDK + # Delegated to API.instance via method_missing + def self.configure: () { (Configuration) -> void } -> void + def self.build_client: (?domain: String?, ?evaluation_context: EvaluationContext?) -> Client + def self.configuration: () -> Configuration + def self.provider: (?domain: String?) -> untyped? + def self.set_provider: (untyped provider, ?domain: String?) -> void + def self.set_provider_and_wait: (untyped provider, ?domain: String?) -> void + def self.hooks: () -> Array[untyped] + def self.add_hooks: (*Array[untyped] new_hooks) -> void + def self.evaluation_context: () -> EvaluationContext? + def self.add_handler: (String event_type, _ToProc handler) -> void + def self.remove_handler: (String event_type, _ToProc handler) -> void + def self.logger: () -> untyped? + def self.logger=: (untyped? new_logger) -> void + def self.set_transaction_context_propagator: (_TransactionContextPropagator propagator) -> void + def self.set_transaction_context: (EvaluationContext evaluation_context) -> void + def self.shutdown: () -> void + def self.provider_metadata: (?domain: String?) -> Provider::ProviderMetadata? + end +end diff --git a/sig/open_feature/sdk/api.rbs b/sig/open_feature/sdk/api.rbs new file mode 100644 index 00000000..62fac8f9 --- /dev/null +++ b/sig/open_feature/sdk/api.rbs @@ -0,0 +1,35 @@ +module OpenFeature + module SDK + class API + include Singleton + extend Forwardable + + def configuration: () -> Configuration + + def configure: () { (Configuration) -> void } -> void + + def build_client: (?domain: String?, ?evaluation_context: EvaluationContext?) -> Client + + def provider_metadata: (?domain: String?) -> Provider::ProviderMetadata? + + # Delegated from Configuration + def provider: (?domain: String?) -> untyped? + def set_provider: (untyped provider, ?domain: String?) -> void + def set_provider_and_wait: (untyped provider, ?domain: String?) -> void + def hooks: () -> Array[untyped] + def add_hooks: (*Array[untyped] new_hooks) -> void + def evaluation_context: () -> EvaluationContext? + + def add_handler: (String event_type, _ToProc handler) -> void + def remove_handler: (String event_type, _ToProc handler) -> void + + def logger: () -> untyped? + def logger=: (untyped? new_logger) -> void + + def set_transaction_context_propagator: (_TransactionContextPropagator propagator) -> void + def set_transaction_context: (EvaluationContext evaluation_context) -> void + + def shutdown: () -> void + end + end +end diff --git a/sig/open_feature/sdk/client.rbs b/sig/open_feature/sdk/client.rbs new file mode 100644 index 00000000..fd5daaf9 --- /dev/null +++ b/sig/open_feature/sdk/client.rbs @@ -0,0 +1,49 @@ +module OpenFeature + module SDK + class Client + TYPE_CLASS_MAP: Hash[Symbol, Array[Class]] + RESULT_TYPE: Array[Symbol] + SUFFIXES: Array[Symbol] + EMPTY_HINTS: Hooks::Hints + + attr_reader metadata: ClientMetadata + attr_reader evaluation_context: EvaluationContext? + attr_accessor hooks: Array[untyped] + + def initialize: (provider: untyped, ?domain: String?, ?evaluation_context: EvaluationContext?) -> void + + def add_hooks: (*Array[untyped] new_hooks) -> void + + def provider_status: () -> String + + def add_handler: (String event_type, ?_ToProc? handler) ?{ (Hash[Symbol, untyped]) -> void } -> void + def remove_handler: (String event_type, ?_ToProc? handler) ?{ (Hash[Symbol, untyped]) -> void } -> void + + def track: (String tracking_event_name, ?evaluation_context: EvaluationContext?, ?tracking_event_details: TrackingEventDetails?) -> void + + # Dynamically generated fetch_*_value methods (6 types) + def fetch_boolean_value: (flag_key: String, default_value: bool, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> bool + def fetch_string_value: (flag_key: String, default_value: String, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> String + def fetch_number_value: (flag_key: String, default_value: Numeric, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> Numeric + def fetch_integer_value: (flag_key: String, default_value: Integer, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> Integer + def fetch_float_value: (flag_key: String, default_value: Float, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> Float + def fetch_object_value: (flag_key: String, default_value: Array[untyped] | Hash[untyped, untyped], ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> (Array[untyped] | Hash[untyped, untyped]) + + # Dynamically generated fetch_*_details methods (6 types) + def fetch_boolean_details: (flag_key: String, default_value: bool, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + def fetch_string_details: (flag_key: String, default_value: String, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + def fetch_number_details: (flag_key: String, default_value: Numeric, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + def fetch_integer_details: (flag_key: String, default_value: Integer, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + def fetch_float_details: (flag_key: String, default_value: Float, ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + def fetch_object_details: (flag_key: String, default_value: Array[untyped] | Hash[untyped, untyped], ?evaluation_context: EvaluationContext?, ?hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails + + private + + def fetch_details: (type: Symbol, flag_key: String, default_value: untyped, ?evaluation_context: EvaluationContext?, ?invocation_hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails? + def evaluate_flag: (type: Symbol, flag_key: String, default_value: untyped, evaluation_context: EvaluationContext?) -> EvaluationDetails + def short_circuit_error_code: (String state) -> String? + def build_evaluation_context: (EvaluationContext? invocation_context) -> EvaluationContext? + def validate_default_value_type: (Symbol type, untyped default_value) -> void + end + end +end diff --git a/sig/open_feature/sdk/client_metadata.rbs b/sig/open_feature/sdk/client_metadata.rbs new file mode 100644 index 00000000..6fb9ca50 --- /dev/null +++ b/sig/open_feature/sdk/client_metadata.rbs @@ -0,0 +1,11 @@ +module OpenFeature + module SDK + class ClientMetadata < Struct[untyped] + attr_accessor domain: String? + + def initialize: (?domain: String?) -> void + + alias name domain + end + end +end diff --git a/sig/open_feature/sdk/configuration.rbs b/sig/open_feature/sdk/configuration.rbs new file mode 100644 index 00000000..05a2f5ad --- /dev/null +++ b/sig/open_feature/sdk/configuration.rbs @@ -0,0 +1,43 @@ +module OpenFeature + module SDK + class Configuration + extend Forwardable + + attr_accessor evaluation_context: EvaluationContext? + attr_accessor hooks: Array[untyped] + attr_accessor transaction_context_propagator: _TransactionContextPropagator? + attr_reader logger: untyped? + + def initialize: () -> void + + def add_hooks: (*Array[untyped] new_hooks) -> void + def provider: (?domain: String?) -> untyped? + def logger=: (untyped? new_logger) -> void + + def add_handler: (String event_type, _ToProc handler) -> void + def remove_handler: (String event_type, _ToProc handler) -> void + + def add_client_handler: (Client client, String event_type, _ToProc handler) -> void + def remove_client_handler: (Client client, String event_type, _ToProc handler) -> void + + def set_provider: (untyped provider, ?domain: String?) -> void + def set_provider_and_wait: (untyped provider, ?domain: String?) -> void + + def provider_state: (untyped provider) -> String + def provider_tracked?: (untyped provider) -> bool + + def shutdown: () -> void + + private + + def reset: () -> void + def set_provider_internal: (untyped provider, domain: String?, wait_for_init: bool) -> void + def init_provider: (untyped provider, EvaluationContext? context, ?raise_on_error: bool) -> void + def dispatch_provider_event: (untyped provider, String event_type, ?Hash[Symbol, untyped] details) -> void + def extract_provider_name: (untyped provider) -> String + def run_handlers_for_provider: (untyped provider, String event_type, Hash[Symbol, untyped] event_details) -> void + def run_immediate_handler: (String event_type, _ToProc handler, Client? client) -> void + def build_event_details: (untyped provider) -> Hash[Symbol, untyped] + end + end +end diff --git a/sig/open_feature/sdk/evaluation_context.rbs b/sig/open_feature/sdk/evaluation_context.rbs new file mode 100644 index 00000000..919fc1ff --- /dev/null +++ b/sig/open_feature/sdk/evaluation_context.rbs @@ -0,0 +1,19 @@ +module OpenFeature + module SDK + class EvaluationContext + TARGETING_KEY: String + + attr_reader fields: Hash[String, untyped] + + def initialize: (**untyped fields) -> void + + def targeting_key: () -> untyped + + def field: (String key) -> untyped + + def merge: (EvaluationContext overriding_context) -> EvaluationContext + + def ==: (untyped other) -> bool + end + end +end diff --git a/sig/open_feature/sdk/evaluation_context_builder.rbs b/sig/open_feature/sdk/evaluation_context_builder.rbs new file mode 100644 index 00000000..b55bb8d8 --- /dev/null +++ b/sig/open_feature/sdk/evaluation_context_builder.rbs @@ -0,0 +1,7 @@ +module OpenFeature + module SDK + class EvaluationContextBuilder + def call: (api_context: EvaluationContext?, client_context: EvaluationContext?, invocation_context: EvaluationContext?, ?transaction_context: EvaluationContext?) -> EvaluationContext? + end + end +end diff --git a/sig/open_feature/sdk/evaluation_details.rbs b/sig/open_feature/sdk/evaluation_details.rbs new file mode 100644 index 00000000..01514138 --- /dev/null +++ b/sig/open_feature/sdk/evaluation_details.rbs @@ -0,0 +1,19 @@ +module OpenFeature + module SDK + class EvaluationDetails < Struct[untyped] + extend Forwardable + + attr_accessor flag_key: String + attr_accessor resolution_details: Provider::ResolutionDetails + + def initialize: (?flag_key: String, ?resolution_details: Provider::ResolutionDetails) -> void + + def value: () -> untyped + def reason: () -> String? + def variant: () -> String? + def error_code: () -> String? + def error_message: () -> String? + def flag_metadata: () -> Hash[String, untyped] + end + end +end diff --git a/sig/open_feature/sdk/event_dispatcher.rbs b/sig/open_feature/sdk/event_dispatcher.rbs new file mode 100644 index 00000000..0bd3d5aa --- /dev/null +++ b/sig/open_feature/sdk/event_dispatcher.rbs @@ -0,0 +1,24 @@ +module OpenFeature + module SDK + class EventDispatcher + attr_writer logger: untyped? + + def initialize: (?untyped? logger) -> void + + def add_handler: (String event_type, _ToProc handler) -> void + def remove_handler: (String event_type, _ToProc handler) -> void + def remove_all_handlers: (String event_type) -> void + def trigger_event: (String event_type, ?Hash[Symbol, untyped] event_details) -> void + def handler_count: (String event_type) -> Integer + def clear_all_handlers: () -> void + + private + + def valid_event?: (String event_type) -> bool + end + + interface _ToProc + def call: (untyped) -> untyped + end + end +end diff --git a/sig/open_feature/sdk/hooks/hints.rbs b/sig/open_feature/sdk/hooks/hints.rbs new file mode 100644 index 00000000..c775ba04 --- /dev/null +++ b/sig/open_feature/sdk/hooks/hints.rbs @@ -0,0 +1,30 @@ +module OpenFeature + module SDK + module Hooks + class Hints + ALLOWED_TYPES: Array[Class] + + def initialize: (?Hash[String | Symbol, untyped] hash) -> void + + def []: (String | Symbol key) -> untyped + def fetch: (String | Symbol key, ?untyped default) -> untyped + | (String | Symbol key) { (String | Symbol) -> untyped } -> untyped + def key?: (String | Symbol key) -> bool + def each: () { (String | Symbol, untyped) -> void } -> Hash[String | Symbol, untyped] + | () -> Enumerator[[String | Symbol, untyped], Hash[String | Symbol, untyped]] + def size: () -> Integer + def empty?: () -> bool + def keys: () -> Array[String | Symbol] + def values: () -> Array[untyped] + def freeze: () -> self + def frozen?: () -> bool + def to_hash: () -> Hash[String | Symbol, untyped] + + private + + def assert_allowed_key: (untyped key) -> void + def assert_allowed_value: (untyped value) -> void + end + end + end +end diff --git a/sig/open_feature/sdk/hooks/hook.rbs b/sig/open_feature/sdk/hooks/hook.rbs new file mode 100644 index 00000000..db2251d0 --- /dev/null +++ b/sig/open_feature/sdk/hooks/hook.rbs @@ -0,0 +1,19 @@ +module OpenFeature + module SDK + module Hooks + interface _Hook + def before: (hook_context: HookContext, hints: Hints) -> EvaluationContext? + def after: (hook_context: HookContext, evaluation_details: EvaluationDetails, hints: Hints) -> void + def error: (hook_context: HookContext, exception: Exception, hints: Hints) -> void + def finally: (hook_context: HookContext, evaluation_details: EvaluationDetails?, hints: Hints) -> void + end + + module Hook + def before: (hook_context: HookContext, hints: Hints) -> EvaluationContext? + def after: (hook_context: HookContext, evaluation_details: EvaluationDetails, hints: Hints) -> nil + def error: (hook_context: HookContext, exception: Exception, hints: Hints) -> nil + def finally: (hook_context: HookContext, evaluation_details: EvaluationDetails?, hints: Hints) -> nil + end + end + end +end diff --git a/sig/open_feature/sdk/hooks/hook_context.rbs b/sig/open_feature/sdk/hooks/hook_context.rbs new file mode 100644 index 00000000..21325382 --- /dev/null +++ b/sig/open_feature/sdk/hooks/hook_context.rbs @@ -0,0 +1,18 @@ +module OpenFeature + module SDK + module Hooks + class HookContext + attr_reader flag_key: String + attr_reader flag_value_type: Symbol + attr_reader default_value: untyped + attr_reader client_metadata: ClientMetadata? + attr_reader provider_metadata: Provider::ProviderMetadata? + attr_accessor evaluation_context: EvaluationContext? + + def initialize: (flag_key: String, flag_value_type: Symbol, default_value: untyped, evaluation_context: EvaluationContext?, ?client_metadata: ClientMetadata?, ?provider_metadata: Provider::ProviderMetadata?) -> void + + def hook_data_for: (untyped hook) -> Hash[untyped, untyped] + end + end + end +end diff --git a/sig/open_feature/sdk/hooks/hook_executor.rbs b/sig/open_feature/sdk/hooks/hook_executor.rbs new file mode 100644 index 00000000..19396cf1 --- /dev/null +++ b/sig/open_feature/sdk/hooks/hook_executor.rbs @@ -0,0 +1,20 @@ +module OpenFeature + module SDK + module Hooks + class HookExecutor + def initialize: (?logger: untyped?) -> void + + def run_short_circuit: (ordered_hooks: Array[untyped], hook_context: HookContext, hints: Hints, evaluation_details: EvaluationDetails) -> void + + def execute: (ordered_hooks: Array[untyped], hook_context: HookContext, hints: Hints) { (HookContext) -> EvaluationDetails } -> EvaluationDetails? + + private + + def run_before_hooks: (Array[untyped] hooks, HookContext hook_context, Hints hints) -> void + def run_after_hooks: (Array[untyped] hooks, HookContext hook_context, EvaluationDetails evaluation_details, Hints hints) -> void + def run_error_hooks: (Array[untyped] hooks, HookContext hook_context, Exception exception, Hints hints) -> void + def run_finally_hooks: (Array[untyped] hooks, HookContext hook_context, EvaluationDetails? evaluation_details, Hints hints) -> void + end + end + end +end diff --git a/sig/open_feature/sdk/hooks/logging_hook.rbs b/sig/open_feature/sdk/hooks/logging_hook.rbs new file mode 100644 index 00000000..e7a9e685 --- /dev/null +++ b/sig/open_feature/sdk/hooks/logging_hook.rbs @@ -0,0 +1,25 @@ +module OpenFeature + module SDK + module Hooks + class LoggingHook + include Hook + + def initialize: (?logger: untyped?, ?include_evaluation_context: bool) -> void + + def before: (hook_context: HookContext, hints: Hints) -> nil + def after: (hook_context: HookContext, evaluation_details: EvaluationDetails, hints: Hints) -> nil + def error: (hook_context: HookContext, exception: Exception, hints: Hints) -> nil + + private + + def logger: () -> untyped? + def build_before_message: (HookContext hook_context) -> String + def build_after_message: (HookContext hook_context, EvaluationDetails evaluation_details) -> String + def build_error_message: (HookContext hook_context, Exception exception) -> String + def base_parts: (String stage, HookContext hook_context) -> Array[String] + def format_context: (EvaluationContext? evaluation_context) -> String + def sanitize: (untyped value) -> String + end + end + end +end diff --git a/sig/open_feature/sdk/provider.rbs b/sig/open_feature/sdk/provider.rbs new file mode 100644 index 00000000..008d6de8 --- /dev/null +++ b/sig/open_feature/sdk/provider.rbs @@ -0,0 +1,24 @@ +module OpenFeature + module SDK + module Provider + interface _Provider + def fetch_boolean_value: (flag_key: String, default_value: bool, ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + def fetch_string_value: (flag_key: String, default_value: String, ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + def fetch_number_value: (flag_key: String, default_value: Numeric, ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + def fetch_integer_value: (flag_key: String, default_value: Integer, ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + def fetch_float_value: (flag_key: String, default_value: Float, ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + def fetch_object_value: (flag_key: String, default_value: Array[untyped] | Hash[untyped, untyped], ?evaluation_context: EvaluationContext?) -> Provider::ResolutionDetails + end + + interface _LifecycleProvider + def init: (?EvaluationContext? evaluation_context) -> void + def shutdown: () -> void + def metadata: () -> ProviderMetadata + end + + interface _TrackableProvider + def track: (String tracking_event_name, ?evaluation_context: EvaluationContext?, ?tracking_event_details: TrackingEventDetails?) -> void + end + end + end +end diff --git a/sig/open_feature/sdk/provider/error_code.rbs b/sig/open_feature/sdk/provider/error_code.rbs new file mode 100644 index 00000000..fd2f4832 --- /dev/null +++ b/sig/open_feature/sdk/provider/error_code.rbs @@ -0,0 +1,16 @@ +module OpenFeature + module SDK + module Provider + module ErrorCode + PROVIDER_NOT_READY: String + FLAG_NOT_FOUND: String + PARSE_ERROR: String + TYPE_MISMATCH: String + TARGETING_KEY_MISSING: String + INVALID_CONTEXT: String + PROVIDER_FATAL: String + GENERAL: String + end + end + end +end diff --git a/sig/open_feature/sdk/provider/event_emitter.rbs b/sig/open_feature/sdk/provider/event_emitter.rbs new file mode 100644 index 00000000..edee523f --- /dev/null +++ b/sig/open_feature/sdk/provider/event_emitter.rbs @@ -0,0 +1,16 @@ +module OpenFeature + module SDK + module Provider + module EventEmitter + def emit_event: (String event_type, ?Hash[Symbol, untyped] details) -> void + + def configuration_attached?: () -> bool + + private + + def attach: (Configuration configuration) -> void + def detach: () -> void + end + end + end +end diff --git a/sig/open_feature/sdk/provider/in_memory_provider.rbs b/sig/open_feature/sdk/provider/in_memory_provider.rbs new file mode 100644 index 00000000..c0d4fbce --- /dev/null +++ b/sig/open_feature/sdk/provider/in_memory_provider.rbs @@ -0,0 +1,35 @@ +module OpenFeature + module SDK + module Provider + class InMemoryProvider + include Provider::EventEmitter + + NAME: String + + attr_reader metadata: ProviderMetadata + + def initialize: (?Hash[String, untyped] flags) -> void + + def init: (?EvaluationContext? evaluation_context) -> void + def shutdown: () -> void + + def add_flag: (flag_key: String, value: untyped) -> void + def update_flags: (Hash[String, untyped] new_flags) -> void + + def fetch_boolean_value: (flag_key: String, default_value: bool, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_string_value: (flag_key: String, default_value: String, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_number_value: (flag_key: String, default_value: Numeric, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_integer_value: (flag_key: String, default_value: Integer, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_float_value: (flag_key: String, default_value: Float, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_object_value: (flag_key: String, default_value: Array[untyped] | Hash[untyped, untyped], ?evaluation_context: EvaluationContext?) -> ResolutionDetails + + private + + attr_reader flags: Hash[String, untyped] + + def fetch_value: (flag_key: String, default_value: untyped, evaluation_context: EvaluationContext?) -> ResolutionDetails + def emit_provider_changed: (Array[String] flag_keys) -> void + end + end + end +end diff --git a/sig/open_feature/sdk/provider/no_op_provider.rbs b/sig/open_feature/sdk/provider/no_op_provider.rbs new file mode 100644 index 00000000..7c75cf8c --- /dev/null +++ b/sig/open_feature/sdk/provider/no_op_provider.rbs @@ -0,0 +1,25 @@ +module OpenFeature + module SDK + module Provider + class NoOpProvider + REASON_NO_OP: String + NAME: String + + attr_reader metadata: ProviderMetadata + + def initialize: () -> void + + def fetch_boolean_value: (flag_key: String, default_value: bool, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_string_value: (flag_key: String, default_value: String, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_number_value: (flag_key: String, default_value: Numeric, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_integer_value: (flag_key: String, default_value: Integer, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_float_value: (flag_key: String, default_value: Float, ?evaluation_context: EvaluationContext?) -> ResolutionDetails + def fetch_object_value: (flag_key: String, default_value: Array[untyped] | Hash[untyped, untyped], ?evaluation_context: EvaluationContext?) -> ResolutionDetails + + private + + def no_op: (untyped default_value) -> ResolutionDetails + end + end + end +end diff --git a/sig/open_feature/sdk/provider/provider_metadata.rbs b/sig/open_feature/sdk/provider/provider_metadata.rbs new file mode 100644 index 00000000..05400f14 --- /dev/null +++ b/sig/open_feature/sdk/provider/provider_metadata.rbs @@ -0,0 +1,11 @@ +module OpenFeature + module SDK + module Provider + class ProviderMetadata < Struct[untyped] + attr_accessor name: String? + + def initialize: (?name: String?) -> void + end + end + end +end diff --git a/sig/open_feature/sdk/provider/reason.rbs b/sig/open_feature/sdk/provider/reason.rbs new file mode 100644 index 00000000..682bfdc0 --- /dev/null +++ b/sig/open_feature/sdk/provider/reason.rbs @@ -0,0 +1,17 @@ +module OpenFeature + module SDK + module Provider + module Reason + STATIC: String + DEFAULT: String + TARGETING_MATCH: String + SPLIT: String + CACHED: String + DISABLED: String + UNKNOWN: String + STALE: String + ERROR: String + end + end + end +end diff --git a/sig/open_feature/sdk/provider/resolution_details.rbs b/sig/open_feature/sdk/provider/resolution_details.rbs new file mode 100644 index 00000000..3ab2dcd9 --- /dev/null +++ b/sig/open_feature/sdk/provider/resolution_details.rbs @@ -0,0 +1,19 @@ +module OpenFeature + module SDK + module Provider + EMPTY_FLAG_METADATA: Hash[String, untyped] + + class ResolutionDetails < Struct[untyped] + attr_accessor value: untyped + attr_accessor reason: String? + attr_accessor variant: String? + attr_accessor error_code: String? + attr_accessor error_message: String? + + def initialize: (?value: untyped, ?reason: String?, ?variant: String?, ?error_code: String?, ?error_message: String?, ?flag_metadata: Hash[String, untyped]?) -> void + + def flag_metadata: () -> Hash[String, untyped] + end + end + end +end diff --git a/sig/open_feature/sdk/provider_event.rbs b/sig/open_feature/sdk/provider_event.rbs new file mode 100644 index 00000000..d301e37b --- /dev/null +++ b/sig/open_feature/sdk/provider_event.rbs @@ -0,0 +1,12 @@ +module OpenFeature + module SDK + module ProviderEvent + PROVIDER_READY: String + PROVIDER_ERROR: String + PROVIDER_CONFIGURATION_CHANGED: String + PROVIDER_STALE: String + + ALL_EVENTS: Array[String] + end + end +end diff --git a/sig/open_feature/sdk/provider_initialization_error.rbs b/sig/open_feature/sdk/provider_initialization_error.rbs new file mode 100644 index 00000000..0e981812 --- /dev/null +++ b/sig/open_feature/sdk/provider_initialization_error.rbs @@ -0,0 +1,11 @@ +module OpenFeature + module SDK + class ProviderInitializationError < StandardError + attr_reader provider: untyped + attr_reader original_error: Exception? + attr_reader error_code: String + + def initialize: (String message, ?provider: untyped, ?original_error: Exception?, ?error_code: String) -> void + end + end +end diff --git a/sig/open_feature/sdk/provider_state.rbs b/sig/open_feature/sdk/provider_state.rbs new file mode 100644 index 00000000..d77ba46c --- /dev/null +++ b/sig/open_feature/sdk/provider_state.rbs @@ -0,0 +1,13 @@ +module OpenFeature + module SDK + module ProviderState + NOT_READY: String + READY: String + ERROR: String + STALE: String + FATAL: String + + ALL_STATES: Array[String] + end + end +end diff --git a/sig/open_feature/sdk/provider_state_registry.rbs b/sig/open_feature/sdk/provider_state_registry.rbs new file mode 100644 index 00000000..6ebfded1 --- /dev/null +++ b/sig/open_feature/sdk/provider_state_registry.rbs @@ -0,0 +1,22 @@ +module OpenFeature + module SDK + class ProviderStateRegistry + def initialize: () -> void + + def set_initial_state: (untyped provider, ?String state) -> void + def update_state_from_event: (untyped provider, String event_type, ?Hash[Symbol, untyped]? event_details) -> String + def get_state: (untyped provider) -> String + def get_details: (untyped provider) -> Hash[Symbol, untyped] + def remove_provider: (untyped provider) -> void + def tracked?: (untyped provider) -> bool + def ready?: (untyped provider) -> bool + def error?: (untyped provider) -> bool + def clear: () -> void + + private + + def state_from_event: (String event_type, ?Hash[Symbol, untyped]? event_details) -> String? + def state_from_error_event: (Hash[Symbol, untyped]? event_details) -> String + end + end +end diff --git a/sig/open_feature/sdk/telemetry.rbs b/sig/open_feature/sdk/telemetry.rbs new file mode 100644 index 00000000..17b88915 --- /dev/null +++ b/sig/open_feature/sdk/telemetry.rbs @@ -0,0 +1,29 @@ +module OpenFeature + module SDK + module Telemetry + EVENT_NAME: String + + FLAG_KEY: String + CONTEXT_ID_KEY: String + ERROR_MESSAGE_KEY: String + ERROR_TYPE_KEY: String + PROVIDER_NAME_KEY: String + RESULT_REASON_KEY: String + RESULT_VALUE_KEY: String + RESULT_VARIANT_KEY: String + FLAG_SET_ID_KEY: String + VERSION_KEY: String + + METADATA_KEY_MAP: Hash[String, String] + + class EvaluationEvent < Struct[untyped] + attr_accessor name: String + attr_accessor attributes: Hash[String, untyped] + + def initialize: (?name: String, ?attributes: Hash[String, untyped]) -> void + end + + def self.create_evaluation_event: (hook_context: Hooks::HookContext, evaluation_details: EvaluationDetails?) -> EvaluationEvent + end + end +end diff --git a/sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs b/sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs new file mode 100644 index 00000000..3713a0ae --- /dev/null +++ b/sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs @@ -0,0 +1,12 @@ +module OpenFeature + module SDK + class ThreadLocalTransactionContextPropagator + include TransactionContextPropagator + + THREAD_KEY: Symbol + + def set_transaction_context: (EvaluationContext evaluation_context) -> void + def get_transaction_context: () -> EvaluationContext? + end + end +end diff --git a/sig/open_feature/sdk/tracking_event_details.rbs b/sig/open_feature/sdk/tracking_event_details.rbs new file mode 100644 index 00000000..171a19d2 --- /dev/null +++ b/sig/open_feature/sdk/tracking_event_details.rbs @@ -0,0 +1,10 @@ +module OpenFeature + module SDK + class TrackingEventDetails + attr_reader value: Numeric? + attr_reader fields: Hash[String, untyped] + + def initialize: (?value: Numeric?, **untyped fields) -> void + end + end +end diff --git a/sig/open_feature/sdk/transaction_context_propagator.rbs b/sig/open_feature/sdk/transaction_context_propagator.rbs new file mode 100644 index 00000000..643fb50a --- /dev/null +++ b/sig/open_feature/sdk/transaction_context_propagator.rbs @@ -0,0 +1,11 @@ +module OpenFeature + module SDK + interface _TransactionContextPropagator + def set_transaction_context: (EvaluationContext evaluation_context) -> void + def get_transaction_context: () -> EvaluationContext? + end + + module TransactionContextPropagator : _TransactionContextPropagator + end + end +end diff --git a/sig/open_feature/sdk/version.rbs b/sig/open_feature/sdk/version.rbs new file mode 100644 index 00000000..0972a97e --- /dev/null +++ b/sig/open_feature/sdk/version.rbs @@ -0,0 +1,5 @@ +module OpenFeature + module SDK + VERSION: String + end +end From d6107eb429f015348e0d4c270976586e09f96007 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 18 Mar 2026 05:28:39 -0700 Subject: [PATCH 3/5] ci: add Steep type check job and update CLAUDE.md - Add steep CI job to GitHub Actions workflow - Gate status check on steep job passing - Document type check command and sig/ convention in CLAUDE.md Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jose Colella --- .github/workflows/main.yml | 13 ++++++++++++- CLAUDE.md | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8751089c..42880116 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,17 @@ jobs: - run: bundle install - name: Cucumber run: bundle exec cucumber + steep: + runs-on: ubuntu-latest + name: Type Check + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4.9" + - run: bundle install + - run: bundle exec rbs collection install + - run: bundle exec steep check security: runs-on: ubuntu-latest name: Security Audit @@ -79,7 +90,7 @@ jobs: status: name: CI Status runs-on: ubuntu-latest - needs: [rspec, standard, cucumber, security] + needs: [rspec, standard, cucumber, security, steep] if: always() steps: - name: Successful CI diff --git a/CLAUDE.md b/CLAUDE.md index d8cb2c85..b52d29a9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,11 +8,13 @@ This is the official OpenFeature SDK for Ruby — an implementation of the [Open ## Commands +- **Install dependencies:** `bundle install` - **Run all tests:** `bundle exec rspec` - **Run a single test file:** `bundle exec rspec spec/open_feature/sdk/client_spec.rb` - **Run a specific test by line:** `bundle exec rspec spec/open_feature/sdk/client_spec.rb:43` - **Lint:** `bundle exec standardrb` - **Lint with autofix:** `bundle exec standardrb --fix` +- **Type check:** `bundle exec steep check` - **Default rake (tests + lint):** `bundle exec rake` Note: Linting uses [Standard Ruby](https://github.com/standardrb/standard) (configured via the `standard` gem), which enforces double-quoted strings and its own opinionated style. There is no `.rubocop.yml` — Standard manages RuboCop configuration internally. Do not use `bundle exec rubocop` directly as a stale RuboCop server may apply different rules; always use `bundle exec standardrb`. @@ -47,5 +49,6 @@ Providers can be registered for specific domains. `Configuration#provider(domain - All `.rb` files must have `# frozen_string_literal: true` as the first line. - Tests live under `spec/` and mirror the `lib/` structure. `spec/specification/` contains tests mapped to OpenFeature spec requirements. +- RBS type signatures live under `sig/` and mirror the `lib/` structure. When changing any file under `lib/`, always update the corresponding `.rbs` file under `sig/` to reflect the new or modified method signatures, constants, or class structure. Run `bundle exec steep check` to verify type correctness after changes. - Always sign git commits using the `-S` flag. - Always include DCO sign-off in commits using the `-s` flag (i.e., `git commit -s -S`). This adds a `Signed-off-by` trailer required by the project's CI. From 41099a44d061d120b4f3d865d54176512697ba1a Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 18 Mar 2026 05:31:22 -0700 Subject: [PATCH 4/5] ci: add permissions block to CI workflow Set explicit top-level permissions with minimal `contents: read` to satisfy security scanning requirements. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jose Colella --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 42880116..9466190b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,9 @@ on: - push - pull_request +permissions: + contents: read + jobs: rspec: runs-on: ${{ matrix.os }} From 0fac9d91f45356d852383dfdad130dc422127b59 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 18 Mar 2026 05:34:04 -0700 Subject: [PATCH 5/5] fix: make _ToProc interface more specific Narrow the handler interface signature from `(untyped) -> untyped` to `(Hash[Symbol, untyped]) -> void` since event handlers always receive event details hashes and their return values are unused. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jose Colella --- sig/open_feature/sdk/event_dispatcher.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sig/open_feature/sdk/event_dispatcher.rbs b/sig/open_feature/sdk/event_dispatcher.rbs index 0bd3d5aa..87f52b80 100644 --- a/sig/open_feature/sdk/event_dispatcher.rbs +++ b/sig/open_feature/sdk/event_dispatcher.rbs @@ -18,7 +18,7 @@ module OpenFeature end interface _ToProc - def call: (untyped) -> untyped + def call: (Hash[Symbol, untyped]) -> void end end end