schematichq 1.5.1 → 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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.fern/metadata.json +3 -3
  3. data/.fern/replay.lock +14 -338
  4. data/.fernignore +6 -0
  5. data/README.md +141 -0
  6. data/WASM_VERSION +1 -1
  7. data/custom.gemspec.rb +6 -0
  8. data/lib/schematic/accounts/types/update_environment_request_body.rb +2 -0
  9. data/lib/schematic/billing/types/create_invoice_request_body.rb +2 -0
  10. data/lib/schematic/client.rb +1 -1
  11. data/lib/schematic/credits/client.rb +50 -6
  12. data/lib/schematic/credits/leases/check.rb +477 -0
  13. data/lib/schematic/credits/leases/lease_manager.rb +565 -0
  14. data/lib/schematic/credits/leases/lease_store.rb +240 -0
  15. data/lib/schematic/credits/leases/redis_lease_store.rb +358 -0
  16. data/lib/schematic/credits/leases/redis_reservation_store.rb +326 -0
  17. data/lib/schematic/credits/leases/reservation_store.rb +161 -0
  18. data/lib/schematic/credits/leases/server_check.rb +237 -0
  19. data/lib/schematic/credits/leases/track.rb +66 -0
  20. data/lib/schematic/credits/leases/types.rb +329 -0
  21. data/lib/schematic/credits/leases/wire_client.rb +83 -0
  22. data/lib/schematic/credits/types/acquire_credit_lease_request_body.rb +2 -0
  23. data/lib/schematic/credits/types/create_credit_spend_policy_request_body.rb +5 -1
  24. data/lib/schematic/credits/types/extend_credit_lease_request_body.rb +2 -0
  25. data/lib/schematic/credits/types/get_credit_spend_policy_usage_params.rb +16 -0
  26. data/lib/schematic/credits/types/get_credit_spend_policy_usage_request.rb +15 -0
  27. data/lib/schematic/credits/types/get_credit_spend_policy_usage_response.rb +13 -0
  28. data/lib/schematic/credits/types/update_credit_spend_policy_request_body.rb +4 -0
  29. data/lib/schematic/datastream/client.rb +32 -2
  30. data/lib/schematic/entitlements/client.rb +165 -0
  31. data/lib/schematic/entitlements/types/count_company_user_usage_params.rb +24 -0
  32. data/lib/schematic/entitlements/types/count_company_user_usage_request.rb +23 -0
  33. data/lib/schematic/entitlements/types/count_company_user_usage_response.rb +13 -0
  34. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_params.rb +16 -0
  35. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_request.rb +15 -0
  36. data/lib/schematic/entitlements/types/get_company_user_usage_metrics_response.rb +13 -0
  37. data/lib/schematic/entitlements/types/list_company_user_usage_params.rb +24 -0
  38. data/lib/schematic/entitlements/types/list_company_user_usage_request.rb +23 -0
  39. data/lib/schematic/entitlements/types/list_company_user_usage_response.rb +13 -0
  40. data/lib/schematic/plangroups/client.rb +2 -0
  41. data/lib/schematic/plangroups/types/create_plan_group_request_body.rb +2 -0
  42. data/lib/schematic/plangroups/types/update_plan_group_request_body.rb +2 -0
  43. data/lib/schematic/planmigrations/client.rb +6 -0
  44. data/lib/schematic/planmigrations/types/count_migrations_params.rb +2 -0
  45. data/lib/schematic/planmigrations/types/count_migrations_request.rb +2 -0
  46. data/lib/schematic/planmigrations/types/create_migration_input.rb +2 -0
  47. data/lib/schematic/planmigrations/types/list_migrations_params.rb +2 -0
  48. data/lib/schematic/planmigrations/types/list_migrations_request.rb +2 -0
  49. data/lib/schematic/plans/types/publish_plan_version_request_body.rb +4 -0
  50. data/lib/schematic/plans/types/retry_custom_plan_billing_request_body.rb +2 -0
  51. data/lib/schematic/rules_engine.rb +37 -0
  52. data/lib/schematic/schematic_client.rb +830 -33
  53. data/lib/schematic/types/capture_raw_event.rb +4 -0
  54. data/lib/schematic/types/change_subscription_internal_request_body.rb +2 -0
  55. data/lib/schematic/types/change_subscription_request_body.rb +2 -0
  56. data/lib/schematic/types/check_flags_response_data.rb +2 -0
  57. data/lib/schematic/types/company_detail_response_data.rb +2 -0
  58. data/lib/schematic/types/company_feature_usage_export_metadata_visible_columns_item.rb +1 -0
  59. data/lib/schematic/types/company_plan_detail_response_data.rb +2 -0
  60. data/lib/schematic/types/company_user_usage_metrics_response_data.rb +15 -0
  61. data/lib/schematic/types/company_user_usage_response_data.rb +19 -0
  62. data/lib/schematic/types/company_user_usage_row_response_data.rb +17 -0
  63. data/lib/schematic/types/component_display_settings.rb +2 -0
  64. data/lib/schematic/types/component_settings_response_data.rb +2 -0
  65. data/lib/schematic/types/credit_event_ledger_response_data.rb +7 -1
  66. data/lib/schematic/types/credit_event_type.rb +3 -0
  67. data/lib/schematic/types/credit_ledger_entry_kind.rb +17 -0
  68. data/lib/schematic/types/credit_spend_policy.rb +25 -0
  69. data/lib/schematic/types/credit_spend_policy_response_data.rb +12 -0
  70. data/lib/schematic/types/credit_spend_window.rb +11 -0
  71. data/lib/schematic/types/credit_spend_window_unit.rb +13 -0
  72. data/lib/schematic/types/custom_plan_billing_response_data.rb +2 -0
  73. data/lib/schematic/types/environment_detail_response_data.rb +2 -0
  74. data/lib/schematic/types/environment_response_data.rb +2 -0
  75. data/lib/schematic/types/estimated_plan_total.rb +13 -0
  76. data/lib/schematic/types/invoice_request_body.rb +2 -0
  77. data/lib/schematic/types/invoice_response_data.rb +2 -0
  78. data/lib/schematic/types/manage_plan_request.rb +2 -0
  79. data/lib/schematic/types/pending_migration_response_data.rb +6 -0
  80. data/lib/schematic/types/plan_version_migration_response_data.rb +2 -0
  81. data/lib/schematic/types/plan_version_migration_strategy.rb +1 -0
  82. data/lib/schematic/types/preview_subscription_finance_response_data.rb +2 -0
  83. data/lib/schematic/types/rules_engine_schema_version.rb +1 -1
  84. data/lib/schematic/types/rulesengine_company.rb +2 -0
  85. data/lib/schematic/types/rulesengine_comparable_operator.rb +18 -0
  86. data/lib/schematic/types/rulesengine_condition.rb +1 -1
  87. data/lib/schematic/types/rulesengine_credit_spend_policy.rb +25 -0
  88. data/lib/schematic/types/rulesengine_credit_spend_policy_scope.rb +13 -0
  89. data/lib/schematic/types/rulesengine_credit_spend_window.rb +11 -0
  90. data/lib/schematic/types/rulesengine_user.rb +2 -0
  91. data/lib/schematic/types/upcoming_invoice_response_data.rb +2 -0
  92. data/lib/schematic/types/user_usage_metric.rb +12 -0
  93. data/lib/schematic/version.rb +1 -1
  94. data/lib/schematic/wasm/rulesengine.wasm +0 -0
  95. data/lib/schematic.rb +27 -2
  96. data/lib/schematichq.rb +10 -0
  97. data/reference.md +485 -12
  98. metadata +37 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a148154577d68e12ccdcd07a3546ec7ed187e39dd3768a0cd7c9519297e08f4
