schematichq 1.5.2 → 1.5.3

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.fern/metadata.json +3 -3
  3. data/.fern/replay.lock +8 -338
  4. data/.fernignore +6 -0
  5. data/README.md +141 -0
  6. data/custom.gemspec.rb +6 -0
  7. data/lib/schematic/accounts/types/update_environment_request_body.rb +2 -0
  8. data/lib/schematic/client.rb +1 -1
  9. data/lib/schematic/credits/client.rb +50 -6
  10. data/lib/schematic/credits/leases/check.rb +477 -0
  11. data/lib/schematic/credits/leases/lease_manager.rb +565 -0
  12. data/lib/schematic/credits/leases/lease_store.rb +240 -0
  13. data/lib/schematic/credits/leases/redis_lease_store.rb +358 -0
  14. data/lib/schematic/credits/leases/redis_reservation_store.rb +326 -0
  15. data/lib/schematic/credits/leases/reservation_store.rb +161 -0
  16. data/lib/schematic/credits/leases/server_check.rb +237 -0
  17. data/lib/schematic/credits/leases/track.rb +66 -0
  18. data/lib/schematic/credits/leases/types.rb +329 -0
  19. data/lib/schematic/credits/leases/wire_client.rb +83 -0
  20. data/lib/schematic/credits/types/acquire_credit_lease_request_body.rb +2 -0
  21. data/lib/schematic/credits/types/create_credit_spend_policy_request_body.rb +5 -1
  22. data/lib/schematic/credits/types/extend_credit_lease_request_body.rb +2 -0
  23. data/lib/schematic/credits/types/get_credit_spend_policy_usage_params.rb +16 -0
  24. data/lib/schematic/credits/types/get_credit_spend_policy_usage_request.rb +15 -0
  25. data/lib/schematic/credits/types/get_credit_spend_policy_usage_response.rb +13 -0
  26. data/lib/schematic/credits/types/update_credit_spend_policy_request_body.rb +4 -0
  27. data/lib/schematic/datastream/client.rb +32 -2
  28. data/lib/schematic/entitlements/client.rb +165 -0
  29. data/lib/schematic/entitlements/types/count_company_user_usage_params.rb +24 -0
  30. data/lib/schematic/entitlements/types/count_company_user_usage_request.rb +23 -0
  31. data/lib/schematic/entitlements/types/count_company_user_usage_response.rb +13 -0
  32. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_params.rb +16 -0
  33. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_request.rb +15 -0
  34. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_response.rb +13 -0
  35. data/lib/schematic/entitlements/types/list_company_user_usage_params.rb +24 -0
  36. data/lib/schematic/entitlements/types/list_company_user_usage_request.rb +23 -0
  37. data/lib/schematic/entitlements/types/list_company_user_usage_response.rb +13 -0
  38. data/lib/schematic/plangroups/client.rb +2 -0
  39. data/lib/schematic/plangroups/types/create_plan_group_request_body.rb +2 -0
  40. data/lib/schematic/plangroups/types/update_plan_group_request_body.rb +2 -0
  41. data/lib/schematic/planmigrations/client.rb +6 -0
  42. data/lib/schematic/planmigrations/types/count_migrations_params.rb +2 -0
  43. data/lib/schematic/planmigrations/types/count_migrations_request.rb +2 -0
  44. data/lib/schematic/planmigrations/types/create_migration_input.rb +2 -0
  45. data/lib/schematic/planmigrations/types/list_migrations_params.rb +2 -0
  46. data/lib/schematic/planmigrations/types/list_migrations_request.rb +2 -0
  47. data/lib/schematic/plans/types/publish_plan_version_request_body.rb +4 -0
  48. data/lib/schematic/plans/types/retry_custom_plan_billing_request_body.rb +2 -0
  49. data/lib/schematic/rules_engine.rb +37 -0
  50. data/lib/schematic/schematic_client.rb +830 -33
  51. data/lib/schematic/types/capture_raw_event.rb +4 -0
  52. data/lib/schematic/types/check_flags_response_data.rb +2 -0
  53. data/lib/schematic/types/company_detail_response_data.rb +2 -0
  54. data/lib/schematic/types/company_plan_detail_response_data.rb +2 -0
  55. data/lib/schematic/types/company_user_usage_metrics_response_data.rb +15 -0
  56. data/lib/schematic/types/company_user_usage_response_data.rb +19 -0
  57. data/lib/schematic/types/company_user_usage_row_response_data.rb +17 -0
  58. data/lib/schematic/types/component_display_settings.rb +2 -0
  59. data/lib/schematic/types/component_settings_response_data.rb +2 -0
  60. data/lib/schematic/types/credit_event_ledger_response_data.rb +5 -1
  61. data/lib/schematic/types/credit_event_type.rb +3 -0
  62. data/lib/schematic/types/credit_ledger_entry_kind.rb +17 -0
  63. data/lib/schematic/types/credit_spend_policy.rb +25 -0
  64. data/lib/schematic/types/credit_spend_policy_response_data.rb +12 -0
  65. data/lib/schematic/types/credit_spend_window.rb +11 -0
  66. data/lib/schematic/types/credit_spend_window_unit.rb +13 -0
  67. data/lib/schematic/types/custom_plan_billing_response_data.rb +2 -0
  68. data/lib/schematic/types/environment_detail_response_data.rb +2 -0
  69. data/lib/schematic/types/environment_response_data.rb +2 -0
  70. data/lib/schematic/types/estimated_plan_total.rb +13 -0
  71. data/lib/schematic/types/pending_migration_response_data.rb +6 -0
  72. data/lib/schematic/types/plan_version_migration_response_data.rb +2 -0
  73. data/lib/schematic/types/plan_version_migration_strategy.rb +1 -0
  74. data/lib/schematic/types/rules_engine_schema_version.rb +1 -1
  75. data/lib/schematic/types/rulesengine_company.rb +2 -0
  76. data/lib/schematic/types/rulesengine_credit_spend_policy.rb +25 -0
  77. data/lib/schematic/types/rulesengine_credit_spend_policy_scope.rb +13 -0
  78. data/lib/schematic/types/rulesengine_credit_spend_window.rb +11 -0
  79. data/lib/schematic/types/rulesengine_user.rb +2 -0
  80. data/lib/schematic/types/user_usage_metric.rb +12 -0
  81. data/lib/schematic/version.rb +1 -1
  82. data/lib/schematic.rb +26 -2
  83. data/lib/schematichq.rb +10 -0
  84. data/reference.md +474 -9
  85. metadata +36 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5f68b11540e455fec79249c0a3e8a6bca7279e00ae3192f855e424eba9314c9
