bitfab 0.73.2 → 0.74.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00d7ec6b274939de9c460f01d0e1c2fc662739aa4bd95e7a18f26d36d4697009
4
- data.tar.gz: 55dc958c35be2de32dd532a87910d0fca80fdbfd840b0f46a9c2a2c6428d820c
3
+ metadata.gz: b1def1a5df451e3c7b1afa22248c2c54b2b135f5712eb19a989b41209a8c5622
4
+ data.tar.gz: 16a9741bbda86c16c78aafec91688d962c9ae58e9b032681a07d7b633497c258
5
5
  SHA512:
6
- metadata.gz: c4d616bbc59c9bb76eaa405104af8b4b1e1011d048bea5476eb1ac3e6dcfcd6832d69e11632334b3b9b458813877867e1cea85c519e59458466564a5600249df
7
- data.tar.gz: 63d3c26eef686ace5ac8578d90bd64f737ae1fcbf3b1d3c3aee134d49afc98bf676d86c0f5bcfa6d6757a790597e4e3e06c8dc75051fde29e9b11caeccde37c2
6
+ metadata.gz: 2be36b8b49c5b7c6d6525364c6f10a24d696eb40d8dae2343bdf1b1017e2f2fd47e6c52d8cdf200b4f1cf22a75fdd0ea41bf217e533090a9a42fa725ee7018a8
7
+ data.tar.gz: 5a4fe53c914d211714bbd2a31d44af37743edf1007f4599f52c6dfcce9e3bf970d90778588b1ee17267394017ed554752882f63d45a06fa7ce693786b97dc152
@@ -4,9 +4,11 @@ require "uri"
4
4
 
5
5
  module Bitfab
6
6
  class Traces
7
+ CODE_DRAFT_SAVE_TIMEOUT_SECONDS = 300
8
+ CODED_KINDS = ["code", "hybrid", :code, :hybrid].freeze
7
9
  CATEGORY_ID_KEYS = ["category_id", :category_id].freeze
8
10
  DEPRECATED_CATEGORY_ID_KEYS = ["category_assertion_id", :category_assertion_id].freeze
9
- private_constant :CATEGORY_ID_KEYS, :DEPRECATED_CATEGORY_ID_KEYS
11
+ private_constant :CODE_DRAFT_SAVE_TIMEOUT_SECONDS, :CODED_KINDS, :CATEGORY_ID_KEYS, :DEPRECATED_CATEGORY_ID_KEYS
10
12
 
11
13
  def initialize(http_client)
12
14
  @http_client = http_client
@@ -44,6 +46,17 @@ module Bitfab
44
46
  # pass "kind" => "judge" to remove the code. Returned assertions carry
45
47
  # "kind", "code", and "codeBrokenReason".
46
48
  #
49
+ # Send "kind" => "code" or "hybrid" without "code", on an assertion that
50
+ # has no code yet, to have Bitfab write the code: the text is checked by
51
+ # the guided rewrite (unless skipped), code of that kind is written and run
52
+ # on the trace, and the save waits for both, so that entry's "rewrite" is
53
+ # already final. Its returned "codeDraft" hash says what happened:
54
+ # "requestedKind", "status" ("written", or "not_written" when no working
55
+ # code could be written and the assertion was saved as "judge" without
56
+ # code), and "reason" (why not, or nil). Every other entry's "codeDraft"
57
+ # is nil. At most 10 assertions per save may ask for code this way, and a
58
+ # save that asks for any waits up to five minutes.
59
+ #
47
60
  # By default, after the save, Bitfab checks each assertion's text against its
48
61
  # trace. When the text bundles two claims, adds a condition, points at a
49
62
  # value instead of stating it, names an id from the recorded run, or cannot
@@ -77,7 +90,8 @@ module Bitfab
77
90
  "updates" => updates.map { |update| with_new_category_key(update) },
78
91
  "source" => source,
79
92
  "guidedRewrite" => !skip_guided_rewrite