4
- data.tar.gz: b5ff4f21ce1fdd2c20e98f9b89c83e253fb7e7aef795537fee19db16f15888d7
3
+ metadata.gz: 42a7ac411958f446624efef8e76092dec19e652603089848b36d93fbe53a5f3f
4
+ data.tar.gz: 942a73f63c86d6c66f3bf57a369578479e18529943a3b234900e6b87116046e9
5
5
  SHA512:
6
- metadata.gz: 1313a590626a5f2414d9b7191282d72ce5ae846f4ec45f3c12c1dfee8e9f89c251724365170eaace986e72da8365f4eae993f52f0fc2fc30c264ca3be1b654dd
7
- data.tar.gz: 9599cf974ed63293f84e6b58bb8f5aac39904f981b8fde146b1b52e3d92cd6a0cca90146074d1dbe5bd533ba0f8cc2d6f9801e1f6a00e54ea2322fa56d297ed3
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": "1d6a1c9cf0e1d72ea8ede6e7e8f84d35ffbd8be3",
16
+ "originGitCommit": "6a2f1e8406783d0f2591db2053e1a8a8df4a9046",
17
17
  "originGitCommitIsDirty": false,
18
18
  "invokedBy": "ci",
19
- "requestedVersion": "1.5.1",
19
+ "requestedVersion": "1.5.3",
20
20
  "ciProvider": "github",
