bitfab 0.38.3 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/bitfab/client.rb +62 -26
- data/lib/bitfab/datasets.rb +107 -0
- data/lib/bitfab/span_context.rb +8 -0
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +8 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5e2025cf56735ed8b8ef283be12a9aba5c4a859bb8bfe6d88479292e2fbba61
|
|
4
|
+
data.tar.gz: 37ec3530d9e57447ebf8ec84c257e8a730e3af776efa7de45941806e9c129b8c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b75605cc3372d3cfddab8a6b18a764137105188902ea05b63a006633c0bf6f1743ca64aac56e19d553b1c75556296928eaf692a9d28cb68e7c0650f2249fbb65
|
|
7
|
+
data.tar.gz: 12b2be619ba694e5f552267a0b8b439cc9cec7f804f6faebe6739e0ebba6e0a1afa0cc413a0a101129158e56b60377c586eba313e4c24e1ddc34692df009fbc4
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -13,7 +13,7 @@ require_relative "warn_once"
|
|
|
13
13
|
|
|
14
14
|
module Bitfab
|
|
15
15
|
class Client
|
|
16
|
-
SPAN_TYPES =
|
|
16
|
+
SPAN_TYPES = ["llm", "agent", "function", "guardrail", "handoff", "custom"].freeze
|
|
17
17
|
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
|
18
18
|
|
|
19
19
|
# Sentinel returned by check_mock_replay when this span should run real
|
|
@@ -30,6 +30,9 @@ module Bitfab
|
|
|
30
30
|
|
|
31
31
|
attr_reader :service_url
|
|
32
32
|
|
|
33
|
+
# Dataset operations for the authenticated organization.
|
|
34
|
+
attr_reader :datasets
|
|
35
|
+
|
|
33
36
|
def initialize(api_key: nil, service_url: nil, enabled: true, strict: false)
|
|
34
37
|
@api_key_config = api_key
|
|
35
38
|
@service_url = service_url || DEFAULT_SERVICE_URL
|
|
@@ -43,6 +46,7 @@ module Bitfab
|
|
|
43
46
|
# The key is NOT read here. HttpClient gets a proc so the key is resolved
|
|
44
47
|
# at send time, after any env loading has run.
|
|
45
48
|
@http_client = HttpClient.new(api_key: -> { resolve_api_key }, service_url: @service_url)
|
|
49
|
+
@datasets = Datasets.new(@http_client)
|
|
46
50
|
# Mock overrides registered via register_mock_override, applied to every
|
|
47
51
|
# replay on this client (after any per-call mock_override). Instance
|
|
48
52
|
# state, no global; clear_mock_overrides resets it.
|
|
@@ -157,10 +161,25 @@ module Bitfab
|
|
|
157
161
|
name: nil, code_change_description: Replay::CODE_CHANGE_UNSET, code_change_files: Replay::CODE_CHANGE_UNSET, experiment_group_id: nil, dataset_id: nil, grader_ids: nil, mock: "marked",
|
|
158
162
|
adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil)
|
|
159
163
|
Replay.run(
|
|
160
|
-
self,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
self,
|
|
165
|
+
receiver,
|
|
166
|
+
method_name,
|
|
167
|
+
trace_function_key:,
|
|
168
|
+
limit:,
|
|
169
|
+
trace_ids:,
|
|
170
|
+
name:,
|
|
171
|
+
max_concurrency:,
|
|
172
|
+
code_change_description:,
|
|
173
|
+
code_change_files:,
|
|
174
|
+
experiment_group_id:,
|
|
175
|
+
dataset_id:,
|
|
176
|
+
grader_ids:,
|
|
177
|
+
mock:,
|
|
178
|
+
adapt_inputs:,
|
|
179
|
+
mock_override:,
|
|
180
|
+
db_branch:,
|
|
181
|
+
on_item_start:,
|
|
182
|
+
on_item_finish:,
|
|
164
183
|
on_progress:
|
|
165
184
|
)
|
|
166
185
|
end
|
|
@@ -203,18 +222,18 @@ module Bitfab
|
|
|
203
222
|
match: ->(node) { node[:trace_function_key] == trace_function_key && keyed_match.call(node) },
|
|
204
223
|
value: keyed_override[:value]
|
|
205
224
|
}
|
|
206
|
-
return
|
|
225
|
+
return
|
|
207
226
|
end
|
|
208
227
|
if keyed_override.respond_to?(:call)
|
|
209
228
|
@mock_overrides << {
|
|
210
229
|
match: ->(node) { node[:trace_function_key] == trace_function_key },
|
|
211
230
|
value: keyed_override
|
|
212
231
|
}
|
|
213
|
-
return
|
|
232
|
+
return
|
|
214
233
|
end
|
|
215
234
|
raise ArgumentError,
|
|
216
235
|
"register_mock_override(trace_function_key, override) requires " \
|
|
217
|
-
|
|
236
|
+
"a { match:, value: } hash or callable resolver."
|
|
218
237
|
elsif positional.length == 1 && positional[0].is_a?(Hash)
|
|
219
238
|
override = positional[0]
|
|
220
239
|
match = override[:match]
|
|
@@ -223,7 +242,7 @@ module Bitfab
|
|
|
223
242
|
value = override.key?(:value) ? override[:value] : VALUE_UNSET
|
|
224
243
|
elsif positional.length == 1 && positional[0].respond_to?(:call)
|
|
225
244
|
@mock_overrides << {match: ->(_) { true }, value: positional[0]}
|
|
226
|
-
return
|
|
245
|
+
return
|
|
227
246
|
elsif positional.length == 2
|
|
228
247
|
match, value = positional
|
|
229
248
|
end
|
|
@@ -232,16 +251,16 @@ module Bitfab
|
|
|
232
251
|
unless match.respond_to?(:call)
|
|
233
252
|
raise ArgumentError,
|
|
234
253
|
"register_mock_override requires a callable match. Pass (match, value) " \
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
"positionally, as keywords (match:, value:), or as a { match:, value: } hash. " \
|
|
255
|
+
"value may be a flat value or a callable."
|
|
237
256
|
end
|
|
238
257
|
# A forgotten value must not silently inject nil. An explicit nil is a
|
|
239
258
|
# legitimate injected value and passes this guard.
|
|
240
259
|
if value.equal?(VALUE_UNSET)
|
|
241
260
|
raise ArgumentError,
|
|
242
261
|
"register_mock_override requires a value (the second argument, or " \
|
|
243
|
-
|
|
244
|
-
|
|
262
|
+
"value:). It may be a flat value or a callable; pass value: nil " \
|
|
263
|
+
"explicitly to inject nil."
|
|
245
264
|
end
|
|
246
265
|
|
|
247
266
|
@mock_overrides << {match:, value:}
|
|
@@ -281,12 +300,13 @@ module Bitfab
|
|
|
281
300
|
def get_trace_span(trace_id, id: nil, name: nil, occurrence: "last")
|
|
282
301
|
validate_trace_id(trace_id)
|
|
283
302
|
raise ArgumentError, "Provide exactly one of id or name" if id.nil? == name.nil?
|
|
303
|
+
|
|
284
304
|
validate_span_id(id) unless id.nil?
|
|
285
305
|
if !name.nil? && (!name.is_a?(String) || name.empty?)
|
|
286
306
|
raise ArgumentError, "name must be a non-empty string"
|
|
287
307
|
end
|
|
288
308
|
|
|
289
|
-
valid_occurrence =
|
|
309
|
+
valid_occurrence = ["first", "last"].include?(occurrence) || (occurrence.is_a?(Integer) && occurrence >= 0)
|
|
290
310
|
unless valid_occurrence
|
|
291
311
|
raise ArgumentError, 'occurrence must be "first", "last", or a non-negative integer'
|
|
292
312
|
end
|
|
@@ -304,11 +324,11 @@ module Bitfab
|
|
|
304
324
|
return yield unless tracing_enabled?
|
|
305
325
|
|
|
306
326
|
resolved_capture_when = capture_when.to_s
|
|
307
|
-
unless
|
|
327
|
+
unless ["always", "nested"].include?(resolved_capture_when)
|
|
308
328
|
Bitfab.warn_once(
|
|
309
329
|
"invalid-capture-when:#{trace_function_key}",
|
|
310
330
|
"unknown capture_when value #{capture_when.inspect}; defaulting to \"always\". " \
|
|
311
|
-
|
|
331
|
+
"Valid values: \"always\", \"nested\"."
|
|
312
332
|
)
|
|
313
333
|
resolved_capture_when = "always"
|
|
314
334
|
end
|
|
@@ -357,8 +377,14 @@ module Bitfab
|
|
|
357
377
|
call_index = advance_mock_counter(replay_ctx, trace_function_key, span_name, is_root_span:)
|
|
358
378
|
if call_index
|
|
359
379
|
mock_result = check_mock_replay(
|
|
360
|
-
replay_ctx,
|
|
361
|
-
|
|
380
|
+
replay_ctx,
|
|
381
|
+
trace_function_key,
|
|
382
|
+
span_name,
|
|
383
|
+
call_index,
|
|
384
|
+
span_type:,
|
|
385
|
+
args:,
|
|
386
|
+
kwargs:,
|
|
387
|
+
mock_on_replay:
|
|
362
388
|
)
|
|
363
389
|
if mock_result != MOCK_REPLAY_MISS
|
|
364
390
|
mocked_output, mock_source = mock_result
|
|
@@ -394,7 +420,7 @@ module Bitfab
|
|
|
394
420
|
Bitfab.warn_once(
|
|
395
421
|
"span-setup:#{trace_function_key}",
|
|
396
422
|
"span setup failed for '#{trace_function_key}'; this call runs untraced. " \
|
|
397
|
-
|
|
423
|
+
"Your method still executes and returns normally."
|
|
398
424
|
)
|
|
399
425
|
return yield
|
|
400
426
|
end
|
|
@@ -410,6 +436,7 @@ module Bitfab
|
|
|
410
436
|
# only the first call sends the span. Subsequent calls (e.g. from the
|
|
411
437
|
# enumerator wrapper after iteration completes) are no-ops.
|
|
412
438
|
next if finalized
|
|
439
|
+
|
|
413
440
|
finalized = true
|
|
414
441
|
|
|
415
442
|
begin
|
|
@@ -551,7 +578,7 @@ module Bitfab
|
|
|
551
578
|
end
|
|
552
579
|
if @explicitly_enabled && !@api_key_warned
|
|
553
580
|
@api_key_warned = true
|
|
554
|
-
warn
|
|
581
|
+
warn("Bitfab: api_key is empty: tracing is disabled. Provide a valid API key to enable tracing.")
|
|
555
582
|
end
|
|
556
583
|
nil
|
|
557
584
|
end
|
|
@@ -622,6 +649,9 @@ module Bitfab
|
|
|
622
649
|
"ended_at" => ended_at
|
|
623
650
|
}
|
|
624
651
|
|
|
652
|
+
if trace_state&.dig(:name)
|
|
653
|
+
raw_trace["name"] = trace_state[:name]
|
|
654
|
+
end
|
|
625
655
|
if trace_state&.dig(:metadata)
|
|
626
656
|
raw_trace["metadata"] = trace_state[:metadata]
|
|
627
657
|
end
|
|
@@ -690,7 +720,7 @@ module Bitfab
|
|
|
690
720
|
# still rides out with dropped: true, so the server scrubs any sibling
|
|
691
721
|
# spans that already raced out before the flag was set.
|
|
692
722
|
trace_dropped = TraceState.get(trace_id)&.dig(:dropped) == true
|
|
693
|
-
return
|
|
723
|
+
return if trace_dropped
|
|
694
724
|
|
|
695
725
|
# Human-readable JSON (input/output fields). The *_with_report variants
|
|
696
726
|
# also report what could not be faithfully captured so a lossy span is
|
|
@@ -772,8 +802,8 @@ module Bitfab
|
|
|
772
802
|
# mocked) so unmarked spans don't silently shift subsequent marked
|
|
773
803
|
# spans' indices. Different (key, name) pairs have independent counters.
|
|
774
804
|
def advance_mock_counter(replay_ctx, trace_function_key, span_name, is_root_span:)
|
|
775
|
-
return
|
|
776
|
-
return
|
|
805
|
+
return if is_root_span
|
|
806
|
+
return unless replay_ctx&.dig(:mock_tree)
|
|
777
807
|
|
|
778
808
|
counters = replay_ctx[:call_counters]
|
|
779
809
|
counter_key = "#{trace_function_key}:#{span_name}"
|
|
@@ -819,6 +849,7 @@ module Bitfab
|
|
|
819
849
|
# output, wrap it in a callable value.
|
|
820
850
|
unless value.respond_to?(:call)
|
|
821
851
|
return [value, "override"] unless value.equal?(NO_MOCK_OVERRIDE)
|
|
852
|
+
|
|
822
853
|
next
|
|
823
854
|
end
|
|
824
855
|
|
|
@@ -826,6 +857,7 @@ module Bitfab
|
|
|
826
857
|
unless mock_entry
|
|
827
858
|
raise "No recorded span to source output for '#{trace_function_key}'."
|
|
828
859
|
end
|
|
860
|
+
|
|
829
861
|
resolve_recorded_output(mock_entry, replay_ctx)
|
|
830
862
|
end
|
|
831
863
|
resolved = value.call({node:, inputs: args, kwargs:, get_original_output:})
|
|
@@ -847,7 +879,7 @@ module Bitfab
|
|
|
847
879
|
unless mock_entry
|
|
848
880
|
raise StandardError,
|
|
849
881
|
"Replay selected span '#{trace_function_key}:#{span_name}' for mocking, " \
|
|
850
|
-
|
|
882
|
+
"but recorded occurrence #{call_index + 1} is unavailable. The real span was not executed."
|
|
851
883
|
end
|
|
852
884
|
|
|
853
885
|
[resolve_recorded_output(mock_entry, replay_ctx), "recorded"]
|
|
@@ -955,9 +987,13 @@ module Bitfab
|
|
|
955
987
|
# mock strategy. A missing selected occurrence fails closed.
|
|
956
988
|
def wrap(klass, method_name, name: nil, type: "custom", capture_when: "always", mock_on_replay: false)
|
|
957
989
|
Bitfab::Traceable.wrap(
|
|
958
|
-
klass,
|
|
990
|
+
klass,
|
|
991
|
+
method_name,
|
|
959
992
|
trace_function_key: @trace_function_key,
|
|
960
|
-
name:,
|
|
993
|
+
name:,
|
|
994
|
+
type:,
|
|
995
|
+
capture_when:,
|
|
996
|
+
mock_on_replay:,
|
|
961
997
|
client: @client
|
|
962
998
|
)
|
|
963
999
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# Dataset operations for the authenticated organization, reached as
|
|
5
|
+
# `client.datasets`. A dataset is a named bucket of traces scoped to one
|
|
6
|
+
# trace function. Experiments replay against it and its graders score its
|
|
7
|
+
# members. Every method returns the parsed JSON response as a Hash with
|
|
8
|
+
# string keys matching the HTTP API ("traceCount", "addedTraceIds", ...).
|
|
9
|
+
class Datasets
|
|
10
|
+
DEFAULT_RERUN_TIMEOUT_SECONDS = 90
|
|
11
|
+
DEFAULT_RERUN_POLL_INTERVAL_SECONDS = 1
|
|
12
|
+
TERMINAL_RERUN_STATUSES = ["completed", "errored"].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(http_client)
|
|
15
|
+
@http_client = http_client
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a dataset, or update the one already named this way under the
|
|
19
|
+
# same trace function. The result's "created" reports which happened. A
|
|
20
|
+
# nil description leaves an existing description untouched.
|
|
21
|
+
def save(trace_function_key:, name:, description: nil)
|
|
22
|
+
payload = {"traceFunctionKey" => trace_function_key, "name" => name}
|
|
23
|
+
payload["description"] = description unless description.nil?
|
|
24
|
+
@http_client.request("/api/sdk/datasets", payload)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# List datasets, scoped to one trace function when given and
|
|
28
|
+
# organization-wide otherwise.
|
|
29
|
+
def list(trace_function_key: nil)
|
|
30
|
+
query = trace_function_key.nil? ? "" : "?#{URI.encode_www_form("traceFunctionKey" => trace_function_key)}"
|
|
31
|
+
@http_client.get("/api/sdk/datasets#{query}")["datasets"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Fetch one dataset by id. A dataset outside this organization raises
|
|
35
|
+
# Net::HTTPError with a 404.
|
|
36
|
+
def get(dataset_id)
|
|
37
|
+
@http_client.get(dataset_path(dataset_id))["dataset"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The ids of every trace in the dataset, the same membership a replay with
|
|
41
|
+
# dataset_id: selects.
|
|
42
|
+
def list_traces(dataset_id)
|
|
43
|
+
@http_client.get(dataset_path(dataset_id, "/traces"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Add traces to the dataset (1 to 100 ids per call). Traces outside the
|
|
47
|
+
# organization or under another trace function are reported in
|
|
48
|
+
# "skippedTraceIds" rather than failing the call.
|
|
49
|
+
def add_traces(dataset_id, trace_ids)
|
|
50
|
+
@http_client.request(dataset_path(dataset_id, "/traces"), {"traceIds" => trace_ids})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Remove traces from the dataset. The traces themselves are never deleted.
|
|
54
|
+
def remove_traces(dataset_id, trace_ids)
|
|
55
|
+
@http_client.request(dataset_path(dataset_id, "/removeTraces"), {"traceIds" => trace_ids})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Assign graders to the dataset (1 to 100 ids per call). Graders outside
|
|
59
|
+
# the organization or under another trace function are reported in
|
|
60
|
+
# "skippedGraderIds" rather than failing the call.
|
|
61
|
+
def add_graders(dataset_id, grader_ids)
|
|
62
|
+
@http_client.request(dataset_path(dataset_id, "/graders"), {"graderIds" => grader_ids})
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Unassign graders from the dataset.
|
|
66
|
+
def remove_graders(dataset_id, grader_ids)
|
|
67
|
+
@http_client.request(dataset_path(dataset_id, "/removeGraders"), {"graderIds" => grader_ids})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Re-run graders over every trace in the dataset. Defaults to every
|
|
71
|
+
# assigned grader, and an unassigned id is rejected. Waits for the run to
|
|
72
|
+
# finish (up to `timeout` seconds) unless `wait:` is false, and returns the
|
|
73
|
+
# last run state seen either way. A request matching an in-flight run
|
|
74
|
+
# joins it.
|
|
75
|
+
def rerun_graders(dataset_id, grader_ids: nil, wait: true,
|
|
76
|
+
timeout: DEFAULT_RERUN_TIMEOUT_SECONDS, poll_interval: DEFAULT_RERUN_POLL_INTERVAL_SECONDS)
|
|
77
|
+
payload = grader_ids.nil? ? {} : {"graderIds" => grader_ids}
|
|
78
|
+
started = @http_client.request(dataset_path(dataset_id, "/rerunGraders"), payload)
|
|
79
|
+
return started unless wait
|
|
80
|
+
|
|
81
|
+
deadline = monotonic_now + timeout
|
|
82
|
+
run = started["run"]
|
|
83
|
+
while !TERMINAL_RERUN_STATUSES.include?(run["status"]) && monotonic_now < deadline
|
|
84
|
+
sleep(poll_interval)
|
|
85
|
+
run = get_grader_rerun(dataset_id, run_id: run["id"]) || run
|
|
86
|
+
end
|
|
87
|
+
{"run" => run, "joinedExisting" => started["joinedExisting"]}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The dataset's active grader re-run, or the run named by run_id:. Returns
|
|
91
|
+
# nil when nothing is active or the run is not this dataset's.
|
|
92
|
+
def get_grader_rerun(dataset_id, run_id: nil)
|
|
93
|
+
query = run_id.nil? ? "" : "?#{URI.encode_www_form("runId" => run_id)}"
|
|
94
|
+
@http_client.get(dataset_path(dataset_id, "/rerunGraders#{query}"))["run"]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def dataset_path(dataset_id, suffix = "")
|
|
100
|
+
"/api/sdk/datasets/#{URI.encode_www_form_component(dataset_id)}#{suffix}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def monotonic_now
|
|
104
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -65,6 +65,14 @@ module Bitfab
|
|
|
65
65
|
# Silently ignore - never crash the host app
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
def set_name(name)
|
|
69
|
+
return unless name.is_a?(String) && !name.empty?
|
|
70
|
+
|
|
71
|
+
get_or_create_trace_state[:name] = name
|
|
72
|
+
rescue
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
# Set metadata for this trace.
|
|
69
77
|
# Metadata is stored in the raw trace data. Subsequent calls merge with
|
|
70
78
|
# existing metadata, with later values taking precedence.
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "bitfab/serialize"
|
|
|
10
10
|
require_relative "bitfab/db_snapshot"
|
|
11
11
|
require_relative "bitfab/span_context"
|
|
12
12
|
require_relative "bitfab/http_client"
|
|
13
|
+
require_relative "bitfab/datasets"
|
|
13
14
|
require_relative "bitfab/mock_override"
|
|
14
15
|
require_relative "bitfab/replay"
|
|
15
16
|
require_relative "bitfab/replay_branch"
|
|
@@ -46,6 +47,9 @@ module Bitfab
|
|
|
46
47
|
# No-op
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
def set_name(_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
49
53
|
def set_metadata(_metadata)
|
|
50
54
|
# No-op
|
|
51
55
|
end
|
|
@@ -119,16 +123,16 @@ module Bitfab
|
|
|
119
123
|
# whose source trace carried no DB snapshot reference
|
|
120
124
|
def current_replay_branch
|
|
121
125
|
ctx = ReplayContext.current
|
|
122
|
-
return
|
|
126
|
+
return unless ctx
|
|
123
127
|
|
|
124
128
|
lease = ctx[:db_branch_lease]
|
|
125
|
-
return
|
|
129
|
+
return unless lease
|
|
126
130
|
|
|
127
131
|
# Surface the Bitfab trace ID (what the customer sees in the dashboard),
|
|
128
132
|
# not the external one. Falling back to the external ID keeps replays from
|
|
129
133
|
# external sources working until that path is fully wired.
|
|
130
134
|
trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
|
|
131
|
-
return
|
|
135
|
+
return unless trace_id
|
|
132
136
|
|
|
133
137
|
ReplayBranch.new(lease, trace_id, ctx)
|
|
134
138
|
end
|
|
@@ -159,7 +163,7 @@ module Bitfab
|
|
|
159
163
|
# succeeded, errored, and item (the object replay already passes to
|
|
160
164
|
# on_item_finish)
|
|
161
165
|
def report_replay_progress(progress)
|
|
162
|
-
warn
|
|
166
|
+
warn("#{BITFAB_PROGRESS_PREFIX}#{Replay.json_safe(progress).to_json}")
|
|
163
167
|
rescue
|
|
164
168
|
nil
|
|
165
169
|
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.
|
|
4
|
+
version: 0.41.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -142,6 +142,7 @@ files:
|
|
|
142
142
|
- lib/bitfab/client.rb
|
|
143
143
|
- lib/bitfab/compress.rb
|
|
144
144
|
- lib/bitfab/constants.rb
|
|
145
|
+
- lib/bitfab/datasets.rb
|
|
145
146
|
- lib/bitfab/db_snapshot.rb
|
|
146
147
|
- lib/bitfab/http_client.rb
|
|
147
148
|
- lib/bitfab/mock_override.rb
|