From eee5cdb99285a896f95d8adbda6ac945bc1312fc Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 4 Mar 2026 22:34:20 -0800 Subject: [PATCH 1/4] chore: add frozen_string_literal magic comment to all Ruby files Add `# frozen_string_literal: true` to the 13 .rb files that were missing it, for consistency across the codebase and to opt into frozen string semantics. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jose Colella --- lib/open_feature/sdk/client_metadata.rb | 2 ++ lib/open_feature/sdk/evaluation_context.rb | 2 ++ .../sdk/evaluation_context_builder.rb | 2 ++ lib/open_feature/sdk/evaluation_details.rb | 2 ++ lib/open_feature/sdk/provider.rb | 2 ++ lib/open_feature/sdk/provider/error_code.rb | 18 ++++++++++-------- .../sdk/provider/in_memory_provider.rb | 2 ++ .../sdk/provider/provider_metadata.rb | 2 ++ lib/open_feature/sdk/provider/reason.rb | 2 ++ .../sdk/provider/resolution_details.rb | 2 ++ .../sdk/evaluation_context_builder_spec.rb | 2 ++ .../sdk/evaluation_context_spec.rb | 2 ++ .../sdk/provider/in_memory_provider_spec.rb | 2 ++ 13 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/open_feature/sdk/client_metadata.rb b/lib/open_feature/sdk/client_metadata.rb index 8bbbcb83..ad33ddaa 100644 --- a/lib/open_feature/sdk/client_metadata.rb +++ b/lib/open_feature/sdk/client_metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK ClientMetadata = Struct.new(:domain, keyword_init: true) diff --git a/lib/open_feature/sdk/evaluation_context.rb b/lib/open_feature/sdk/evaluation_context.rb index b9417ceb..e4fb3581 100644 --- a/lib/open_feature/sdk/evaluation_context.rb +++ b/lib/open_feature/sdk/evaluation_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK class EvaluationContext diff --git a/lib/open_feature/sdk/evaluation_context_builder.rb b/lib/open_feature/sdk/evaluation_context_builder.rb index 0b3b7c06..2d1a159a 100644 --- a/lib/open_feature/sdk/evaluation_context_builder.rb +++ b/lib/open_feature/sdk/evaluation_context_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK # Used to combine evaluation contexts from different sources diff --git a/lib/open_feature/sdk/evaluation_details.rb b/lib/open_feature/sdk/evaluation_details.rb index 4faccd28..a840e783 100644 --- a/lib/open_feature/sdk/evaluation_details.rb +++ b/lib/open_feature/sdk/evaluation_details.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK EvaluationDetails = Struct.new(:flag_key, :resolution_details, keyword_init: true) do diff --git a/lib/open_feature/sdk/provider.rb b/lib/open_feature/sdk/provider.rb index 6488646b..f3f0fe8e 100644 --- a/lib/open_feature/sdk/provider.rb +++ b/lib/open_feature/sdk/provider.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "provider/error_code" require_relative "provider/reason" require_relative "provider/resolution_details" diff --git a/lib/open_feature/sdk/provider/error_code.rb b/lib/open_feature/sdk/provider/error_code.rb index e133e2a0..48735ae2 100644 --- a/lib/open_feature/sdk/provider/error_code.rb +++ b/lib/open_feature/sdk/provider/error_code.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + module OpenFeature module SDK module Provider module ErrorCode - PROVIDER_NOT_READY = "PROVIDER_NOT_READY" - FLAG_NOT_FOUND = "FLAG_NOT_FOUND" - PARSE_ERROR = "PARSE_ERROR" - TYPE_MISMATCH = "TYPE_MISMATCH" - TARGETING_KEY_MISSING = "TARGETING_KEY_MISSING" - INVALID_CONTEXT = "INVALID_CONTEXT" - PROVIDER_FATAL = "PROVIDER_FATAL" - GENERAL = "GENERAL" + PROVIDER_NOT_READY = 'PROVIDER_NOT_READY' + FLAG_NOT_FOUND = 'FLAG_NOT_FOUND' + PARSE_ERROR = 'PARSE_ERROR' + TYPE_MISMATCH = 'TYPE_MISMATCH' + TARGETING_KEY_MISSING = 'TARGETING_KEY_MISSING' + INVALID_CONTEXT = 'INVALID_CONTEXT' + PROVIDER_FATAL = 'PROVIDER_FATAL' + GENERAL = 'GENERAL' end end end diff --git a/lib/open_feature/sdk/provider/in_memory_provider.rb b/lib/open_feature/sdk/provider/in_memory_provider.rb index 3bfcb971..e74858f0 100644 --- a/lib/open_feature/sdk/provider/in_memory_provider.rb +++ b/lib/open_feature/sdk/provider/in_memory_provider.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK module Provider diff --git a/lib/open_feature/sdk/provider/provider_metadata.rb b/lib/open_feature/sdk/provider/provider_metadata.rb index b4497c0b..4b648cec 100644 --- a/lib/open_feature/sdk/provider/provider_metadata.rb +++ b/lib/open_feature/sdk/provider/provider_metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK module Provider diff --git a/lib/open_feature/sdk/provider/reason.rb b/lib/open_feature/sdk/provider/reason.rb index 48d9be60..28a85d44 100644 --- a/lib/open_feature/sdk/provider/reason.rb +++ b/lib/open_feature/sdk/provider/reason.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK module Provider diff --git a/lib/open_feature/sdk/provider/resolution_details.rb b/lib/open_feature/sdk/provider/resolution_details.rb index c7675fce..8168b9fa 100644 --- a/lib/open_feature/sdk/provider/resolution_details.rb +++ b/lib/open_feature/sdk/provider/resolution_details.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module OpenFeature module SDK module Provider diff --git a/spec/open_feature/sdk/evaluation_context_builder_spec.rb b/spec/open_feature/sdk/evaluation_context_builder_spec.rb index e1fbf4f8..152329eb 100644 --- a/spec/open_feature/sdk/evaluation_context_builder_spec.rb +++ b/spec/open_feature/sdk/evaluation_context_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe OpenFeature::SDK::EvaluationContextBuilder do diff --git a/spec/open_feature/sdk/evaluation_context_spec.rb b/spec/open_feature/sdk/evaluation_context_spec.rb index d8433e9e..09040425 100644 --- a/spec/open_feature/sdk/evaluation_context_spec.rb +++ b/spec/open_feature/sdk/evaluation_context_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe OpenFeature::SDK::EvaluationContext do diff --git a/spec/open_feature/sdk/provider/in_memory_provider_spec.rb b/spec/open_feature/sdk/provider/in_memory_provider_spec.rb index 1f919299..8b7093ce 100644 --- a/spec/open_feature/sdk/provider/in_memory_provider_spec.rb +++ b/spec/open_feature/sdk/provider/in_memory_provider_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe OpenFeature::SDK::Provider::InMemoryProvider do From a5c3def7a108ddd33f45a2cfd691f0a9b25c7f75 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Wed, 4 Mar 2026 22:35:45 -0800 Subject: [PATCH 2/4] chore: add CLAUDE.md with project conventions Include commit signing requirement and development commands. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jose Colella --- CLAUDE.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..59ed9901 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,50 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is the official OpenFeature SDK for Ruby — an implementation of the [OpenFeature specification](https://openfeature.dev) providing a vendor-agnostic API for feature flag evaluation. Published as the `openfeature-sdk` gem. Requires Ruby >= 3.1. + +## Commands + +- **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` +- **Default rake (tests + lint):** `bundle exec rake` + +Note: Linting uses [Standard Ruby](https://github.com/standardrb/standard) (configured via the `standard` gem), which enforces single-quoted strings and its own opinionated style. There is no `.rubocop.yml` — Standard manages RuboCop configuration internally. + +## Architecture + +### Entry point and API singleton + +`OpenFeature::SDK` (in `lib/open_feature/sdk.rb`) delegates all method calls to `API.instance` via `method_missing`. `API` is a Singleton that holds a `Configuration` object and builds `Client` instances. + +### Provider duck type + +Providers are not subclasses — they follow a duck type interface. Any object implementing `fetch_boolean_value`, `fetch_string_value`, `fetch_number_value`, `fetch_integer_value`, `fetch_float_value`, and `fetch_object_value` (all accepting `flag_key:`, `default_value:`, `evaluation_context:`) works as a provider. Each method must return a `ResolutionDetails` struct. Two built-in providers exist: `NoOpProvider` (default) and `InMemoryProvider` (for testing). Providers may optionally implement `init(evaluation_context)`, `shutdown`, and `metadata`. + +### Client dynamic method generation + +`Client` uses `class_eval` to metaprogram `fetch__value` and `fetch__details` methods from `RESULT_TYPE` and `SUFFIXES` arrays. This generates 12 public methods (6 types × 2 suffixes). + +### Evaluation context merging + +`EvaluationContextBuilder` merges three layers of context with this precedence: invocation > client > API (global). Context is a hash-like object with a special `targeting_key` field. + +### Provider eventing + +`Configuration` manages provider lifecycle events (READY, ERROR, STALE, CONFIGURATION_CHANGED). Providers can emit spontaneous events by including `Provider::EventEmitter`. Event handlers can be registered at API level (global) or client level (domain-scoped). `ProviderStateRegistry` tracks provider states; `EventDispatcher` manages handler registration and invocation. + +### Domain-based provider binding + +Providers can be registered for specific domains. `Configuration#provider(domain:)` resolves domain-specific providers, falling back to the default (nil-domain) provider. Clients are built with an optional `domain:` that binds them to a specific provider. + +## Conventions + +- 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. +- Always sign git commits using the `-S` flag. From 786b9eb31a27d9be035be9b27ab454982eff46f5 Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Thu, 5 Mar 2026 07:31:58 -0800 Subject: [PATCH 3/4] fix: use double-quoted strings in error_code.rb per standardrb Also correct CLAUDE.md to document that Standard Ruby enforces double-quoted strings, not single-quoted. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jose Colella --- CLAUDE.md | 2 +- lib/open_feature/sdk/provider/error_code.rb | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 59ed9901..e43a41ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ This is the official OpenFeature SDK for Ruby — an implementation of the [Open - **Lint with autofix:** `bundle exec standardrb --fix` - **Default rake (tests + lint):** `bundle exec rake` -Note: Linting uses [Standard Ruby](https://github.com/standardrb/standard) (configured via the `standard` gem), which enforces single-quoted strings and its own opinionated style. There is no `.rubocop.yml` — Standard manages RuboCop configuration internally. +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`. ## Architecture diff --git a/lib/open_feature/sdk/provider/error_code.rb b/lib/open_feature/sdk/provider/error_code.rb index 48735ae2..335f7a81 100644 --- a/lib/open_feature/sdk/provider/error_code.rb +++ b/lib/open_feature/sdk/provider/error_code.rb @@ -4,14 +4,14 @@ module OpenFeature module SDK module Provider module ErrorCode - PROVIDER_NOT_READY = 'PROVIDER_NOT_READY' - FLAG_NOT_FOUND = 'FLAG_NOT_FOUND' - PARSE_ERROR = 'PARSE_ERROR' - TYPE_MISMATCH = 'TYPE_MISMATCH' - TARGETING_KEY_MISSING = 'TARGETING_KEY_MISSING' - INVALID_CONTEXT = 'INVALID_CONTEXT' - PROVIDER_FATAL = 'PROVIDER_FATAL' - GENERAL = 'GENERAL' + PROVIDER_NOT_READY = "PROVIDER_NOT_READY" + FLAG_NOT_FOUND = "FLAG_NOT_FOUND" + PARSE_ERROR = "PARSE_ERROR" + TYPE_MISMATCH = "TYPE_MISMATCH" + TARGETING_KEY_MISSING = "TARGETING_KEY_MISSING" + INVALID_CONTEXT = "INVALID_CONTEXT" + PROVIDER_FATAL = "PROVIDER_FATAL" + GENERAL = "GENERAL" end end end From 3c4670ebee114853cd14424aee3a223b241de9dc Mon Sep 17 00:00:00 2001 From: Jose Colella Date: Thu, 5 Mar 2026 07:35:08 -0800 Subject: [PATCH 4/4] chore: document DCO sign-off requirement in CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jose Colella --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index e43a41ff..d8cb2c85 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,3 +48,4 @@ 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. - 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.