21
- "sdkVersion": "1.5.1"
21
+ "sdkVersion": "1.5.3"
22
22
  }
data/.fern/replay.lock CHANGED
@@ -108,341 +108,17 @@ generations:
108
108
  cli_version: unknown
109
109
  generator_versions:
110
110
  fernapi/fern-ruby-sdk: 1.25.0
111
- current_generation: e22ae14fff5d0a5d3222b98154a1d1bc42b99dd8
112
- patches:
113
- - id: patch-5da692c2
114
- content_hash: sha256:e652287219a0a709e16112ced444e6472c6fa8bdfe888edf868caaf9769a9472
115
- original_commit: 5da692c20aef08577d900f9a860b5f78a549418e
116
- original_message: inject host time into datastream wasm rules engine (SCHY-471)
117
- original_author: Christopher Brady <chris@schematichq.com>
118
- base_generation: e22ae14fff5d0a5d3222b98154a1d1bc42b99dd8
119
- files:
120
- - test/rules_engine_clock_test.rb
121
- patch_content: |
122
- diff --git a/test/rules_engine_clock_test.rb b/test/rules_engine_clock_test.rb
123
- new file mode 100644
124
- index 0000000..8711b5e
125
- --- /dev/null
126
- +++ b/test/rules_engine_clock_test.rb
127
- @@ -0,0 +1,75 @@
128
- +# frozen_string_literal: true
129
- +
130
- +require_relative "test_helper"
131
- +require "minitest/autorun"
132
- +
133
- +# Regression for SCHY-471.
134
- +#
135
- +# A company-override entitlement rule whose metric condition uses a
136
- +# calendar/billing metric period drives the engine into the metric-period-reset
137
- +# code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
138
- +# that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
139
- +# its default value for a company that is legitimately entitled. The host now
140
- +# injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
141
- +class RulesEngineClockTest < Minitest::Test
142
- + def setup
143
- + @engine = Schematic::RulesEngine.new
144
- + @engine.initialize!
145
- + # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
146
- + # (or the wasmtime gem missing) in some environments — skip rather than fail.
147
- + skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
148
- + end
149
- +
150
- + def flag
151
- + {
152
- + "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
153
- + "environment_id" => "env_1", "default_value" => false, "rules" => []
154
- + }
155
- + end
156
- +
157
- + # Usage 40 < allocation 100, so the override grants the feature.
158
- + def entitled_company
159
- + {
160
- + "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
161
- + "keys" => { "id" => "co_entitled" },
162
- + "metrics" => [{
163
- + "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
164
- + "event_subtype" => "api-calls", "period" => "current_month",
165
- + "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
166
- + }],
167
- + "rules" => [{
168
- + "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
169
- + "environment_id" => "env_1", "name" => "Company Override",
170
- + "rule_type" => "company_override", "priority" => 0, "value" => true,
171
- + "condition_groups" => [],
172
- + "conditions" => [
173
- + {
174
- + "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
175
- + "condition_type" => "company", "operator" => "eq",
176
- + "resource_ids" => ["co_entitled"], "trait_value" => ""
177
- + },
178
- + {
179
- + "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
180
- + "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
181
- + "metric_value" => 100, "metric_period" => "current_month",
182
- + "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
183
- + }
184
- + ]
185
- + }]
186
- + }
187
- + end
188
- +
189
- + def test_billing_metric_override_evaluates_without_trapping
190
- + result = @engine.check_flag(flag, entitled_company)
191
- +
192
- + assert result[:value], "company override should grant the flag, not fall back to default false"
193
- + assert_match(/override/i, result[:reason].to_s)
194
- + end
195
- +
196
- + def test_billing_metric_override_populates_reset_at
197
- + result = @engine.check_flag(flag, entitled_company)
198
- +
199
- + refute_nil result[:feature_usage_reset_at],
200
- + "reset-at should be computed from the injected host time"
201
- + end
202
- +end
203
- theirs_snapshot:
204
- test/rules_engine_clock_test.rb: |
205
- # frozen_string_literal: true
206
-
207
- require_relative "test_helper"
208
- require "minitest/autorun"
209
-
210
- # Regression for SCHY-471.
211
- #
212
- # A company-override entitlement rule whose metric condition uses a
213
- # calendar/billing metric period drives the engine into the metric-period-reset
214
- # code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
215
- # that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
216
- # its default value for a company that is legitimately entitled. The host now
217
- # injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
218
- class RulesEngineClockTest < Minitest::Test
219
- def setup
220
- @engine = Schematic::RulesEngine.new
221
- @engine.initialize!
222
- # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
223
- # (or the wasmtime gem missing) in some environments — skip rather than fail.
224
- skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
225
- end
226
-
227
- def flag
228
- {
229
- "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
230
- "environment_id" => "env_1", "default_value" => false, "rules" => []
231
- }
232
- end
233
-
234
- # Usage 40 < allocation 100, so the override grants the feature.
235
- def entitled_company
236
- {
237
- "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
238
- "keys" => { "id" => "co_entitled" },
239
- "metrics" => [{
240
- "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
241
- "event_subtype" => "api-calls", "period" => "current_month",
242
- "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
243
- }],
244
- "rules" => [{
245
- "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
246
- "environment_id" => "env_1", "name" => "Company Override",
247
- "rule_type" => "company_override", "priority" => 0, "value" => true,
248
- "condition_groups" => [],
249
- "conditions" => [
250
- {
251
- "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
252
- "condition_type" => "company", "operator" => "eq",
253
- "resource_ids" => ["co_entitled"], "trait_value" => ""
254
- },
255
- {
256
- "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
257
- "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
258
- "metric_value" => 100, "metric_period" => "current_month",
259
- "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
260
- }
261
- ]
262
- }]
263
- }
264
- end
265
-
266
- def test_billing_metric_override_evaluates_without_trapping
267
- result = @engine.check_flag(flag, entitled_company)
268
-
269
- assert result[:value], "company override should grant the flag, not fall back to default false"
270
- assert_match(/override/i, result[:reason].to_s)
271
- end
272
-
273
- def test_billing_metric_override_populates_reset_at
274
- result = @engine.check_flag(flag, entitled_company)
275
-
276
- refute_nil result[:feature_usage_reset_at],
277
- "reset-at should be computed from the injected host time"
278
- end
279
- end
280
- user_owned: true
281
- - id: patch-ee61788c
282
- content_hash: sha256:e652287219a0a709e16112ced444e6472c6fa8bdfe888edf868caaf9769a9472
283
- original_commit: ee61788c5a83fce5dd5768b2536391459550d176
284
- original_message: fix rubocop offenses in clock regression test (SCHY-471)
285
- original_author: Christopher Brady <chris@schematichq.com>
286
- base_generation: e22ae14fff5d0a5d3222b98154a1d1bc42b99dd8
287
- files:
288
- - test/rules_engine_clock_test.rb
289
- patch_content: |
290
- diff --git a/test/rules_engine_clock_test.rb b/test/rules_engine_clock_test.rb
291
- new file mode 100644
292
- index 0000000..8711b5e
293
- --- /dev/null
294
- +++ b/test/rules_engine_clock_test.rb
295
- @@ -0,0 +1,75 @@
296
- +# frozen_string_literal: true
297
- +
298
- +require_relative "test_helper"
299
- +require "minitest/autorun"
300
- +
301
- +# Regression for SCHY-471.
302
- +#
303
- +# A company-override entitlement rule whose metric condition uses a
304
- +# calendar/billing metric period drives the engine into the metric-period-reset
305
- +# code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
306
- +# that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
307
- +# its default value for a company that is legitimately entitled. The host now
308
- +# injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
309
- +class RulesEngineClockTest < Minitest::Test
310
- + def setup
311
- + @engine = Schematic::RulesEngine.new
312
- + @engine.initialize!
313
- + # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
314
- + # (or the wasmtime gem missing) in some environments — skip rather than fail.
315
- + skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
316
- + end
317
- +
318
- + def flag
319
- + {
320
- + "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
321
- + "environment_id" => "env_1", "default_value" => false, "rules" => []
322
- + }
323
- + end
324
- +
325
- + # Usage 40 < allocation 100, so the override grants the feature.
326
- + def entitled_company
327
- + {
328
- + "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
329
- + "keys" => { "id" => "co_entitled" },
330
- + "metrics" => [{
331
- + "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
332
- + "event_subtype" => "api-calls", "period" => "current_month",
333
- + "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
334
- + }],
335
- + "rules" => [{
336
- + "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
337
- + "environment_id" => "env_1", "name" => "Company Override",
338
- + "rule_type" => "company_override", "priority" => 0, "value" => true,
339
- + "condition_groups" => [],
340
- + "conditions" => [
341
- + {
342
- + "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
343
- + "condition_type" => "company", "operator" => "eq",
344
- + "resource_ids" => ["co_entitled"], "trait_value" => ""
345
- + },
346
- + {
347
- + "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
348
- + "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
349
- + "metric_value" => 100, "metric_period" => "current_month",
350
- + "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
351
- + }
352
- + ]
353
- + }]
354
- + }
355
- + end
356
- +
357
- + def test_billing_metric_override_evaluates_without_trapping
358
- + result = @engine.check_flag(flag, entitled_company)
359
- +
360
- + assert result[:value], "company override should grant the flag, not fall back to default false"
361
- + assert_match(/override/i, result[:reason].to_s)
362
- + end
363
- +
364
- + def test_billing_metric_override_populates_reset_at
365
- + result = @engine.check_flag(flag, entitled_company)
366
- +
367
- + refute_nil result[:feature_usage_reset_at],
368
- + "reset-at should be computed from the injected host time"
369
- + end
370
- +end
371
- theirs_snapshot:
372
- test/rules_engine_clock_test.rb: |
373
- # frozen_string_literal: true
374
-
375
- require_relative "test_helper"
376
- require "minitest/autorun"
377
-
378
- # Regression for SCHY-471.
379
- #
380
- # A company-override entitlement rule whose metric condition uses a
381
- # calendar/billing metric period drives the engine into the metric-period-reset
382
- # code path, which needs a wall clock. On the raw wasm32-unknown-unknown build
383
- # that path used to trap (+wasm unreachable+); the flag then wrongly fell back to
384
- # its default value for a company that is legitimately entitled. The host now
385
- # injects the current time via +setCurrentTimeMillis+ so this evaluates cleanly.
386
- class RulesEngineClockTest < Minitest::Test
387
- def setup
388
- @engine = Schematic::RulesEngine.new
389
- @engine.initialize!
390
- # The wasm binary is fetched via scripts/download-wasm.sh and may be absent
391
- # (or the wasmtime gem missing) in some environments — skip rather than fail.
392
- skip "WASM rules engine unavailable (wasm binary or wasmtime gem missing)" unless @engine.initialized?
393
- end
394
-
395
- def flag
396
- {
397
- "id" => "flag1", "key" => "mcp-access", "account_id" => "acc_1",
398
- "environment_id" => "env_1", "default_value" => false, "rules" => []
399
- }
400
- end
401
-
402
- # Usage 40 < allocation 100, so the override grants the feature.
403
- def entitled_company
404
- {
405
- "id" => "co_entitled", "account_id" => "acc_1", "environment_id" => "env_1",
406
- "keys" => { "id" => "co_entitled" },
407
- "metrics" => [{
408
- "account_id" => "acc_1", "environment_id" => "env_1", "company_id" => "co_entitled",
409
- "event_subtype" => "api-calls", "period" => "current_month",
410
- "month_reset" => "billing_cycle", "value" => 40, "created_at" => "2023-01-01T00:00:00Z"
411
- }],
412
- "rules" => [{
413
- "id" => "rule_override", "flag_id" => "flag1", "account_id" => "acc_1",
414
- "environment_id" => "env_1", "name" => "Company Override",
415
- "rule_type" => "company_override", "priority" => 0, "value" => true,
416
- "condition_groups" => [],
417
- "conditions" => [
418
- {
419
- "id" => "cond_company", "account_id" => "acc_1", "environment_id" => "env_1",
420
- "condition_type" => "company", "operator" => "eq",
421
- "resource_ids" => ["co_entitled"], "trait_value" => ""
422
- },
423
- {
424
- "id" => "cond_metric", "account_id" => "acc_1", "environment_id" => "env_1",
425
- "condition_type" => "metric", "operator" => "lt", "event_subtype" => "api-calls",
426
- "metric_value" => 100, "metric_period" => "current_month",
427
- "metric_period_month_reset" => "billing_cycle", "trait_value" => "100"
428
- }
429
- ]
430
- }]
431
- }
432
- end
433
-
434
- def test_billing_metric_override_evaluates_without_trapping
435
- result = @engine.check_flag(flag, entitled_company)
436
-
437
- assert result[:value], "company override should grant the flag, not fall back to default false"
438
- assert_match(/override/i, result[:reason].to_s)
439
- end
440
-
441
- def test_billing_metric_override_populates_reset_at
442
- result = @engine.check_flag(flag, entitled_company)
443
-
444
- refute_nil result[:feature_usage_reset_at],
445
- "reset-at should be computed from the injected host time"
446
- end
447
- end
448
- user_owned: true
111
+ - commit_sha: ec0eabecc3325dfe0a69590f9a2f4e52c3c18ade
112
+ tree_hash: 10ac19e0d7c216b0cf04c6620979634798d2ca8a
113
+ timestamp: 2026-09-28T10:03:35.278Z
114
+ cli_version: unknown
115
+ generator_versions:
116
+ fernapi/fern-ruby-sdk: 1.25.0
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/WASM_VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.4
1
+ 0.9.0
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
@@ -32,6 +32,8 @@ module Schematic
32
32
 
33
33
  field :subtotal, -> { Integer }, optional: false, nullable: false
34
34
 
35
+ field :total, -> { Integer }, optional: true, nullable: false
36
+
35
37
  field :url, -> { String }, optional: true, nullable: false
36
38
  end
37
39
  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.1",
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
  },