quonfig-openfeature 1.0.3 → 1.1.0
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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +20 -0
- data/VERSION +1 -1
- data/lib/quonfig/openfeature/errors.rb +1 -1
- data/lib/quonfig/openfeature/provider.rb +62 -4
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 518ef7e90608d00dc60292d93fa9c9b6a14037df9194597a1cc309a4c02ea166
|
|
4
|
+
data.tar.gz: bc52ae8cda109bd7aa20dcc5e0a235716a503d497987d4000d45a97ae6541bba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '080feecf4971e26beff1c05427d6ffa0d9c494c26696968502ae7fe8ea462e6eb770a1a57d6486bf8d39093133902b8672cebea3f3f4cd3fc4a92ed0355ba5ae'
|
|
7
|
+
data.tar.gz: e783174efaa4e7c8577259bec7ebaa7ccc3eb1f900a844fed68e1449cabffeada4b2925fc5d650f15c51c52d27da35a79380bec8d716e6cf4259bd4f5d93ee6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 - 2026-10-23
|
|
4
|
+
|
|
5
|
+
- **UPGRADE: raise the `quonfig` floor from `>= 1.10.0` to `>= 1.11.0`. Requires sdk-ruby 1.11.0:
|
|
6
|
+
SSE event and config body size caps, `Client#callback_errors_total`.** The provider code does not
|
|
7
|
+
change for 1.11.0. The new floor means every install of this provider gets those sdk-ruby changes:
|
|
8
|
+
an oversized SSE event (over 4 MiB) or config body (over 16 MiB) is treated as a failed attempt
|
|
9
|
+
instead of being read without limit, and `Dir.home` failures no longer raise from `Client.new`.
|
|
10
|
+
Read the 1.11.0 entry in the sdk-ruby CHANGELOG. Build and test against `quonfig` 1.11.0
|
|
11
|
+
(Gemfile.lock).
|
|
12
|
+
- **UPGRADE: raise the `quonfig` floor from `>= 1.7.2` to `>= 1.10.0` (qfg-9p4i.12).** The provider
|
|
13
|
+
now uses sdk-ruby 1.10.0's init-failure surface (`on_init_failure: :fail | :wait | :continue`,
|
|
14
|
+
`init_timeout_ms`, `Client#ready?` / `#wait_for_ready`, `Quonfig::Errors::InitializationError`).
|
|
15
|
+
Read the 1.10.0 entry in the sdk-ruby CHANGELOG: `Client.new` now retries a failed boot until
|
|
16
|
+
`init_timeout_ms` (default 10s) before it raises, instead of raising on the first failed attempt.
|
|
17
|
+
- **Added (minor): the provider emits OpenFeature events and its status follows `on_init_failure`
|
|
18
|
+
(init-failure design B.5).** Pass `on_init_failure:` and `init_timeout_ms:` to `Provider.new`; they
|
|
19
|
+
are forwarded to `Quonfig::Client.new` as before.
|
|
20
|
+
- `:fail` (the default): `init` raises `Quonfig::Errors::InitializationError` when no config loads,
|
|
21
|
+
so `set_provider_and_wait` raises and the provider is ERROR. sdk-ruby returns no client under
|
|
22
|
+
`:fail`, so nothing retries in the background; the process restart is the retry.
|
|
23
|
+
- `:wait`: `init` blocks until config loads. The provider is NOT_READY for the whole wait, then READY.
|
|
24
|
+
- `:continue` with no config: `init` resolves, then the provider emits `PROVIDER_ERROR` (error code
|
|
25
|
+
`PROVIDER_NOT_READY`), so its status is ERROR rather than READY while every evaluation returns the
|
|
26
|
+
code default. When the client's background retry loads config, the provider emits
|
|
27
|
+
`PROVIDER_READY`. Before, the provider reported READY and emitted no events.
|
|
28
|
+
- A client injected with `client:` gets the same treatment: if it has no config at `init`, the
|
|
29
|
+
provider reports ERROR until it heals.
|
|
30
|
+
- **Fix: an evaluation before config loads reports `PROVIDER_NOT_READY`.** sdk-ruby 1.10.0's
|
|
31
|
+
`*_details` getters return error code `PROVIDER_NOT_READY` on a client with no config; the provider
|
|
32
|
+
mapped that to `GENERAL`. `Quonfig::Errors::InitializationError` (any reason, not only the timeout
|
|
33
|
+
subclass) now maps to `PROVIDER_NOT_READY` too.
|
|
34
|
+
|
|
3
35
|
## 1.0.3 - 2026-10-16
|
|
4
36
|
|
|
5
37
|
- Build and test against `quonfig` 1.8.0 (Gemfile.lock). The runtime floor stays at `>= 1.7.2`: this
|
data/README.md
CHANGED
|
@@ -56,6 +56,26 @@ is_pro = client.fetch_boolean_value(flag_key: 'pro-feature', default_value: fals
|
|
|
56
56
|
evaluation_context: ctx)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Startup failure and provider status
|
|
60
|
+
|
|
61
|
+
The provider forwards `on_init_failure:` and `init_timeout_ms:` to `Quonfig::Client.new`, and its
|
|
62
|
+
OpenFeature status follows the mode:
|
|
63
|
+
|
|
64
|
+
| `on_init_failure` | `init` when no config loads | Provider status | Later |
|
|
65
|
+
|-------------------|-----------------------------|-----------------|-------|
|
|
66
|
+
| `:fail` (default) | raises `Quonfig::Errors::InitializationError` after `init_timeout_ms` (at once if the SDK key is rejected) | ERROR | No client exists, so nothing retries; restart the process |
|
|
67
|
+
| `:wait` | blocks and retries with no upper bound | NOT_READY while waiting | READY when config loads |
|
|
68
|
+
| `:continue` | returns; emits `PROVIDER_ERROR` | ERROR; evaluations return your default with `PROVIDER_NOT_READY` | emits `PROVIDER_READY` when the background retry loads config |
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
provider = Quonfig::OpenFeature::Provider.new(
|
|
72
|
+
sdk_key: 'qf_sk_production_...',
|
|
73
|
+
on_init_failure: :continue, # :fail (default), :wait or :continue
|
|
74
|
+
init_timeout_ms: 10_000
|
|
75
|
+
)
|
|
76
|
+
OpenFeature::SDK.set_provider_and_wait(provider)
|
|
77
|
+
```
|
|
78
|
+
|
|
59
79
|
## Context mapping
|
|
60
80
|
|
|
61
81
|
OpenFeature context is flat; Quonfig context is nested by namespace. This provider
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0
|
|
1
|
+
1.1.0
|
|
@@ -21,7 +21,7 @@ module Quonfig
|
|
|
21
21
|
return ErrorCode::TYPE_MISMATCH if defined?(::Quonfig::Errors::TypeMismatchError) && err.is_a?(::Quonfig::Errors::TypeMismatchError)
|
|
22
22
|
|
|
23
23
|
if (defined?(::Quonfig::Errors::UninitializedError) && err.is_a?(::Quonfig::Errors::UninitializedError)) ||
|
|
24
|
-
(defined?(::Quonfig::Errors::
|
|
24
|
+
(defined?(::Quonfig::Errors::InitializationError) && err.is_a?(::Quonfig::Errors::InitializationError))
|
|
25
25
|
return ErrorCode::PROVIDER_NOT_READY
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -31,9 +31,26 @@ module Quonfig
|
|
|
31
31
|
#
|
|
32
32
|
# client = OpenFeature::SDK.build_client
|
|
33
33
|
# client.fetch_boolean_value(flag_key: 'my-flag', default_value: false)
|
|
34
|
+
#
|
|
35
|
+
# Provider status follows the SDK's +on_init_failure+ mode (init-failure
|
|
36
|
+
# design B.5). Pass +on_init_failure:+ and +init_timeout_ms:+ through
|
|
37
|
+
# +Provider.new+; they are forwarded to +Quonfig::Client.new+.
|
|
38
|
+
#
|
|
39
|
+
# * +:fail+ (default): +init+ raises, so the provider is ERROR. sdk-ruby
|
|
40
|
+
# returns no client, so nothing retries; restart the process.
|
|
41
|
+
# * +:wait+: +init+ blocks until config loads (NOT_READY), then READY.
|
|
42
|
+
# * +:continue+: +init+ resolves; with no config yet the provider emits
|
|
43
|
+
# PROVIDER_ERROR (ERROR, evaluations return the default with
|
|
44
|
+
# PROVIDER_NOT_READY), then PROVIDER_READY when the client heals.
|
|
34
45
|
class Provider
|
|
46
|
+
include ::OpenFeature::SDK::Provider::EventEmitter
|
|
47
|
+
|
|
35
48
|
NAME = 'quonfig'
|
|
36
49
|
|
|
50
|
+
# How long the heal watcher waits for OpenFeature to record the READY it
|
|
51
|
+
# dispatches when +init+ returns, before it reports PROVIDER_ERROR.
|
|
52
|
+
INIT_DISPATCH_WAIT_S = 5
|
|
53
|
+
|
|
37
54
|
ResolutionDetails = ::OpenFeature::SDK::Provider::ResolutionDetails
|
|
38
55
|
Reason = ::OpenFeature::SDK::Provider::Reason
|
|
39
56
|
ErrorCode = ::OpenFeature::SDK::Provider::ErrorCode
|
|
@@ -63,16 +80,19 @@ module Quonfig
|
|
|
63
80
|
environment: environment,
|
|
64
81
|
extra: quonfig_options
|
|
65
82
|
)
|
|
66
|
-
@initialized =
|
|
83
|
+
@initialized = false
|
|
67
84
|
end
|
|
68
85
|
|
|
69
86
|
# Initialize the underlying Quonfig client. Called by
|
|
70
|
-
# +OpenFeature::SDK.set_provider_and_wait+.
|
|
87
|
+
# +OpenFeature::SDK.set_provider_and_wait+. Raises under
|
|
88
|
+
# +on_init_failure: :fail+ when no config loads, which OpenFeature
|
|
89
|
+
# records as ERROR.
|
|
71
90
|
def init(_evaluation_context = nil)
|
|
72
91
|
return if @initialized
|
|
73
92
|
|
|
74
|
-
@client
|
|
93
|
+
@client ||= ::Quonfig::Client.new(@quonfig_options)
|
|
75
94
|
@initialized = true
|
|
95
|
+
watch_for_heal(@client) unless client_ready?(@client)
|
|
76
96
|
nil
|
|
77
97
|
end
|
|
78
98
|
|
|
@@ -83,6 +103,8 @@ module Quonfig
|
|
|
83
103
|
@client = nil
|
|
84
104
|
@initialized = false
|
|
85
105
|
client&.stop
|
|
106
|
+
@heal_thread&.join(1)
|
|
107
|
+
@heal_thread = nil
|
|
86
108
|
nil
|
|
87
109
|
end
|
|
88
110
|
|
|
@@ -141,6 +163,41 @@ module Quonfig
|
|
|
141
163
|
|
|
142
164
|
private
|
|
143
165
|
|
|
166
|
+
def client_ready?(client)
|
|
167
|
+
!client.respond_to?(:ready?) || client.ready?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# +on_init_failure: :continue+ left the client without config. Report
|
|
171
|
+
# ERROR once OpenFeature has recorded the READY it dispatches after +init+
|
|
172
|
+
# returns, then READY when the client's background retry heals.
|
|
173
|
+
def watch_for_heal(client)
|
|
174
|
+
@heal_thread = Thread.new do
|
|
175
|
+
await_init_dispatch
|
|
176
|
+
unless client.ready?
|
|
177
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR,
|
|
178
|
+
error_code: ErrorCode::PROVIDER_NOT_READY,
|
|
179
|
+
message: 'Quonfig has no config yet (on_init_failure: :continue); ' \
|
|
180
|
+
'serving code defaults while it retries')
|
|
181
|
+
end
|
|
182
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY) if client.wait_for_ready && @client.equal?(client)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# OpenFeature dispatches PROVIDER_READY after +init+ returns. Wait for it
|
|
187
|
+
# so our PROVIDER_ERROR lands after it, not before. +provider_state+ is
|
|
188
|
+
# private in openfeature-sdk 0.5.x and public in 0.6.x.
|
|
189
|
+
def await_init_dispatch
|
|
190
|
+
config = @configuration
|
|
191
|
+
return unless config.respond_to?(:provider_state, true)
|
|
192
|
+
|
|
193
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + INIT_DISPATCH_WAIT_S
|
|
194
|
+
while config.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::NOT_READY
|
|
195
|
+
break if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
196
|
+
|
|
197
|
+
sleep 0.01
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
144
201
|
# Map a Quonfig::EvaluationDetails to the OpenFeature ResolutionDetails
|
|
145
202
|
# the SDK consumes. The Quonfig SDK's *_details methods don't raise, so
|
|
146
203
|
# this is a pure mapping layer.
|
|
@@ -187,6 +244,7 @@ module Quonfig
|
|
|
187
244
|
case error_code
|
|
188
245
|
when ::Quonfig::EvaluationDetails::ERROR_FLAG_NOT_FOUND then ErrorCode::FLAG_NOT_FOUND
|
|
189
246
|
when ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH then ErrorCode::TYPE_MISMATCH
|
|
247
|
+
when ::Quonfig::EvaluationDetails::ERROR_PROVIDER_NOT_READY then ErrorCode::PROVIDER_NOT_READY
|
|
190
248
|
else ErrorCode::GENERAL
|
|
191
249
|
end
|
|
192
250
|
end
|
|
@@ -204,7 +262,7 @@ module Quonfig
|
|
|
204
262
|
|
|
205
263
|
mapped_ctx = Context.map_context(evaluation_context, @targeting_key_mapping)
|
|
206
264
|
yield(client, mapped_ctx)
|
|
207
|
-
rescue ::Quonfig::Errors::UninitializedError, ::Quonfig::Errors::
|
|
265
|
+
rescue ::Quonfig::Errors::UninitializedError, ::Quonfig::Errors::InitializationError => e
|
|
208
266
|
ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
|
|
209
267
|
error_code: ErrorCode::PROVIDER_NOT_READY,
|
|
210
268
|
error_message: e.message)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quonfig-openfeature
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeff Dwyer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: openfeature-sdk
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.
|
|
33
|
+
version: 1.11.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 1.
|
|
40
|
+
version: 1.11.0
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: minitest
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|