80
- }
93
+ },
94
+ timeout: save_timeout(updates)
81
95
  )["assertions"]
82
96
  saved.is_a?(Array) ? saved.map { |assertion| with_both_category_keys(assertion) } : saved
83
97
  end
@@ -88,6 +102,18 @@ module Bitfab
88
102
 
89
103
  private
90
104
 
105
+ def save_timeout(updates)
106
+ asks = updates.any? do |update|
107
+ (update["assertions"] || update[:assertions] || []).any? { |assertion| asks_bitfab_to_write_code?(assertion) }
108
+ end
109
+ asks ? CODE_DRAFT_SAVE_TIMEOUT_SECONDS : nil
110
+ end
111
+
112
+ def asks_bitfab_to_write_code?(assertion)
113
+ kind = assertion["kind"] || assertion[:kind]
114
+ CODED_KINDS.include?(kind) && !assertion.key?("code") && !assertion.key?(:code)
115
+ end
116
+
91
117
  def assertions_path(trace_id, suffix = "")
92
118
  "/api/sdk/traces/#{URI.encode_www_form_component(trace_id).gsub("+", "%20")}/assertions#{suffix}"
93
119
  end
data/lib/bitfab/client.rb CHANGED
@@ -182,6 +182,12 @@ module Bitfab
182
182
  # directly to this experiment, graded as the union with the dataset's
183
183
  # runnable graders at completion; each must be an active/live grader in the
184
184
  # same org and trace function or the server rejects the replay
185
+ # @param category_ids [Array<String>, nil] optional category UUIDs. With
186
+ # trace_ids, dataset_id or dataset_ids, only the selected traces carrying
187
+ # an approved assertion in one of these categories replay. It changes
188
+ # which traces replay, not which assertions are judged. Raises
189
+ # ArgumentError when empty or passed without a trace ID or dataset
190
+ # selection.
185
191
  # @param mock [String] mock strategy for child spans: "marked" (default),
186
192
  # "none", or "all". "marked" only mocks spans declared with
187
193
  # mock_on_replay: true; "all" mocks every matched recorded child span. A selected
@@ -228,7 +234,7 @@ module Bitfab
228
234
  # @return [Hash] with :items, :experiment_id, :experiment_url, and the
229
235
  # deprecated aliases :test_run_id and :test_run_url carrying the same values
230
236
  def replay(receiver, method_name = nil, trace_function_key:, limit: nil, trace_ids: nil, concurrency: nil, max_concurrency: ReplayConcurrency::UNSET,
231
- name: nil, notes: nil, metadata: nil, code_change: nil, code_change_description: nil, code_change_files: nil, code_change_base: nil, experiment_group_id: nil, experiment_group_name: nil, dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, skip_assertion_judging: false, judge_assertions: nil, dry_run: false,
237
+ name: nil, notes: nil, metadata: nil, code_change: nil, code_change_description: nil, code_change_files: nil, code_change_base: nil, experiment_group_id: nil, experiment_group_name: nil, dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, category_ids: nil, skip_assertion_judging: false, judge_assertions: nil, dry_run: false,
232
238
  adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil,
233
239
  on_experiment_start: nil, experimental_selective_replay: nil)