4
- data.tar.gz: 732ed6c93fc15dd15b453fb4b873c4f1c252db187a750e6a633a76c6088feefa
3
+ metadata.gz: 42a7ac411958f446624efef8e76092dec19e652603089848b36d93fbe53a5f3f
4
+ data.tar.gz: 942a73f63c86d6c66f3bf57a369578479e18529943a3b234900e6b87116046e9
5
5
  SHA512:
6
- metadata.gz: 8a3063e0d0d534c99d77b4cbc98272953ccbc04292f67768e7323d99faeaddc69f520ac91922c7c9d695fdb2b4240643213ecbdbe75d03a249871385e7ec5386
7
- data.tar.gz: 67d03301abfdbb17162052217094762e656afee2b0f2e4ed32244858f17f5a807c62a866281de236776d53838e40bc08fa1537cbb3794f0e4c7d081d2de0e12b
6
+ metadata.gz: 90da04958f952788cb9fa76c46f5c791e2eba2754aec598007b7c66c0a5f80777e325345fe337155055a5301e3b61c482204dde4ef7d74830bb87d0f539a9126
7
+ data.tar.gz: 276cd27a9f3c58009313ff802cf87f09aafc7f689ab5d916e10764dda976dd06a553d7a65e8e69a02b9f358e3409faba975d26eac3d52c222aed8895673cf05f
data/.fern/metadata.json CHANGED
@@ -13,10 +13,10 @@
13
13
  "webrick": ">= 1.0"
14
14
  }
15
15
  },
16
- "originGitCommit": "4ca95f7ba356ea2c3b42db5e3e78c064477b3a8d",
16
+ "originGitCommit": "6a2f1e8406783d0f2591db2053e1a8a8df4a9046",
17
17
  "originGitCommitIsDirty": false,
18
18
  "invokedBy": "ci",
19
- "requestedVersion": "1.5.2",
19
+ "requestedVersion": "1.5.3",
20
20
  "ciProvider": "github",
21
- "sdkVersion": "1.5.2"
21
+ "sdkVersion": "1.5.3"
22
22
  }
data/.fern/replay.lock CHANGED
@@ -114,341 +114,11 @@ generations:
114
114
  cli_version: unknown
115
115
  generator_versions:
116
116
  fernapi/fern-ruby-sdk: 1.25.0
