quonfig 0.0.17 → 0.0.18
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 +8 -0
- data/lib/quonfig/datadir.rb +38 -0
- data/lib/quonfig/sse_config_client.rb +16 -2
- data/lib/quonfig/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 125753bc634b155cdae7cf4772705ee74c5ec37f4a612c9c52ea873a94d0c5ca
|
|
4
|
+
data.tar.gz: 2c6e09f01e7ed54cf7e6f0fbb33784ea62e0d2bd069a87e3d74dafe3ed97aeb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e9474c7aa96611977db52658ba730efab5aafddb811e68dbd8ce90833d3c3017f6d5bc9b870db54be4b125844f43d46d96c067179a5cfd744880e4ca32cbd79
|
|
7
|
+
data.tar.gz: 26e638dbdeb223f06d9742cd155fd2b5b33073b20ee6e397a3322564979042c9c9dff8a3f893127c30ca70544202fc04ba2e3ce6c6c2f58332613dba884f9c33
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.18 - 2026-05-21
|
|
4
|
+
|
|
5
|
+
- **Fix (SSE): give Net `read_timeout` headroom over the watchdog deadline (qfg-6y44).** `stream_once` armed two read deadlines at the identical `sse_read_timeout` value: `Net::HTTP#read_timeout` and the `ReadDeadlineWatchdog`. On the body read both were live, and the watchdog carries up to `POLL_INTERVAL` (0.25 s) of polling latency on top of its deadline — so when Net's (unreliable on the `read_body` path) stdlib timeout did fire, it could beat the watchdog and surface a `Net::ReadTimeout` instead of the `SSEReadDeadlineExceeded` the SDK is instrumented around. A new `READ_TIMEOUT_HEADROOM` (30 s) keeps Net's `read_timeout` as a redundant backstop while guaranteeing the watchdog fires first.
|
|
6
|
+
- **Fix (datadir): coerce int/double config values to numbers at load time (qfg-38sf.8).** Config files store `int`/`double` Value fields as JSON strings (`{"type":"int","value":"123"}`). api-delivery normalizes these to real numbers at config-load time (`Value.UnmarshalJSON`), so every HTTP/SSE envelope already carries JSON numbers — but the datadir loader read the files directly and passed the strings through verbatim. `Quonfig::Datadir` now runs a generic recursive `coerce_numeric_values` walk over each parsed config document before projecting it to a `ConfigResponse`: any Value node (`type` of `"int"`/`"double"` with a String `value`) is coerced via `Integer(value, 10)` / `Float(value)`, covering `default.rules[].value`, environment rules, `criteria[].valueToMatch`, weighted-value arms, and variants. An unparseable numeric string is left untouched (passthrough — never raises). Brings the datadir loader in line with sdk-go and api-delivery so the loaded envelope always carries real numbers regardless of who consumes it.
|
|
7
|
+
- **CI: pin `integration-test-data` to v2026.05.20 and guard against stale generated tests (#12).** The integration suite now resolves its YAML specs from a pinned tag, and a guard fails the build if the generated tests drift from the templates.
|
|
8
|
+
- **CI: skip the Chaos workflow on Dependabot PRs and de-flake the datadir-reload test (8f60d9c).**
|
|
9
|
+
- **Dependency bumps (CI actions):** `actions/setup-go` 5.6.0 → 6.4.0 (#7), `actions/checkout` 4.3.1 → 6.0.2 (#8), `actions/upload-artifact` 4.6.2 → 7.0.1 (#9), `ruby/setup-ruby` 1.306.0 → 1.310.0 (#10).
|
|
10
|
+
|
|
3
11
|
## 0.0.17 - 2026-05-19
|
|
4
12
|
|
|
5
13
|
- **Feat (datadir): opt-in `data_dir_auto_reload` (qfg-mol-2da).** Datadir mode previously loaded the workspace once at construction and served purely from memory. Set `data_dir_auto_reload: true` to have the SDK watch the configured `datadir`, re-read `Quonfig::Datadir.load_envelope`, and fire the existing `on_update` callback whenever files change. Adds `listen ~> 3.8` (FSEvents on macOS, inotify on Linux, polling fallback on Windows) as a runtime dep. Behavior: parse-then-swap (a failed parse keeps the previous envelope and skips the callback), debounced (`data_dir_auto_reload_debounce_ms`, default 200 ms — bursts coalesce to one reload), and gracefully downgrades when watch registration fails (read-only fs, immutable container, missing native backend). Symlinked datadirs are resolved to their real path before watching. Default is `false`; opt-in only.
|
data/lib/quonfig/datadir.rb
CHANGED
|
@@ -42,6 +42,7 @@ module Quonfig
|
|
|
42
42
|
raw = JSON.parse(File.read(path))
|
|
43
43
|
raise ArgumentError, "[quonfig] config has empty key — file is not a Quonfig Config: #{path}" if raw['key'].nil? || raw['key'].to_s.empty?
|
|
44
44
|
|
|
45
|
+
coerce_numeric_values(raw)
|
|
45
46
|
configs << to_config_response(raw, env_id)
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -94,5 +95,42 @@ module Quonfig
|
|
|
94
95
|
|
|
95
96
|
raw || false
|
|
96
97
|
end
|
|
98
|
+
|
|
99
|
+
# Config files store int/double Value fields as JSON strings
|
|
100
|
+
# (`{"type":"int","value":"123"}`). api-delivery normalizes these to real
|
|
101
|
+
# numbers at config-load time (`Value.UnmarshalJSON`), so every envelope it
|
|
102
|
+
# emits over HTTP/SSE already carries JSON numbers. In datadir mode we read
|
|
103
|
+
# the files directly, so we must coerce here to match.
|
|
104
|
+
#
|
|
105
|
+
# Walks the parsed config document in place, coercing every Value node — any
|
|
106
|
+
# Hash with a `type` of `"int"`/`"double"` and a String `value` — to a real
|
|
107
|
+
# number. A generic recursive walk covers `default.rules[].value`,
|
|
108
|
+
# environment rules, `criteria[].valueToMatch`, weighted-value arms, and
|
|
109
|
+
# variants without enumerating each location. On parse failure the original
|
|
110
|
+
# string is left in place (passthrough — never raise).
|
|
111
|
+
def coerce_numeric_values(node)
|
|
112
|
+
case node
|
|
113
|
+
when Hash
|
|
114
|
+
coerce_numeric_value_field(node)
|
|
115
|
+
node.each_value { |child| coerce_numeric_values(child) }
|
|
116
|
+
when Array
|
|
117
|
+
node.each { |child| coerce_numeric_values(child) }
|
|
118
|
+
end
|
|
119
|
+
node
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def coerce_numeric_value_field(hash)
|
|
123
|
+
value = hash['value']
|
|
124
|
+
return unless value.is_a?(String)
|
|
125
|
+
|
|
126
|
+
case hash['type']
|
|
127
|
+
when 'int'
|
|
128
|
+
hash['value'] = Integer(value, 10)
|
|
129
|
+
when 'double'
|
|
130
|
+
hash['value'] = Float(value)
|
|
131
|
+
end
|
|
132
|
+
rescue ArgumentError, TypeError
|
|
133
|
+
# Unparseable numeric string — leave the original value untouched.
|
|
134
|
+
end
|
|
97
135
|
end
|
|
98
136
|
end
|
|
@@ -56,6 +56,17 @@ module Quonfig
|
|
|
56
56
|
# Anything else (5xx, 429, network errors) stays on the transient path.
|
|
57
57
|
TERMINAL_HTTP_CODES = [401, 403, 404].freeze
|
|
58
58
|
|
|
59
|
+
# qfg-6y44: headroom added to +sse_read_timeout+ when configuring
|
|
60
|
+
# Net::HTTP#read_timeout. The ReadDeadlineWatchdog already covers both the
|
|
61
|
+
# header and body reads at exactly +sse_read_timeout+; Net's own
|
|
62
|
+
# read_timeout is only a redundant backstop. Arming it at the *same* value
|
|
63
|
+
# as the watchdog makes the two race — and on the body path Net's
|
|
64
|
+
# (unreliable) timeout can fire first, surfacing a Net::ReadTimeout instead
|
|
65
|
+
# of the SSEReadDeadlineExceeded the SDK is instrumented around. Giving
|
|
66
|
+
# Net's read_timeout this much headroom keeps it a backstop without ever
|
|
67
|
+
# letting it win the race.
|
|
68
|
+
READ_TIMEOUT_HEADROOM = 30
|
|
69
|
+
|
|
59
70
|
# +on_error+: optional callable invoked on every SSE error edge. Parent
|
|
60
71
|
# Quonfig::Client wires this to drive @sse_state -> :error so that
|
|
61
72
|
# +connection_state+ reflects the disconnect (qfg-47c2.27).
|
|
@@ -254,8 +265,11 @@ module Quonfig
|
|
|
254
265
|
http.use_ssl = (uri.scheme == 'https')
|
|
255
266
|
http.open_timeout = @options.sse_connect_timeout
|
|
256
267
|
# Keep Net::HTTP's read_timeout as a backstop for the header read
|
|
257
|
-
# (where it does apply reliably). The watchdog covers the body path
|
|
258
|
-
|
|
268
|
+
# (where it does apply reliably). The watchdog covers the body path —
|
|
269
|
+
# so read_timeout gets READ_TIMEOUT_HEADROOM over the watchdog deadline
|
|
270
|
+
# to guarantee the watchdog fires first and we get a deterministic
|
|
271
|
+
# SSEReadDeadlineExceeded rather than a racy Net::ReadTimeout (qfg-6y44).
|
|
272
|
+
http.read_timeout = @options.sse_read_timeout + READ_TIMEOUT_HEADROOM
|
|
259
273
|
|
|
260
274
|
req = Net::HTTP::Get.new(uri.request_uri, headers)
|
|
261
275
|
|
data/lib/quonfig/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quonfig
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.18
|
|
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-05-
|
|
11
|
+
date: 2026-05-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|