openfeature-sdk 0.6.4 → 0.6.6

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.ruby-version +1 -1
  4. data/.tool-versions +1 -1
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +20 -0
  7. data/CLAUDE.md +4 -1
  8. data/CONTRIBUTING.md +15 -1
  9. data/Gemfile +17 -2
  10. data/Gemfile.lock +98 -67
  11. data/README.md +24 -8
  12. data/Rakefile +14 -0
  13. data/SECURITY.md +10 -0
  14. data/Steepfile +7 -0
  15. data/examples/basic_usage.rb +22 -0
  16. data/examples/custom_provider.rb +56 -0
  17. data/examples/rails_integration.rb +60 -0
  18. data/lib/open_feature/sdk/api.rb +1 -0
  19. data/lib/open_feature/sdk/client.rb +2 -3
  20. data/lib/open_feature/sdk/configuration.rb +26 -13
  21. data/lib/open_feature/sdk/evaluation_error.rb +28 -0
  22. data/lib/open_feature/sdk/hooks/hook_executor.rb +5 -5
  23. data/lib/open_feature/sdk/hooks/logging_hook.rb +1 -2
  24. data/lib/open_feature/sdk/provider/in_memory_provider.rb +24 -0
  25. data/lib/open_feature/sdk/provider_event.rb +5 -1
  26. data/lib/open_feature/sdk/provider_state_registry.rb +2 -2
  27. data/lib/open_feature/sdk/version.rb +1 -1
  28. data/openfeature-sdk.gemspec +34 -0
  29. data/rbs_collection.lock.yaml +292 -0
  30. data/rbs_collection.yaml +14 -0
  31. data/renovate.json +1 -1
  32. data/sig/open_feature/sdk/api.rbs +35 -0
  33. data/sig/open_feature/sdk/client.rbs +49 -0
  34. data/sig/open_feature/sdk/client_metadata.rbs +11 -0
  35. data/sig/open_feature/sdk/configuration.rbs +44 -0
  36. data/sig/open_feature/sdk/evaluation_context.rbs +19 -0
  37. data/sig/open_feature/sdk/evaluation_context_builder.rbs +7 -0
  38. data/sig/open_feature/sdk/evaluation_details.rbs +19 -0
  39. data/sig/open_feature/sdk/evaluation_error.rbs +11 -0
  40. data/sig/open_feature/sdk/event_dispatcher.rbs +24 -0
  41. data/sig/open_feature/sdk/hooks/hints.rbs +30 -0
  42. data/sig/open_feature/sdk/hooks/hook.rbs +19 -0
  43. data/sig/open_feature/sdk/hooks/hook_context.rbs +18 -0
  44. data/sig/open_feature/sdk/hooks/hook_executor.rbs +20 -0
  45. data/sig/open_feature/sdk/hooks/logging_hook.rbs +25 -0
  46. data/sig/open_feature/sdk/provider/error_code.rbs +16 -0
  47. data/sig/open_feature/sdk/provider/event_emitter.rbs +16 -0
  48. data/sig/open_feature/sdk/provider/in_memory_provider.rbs +46 -0
  49. data/sig/open_feature/sdk/provider/no_op_provider.rbs +25 -0
  50. data/sig/open_feature/sdk/provider/provider_metadata.rbs +11 -0
  51. data/sig/open_feature/sdk/provider/reason.rbs +17 -0
  52. data/sig/open_feature/sdk/provider/resolution_details.rbs +19 -0
  53. data/sig/open_feature/sdk/provider.rbs +24 -0
  54. data/sig/open_feature/sdk/provider_event.rbs +14 -0
  55. data/sig/open_feature/sdk/provider_initialization_error.rbs +11 -0
  56. data/sig/open_feature/sdk/provider_state.rbs +13 -0
  57. data/sig/open_feature/sdk/provider_state_registry.rbs +22 -0
  58. data/sig/open_feature/sdk/telemetry.rbs +29 -0
  59. data/sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs +12 -0
  60. data/sig/open_feature/sdk/tracking_event_details.rbs +10 -0
  61. data/sig/open_feature/sdk/transaction_context_propagator.rbs +11 -0
  62. data/sig/open_feature/sdk/version.rbs +5 -0
  63. data/sig/open_feature/sdk.rbs +22 -0
  64. metadata +54 -140
  65. data/.simplecov +0 -12
  66. data/docs/plans/2026-03-07-telemetry-utility-design.md +0 -98
  67. data/docs/plans/2026-03-07-telemetry-utility-plan.md +0 -578