234
240
  Replay.run(
@@ -245,6 +251,7 @@ module Bitfab
245
251
  max_concurrency:,
246
252
  attempts:,
247
253
  only_with_assertions:,
254
+ category_ids:,
248
255
  skip_assertion_judging:,
249
256
  judge_assertions:,
250
257
  dry_run:,
@@ -276,9 +276,15 @@ module Bitfab
276
276
  # directly to this experiment, graded as the union with the dataset's
277
277
  # runnable graders at completion; each must be an active/live grader in the
278
278
  # same org and trace function or the server rejects the replay
279
+ # @param category_ids [Array<String>, nil] optional category UUIDs. With
280
+ # trace_ids, dataset_id or dataset_ids, only the selected traces carrying
281
+ # an approved assertion in one of these categories replay. It changes
282
+ # which traces replay, not which assertions are judged. Raises
283
+ # ArgumentError when empty or passed without a trace ID or dataset
284
+ # selection.
279
285
  def start_replay(trace_function_key, limit, trace_ids: nil, code_change_type: nil, code_change_description: nil,
280
286
  code_change_files: nil, experiment_group_id: nil, experiment_group_name: nil, name: nil, notes: nil, metadata: nil, include_db_branch_lease: false, dataset_ids: nil,
281
- grader_ids: nil, db_branch_settings: nil, attempts: 1, only_with_assertions: false, skip_assertion_judging: false, include_original_metadata: false, experimental_selective_replay: nil, dry_run: false, invocation: nil)
287
+ grader_ids: nil, db_branch_settings: nil, attempts: 1, only_with_assertions: false, category_ids: nil, skip_assertion_judging: false, include_original_metadata: false, experimental_selective_replay: nil, dry_run: false, invocation: nil)
282
288
  payload = {
283
289
  "traceFunctionKey" => trace_function_key
284
290
  }
@@ -306,6 +312,7 @@ module Bitfab
306
312
  payload["graderIds"] = grader_ids unless grader_ids.nil?
307
313
  payload["attempts"] = attempts if attempts > 1
308
314
  payload["onlyWithAssertions"] = true if only_with_assertions
315
+ payload["categoryIds"] = category_ids unless category_ids.nil?
309
316
  payload["judgeAssertions"] = true unless skip_assertion_judging
310
317
  payload["dryRun"] = true if dry_run
311
318
  payload["includeOriginalMetadata"] = true if include_original_metadata
data/lib/bitfab/replay.rb CHANGED
@@ -274,6 +274,12 @@ module Bitfab
274
274
  # directly to this experiment, graded as the union with the dataset's
275
275
  # runnable graders at completion; each must be an active/live grader in the
276
276
  # same org and trace function or the server rejects the replay
277
+ # @param category_ids [Array<String>, nil] optional category UUIDs. With
278
+ # trace_ids, dataset_id or dataset_ids, only the selected traces carrying
279
+ # an approved assertion in one of these categories replay. It changes
280
+ # which traces replay, not which assertions are judged. Raises
281
+ # ArgumentError when empty or passed without a trace ID or dataset
282
+ # selection.
277
283
  # @param mock [String] mock strategy for child spans: "marked" (default),
278
284
  # "none", or "all". "marked" only mocks spans declared with
279
285
  # mock_on_replay: true; "all" mocks every matched recorded child span.
@@ -320,7 +326,7 @@ module Bitfab
320
326
  # and progress events then carry a nil :experiment_id too.
321
327
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, name: nil, notes: nil, metadata: nil,
322
328
  concurrency: nil, max_concurrency: ReplayConcurrency::UNSET, code_change: nil, code_change_description: nil, code_change_files: nil, code_change_base: nil, experiment_group_id: nil, experiment_group_name: nil,
323
- dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, skip_assertion_judging: false, judge_assertions: nil, dry_run: false,
329
+ dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, category_ids: nil, skip_assertion_judging: false, judge_assertions: nil, dry_run: false,
324
330
  adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil,
325
331
  on_experiment_start: nil, experimental_selective_replay: nil)
326
332
  validate_code_change(code_change, code_change_description, code_change_files, code_change_base)
@@ -355,6 +361,7 @@ module Bitfab
355
361
  end
356
362
  end
357
363
  resolved_dataset_ids = resolve_dataset_ids(dataset_id, dataset_ids)
364
+ validate_category_ids(category_ids, trace_ids, resolved_dataset_ids)
358
365
  if limit && trace_ids
359
366
  warn "Bitfab: limit is ignored when trace_ids is passed: the explicit trace ID list already " \
360
367
  "determines how many traces replay."