117
- current_generation: ec0eabecc3325dfe0a69590f9a2f4e52c3c18ade
118
- patches:
119
- - id: patch-5da692c2
120
- content_hash: sha256:e652287219a0a709e16112ced444e6472c6fa8bdfe888edf868caaf9769a9472
121
- original_commit: 5da692c20aef08577d900f9a860b5f78a549418e
122
- original_message: inject host time into datastream wasm rules engine (SCHY-471)
123
- original_author: Christopher Brady <chris@schematichq.com>
124
- base_generation: ec0eabecc3325dfe0a69590f9a2f4e52c3c18ade
125
- files:
126
- - test/rules_engine_clock_test.rb
127
- patch_content: |
128
- diff --git a/test/rules_engine_clock_test.rb b/test/rules_engine_clock_test.rb
129
- new file mode 100644
130
- index 0000000..8711b5e
131
- --- /dev/null
132
- +++ b/test/rules_engine_clock_test.rb
133
- @@ -0,0 +1,75 @@
134
- +# frozen_string_literal: true
135
- +
136
- +require_relative "test_helper"
137
- +require "minitest/autorun"
138
- +
139
- +# Regression for SCHY-471.
140
- +#
141
- +# A company-override entitlement rule whose metric condition uses a
142
- +# calendar/billing metric period drives the engine into the metric-period-reset
143
- +# code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
144
- +# that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
145
- +# its default value for a company that is legitimately entitled. The host now
146
- +# injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
147
- +class RulesEngineClockTest < Minitest::Test
148
- + def setup
149
- + @engine = Schematic::RulesEngine.new
150
- + @engine.initialize!
151
- + # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
152
- + # (or the wasmtime gem missing) in some environments — skip rather than fail.
153
- + skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
154
- + end
155
- +
156
- + def flag
157
- + {
158
- + "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
159
- + "environment_id" => "env_1", "default_value" => false, "rules" => []
160
- + }
161
- + end
162
- +
163
- + # Usage 40 < allocation 100, so the override grants the feature.
164
- + def entitled_company
165
- + {
166
- + "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
167
- + "keys" => { "id" => "co_entitled" },
168
- + "metrics" => [{
169
- + "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
170
- + "event_subtype" => "api-calls", "period" => "current_month",
171
- + "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
172
- + }],
173
- + "rules" => [{
174
- + "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
175
- + "environment_id" => "env_1", "name" => "Company Override",
176
- + "rule_type" => "company_override", "priority" => 0, "value" => true,
177
- + "condition_groups" => [],
178
- + "conditions" => [
179
- + {
180
- + "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
181
- + "condition_type" => "company", "operator" => "eq",
182
- + "resource_ids" => ["co_entitled"], "trait_value" => ""
183
- + },
184
- + {
185
- + "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
186
- + "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
187
- + "metric_value" => 100, "metric_period" => "current_month",
188
- + "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
189
- + }
190
- + ]
191
- + }]
192
- + }
193
- + end
194
- +
195
- + def test_billing_metric_override_evaluates_without_trapping
196
- + result = @engine.check_flag(flag, entitled_company)
197
- +
198
- + assert result[:value], "company override should grant the flag, not fall back to default false"
199
- + assert_match(/override/i, result[:reason].to_s)
200
- + end
201
- +
202
- + def test_billing_metric_override_populates_reset_at
203
- + result = @engine.check_flag(flag, entitled_company)
204
- +
205
- + refute_nil result[:feature_usage_reset_at],
206
- + "reset-at should be computed from the injected host time"
207
- + end
208
- +end
209
- theirs_snapshot:
210
- test/rules_engine_clock_test.rb: |
211
- # frozen_string_literal: true
212
-
213
- require_relative "test_helper"
214
- require "minitest/autorun"
215
-
216
- # Regression for SCHY-471.
217
- #
218
- # A company-override entitlement rule whose metric condition uses a
219
- # calendar/billing metric period drives the engine into the metric-period-reset
220
- # code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
221
- # that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
222
- # its default value for a company that is legitimately entitled. The host now
223
- # injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
224
- class RulesEngineClockTest < Minitest::Test
225
- def setup
226
- @engine = Schematic::RulesEngine.new
227
- @engine.initialize!
228
- # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
229
- # (or the wasmtime gem missing) in some environments — skip rather than fail.
230
- skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
231
- end
232
-
233
- def flag
234
- {
235
- "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
236
- "environment_id" => "env_1", "default_value" => false, "rules" => []
237
- }
238
- end
239
-
240
- # Usage 40 < allocation 100, so the override grants the feature.
241
- def entitled_company
242
- {
243
- "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
244
- "keys" => { "id" => "co_entitled" },
245
- "metrics" => [{
246
- "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
247
- "event_subtype" => "api-calls", "period" => "current_month",
248
- "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
249
- }],
250
- "rules" => [{
251
- "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
252
- "environment_id" => "env_1", "name" => "Company Override",
253
- "rule_type" => "company_override", "priority" => 0, "value" => true,
254
- "condition_groups" => [],
255
- "conditions" => [
256
- {
257
- "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
258
- "condition_type" => "company", "operator" => "eq",
259
- "resource_ids" => ["co_entitled"], "trait_value" => ""
260
- },
261
- {
262
- "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
263
- "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
264
- "metric_value" => 100, "metric_period" => "current_month",
265
- "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
266
- }
267
- ]
268
- }]
269
- }
270
- end
271
-
272
- def test_billing_metric_override_evaluates_without_trapping
273
- result = @engine.check_flag(flag, entitled_company)
274
-
275
- assert result[:value], "company override should grant the flag, not fall back to default false"
276
- assert_match(/override/i, result[:reason].to_s)
277
- end
278
-
279
- def test_billing_metric_override_populates_reset_at
280
- result = @engine.check_flag(flag, entitled_company)
281
-
282
- refute_nil result[:feature_usage_reset_at],
283
- "reset-at should be computed from the injected host time"
284
- end
285
- end
286
- user_owned: true
287
- - id: patch-ee61788c
288
- content_hash: sha256:e652287219a0a709e16112ced444e6472c6fa8bdfe888edf868caaf9769a9472
289
- original_commit: ee61788c5a83fce5dd5768b2536391459550d176
290
- original_message: fix rubocop offenses in clock regression test (SCHY-471)
291
- original_author: Christopher Brady <chris@schematichq.com>
292
- base_generation: ec0eabecc3325dfe0a69590f9a2f4e52c3c18ade
293
- files:
294
- - test/rules_engine_clock_test.rb
295
- patch_content: |
296
- diff --git a/test/rules_engine_clock_test.rb b/test/rules_engine_clock_test.rb
297
- new file mode 100644
298
- index 0000000..8711b5e
299
- --- /dev/null
300
- +++ b/test/rules_engine_clock_test.rb
301
- @@ -0,0 +1,75 @@
302
- +# frozen_string_literal: true
303
- +
304
- +require_relative "test_helper"
305
- +require "minitest/autorun"
306
- +
307
- +# Regression for SCHY-471.
308
- +#
309
- +# A company-override entitlement rule whose metric condition uses a
310
- +# calendar/billing metric period drives the engine into the metric-period-reset
311
- +# code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
312
- +# that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
313
- +# its default value for a company that is legitimately entitled. The host now
314
- +# injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
315
- +class RulesEngineClockTest < Minitest::Test
316
- + def setup
317
- + @engine = Schematic::RulesEngine.new
318
- + @engine.initialize!
319
- + # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
320
- + # (or the wasmtime gem missing) in some environments — skip rather than fail.
321
- + skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
322
- + end
323
- +
324
- + def flag
325
- + {
326
- + "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
327
- + "environment_id" => "env_1", "default_value" => false, "rules" => []
328
- + }
329
- + end
330
- +
331
- + # Usage 40 < allocation 100, so the override grants the feature.
332
- + def entitled_company
333
- + {
334
- + "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
335
- + "keys" => { "id" => "co_entitled" },
336
- + "metrics" => [{
337
- + "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
338
- + "event_subtype" => "api-calls", "period" => "current_month",
339
- + "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
340
- + }],
341
- + "rules" => [{
342
- + "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
343
- + "environment_id" => "env_1", "name" => "Company Override",
344
- + "rule_type" => "company_override", "priority" => 0, "value" => true,
345
- + "condition_groups" => [],
346
- + "conditions" => [
347
- + {
348
- + "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
349
- + "condition_type" => "company", "operator" => "eq",
350
- + "resource_ids" => ["co_entitled"], "trait_value" => ""
351
- + },
352
- + {
353
- + "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
354
- + "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
355
- + "metric_value" => 100, "metric_period" => "current_month",
356
- + "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
357
- + }
358
- + ]
359
- + }]
360
- + }
361
- + end
362
- +
363
- + def test_billing_metric_override_evaluates_without_trapping
364
- + result = @engine.check_flag(flag, entitled_company)
365
- +
366
- + assert result[:value], "company override should grant the flag, not fall back to default false"
367
- + assert_match(/override/i, result[:reason].to_s)
368
- + end
369
- +
370
- + def test_billing_metric_override_populates_reset_at
371
- + result = @engine.check_flag(flag, entitled_company)
372
- +
373
- + refute_nil result[:feature_usage_reset_at],
374
- + "reset-at should be computed from the injected host time"
375
- + end
376
- +end
377
- theirs_snapshot:
378
- test/rules_engine_clock_test.rb: |
379
- # frozen_string_literal: true
380
-
381
- require_relative "test_helper"
382
- require "minitest/autorun"
383
-
384
- # Regression for SCHY-471.
385
- #
386
- # A company-override entitlement rule whose metric condition uses a
387
- # calendar/billing metric period drives the engine into the metric-period-reset
388
- # code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
389
- # that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
390
- # its default value for a company that is legitimately entitled. The host now
391
- # injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
392
- class RulesEngineClockTest < Minitest::Test
393
- def setup
394
- @engine = Schematic::RulesEngine.new
395
- @engine.initialize!
396
- # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
397
- # (or the wasmtime gem missing) in some environments — skip rather than fail.
398
- skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
399
- end
400
-
401
- def flag
402
- {
403
- "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
404
- "environment_id" => "env_1", "default_value" => false, "rules" => []
405
- }
406
- end
407
-
408
- # Usage 40 < allocation 100, so the override grants the feature.
409
- def entitled_company
410
- {
411
- "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
412
- "keys" => { "id" => "co_entitled" },
413
- "metrics" => [{
414
- "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
415
- "event_subtype" => "api-calls", "period" => "current_month",
416
- "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
417
- }],
418
- "rules" => [{
419
- "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
420
- "environment_id" => "env_1", "name" => "Company Override",
421
- "rule_type" => "company_override", "priority" => 0, "value" => true,
422
- "condition_groups" => [],
423
- "conditions" => [
424
- {
425
- "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
426
- "condition_type" => "company", "operator" => "eq",
427
- "resource_ids" => ["co_entitled"], "trait_value" => ""
428
- },
429
- {
430
- "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
431
- "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
432
- "metric_value" => 100, "metric_period" => "current_month",
433
- "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
434
- }
435
- ]
436
- }]
437
- }
438
- end
439
-
440
- def test_billing_metric_override_evaluates_without_trapping
441
- result = @engine.check_flag(flag, entitled_company)
442
-
443
- assert result[:value], "company override should grant the flag, not fall back to default false"
444
- assert_match(/override/i, result[:reason].to_s)
445
- end
446
-
447
- def test_billing_metric_override_populates_reset_at
448
- result = @engine.check_flag(flag, entitled_company)
449
-
450
- refute_nil result[:feature_usage_reset_at],
451
- "reset-at should be computed from the injected host time"
452
- end
453
- end
454
- user_owned: true
117
+ - commit_sha: eae816455d05776057b4d684b5cd4effe47ef299
118
+ tree_hash: 3af6229006f1467db74835ba62abc1dcff846262
119
+ timestamp: 2026-10-05T10:05:38.729Z
120
+ cli_version: unknown
121
+ generator_versions:
122
+ fernapi/fern-ruby-sdk: 1.25.0
123
+ current_generation: eae816455d05776057b4d684b5cd4effe47ef299
124
+ patches: []
data/.fernignore CHANGED
@@ -6,8 +6,10 @@ CONTRIBUTING.md
6
6
  LICENSE