@@ -1,578 +0,0 @@
1
- # Telemetry Utility Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Add a dependency-free `OpenFeature::SDK::Telemetry` module that creates OTel-compatible evaluation events from hook context and evaluation details.
6
-
7
- **Architecture:** Single module (`Telemetry`) in one file with constants, a Struct, and one public `module_function`. Designed for consumption in a hook's `finally` stage. No third-party dependencies.
8
-
9
- **Tech Stack:** Ruby >= 3.1, RSpec, Standard Ruby (linter)
10
-
11
- ---
12
-
13
- ### Task 1: Create telemetry module with constants and struct
14
-
15
- **Files:**
16
- - Create: `lib/open_feature/sdk/telemetry.rb`
17
-
18
- **Step 1: Create the module file with constants and struct**
19
-
20
- ```ruby
21
- # frozen_string_literal: true
22
-
23
- module OpenFeature
24
- module SDK
25
- module Telemetry
26
- EVENT_NAME = "feature_flag.evaluation"
27
-
28
- FLAG_KEY = "feature_flag.key"
29
- CONTEXT_ID_KEY = "feature_flag.context.id"
30
- ERROR_MESSAGE_KEY = "error.message"
31
- ERROR_TYPE_KEY = "error.type"
32
- PROVIDER_NAME_KEY = "feature_flag.provider.name"
33
- RESULT_REASON_KEY = "feature_flag.result.reason"
34
- RESULT_VALUE_KEY = "feature_flag.result.value"
35
- RESULT_VARIANT_KEY = "feature_flag.result.variant"
36
- FLAG_SET_ID_KEY = "feature_flag.set.id"
37
- VERSION_KEY = "feature_flag.version"
38
-
39
- METADATA_KEY_MAP = {
40
- "contextId" => CONTEXT_ID_KEY,
41
- "flagSetId" => FLAG_SET_ID_KEY,
42
- "version" => VERSION_KEY
43
- }.freeze
44
-
45
- EvaluationEvent = Struct.new(:name, :attributes, keyword_init: true)
46
- end
47
- end
48
- end
49
- ```
50
-
51
- **Step 2: Wire up the require**
52
-
53
- Modify: `lib/open_feature/sdk.rb` — add `require_relative "sdk/telemetry"` after the existing requires (line 3 area).
54
-
55
- **Step 3: Verify it loads**
56
-
57
- Run: `bundle exec ruby -e "require 'open_feature/sdk'; puts OpenFeature::SDK::Telemetry::EVENT_NAME"`
58
- Expected: `feature_flag.evaluation`
59
-
60
- **Step 4: Commit**
61
-
62
- ```bash
63
- git add lib/open_feature/sdk/telemetry.rb lib/open_feature/sdk.rb
64
- git commit -s -S -m "feat(telemetry): add Telemetry module with OTel constants and EvaluationEvent struct"
65
- ```
66
-
67
- ---
68
-
69
- ### Task 2: Write tests for happy path and return type
70
-
71
- **Files:**
72
- - Create: `spec/open_feature/sdk/telemetry_spec.rb`
73
-
74
- **Step 1: Write the test file with happy path and return type tests**
75
-
76
- ```ruby
77
- # frozen_string_literal: true
78
-
79
- require "spec_helper"
80
-
81
- RSpec.describe OpenFeature::SDK::Telemetry do
82
- let(:client_metadata) { OpenFeature::SDK::ClientMetadata.new(domain: "test-domain") }
83
- let(:provider_metadata) { OpenFeature::SDK::Provider::ProviderMetadata.new(name: "test-provider") }
84
- let(:evaluation_context) { OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123") }
85
-
86
- let(:hook_context) do
87
- OpenFeature::SDK::Hooks::HookContext.new(
88
- flag_key: "my-flag",
89
- flag_value_type: :boolean,
90
- default_value: false,
91
- evaluation_context: evaluation_context,
92
- client_metadata: client_metadata,
93
- provider_metadata: provider_metadata
94
- )
95
- end
96
-
97
- let(:flag_metadata) do
98
- {
99
- "contextId" => "ctx-456",
100
- "flagSetId" => "set-789",
101
- "version" => "v1.0"
102
- }
103
- end
104
-
105
- let(:resolution_details) do
106
- OpenFeature::SDK::Provider::ResolutionDetails.new(
107
- value: true,
108
- reason: "TARGETING_MATCH",
109
- variant: "enabled",
110
- flag_metadata: flag_metadata
111
- )
112
- end
113
-
114
- let(:evaluation_details) do
115
- OpenFeature::SDK::EvaluationDetails.new(
116
- flag_key: "my-flag",
117
- resolution_details: resolution_details
118
- )
119
- end
120
-
121
- describe ".create_evaluation_event" do
122
- context "with full data" do
123
- it "returns an EvaluationEvent with all attributes populated" do
124
- event = described_class.create_evaluation_event(
125
- hook_context: hook_context,
126
- evaluation_details: evaluation_details
127
- )
128
-
129
- expect(event).to be_a(OpenFeature::SDK::Telemetry::EvaluationEvent)
130
- expect(event.name).to eq("feature_flag.evaluation")
131
- expect(event.attributes).to eq(
132
- "feature_flag.key" => "my-flag",
133
- "feature_flag.provider.name" => "test-provider",
134
- "feature_flag.result.variant" => "enabled",
135
- "feature_flag.result.reason" => "targeting_match",
136
- "feature_flag.context.id" => "ctx-456",
137
- "feature_flag.set.id" => "set-789",
138
- "feature_flag.version" => "v1.0"
139
- )
140
- end
141
- end
142
- end
143
- end
144
- ```
145
-
146
- **Step 2: Run the test to verify it fails**
147
-
148
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
149
- Expected: FAIL — `NoMethodError: undefined method 'create_evaluation_event'`
150
-
151
- **Step 3: Commit the failing test**
152
-
153
- ```bash
154
- git add spec/open_feature/sdk/telemetry_spec.rb
155
- git commit -s -S -m "test(telemetry): add failing test for happy path"
156
- ```
157
-
158
- ---
159
-
160
- ### Task 3: Implement create_evaluation_event
161
-
162
- **Files:**
163
- - Modify: `lib/open_feature/sdk/telemetry.rb`
164
-
165
- **Step 1: Add the implementation to the Telemetry module**
166
-
167
- Add after the `EvaluationEvent` struct definition:
168
-
169
- ```ruby
170
- module_function
171
-
172
- def create_evaluation_event(hook_context:, evaluation_details:)
173
- attributes = {FLAG_KEY => hook_context.flag_key}
174
-
175
- provider_name = hook_context.provider_metadata&.name
176
- attributes[PROVIDER_NAME_KEY] = provider_name if provider_name
177
-
178
- targeting_key = hook_context.evaluation_context&.targeting_key
179
- attributes[CONTEXT_ID_KEY] = targeting_key if targeting_key
180
-
181
- if evaluation_details
182
- if evaluation_details.variant
183
- attributes[RESULT_VARIANT_KEY] = evaluation_details.variant
184
- else
185
- attributes[RESULT_VALUE_KEY] = evaluation_details.value
186
- end
187
-
188
- if evaluation_details.reason
189
- attributes[RESULT_REASON_KEY] = evaluation_details.reason.downcase
190
- end
191
-
192
- if evaluation_details.error_code
193
- attributes[ERROR_TYPE_KEY] = evaluation_details.error_code.downcase
194
- end
195
-
196
- if evaluation_details.error_message
197
- attributes[ERROR_MESSAGE_KEY] = evaluation_details.error_message
198
- end
199
-
200
- extract_metadata(evaluation_details.flag_metadata, attributes)
201
- end
202
-
203
- EvaluationEvent.new(name: EVENT_NAME, attributes: attributes)
204
- end
205
-
206
- def extract_metadata(flag_metadata, attributes)
207
- return unless flag_metadata
208
-
209
- METADATA_KEY_MAP.each do |metadata_key, otel_key|
210
- value = flag_metadata[metadata_key]
211
- attributes[otel_key] = value unless value.nil?
212
- end
213
- end
214
-
215
- private_class_method :extract_metadata
216
- ```
217
-
218
- **Step 2: Run the test to verify it passes**
219
-
220
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
221
- Expected: PASS (1 example, 0 failures)
222
-
223
- **Step 3: Commit**
224
-
225
- ```bash
226
- git add lib/open_feature/sdk/telemetry.rb
227
- git commit -s -S -m "feat(telemetry): implement create_evaluation_event"
228
- ```
229
-
230
- ---
231
-
232
- ### Task 4: Add tests for variant vs value precedence
233
-
234
- **Files:**
235
- - Modify: `spec/open_feature/sdk/telemetry_spec.rb`
236
-
237
- **Step 1: Add variant/value precedence tests**
238
-
239
- Add inside the `describe ".create_evaluation_event"` block:
240
-
241
- ```ruby
242
- context "variant vs value precedence" do
243
- it "uses variant when present and omits value" do
244
- event = described_class.create_evaluation_event(
245
- hook_context: hook_context,
246
- evaluation_details: evaluation_details
247
- )
248
-
249
- expect(event.attributes).to have_key("feature_flag.result.variant")
250
- expect(event.attributes).not_to have_key("feature_flag.result.value")
251
- end
252
-
253
- it "uses value when variant is nil" do
254
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
255
- value: "blue",
256
- reason: "STATIC"
257
- )
258
- details = OpenFeature::SDK::EvaluationDetails.new(
259
- flag_key: "color-flag",
260
- resolution_details: resolution
261
- )
262
-
263
- event = described_class.create_evaluation_event(
264
- hook_context: hook_context,
265
- evaluation_details: details
266
- )
267
-
268
- expect(event.attributes["feature_flag.result.value"]).to eq("blue")
269
- expect(event.attributes).not_to have_key("feature_flag.result.variant")
270
- end
271
- end
272
- ```
273
-
274
- **Step 2: Run tests**
275
-
276
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
277
- Expected: PASS (3 examples, 0 failures)
278
-
279
- **Step 3: Commit**
280
-
281
- ```bash
282
- git add spec/open_feature/sdk/telemetry_spec.rb
283
- git commit -s -S -m "test(telemetry): add variant vs value precedence tests"
284
- ```
285
-
286
- ---
287
-
288
- ### Task 5: Add tests for enum downcasing
289
-
290
- **Files:**
291
- - Modify: `spec/open_feature/sdk/telemetry_spec.rb`
292
-
293
- **Step 1: Add enum downcasing tests**
294
-
295
- Add inside the `describe ".create_evaluation_event"` block:
296
-
297
- ```ruby
298
- context "enum downcasing" do
299
- it "downcases reason to OTel convention" do
300
- event = described_class.create_evaluation_event(
301
- hook_context: hook_context,
302
- evaluation_details: evaluation_details
303
- )
304
-
305
- expect(event.attributes["feature_flag.result.reason"]).to eq("targeting_match")
306
- end
307
-
308
- it "downcases error_code to OTel convention" do
309
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
310
- value: false,
311
- reason: "ERROR",
312
- error_code: "FLAG_NOT_FOUND",
313
- error_message: "Flag not found"
314
- )
315
- details = OpenFeature::SDK::EvaluationDetails.new(
316
- flag_key: "missing-flag",
317
- resolution_details: resolution
318
- )
319
-
320
- event = described_class.create_evaluation_event(
321
- hook_context: hook_context,
322
- evaluation_details: details
323
- )
324
-
325
- expect(event.attributes["error.type"]).to eq("flag_not_found")
326
- expect(event.attributes["feature_flag.result.reason"]).to eq("error")
327
- end
328
- end
329
- ```
330
-
331
- **Step 2: Run tests**
332
-
333
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
334
- Expected: PASS (5 examples, 0 failures)
335
-
336
- **Step 3: Commit**
337
-
338
- ```bash
339
- git add spec/open_feature/sdk/telemetry_spec.rb
340
- git commit -s -S -m "test(telemetry): add enum downcasing tests"
341
- ```
342
-
343
- ---
344
-
345
- ### Task 6: Add tests for error attributes and nil evaluation_details
346
-
347
- **Files:**
348
- - Modify: `spec/open_feature/sdk/telemetry_spec.rb`
349
-
350
- **Step 1: Add error and nil evaluation_details tests**
351
-
352
- Add inside the `describe ".create_evaluation_event"` block:
353
-
354
- ```ruby
355
- context "error attributes" do
356
- it "includes error attributes only when error occurred" do
357
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
358
- value: false,
359
- reason: "ERROR",
360
- error_code: "PARSE_ERROR",
361
- error_message: "Could not parse flag"
362
- )
363
- details = OpenFeature::SDK::EvaluationDetails.new(
364
- flag_key: "bad-flag",
365
- resolution_details: resolution
366
- )
367
-
368
- event = described_class.create_evaluation_event(
369
- hook_context: hook_context,
370
- evaluation_details: details
371
- )
372
-
373
- expect(event.attributes["error.type"]).to eq("parse_error")
374
- expect(event.attributes["error.message"]).to eq("Could not parse flag")
375
- end
376
-
377
- it "omits error attributes when no error" do
378
- event = described_class.create_evaluation_event(
379
- hook_context: hook_context,
380
- evaluation_details: evaluation_details
381
- )
382
-
383
- expect(event.attributes).not_to have_key("error.type")
384
- expect(event.attributes).not_to have_key("error.message")
385
- end
386
- end
387
-
388
- context "nil evaluation_details" do
389
- it "returns event with only flag_key and available context" do
390
- event = described_class.create_evaluation_event(
391
- hook_context: hook_context,
392
- evaluation_details: nil
393
- )
394
-
395
- expect(event.name).to eq("feature_flag.evaluation")
396
- expect(event.attributes).to eq(
397
- "feature_flag.key" => "my-flag",
398
- "feature_flag.provider.name" => "test-provider",
399
- "feature_flag.context.id" => "user-123"
400
- )
401
- end
402
- end
403
- ```
404
-
405
- **Step 2: Run tests**
406
-
407
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
408
- Expected: PASS (8 examples, 0 failures)
409
-
410
- **Step 3: Commit**
411
-
412
- ```bash
413
- git add spec/open_feature/sdk/telemetry_spec.rb
414
- git commit -s -S -m "test(telemetry): add error attributes and nil evaluation_details tests"
415
- ```
416
-
417
- ---
418
-
419
- ### Task 7: Add tests for flag metadata and context ID precedence
420
-
421
- **Files:**
422
- - Modify: `spec/open_feature/sdk/telemetry_spec.rb`
423
-
424
- **Step 1: Add metadata and context ID tests**
425
-
426
- Add inside the `describe ".create_evaluation_event"` block:
427
-
428
- ```ruby
429
- context "flag metadata" do
430
- it "ignores nil flag_metadata" do
431
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
432
- value: true,
433
- variant: "on"
434
- )
435
- details = OpenFeature::SDK::EvaluationDetails.new(
436
- flag_key: "my-flag",
437
- resolution_details: resolution
438
- )
439
-
440
- event = described_class.create_evaluation_event(
441
- hook_context: hook_context,
442
- evaluation_details: details
443
- )
444
-
445
- expect(event.attributes).not_to have_key("feature_flag.set.id")
446
- expect(event.attributes).not_to have_key("feature_flag.version")
447
- end
448
-
449
- it "ignores empty flag_metadata" do
450
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
451
- value: true,
452
- variant: "on",
453
- flag_metadata: {}
454
- )
455
- details = OpenFeature::SDK::EvaluationDetails.new(
456
- flag_key: "my-flag",
457
- resolution_details: resolution
458
- )
459
-
460
- event = described_class.create_evaluation_event(
461
- hook_context: hook_context,
462
- evaluation_details: details
463
- )
464
-
465
- expect(event.attributes).not_to have_key("feature_flag.set.id")
466
- expect(event.attributes).not_to have_key("feature_flag.version")
467
- end
468
-
469
- it "ignores unknown metadata keys" do
470
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
471
- value: true,
472
- variant: "on",
473
- flag_metadata: {"customKey" => "custom-value", "anotherKey" => 42}
474
- )
475
- details = OpenFeature::SDK::EvaluationDetails.new(
476
- flag_key: "my-flag",
477
- resolution_details: resolution
478
- )
479
-
480
- event = described_class.create_evaluation_event(
481
- hook_context: hook_context,
482
- evaluation_details: details
483
- )
484
-
485
- expect(event.attributes).not_to have_key("customKey")
486
- expect(event.attributes).not_to have_key("anotherKey")
487
- end
488
- end
489
-
490
- context "context ID precedence" do
491
- it "uses metadata contextId over targeting_key" do
492
- event = described_class.create_evaluation_event(
493
- hook_context: hook_context,
494
- evaluation_details: evaluation_details
495
- )
496
-
497
- # flag_metadata has contextId "ctx-456", targeting_key is "user-123"
498
- expect(event.attributes["feature_flag.context.id"]).to eq("ctx-456")
499
- end
500
-
501
- it "falls back to targeting_key when no contextId in metadata" do
502
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
503
- value: true,
504
- variant: "on",
505
- flag_metadata: {"flagSetId" => "set-1"}
506
- )
507
- details = OpenFeature::SDK::EvaluationDetails.new(
508
- flag_key: "my-flag",
509
- resolution_details: resolution
510
- )
511
-
512
- event = described_class.create_evaluation_event(
513
- hook_context: hook_context,
514
- evaluation_details: details
515
- )
516
-
517
- expect(event.attributes["feature_flag.context.id"]).to eq("user-123")
518
- end
519
-
520
- it "omits context ID when neither targeting_key nor contextId available" do
521
- bare_context = OpenFeature::SDK::EvaluationContext.new(env: "prod")
522
- bare_hook_context = OpenFeature::SDK::Hooks::HookContext.new(
523
- flag_key: "my-flag",
524
- flag_value_type: :boolean,
525
- default_value: false,
526
- evaluation_context: bare_context
527
- )
528
- resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
529
- value: true,
530
- variant: "on"
531
- )
532
- details = OpenFeature::SDK::EvaluationDetails.new(
533
- flag_key: "my-flag",
534
- resolution_details: resolution
535
- )
536
-
537
- event = described_class.create_evaluation_event(
538
- hook_context: bare_hook_context,
539
- evaluation_details: details
540
- )
541
-
542
- expect(event.attributes).not_to have_key("feature_flag.context.id")
543
- end
544
- end
545
- ```
546
-
547
- **Step 2: Run tests**
548
-
549
- Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
550
- Expected: PASS (14 examples, 0 failures)
551
-
552
- **Step 3: Commit**
553
-
554
- ```bash
555
- git add spec/open_feature/sdk/telemetry_spec.rb
556
- git commit -s -S -m "test(telemetry): add flag metadata and context ID precedence tests"
557
- ```
558
-
559
- ---
560
-
561
- ### Task 8: Run full suite and lint
562
-
563
- **Step 1: Run full test suite**
564
-
565
- Run: `bundle exec rspec`
566
- Expected: All existing tests still pass, plus 14 new telemetry tests.
567
-
568
- **Step 2: Run linter**
569
-
570
- Run: `bundle exec standardrb`
571
- Expected: No offenses. If any, fix with `bundle exec standardrb --fix`.
572
-
573
- **Step 3: Final commit if lint fixes needed**
574
-
575
- ```bash
576
- git add -A
577
- git commit -s -S -m "style(telemetry): fix standardrb lint issues"
578
- ```