@@ -422,6 +429,7 @@ module Bitfab
422
429
  grader_ids:,
423
430
  attempts:,
424
431
  only_with_assertions:,
432
+ category_ids:,
425
433
  skip_assertion_judging:,
426
434
  include_original_metadata: !adapt_inputs.nil?,
427
435
  db_branch_settings: resolved_db_branch_settings,
@@ -613,6 +621,17 @@ module Bitfab
613
621
  skip_assertion_judging || judge_assertions == false
614
622
  end
615
623
 
624
+ def validate_category_ids(category_ids, trace_ids, dataset_ids)
625
+ return if category_ids.nil?
626
+ if category_ids.empty?
627
+ raise ArgumentError, "category_ids must contain at least one category ID."
628
+ end
629
+ return if trace_ids || dataset_ids
630
+
631
+ raise ArgumentError,
632
+ "category_ids narrows a trace ID or dataset selection. Pass it with trace_ids, dataset_id or dataset_ids."
633
+ end
634
+
616
635
  def resolve_dataset_ids(dataset_id, dataset_ids)
617
636
  if dataset_id && dataset_ids
618
637
  raise ArgumentError,
@@ -20,7 +20,7 @@ module Bitfab
20
20
  class ReplayRegistry
21
21
  OPTION_NAMES = %i[
22
22
  limit trace_ids name notes metadata concurrency max_concurrency experiment_group_id experiment_group_name dataset_id dataset_ids grader_ids mock
23
- mock_override adapt_inputs db_branch attempts only_with_assertions skip_assertion_judging judge_assertions dry_run on_item_finish
23
+ mock_override adapt_inputs db_branch attempts only_with_assertions category_ids skip_assertion_judging judge_assertions dry_run on_item_finish
24
24
  ].freeze
25
25
 
26
26
  def initialize
@@ -97,31 +97,39 @@ module Bitfab
97
97
  # approved assertion is checked on a replay, so narrowing on anything else
98
98
  # would pick traces the run cannot be judged against. The server applies the
99
99
  # same rule when it revalidates the selection.
100
- def approved_assertion?(assertions)
101
- assertions.any? { |assertion| assertion["approvalState"] == "approved" }
100
+ def approved_assertion?(assertions, category_ids = nil)
101
+ assertions.any? do |assertion|
102
+ assertion["approvalState"] == "approved" &&
103
+ (category_ids.nil? || category_ids.include?(assertion["category_id"]))
104
+ end
102
105
  end
103
106
 
104
- def bound_trace_ids(client, trace_ids, limit, only_with_assertions)
107
+ def bound_trace_ids(client, trace_ids, limit, only_with_assertions, category_ids = nil)
105
108
  unless limit.is_a?(Integer) && limit.positive?
106
109
  raise ArgumentError, "Replay limit must be a positive integer."
107
110
  end
108
- return trace_ids.first(limit) if !only_with_assertions || limit >= trace_ids.length
111
+ narrows = only_with_assertions || !category_ids.nil?
112
+ return trace_ids.first(limit) if !narrows || limit >= trace_ids.length
109
113
 
110
114
  selected = []
111
115
  trace_ids.each do |trace_id|
112
116
  result = client.traces.get_assertions(trace_id)
113
- if result.fetch("inheritedFrom").nil? && approved_assertion?(result.fetch("assertions"))
117
+ if result.fetch("inheritedFrom").nil? && approved_assertion?(result.fetch("assertions"), category_ids)
114
118
  selected << trace_id
115
119
  break if selected.length == limit
116
120
  end
117
121
  end
118
122
  if selected.empty?
123
+ if category_ids
124
+ raise ArgumentError,
125
+ "No traces with an approved assertion in the given categories matched this replay selection."
126
+ end
119
127
  raise ArgumentError, "No traces with approved assertions matched this replay selection."
120
128
  end
121
129
  selected
122
130
  end
123
131
 