7
7
  README.md
8
8
  WASM_VERSION
9
+ conformance/
9
10
  custom.gemspec.rb
10
11
  lib/schematic/cache.rb
12
+ lib/schematic/credits/leases/
11
13
  lib/schematic/datastream/
12
14
  lib/schematic/event_buffer.rb
13
15
  lib/schematic/logger.rb
@@ -18,7 +20,11 @@ lib/schematic/wasm/
18
20
  lib/schematic/webhook_verification.rb
19
21
  lib/schematichq.rb
20
22
  scripts/
23
+ test/conformance_test.rb
24
+ test/credits_test.rb
21
25
  test/custom.test.rb
26
+ test/lease_support.rb
27
+ test/rules_engine_clock_test.rb
22
28
  testapp/
23
29
  .fern/replay.lock
24
30
  .fern/replay.yml
data/README.md CHANGED
@@ -363,6 +363,147 @@ end
363
363
  client.close
364
364
  ```
365
365
 
366
+ ### Credit Leases and Reservations
367
+
368
+ For features metered by credit burndown (e.g. inference tokens), `check` reserves credits for the work about to run and `track_with_reservation` settles the reservation with actual usage. The SDK gates in one of two modes:
369
+
370
+ - **Client mode** acquires a **lease**, a tranche of credits held against the company's balance, and carves a per-request **reservation** out of it locally, so a check needs no API call. It requires [DataStream](#datastream) (or [Replicator Mode](#replicator-mode)) and, across multiple processes, a shared Redis so every instance gates against the same lease.
371
+ - **Server mode** makes one `check-and-reserve` API call per check. No lease, no Redis, no local state.
372
+
373
+ `credit_leases[:mode]` defaults to `:auto`: client when DataStream is enabled, server otherwise. Client mode suits high-throughput gating; server mode suits low-volume checks and operations that run for seconds.
374
+
375
+ Durations are in milliseconds, as in the other Schematic SDKs.
376
+
377
+ #### Setup
378
+
379
+ The `redis` gem is not a dependency; pass a client you already have.
380
+
381
+ ```ruby
382
+ require "redis"
383
+ require "schematichq"
384
+
385
+ redis_client = Redis.new(url: "redis://localhost:6379")
386
+
387
+ client = Schematic::SchematicClient.new(
388
+ api_key: ENV["SCHEMATIC_API_KEY"],
389
+ use_data_stream: true,
390
+ datastream_options: {
391
+ redis_client: redis_client # also backs lease and reservation state
392
+ },
393
+ credit_leases: {
394
+ default_lease_size: 10_000, # credits requested per lease
395
+ default_lease_duration: 5 * 60 * 1000, # lease lifetime (ms)
396
+ default_reservation_ttl: 60 * 1000 # how long a reservation is held if no track settles it (ms)
397
+ }
398
+ )
399
+ ```
400
+
401
+ Set `credit_leases[:redis_client]` to keep lease state in a different Redis than the DataStream cache.
402
+
403
+ Server mode needs only a TTL:
404
+
405
+ ```ruby
406
+ client = Schematic::SchematicClient.new(
407
+ api_key: ENV["SCHEMATIC_API_KEY"],
408
+ credit_leases: {
409
+ default_reservation_ttl: 60 * 1000 # how long the server holds the credits if no track settles them (ms, max 1 hour)
410
+ }
411
+ )
412
+ ```
413
+
414
+ Only `mode` and `default_reservation_ttl` apply in server mode; the SDK warns at startup if a client-only option is set.
415
+
416
+ #### Checking and tracking
417
+
418
+ ```ruby
419
+ # Reserve up to max_tokens for this operation.
420
+ result = client.check(
421
+ "inference",
422
+ company: { "id" => "your-company-id" },
423
+ usage: max_tokens, # upper bound for this operation
424
+ event_subtype: "inference_tokens" # the metered event
425
+ )
426
+ raise "credit balance exceeded" unless result.allowed?
427
+
428
+ inference = run_inference(...)
429
+
430
+ # Report actual usage; the unused slice of the reservation is refunded.
431
+ if result.reservation
432
+ client.track_with_reservation(result.reservation, inference.tokens_used)
433
+ else
434
+ client.track({
435
+ event: "inference_tokens",
436
+ company: { "id" => "your-company-id" },
437
+ quantity: inference.tokens_used
438
+ })
439
+ end
440
+ ```
441
+
442
+ A check can allow without reserving credits (the feature is not credit-metered, `usage` is 0, or the check failed open), and that usage still has to be tracked.
443
+
444
+ Without `credit_leases` configured, or without a `usage`, `check` falls through to a plain flag check with no reservation. `usage` is still sent as a preflight, locally or to the API, so the verdict accounts for what the call is about to spend. Preflighted verdicts are not cached.
445
+
446
+ `usage` may be fractional. A client-mode reservation is sized in whole event units, `ceil(usage) x consumption_rate`, and a settle debits the lease by `ceil(actual) x consumption_rate`, so the local ledger moves by exactly what the track event bills. The reservation still records the fractional quantity the caller declared. Everything on the wire rounds up with the debit: the track event's quantity, a server-mode reservation, and the preflight quantity, whether it goes to the API or the local engine.
447
+
448
+ `result.entitlement` is a symbol-keyed hash with the field names of `Schematic::Types::FeatureEntitlement`, the same in both modes.
449
+
450
+ An unsettled reservation expires after `default_reservation_ttl` and its credits return to the lease. A late settle still bills the usage (the track event carries a deterministic idempotency key, so it never double-bills) but does not re-debit the local lease, so set `default_reservation_ttl` above the longest expected gap between `check` and `track_with_reservation`.
451
+
452
+ #### Pre-warming
453
+
454
+ Pre-warm leases when the user is identified, so a session's first check does not wait on a lease acquire:
455
+
456
+ ```ruby
457
+ client.identify(
458
+ {
459
+ keys: { "user_id" => "your-user-id" },
460
+ company: { keys: { "id" => "your-company-id" } }
461
+ },
462
+ prewarm: ["credit-type-id"] # credit type IDs to acquire leases for
463
+ )
464
+ ```
465
+
466
+ Or call `client.prewarm(credit_type_ids, company:)` directly. Both are no-ops in server mode.
467
+
468
+ Pre-warming resolves the company the way the server does: it looks the keys up first, whatever they are named, and only when nothing matches does it read a value carrying Schematic's `comp_` prefix as the company id.
469
+
470
+ #### Failure behavior
471
+
472
+ A check that cannot be gated (API unreachable, Redis down, lease exhausted) fails closed by default. Override per check:
473
+
474
+ ```ruby
475
+ result = client.check(
476
+ "inference",
477
+ company: { "id" => "your-company-id" },
478
+ usage: max_tokens,
479
+ event_subtype: "inference_tokens",
480
+ on_acquire_failure: :fail_open
481
+ )
482
+ ```
483
+
484
+ In server mode, a check that times out after the server has already reserved leaves those credits reserved until the TTL expires, so keep `default_reservation_ttl` short there.
485
+
486
+ In client mode, `:fail_open` still evaluates the flag's rules with the credit balance assumed sufficient, so plan targeting and all non-credit conditions apply and only the credit gate is bypassed. In server mode it returns the flag's default value, which is `false` unless you pass `default_value` or configure a `flag_defaults` entry.
487
+
488
+ `default_value` also applies when a check falls back to a plain flag check and that check fails. Pass a boolean or a callable.
489
+
490
+ `check` accepts `timeout_ms`, but no timeout is configurable yet: the generated HTTP transport ignores per-request timeouts and the client exposes no setting. The option is carried on the request so it takes effect once the transport reads it.
491
+
492
+ #### Configuration options
493
+
494
+ | Option | Type | Default | Description |
495
+ |---|---|---|---|
496
+ | `mode` | `:client`, `:server`, `:auto` | `:auto` | Where credits are reserved; `:auto` picks client when DataStream is enabled, server otherwise |
497
+ | `default_reservation_ttl` | `Integer` | 60000 (60 seconds) | How long an unsettled reservation is held (ms) |
498
+ | `default_lease_duration` | `Integer` | 300000 (5 minutes) | (client mode) Lease lifetime (ms) |
499
+ | `default_lease_size` | `Numeric` | 10000 | (client mode) Credits requested per lease acquire or extend |
500
+ | `low_water_mark` | `Float` | 0.25 | (client mode) Extend in the background when the lease balance dips below this fraction |
501
+ | `sweep_interval_ms` | `Integer` | 1000 | (client mode) Sweep interval for expired reservations (ms) |
502
+ | `prewarm_resolve_timeout_ms` | `Integer` | 5000 | (client mode) How long `prewarm` waits for a freshly identified company to surface (ms); 0 resolves from the DataStream cache only |
503
+ | `redis_client` | Redis client | `datastream_options[:redis_client]` | (client mode) Redis client for lease and reservation state |
504
+ | `redis_key_prefix` | `String` | `datastream_options[:redis_key_prefix]` | (client mode) Key prefix for lease and reservation keys |
505
+ | `overrides` | `Hash` | none | (client mode) Per-credit-type overrides of the above, keyed by credit type ID |
506
+
366
507
  ### Other API operations
367
508
 
368
509
  The Schematic API supports many operations beyond these, accessible via the API modules on the client: `accounts`, `billing`, `companies`, `credits`, `entitlements`, `events`, `features`, and `plans`.
data/custom.gemspec.rb CHANGED
@@ -3,6 +3,12 @@
3
3
  def add_custom_gemspec_data(spec)
4
4
  spec.homepage = "https://github.com/SchematicHQ/schematic-ruby"
5
5
 
6
+ # Cross-SDK test fixtures, read only by test/conformance_test.rb, which the
7
+ # generated gemspec already drops with the rest of test/. Rejected here rather
8
+ # than there because that file is generated; npm's allowlist keeps them out of
9
+ # schematic-node the same way.
10
+ spec.files.reject! { |f| f.start_with?("conformance/") }
11
+
6
12
  # The WASM binary is gitignored (downloaded at build time via scripts/download-wasm.sh)
7
13
  # but must be included in the published gem. Append it to the file list.
8
14
  wasm_file = "lib/schematic/wasm/rulesengine.wasm"
@@ -9,6 +9,8 @@ module Schematic
9
9
  field :environment_type, -> { Schematic::Types::EnvironmentType }, optional: true, nullable: false
10
10
 
11
11
  field :name, -> { String }, optional: true, nullable: false
12
+
13
+ field :require_context_signature, -> { Internal::Types::Boolean }, optional: true, nullable: false
12
14
  end
13
15
  end
14
16
  end
@@ -42,7 +42,7 @@ module Schematic
42
42
  @raw_client = Schematic::Internal::Http::RawClient.new(
43
43
  base_url: base_url || Schematic::Environment::DEFAULT,
44
44
  headers: {
45
- "User-Agent" => "schematichq/1.5.2",
45
+ "User-Agent" => "schematichq/1.5.3",
46
46
  "X-Fern-Language" => "Ruby",
47
47
  "X-Schematic-Api-Key" => api_key.to_s
48
48
  },
@@ -1399,10 +1399,7 @@ module Schematic
1399
1399
  # @option request_options [Integer] :timeout_in_seconds
1400
1400
  #
1401
1401
  # @example
1402
- # client.credits.create_credit_spend_policy(
1403
- # billing_credit_id: "billing_credit_id",
1404
- # max_per_draw: 1.1
1405
- # )
1402
+ # client.credits.create_credit_spend_policy(billing_credit_id: "billing_credit_id")
1406
1403
  #
1407
1404
  # @return [Schematic::Credits::Types::CreateCreditSpendPolicyResponse]
1408
1405
  def create_credit_spend_policy(request_options: {}, **params)
@@ -1597,6 +1594,53 @@ module Schematic
1597
1594
  end
1598
1595
  end
1599
1596
 
1597
+ # @param request_options [Hash]
1598
+ # @param params [Hash]
1599
+ # @option request_options [String] :base_url
1600
+ # @option request_options [Hash{String => Object}] :additional_headers
1601
+ # @option request_options [Hash{String => Object}] :additional_query_parameters
1602
+ # @option request_options [Hash{String => Object}] :additional_body_parameters
1603
+ # @option request_options [Integer] :timeout_in_seconds
1604
+ # @option params [String, nil] :billing_credit_id
1605
+ # @option params [String] :company_id
1606
+ # @option params [String, nil] :user_ids
1607
+ #
1608
+ # @example
1609
+ # client.credits.get_credit_spend_policy_usage(
1610
+ # billing_credit_id: "billing_credit_id",
1611
+ # company_id: "company_id",
1612
+ # user_ids: ["user_ids"]
1613
+ # )
1614
+ #
1615
+ # @return [Schematic::Credits::Types::GetCreditSpendPolicyUsageResponse]
1616
+ def get_credit_spend_policy_usage(request_options: {}, **params)
1617
+ params = Schematic::Internal::Types::Utils.normalize_keys(params)
1618
+ query_params = {}
1619
+ query_params["billing_credit_id"] = params[:billing_credit_id] if params.key?(:billing_credit_id)
1620
+ query_params["company_id"] = params[:company_id] if params.key?(:company_id)
1621
+ query_params["user_ids"] = params[:user_ids] if params.key?(:user_ids)
1622
+
1623
+ request = Schematic::Internal::JSON::Request.new(
1624
+ base_url: request_options[:base_url],
1625
+ method: "GET",
1626
+ path: "billing/credits/spend-policies/usage",
1627
+ query: query_params,
1628
+ request_options: request_options
1629
+ )
1630
+ begin
1631
+ response = @client.send(request)
1632
+ rescue Net::HTTPRequestTimeout
1633
+ raise Schematic::Errors::TimeoutError
1634
+ end
1635
+ code = response.code.to_i
1636
+ if code.between?(200, 299)
1637
+ (response.body.to_s.empty? ? nil : Schematic::Credits::Types::GetCreditSpendPolicyUsageResponse.load(response.body))
1638
+ else
1639
+ error_class = Schematic::Errors::ResponseError.subclass_for_code(code)
1640
+ raise error_class.new(response.body, code: code)
1641
+ end
1642
+ end
1643
+
1600
1644
  # @param request_options [Hash]
1601
1645
  # @param params [Hash]
1602
1646
  # @option request_options [String] :base_url
@@ -1618,7 +1662,7 @@ module Schematic
1618
1662
  # billing_credit_id: "billing_credit_id",
1619
1663
  # company_id: "company_id",
1620
1664
  # end_time: "end_time",
1621
- # event_type: "grant",
1665
+ # event_type: "adjustment",
1622
1666
  # feature_id: "feature_id",
1623
1667
  # start_time: "start_time",
1624
1668
  # limit: 1000000,
@@ -1680,7 +1724,7 @@ module Schematic
1680
1724
  # billing_credit_id: "billing_credit_id",
1681
1725
  # company_id: "company_id",
1682
1726
  # end_time: "end_time",
1683
- # event_type: "grant",
1727
+ # event_type: "adjustment",
1684
1728
  # feature_id: "feature_id",
1685
1729
  # start_time: "start_time",
1686
1730
  # limit: 1000000,