124
- def pinned_dataset_members(client, dataset_ids, limit, only_with_assertions = false)
132
+ def pinned_dataset_members(client, dataset_ids, limit, only_with_assertions = false, category_ids = nil)
125
133
  members = dataset_ids.flat_map { |dataset_id|
126
134
  client.datasets.list_traces(dataset_id).fetch("traceIds")
127
135
  }.uniq.sort
@@ -134,7 +142,7 @@ module Bitfab
134
142
  "every selected dataset in full."
135
143
  end
136
144
 
137
- bound_trace_ids(client, members, limit, only_with_assertions)
145
+ bound_trace_ids(client, members, limit, only_with_assertions, category_ids)
138
146
  end
139
147
 
140
148
  def run(registry, argv: ARGV, stdout: $stdout, stderr: $stderr)
@@ -173,24 +181,32 @@ module Bitfab
173
181
  end
174
182
 
175
183
  only_with_assertions = args[:only_with_assertions] || options[:only_with_assertions]
184
+ category_ids = args[:category_ids] || options[:category_ids]
176
185
  bound = args[:limit] || options[:limit]
177
186
  options.delete(:limit)
178
187
  bounded_dataset_ids = args[:dataset_ids] || options[:dataset_ids]
179
188
 
189
+ if category_ids && options[:trace_ids].nil? && bounded_dataset_ids.nil?
190
+ raise ArgumentError,
191
+ "--category-ids narrows a trace ID or dataset selection. Pass it with --trace-ids or --dataset-ids."
192
+ end
193
+
180
194
  if options[:trace_ids]
181
195
  if bound
182
- options[:trace_ids] = bound_trace_ids(registration.client, options[:trace_ids], bound, only_with_assertions)
196
+ options[:trace_ids] = bound_trace_ids(registration.client, options[:trace_ids], bound, only_with_assertions,
197
+ category_ids)
183
198
  end
184
199
  elsif bounded_dataset_ids
185
200
  if bound
186
- pinned = pinned_dataset_members(registration.client, bounded_dataset_ids, bound, only_with_assertions)
201
+ pinned = pinned_dataset_members(registration.client, bounded_dataset_ids, bound, only_with_assertions,
202
+ category_ids)
187
203
  options[:trace_ids] = pinned if pinned
188
204
  end
189
205
  else
190
206
  options[:limit] = bound || 10
191
207
  end
192
208
 
193
- %i[name notes metadata max_concurrency experiment_group_id experiment_group_name dataset_ids grader_ids mock attempts only_with_assertions skip_assertion_judging dry_run].each do |key|
209
+ %i[name notes metadata max_concurrency experiment_group_id experiment_group_name dataset_ids grader_ids mock attempts only_with_assertions category_ids skip_assertion_judging dry_run].each do |key|
194
210
  options[key] = args[key] unless args[key].nil?
195
211
  end
196
212
 
@@ -363,6 +379,11 @@ module Bitfab
363
379
  options.on("--grader-ids IDS") { |value| args[:grader_ids] = comma_separated("--grader-ids", value) }
364
380
  options.on("--attempts N", Integer) { |value| args[:attempts] = positive_integer("--attempts", value) }
365
381
  options.on("--only-with-assertions") { args[:only_with_assertions] = true }
382
+ options.on("--category-ids id1,id2",
383
+ "With --trace-ids or --dataset-ids: replay only the traces carrying an approved assertion in one of " \
384
+ "these categories; which assertions are judged stays the same") do |value|
385
+ args[:category_ids] = comma_separated("--category-ids", value)
386
+ end
366
387
  options.on("--skip-assertion-judging",
367
388
  "Don't judge approved assertions on each replay; judging is on by default and costs model calls") { args[:skip_assertion_judging] = true }
368
389
  options.on("--judge-assertions", "Deprecated: judging is on by default") { args[:judge_assertions] = true }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.73.2"
4
+ VERSION = "0.74.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.73.2
4
+ version: 0.74.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team